Implementing M-Register Read/Write for Mitsubishi PLCs Using C# Serial Communication
This guide outlines a method for accessing the M-register (auxiliary relay area) of a Mitsubishi PLC from a C# application using the MELSEC Communication (MC) protocol over a serial connection.
Hardware Setup and Protocol Configuration
Required Hardware
- RS-485 to USB converter module for PC connection.
- PLC must be equipped with an RS-485 communication interface (e.g., FX2N-485BD).
- Wiring: Connect A+ and B- to the converter's RDA+ and RDB- lines respective. A 120Ω terminating resistor is typically required.
Protocol Parameters
The MC protocol requires specific serial port settings:
Baud Rate: 9600 bps
Data Bits: 7
Stop Bits: 2
Parity: Even
Handshake: None
Establishing Serial Communication in C#
Initializing the Serial Port
using System.IO.Ports;
SerialPort mitsubishiPort = new SerialPort("COM3", 9600, Parity.Even, 7, StopBits.Two);
mitsubishiPort.Handshake = Handshake.None;
mitsubishiPort.ReadTimeout = 5000;
mitsubishiPort.WriteTimeout = 5000;
try
{
mitsubishiPort.Open();
}
catch (Exception ex)
{
Console.WriteLine($"Port open failed: {ex.Message}");
}
Constructing MC Protocol Frames
The following helper class demonstrates building read and write commands for the M-register.
public class MelsecFrameBuilder
{
public byte[] BuildReadCommand(int startIndex, int bitCount)
{
// STX + device code (ASCII '7','0','7','0','5')
byte[] baseFrame = { 0x02, 0x37, 0x30, 0x37, 0x30, 0x35, 0x03 };
// Station number (ASCII '6','0','0','0','0','0','0','0') for station 0
byte[] station = { 0x36, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30 };
// Convert start address to ASCII hex representation
string addrHex = startIndex.ToString("X4");
byte[] address = Encoding.ASCII.GetBytes(addrHex);
// Convert read length (in bytes) to ASCII hex
int byteLength = (bitCount + 7) / 8;
string lenHex = byteLength.ToString("X4");
byte[] length = Encoding.ASCII.GetBytes(lenHex);
// Combine all parts
List<byte> command = new List<byte>();
command.AddRange(baseFrame);
command.AddRange(station);
command.AddRange(address);
command.AddRange(length);
// Append checksum
byte checksum = ComputeChecksum(command.ToArray());
command.Add(checksum);
return command.ToArray();
}
private byte ComputeChecksum(byte[] frameData)
{
int sum = 0;
foreach (byte b in frameData) sum += b;
return (byte)((~sum + 1) & 0xFF); // Two's complement low byte
}
}
Performing Read and Write Operations
// Instantiate the frame builder
MelsecFrameBuilder frameBuilder = new MelsecFrameBuilder();
// Reading M0-M15 (16 bits)
byte[] readFrame = frameBuilder.BuildReadCommand(0, 16);
mitsubishiPort.Write(readFrame, 0, readFrame.Length);
// Read response (adjust size based on expected data)
byte[] responseBuffer = new byte[mitsubishiPort.BytesToRead];
mitsubishiPort.Read(responseBuffer, 0, responseBuffer.Length);
// Process response: status bits start after header (e.g., at index 6)
if (responseBuffer.Length >= 6 + 2) // Header + at least 2 data bytes
{
// Extract data bytes and convert to bits
byte dataByte1 = responseBuffer[6];
byte dataByte2 = responseBuffer[7];
bool m0Status = (dataByte1 & 0x01) != 0;
bool m15Status = (dataByte2 & 0x80) != 0; // Assuming bits in second byte
}
// Writing to a single M-register (e.g., set M0 ON)
// Frame construction for write is similar but includes data bytes.
Critical Implementation Details
- Frame Structure: Commands start with STX (0x02) and end with ETX (0x03). Addresses and lengths are sent as ASCII characters.
- Address Mapping: M-register addresses are converted to thier hexadeciaml ASCII representation.
- Error Handling: Always implement try-catch blocks for timeouts and I/O exceptions.
- Data Size: Limit single read/write operations for reliability. Process data in chunks if necessary.
Testing and Troubleshooting
- Use Mitsubishi's GX Works2 software to verify PLC communication parameters.
- Employ a serial port monitor (e.g., PuTTY, Tera Term) to capture and inspect raw data frames.
- Common issues include incorrect station number, missing terminating resistor, or parity mismatch.
Structuring the Application
For production code, encapsulate the communication logic within a dedicated class.
public class MitsubishiPlcCommunicator
{
private SerialPort _serialPort;
private MelsecFrameBuilder _frameBuilder;
public bool EstablishConnection(string comPort)
{
_serialPort = new SerialPort(comPort, 9600, Parity.Even, 7, StopBits.Two);
_serialPort.Open();
_frameBuilder = new MelsecFrameBuilder();
return _serialPort.IsOpen;
}
public bool[] ReadMRegisters(int startIndex, int count)
{
byte[] command = _frameBuilder.BuildReadCommand(startIndex, count);
_serialPort.Write(command, 0, command.Length);
// ... read and parse response into a boolean array ...
return new bool[count]; // placeholder
}
// Implement WriteMRegister method similarly.
}