Implementing Modbus RTU Master Communication in C#

Abstract

This article describes how to use C# to call the NModbus4 library and the System.IO.Ports library to implement Modbus RTU communication functionality【Project address at the end of the article】.

Introduction

Modbus RTU is a serial communication protocol, where the communication mechanism is a polling mechanism where the master device sequentially queries the slave devices. This protocol is commonly used in industrial fields. This case study is a simple hands-on example of how to implement Modbus RTU communication using C#, allowing for a preliminary understanding of its basic functionalities.

This case study implements Modbus RTU communication functionality by calling NModbus4 and System.IO.Ports in a C# program. The case study only implements functional calls, so it directly uses the built-in read and write methods without concerning itself with the underlying messages. Of course, there is a reason why NModbus4 does not expose the raw messages by default; this article does not implement the display of raw messages, which will be addressed in subsequent case studies.

(1) Implemented Functions (Modbus RTU)

Function Code 01: Read Coil Status (ReadCoils)

Function Code 02: Read Input Status (ReadInputs)

Function Code 03: Read Holding Registers (ReadHoldingRegisters)

Function Code 04: Read Input Registers (ReadInputRegisters)

Function Code 05: Write Single Coil (WriteSingleCoil)

Function Code 06: Write Single Register (WriteSingleRegister)

Function Code 15: Write Multiple Coils (WriteMultipleCoils)

Function Code 16: Write Multiple Registers (WriteMultipleRegisters)

Operating Environment

Operating System: Win11

Development Software: Visual Studio 2022

.Net Version: .Net Framework 4.8.0

Dependency Version: NModbus4 2.1.0

1. Preview

(1) Running EffectImplementing Modbus RTU Master Communication in C#

2. Code

(1) MainForm Class CodeThe MainForm class code roughly implements the following functionalities:1. Initialize the form, load the form, and initialize configuration parameters.2. Connect, read, and write.3. Timer: connection status, cyclic reading and displaying data.4. Control enable updates, parameter changes.5. Operation message updates.

public partial class MainForm : WinFormBase{    #region Objects    ModbusRtuMaster modbusMaster;    States connectState = States.Stop;    Timer connectStateTimer = new Timer();    Timer resultShowTimer = new Timer();    #endregion
    #region Initialize Form, Load Form, Initialize Configuration Parameters    public MainForm()    {        InitializeComponent();        this.CenterToParent();    }    private void MainForm_Load(object sender, EventArgs e)    {        Initialize();    }    private void Initialize()    {        // Initialize Modbus        modbusMaster = new ModbusRtuMaster();        // Initialize parameters        cbx_SerialPort.DataSource = modbusMaster.GetSerialPortArray();        cbx_BaudRate.DataSource = modbusMaster.GetBaudRateArray();        cbx_Parity.DataSource = modbusMaster.GetParityArray();        cbx_DataBits.DataSource = modbusMaster.GetDataBitArray();        cbx_StopBits.DataSource = modbusMaster.GetStopBitArray();        cbx_FuncCode.SelectedIndex = 0;        // Initialize timers        connectStateTimer = new Timer();        connectStateTimer.Interval = 500;        connectStateTimer.Tick += ConnectStateTimer_Tick;        resultShowTimer = new Timer();        resultShowTimer.Interval = 500;        connectStateTimer.Tick += ResultShowTimer_Tick;        // Initialize table        dataGridView.ReadOnly = true;        dataGridView.Columns[0].Width = 100;        dataGridView.Columns[1].Width = 100;        dataGridView.Columns[0].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;        dataGridView.Columns[1].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;        dataGridView.RowHeadersVisible = false;        // Initialize table data        for (int i = 0; i < 10; i++)        {            dataGridView.Rows.Add(new object[] { i, 0 });        }        // Initialize        rtbx_Message.ForeColor = Color.Gray;        btn_WriteData.Enabled = false;        rsc_ConnectState.State = States.Stop;    }    #endregion
    #region Connect, Read, Write    private void btn_OpenRTU_Click(object sender, EventArgs e)    {        Open();    }    private void btn_WriteData_Click(object sender, EventArgs e)    {        WriteData();    }    private void Open()    {        if (btn_OpenRTU.Text.Equals("Connect"))        {            MessageUpdate("Creating Modbus RTU connection...", Color.Green);            btn_OpenRTU.Enabled = false;            if (modbusMaster.IsConnected) modbusMaster.DisConnect();            if (!modbusMaster.Connect(modbusMaster.protModel.PortName))            {                modbusMaster.DisConnect();                Debug.WriteLine("Unable to establish connection with Modbus RTU slave");                MessageUpdate("Unable to establish connection with Modbus RTU slave", Color.Red);                btn_OpenRTU.Enabled = true;                return;            }            try            {                MessageUpdate("Modbus RTU connection established successfully...", Color.Green);                connectState = States.Running;                Task<bool[]> registers = modbusMaster.ReadCoilsAsync(modbusMaster.SlaveId, 0, 1);                if (registers.Result != null)                {                    Debug.WriteLine($"Attempting to read value: value = {registers.Result[0]}");                    MessageUpdate($"Attempting to read value: value = {registers.Result[0]}", Color.Green);                }                ControlEnableUpdate();                connectStateTimer.Start();                resultShowTimer.Start();                btn_OpenRTU.Enabled = true;            }            catch (Exception ex)            {                Debug.WriteLine($"Exception triggered: {ex.Message}");                MessageUpdate($"Failed to read value, connection failed... Exception reason: {ex.Message}", Color.Red);                modbusMaster.DisConnect();                connectState = States.Stop;                connectStateTimer.Stop();                resultShowTimer.Stop();                ControlEnableUpdate();                btn_OpenRTU.Enabled = true;            }        }        else        {            MessageUpdate("Disconnecting Modbus RTU communication...", Color.Red);            modbusMaster.DisConnect();            connectState = States.Stop;            connectStateTimer.Stop();            resultShowTimer.Stop();            rsc_ConnectState.State = connectState;            ControlEnableUpdate();            btn_OpenRTU.Enabled = true;        }    }    /// <summary>    /// Write data    /// </summary>    private void WriteData()    {        ushort startAddress = (ushort)nudx_WriteStartAddress.Value;        try        {            switch (modbusMaster.FuncCode)            {                case "05":                    modbusMaster.WriteSingleCoilAsync(modbusMaster.SlaveId, startAddress, bool.Parse(tbx_WriteData.Text));                    UpdateDataShow($"Slave={modbusMaster.SlaveId}, Function Code = {modbusMaster.FuncCode}," +                        $" Start Address = {modbusMaster.StartAddress}, Write Value={tbx_WriteData.Text}");                    break;                case "06":                    modbusMaster.WriteSingleRegisterAsync(modbusMaster.SlaveId, startAddress, ushort.Parse(tbx_WriteData.Text));                    UpdateDataShow($"Slave={modbusMaster.SlaveId}, Function Code = {modbusMaster.FuncCode}," +                       $" Start Address = {modbusMaster.StartAddress}, Write Value={tbx_WriteData.Text}");                    break;                case "15":                    bool[] dataBool = ParseArray<bool>(tbx_WriteData.Text);                    modbusMaster.WriteMultipleCoilsAsync(modbusMaster.SlaveId, startAddress, dataBool);                    UpdateDataShow($"Slave={modbusMaster.SlaveId}, Function Code = {modbusMaster.FuncCode}," +                       $" Start Address = {modbusMaster.StartAddress}, Write Value={tbx_WriteData.Text}");                    break;                case "16":                    ushort[] dataUshort = ParseArray< ushort > (tbx_WriteData.Text);                    modbusMaster.WriteMultipleRegistersAsync(modbusMaster.SlaveId, startAddress, dataUshort);                    UpdateDataShow($"Slave={modbusMaster.SlaveId}, Function Code = {modbusMaster.FuncCode}," +                      $" Start Address = {modbusMaster.StartAddress}, Write Value={tbx_WriteData.Text}");                    break;                default:                    MessageUpdate($"Function Code = {modbusMaster.FuncCode}, does not match...",Color.Red);                    break;            }        }        catch (Exception ex)        {            UpdateDataShow($"Write exception, {ex.Message}");        }    }    /// <summary>    /// Convert string to array    /// </summary>    private T[] ParseArray<T>(string input)    {        string[] items = input.Trim('[', ']').Split(',');        return items.Select(item => (T)Convert.ChangeType(item.Trim(), typeof(T))).ToArray();    }    #endregion
    #region Timer
    /// <summary>    /// Connection status timer    /// </summary>    private void ConnectStateTimer_Tick(object sender, EventArgs e)    {        if (modbusMaster.IsConnected)        {            rsc_ConnectState.Invoke(new Action(() => {                if (connectState == States.Running)                {                    rsc_ConnectState.State = (rsc_ConnectState.State == States.None ? States.Running : States.None);                    Task.Delay(500);                }            }));        }    }
    /// <summary>    /// Address result timer    /// </summary>    private void ResultShowTimer_Tick(object sender, EventArgs e)    {        if (!modbusMaster.IsConnected || !checkBx_LoopRead.Checked) return;        try        {            dataGridView.Invoke(new Action(() =>            {                Task<bool[]> result;                Task<ushort[]> registers;                switch (modbusMaster.FuncCode)                {                    case "01":                        result = modbusMaster.ReadCoilsAsync(modbusMaster.SlaveId, modbusMaster.StartAddress, (ushort)modbusMaster.DataLength);                        UpdateDataShow<bool>(result?.Result);                        break;                    case "02":                        result = modbusMaster.ReadInputsAsync(modbusMaster.SlaveId, modbusMaster.StartAddress, (ushort)modbusMaster.DataLength);                        UpdateDataShow<bool>(result?.Result);                        break;                    case "03":                        registers = modbusMaster.ReadHoldingRegistersAsync(modbusMaster.SlaveId, modbusMaster.StartAddress, (ushort)modbusMaster.DataLength);                        UpdateDataShow<ushort>(registers?.Result);                        break;                    case "04":                        registers = modbusMaster.ReadInputRegistersAsync(modbusMaster.SlaveId, modbusMaster.StartAddress, (ushort)modbusMaster.DataLength);                        UpdateDataShow<ushort>(registers?.Result);                        break;                    case "05":                        result = modbusMaster.ReadCoilsAsync(modbusMaster.SlaveId, modbusMaster.StartAddress, (ushort)modbusMaster.DataLength);                        UpdateDataShow<bool>(result?.Result);                        break;                    case "06":                        registers = modbusMaster.ReadHoldingRegistersAsync(modbusMaster.SlaveId, modbusMaster.StartAddress, (ushort)modbusMaster.DataLength);                        UpdateDataShow<ushort>(registers.Result);                        break;                    case "15":                        result = modbusMaster.ReadCoilsAsync(modbusMaster.SlaveId, modbusMaster.StartAddress, (ushort)modbusMaster.DataLength);                        UpdateDataShow<bool>(result?.Result);                        break;                    case "16":                        registers = modbusMaster.ReadHoldingRegistersAsync(modbusMaster.SlaveId, modbusMaster.StartAddress, (ushort)modbusMaster.DataLength);                        UpdateDataShow<ushort>(registers?.Result);                        break;                    default:                        result = modbusMaster.ReadCoilsAsync(modbusMaster.SlaveId, modbusMaster.StartAddress, (ushort)modbusMaster.DataLength);                        UpdateDataShow<bool>(result?.Result);                        break;                }            }));        }        catch (Exception ex)        {            MessageUpdate($"{ex.Message}",Color.Red);        }    }
    /// <summary>    /// Data update display    /// </summary>    private void UpdateDataShow<T>(T[] array,string appendText = null)    {        for (int i = 0; i < modbusMaster.DataLength && i < 10; i++)        {            dataGridView.Rows[i].Cells[0].Value = (modbusMaster.StartAddress + i);            dataGridView.Rows[i].Cells[1].Value = array[i];        }        if (!checkBx_LoopShow.Checked) return;            MessageUpdate($"[{modbusMaster.StartAddress}][{ArrayToString<T>(array)}]", Color.Blue, "# Received ASCII>");    }    private void UpdateDataShow<T>(T result, string appendText = null)    {        MessageUpdate($"[{modbusMaster.StartAddress}][{result}]", Color.Green, "# Sent ASCII>");    }
    /// <summary>    /// Array to string conversion    /// </summary>    private string ArrayToString<T>(T[] values, string sep = " ")    {        return string.Join(sep, values.Select(r => Convert.ToString(r)).ToArray());    }    #endregion
    #region Control Enable Update    /// <summary>    /// Control enable update    /// </summary>    private void ControlEnableUpdate()    {        ControlEnabled(cbx_SerialPort, !modbusMaster.IsConnected);        ControlEnabled(cbx_BaudRate, !modbusMaster.IsConnected);        ControlEnabled(cbx_Parity, !modbusMaster.IsConnected);        ControlEnabled(cbx_DataBits, !modbusMaster.IsConnected);        ControlEnabled(cbx_StopBits, !modbusMaster.IsConnected);        ControlEnabled(nudx_SlaveId, !modbusMaster.IsConnected);        btn_OpenRTU.Invoke(new Action(() => {            btn_OpenRTU.Text = modbusMaster.IsConnected ? "Disconnect" : "Connect";        }));    }    /// <summary>    /// Control enable activation    /// </summary>    public void ControlEnabled(Control control,bool flag)    {        control.Invoke(new Action(() =>        {            control.Enabled = flag;        }));    }    #endregion
    #region Parameter Changes    private void cbx_SerialPort_SelectedIndexChanged(object sender, EventArgs e)    {        if (cbx_SerialPort == null || cbx_SerialPort.SelectedItem == null) return;            modbusMaster.protModel.PortName = cbx_SerialPort.SelectedItem.ToString();    }    private void cbx_BaudRate_SelectedIndexChanged(object sender, EventArgs e)    {        if (cbx_BaudRate == null || cbx_BaudRate.SelectedItem == null) return;        if (int.TryParse(cbx_BaudRate.SelectedItem.ToString(),out int result))        {            modbusMaster.protModel.BaudRate = result;        }        else        {            cbx_BaudRate.SelectedItem = modbusMaster.protModel.BaudRate.ToString();        }
    }    private void cbx_Parity_SelectedIndexChanged(object sender, EventArgs e)    {        if (Enum.TryParse(cbx_Parity.SelectedItem.ToString(), out Parity result))        {            modbusMaster.protModel.Parity = result;        }        else        {            cbx_Parity.SelectedItem = modbusMaster.protModel.Parity.ToString();        }    }    private void cbx_DataBits_SelectedIndexChanged(object sender, EventArgs e)    {        if (int.TryParse(cbx_DataBits.SelectedItem.ToString(), out int result))        {            modbusMaster.protModel.DataBits = result;        }        else        {            cbx_DataBits.SelectedItem = modbusMaster.protModel.DataBits.ToString();        }    }    private void cbx_StopBits_SelectedIndexChanged(object sender, EventArgs e)    {        if (Enum.TryParse(cbx_StopBits.SelectedItem.ToString(), out StopBits result))        {            modbusMaster.protModel.StopBits = result;        }        else        {            cbx_StopBits.SelectedItem = modbusMaster.protModel.StopBits.ToString();        }    }    private void cbx_FuncCode_SelectedIndexChanged(object sender, EventArgs e)    {        if (cbx_FuncCode==null)return;            modbusMaster.FuncCode = cbx_FuncCode.SelectedItem.ToString().Split('_')[0];        byte funcCode = byte.Parse(modbusMaster.FuncCode);        if (modbusMaster.IsConnected &&  funcCode== 5 || funcCode == 6 || funcCode == 15 || funcCode == 16)        {            btn_WriteData.Enabled = true;        }        else        {            btn_WriteData.Enabled = false;        }
    }    private void nudx_StartAddress_ValueChanged(object sender, EventArgs e)    {        if (nudx_StartAddress.Value < ushort.MaxValue)        {            modbusMaster.StartAddress = (ushort)nudx_StartAddress.Value;        }        else        {            nudx_StartAddress.Value = modbusMaster.StartAddress;        }    }    private void nudx_DataLength_ValueChanged(object sender, EventArgs e)    {        if (ushort.Parse(nudx_DataLength.Value.ToString()) < ushort.MaxValue)        {            modbusMaster.DataLength = (ushort)nudx_DataLength.Value;        }        else        {            nudx_DataLength.Value = modbusMaster.DataLength;        }    }    private void nudx_SlaveId_ValueChanged(object sender, EventArgs e)    {        if (byte.Parse(nudx_SlaveId.Value.ToString()) < byte.MaxValue)        {            modbusMaster.SlaveId = (byte)nudx_SlaveId.Value;        }        else        {            nudx_SlaveId.Value = modbusMaster.SlaveId;        }    }    private void nudx_WriteStartAddress_ValueChanged(object sender, EventArgs e)    {        if (ushort.Parse(nudx_WriteStartAddress.Value.ToString()) < ushort.MaxValue)        {            modbusMaster.WriteStartAddress = (ushort)nudx_WriteStartAddress.Value;        }        else        {            nudx_WriteStartAddress.Value = modbusMaster.WriteStartAddress;        }    }    private void nudx_WriteDataLength_ValueChanged(object sender, EventArgs e)    {        if (ushort.Parse(nudx_WriteDataLength.Value.ToString()) < ushort.MaxValue)        {            modbusMaster.WriteDataLength = (ushort)nudx_WriteDataLength.Value;        }        else        {            nudx_WriteDataLength.Value = modbusMaster.WriteDataLength;        }    }    #endregion
    #region Operation Message Update    /// <summary>    /// Operation message update    /// </summary>    /// <param name="data"></param>    /// <param name="color"></param>    /// <param name="appendText"></param>    /// <param name="maxLineNum"></param>    /// <param name="isAppendTime"></param>    private void MessageUpdate(string data, Color color, string appendText = null, int maxLineNum = 1000, bool isAppendTime = true)    {        // Empty data check        if (string.IsNullOrEmpty(data)) return;        // Thread-safe call        if (rtbx_Message.InvokeRequired)        {            rtbx_Message.BeginInvoke(new Action(() =>MessageUpdate(data, color,  appendText, maxLineNum, isAppendTime)));            return;        }        lock (rtbx_Message)        {            rtbx_Message.SuspendLayout(); // Pause redraw to improve performance            try            {                if (rtbx_Message.Lines.Length > maxLineNum)                {                    rtbx_Message.Clear();                }                if (isAppendTime)                {                    rtbx_Message.AppendText($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}]:");                }                if (!string.IsNullOrEmpty(appendText))                {                    rtbx_Message.AppendText($"{appendText}{Environment.NewLine}");                }                else                {                    rtbx_Message.AppendText($"{Environment.NewLine}");                }                int startIndex = rtbx_Message.TextLength;                rtbx_Message.ScrollToCaret();                rtbx_Message.SelectionStart = rtbx_Message.TextLength;                rtbx_Message.AppendText($"{data}{Environment.NewLine}");                SetTextColor(rtbx_Message, startIndex, data.Length, color);            }            finally            {                rtbx_Message.ResumeLayout(); // Resume redraw            }        }    }    /// <summary>    /// Set text color in the specified range of the textbox    /// </summary>    private void SetTextColor(RichTextBox rtb, int startIndex, int length, Color color)    {        rtb.Invoke(new Action(() => {            // Save current selection state            int originalStart = rtb.SelectionStart;            int originalLength = rtb.SelectionLength;            // Set new selection range            rtb.Select(startIndex, length);            // Change the color of the selected text            rtb.SelectionColor = color;            // Restore original selection state            rtb.Select(originalStart, originalLength);        }));    }
    #endregion
}    

(2) ModbusRtuMaster ClassThe Modbus master class implements the following functionalities:1. Connection and disconnection of communication.2. Data reading and writing.3. Methods for obtaining communication parameters.

public class ModbusRtuMaster{    private SerialPort _serialPort;    private IModbusSerialMaster _master;    public SerialPortModel protModel;    public byte SlaveId { get; set; } = 1;
    public bool IsConnected {  get; private set; } = false;    public string FuncCode { get; internal set; } = "01";    public ushort StartAddress { get; set; } = 0;    public ushort DataLength { get; set; } = 1;    public ushort WriteStartAddress { get; set; } = 0;    public ushort WriteDataLength { get; set; } = 1;    public ModbusRtuMaster()    {        protModel = new SerialPortModel();        _serialPort = new SerialPort();    }
    /// <summary>    /// Baud rate, data bits, stop bits, parity bits    /// </summary>    /// <param name="portName"></param>    /// <param name="baudRate">Baud rate</param>    /// <param name="parity">Parity bit</param>    /// <param name="dataBits">Data bits</param>    /// <param name="stopBits">Stop bits</param>    /// <returns>Connection</returns>    public bool Connect(string portName,        int baudRate = 9600, int dataBits = 8,        Parity parity = Parity.None, StopBits stopBits = StopBits.One)    {        try        {            if (_serialPort == null)                _serialPort = new SerialPort();            _serialPort.PortName = portName;            _serialPort.BaudRate = baudRate;            _serialPort.DataBits = dataBits;            _serialPort.Parity = parity;            _serialPort.StopBits = stopBits;            SetTimeout();            _serialPort.Open();            _master = ModbusSerialMaster.CreateRtu(_serialPort);            Debug.WriteLine($"Initialization successful!");            IsConnected = true;            return true;        }        catch (Exception ex)        {            Debug.WriteLine($"Initialization failed: {ex.Message}");            IsConnected = false;            return false;        }    }
    public bool Connect(SerialPortModel portModel)    {        return Connect(portModel.PortName,         portModel.BaudRate, portModel.DataBits,         portModel.Parity, portModel.StopBits);    }
    public void DisConnect()    {        _master?.Dispose();        _serialPort?.Close();        _serialPort.Dispose();        IsConnected =false;    }
    /// <summary>    /// Timeout settings    /// </summary>    public void SetTimeout(int readTimeout = 2000, int writeTimeout = 2000)    {        _serialPort.ReadTimeout = readTimeout;        _serialPort.WriteTimeout = writeTimeout;    }
    #region Reading    /// <summary>    /// Read Coil Status (Function Code 01)    /// </summary>    public Task<bool[]> ReadCoilsAsync(byte slaveId, ushort startAddress, ushort numberOfPoints)    {        try        {            Task<bool[]> result = _master.ReadCoilsAsync(slaveId, startAddress, numberOfPoints);            return result;        }        catch (Exception ex)        {            Debug.WriteLine($"Failed to read coils: {ex.Message}");            return null;        }    }
    /// <summary>    /// Read Input Status (Function Code 02)    /// </summary>    public Task<bool[]> ReadInputsAsync(byte slaveId, ushort startAddress, ushort numberOfPoints)    {        try        {            return _master.ReadInputsAsync(slaveId, startAddress, numberOfPoints);        }        catch (Exception ex)        {            Debug.WriteLine($"Failed to read input status: {ex.Message}");            return null;        }    }
    /// <summary>    /// Read Holding Registers (Function Code 03)    /// </summary>    public Task<ushort[]> ReadHoldingRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfPoints)    {        try        {            return _master.ReadHoldingRegistersAsync(slaveId, startAddress, numberOfPoints);        }        catch (Exception ex)        {            Debug.WriteLine($"Failed to read holding registers: {ex.Message}");            throw new Exception($"Failed to read holding registers: {ex.Message}");        }    }
    /// <summary>    /// Read Input Registers (Function Code 04)    /// </summary>    public Task<ushort[]> ReadInputRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfPoints)    {        try        {            return _master.ReadInputRegistersAsync(slaveId, startAddress, numberOfPoints);        }        catch (Exception ex)        {            Debug.WriteLine($"Failed to read input registers: {ex.Message}");            return null;        }    }
    #endregion
    #region Writing    /// <summary>    /// Write Single Coil (Function Code 05)    /// </summary>    public bool WriteSingleCoilAsync(byte slaveId, ushort coilAddress, bool value)    {        try        {            _master.WriteSingleCoilAsync(slaveId, coilAddress, value);            return true;        }        catch (Exception ex)        {            Debug.WriteLine($"Failed to write single coil: {ex.Message}");            return false;        }    }
    /// <summary>    /// Write Single Register (Function Code 06)    /// </summary>    public bool WriteSingleRegisterAsync(byte slaveId, ushort registerAddress, ushort value)    {        try        {            _master.WriteSingleRegisterAsync(slaveId, registerAddress, value);            return true;        }        catch (Exception ex)        {            Debug.WriteLine($"Failed to write single register: {ex.Message}");            return false;        }    }
    /// <summary>    /// Write Multiple Coils (Function Code 15)    /// </summary>    public bool WriteMultipleCoilsAsync(byte slaveId, ushort startAddress, bool[] data)    {        try        {            _master.WriteMultipleCoilsAsync(slaveId, startAddress, data);            return true;        }        catch (Exception ex)        {            Debug.WriteLine($"Failed to write multiple coils: {ex.Message}");            return false;        }    }
    /// <summary>    /// Write Multiple Registers (Function Code 16)    /// </summary>    public bool WriteMultipleRegistersAsync(byte slaveId, ushort startAddress, ushort[] data)    {        try        {            _master.WriteMultipleRegistersAsync(slaveId, startAddress, data);            return true;        }        catch (Exception ex)        {            Debug.WriteLine($"Failed to write multiple registers: {ex.Message}");            return false;        }    }
    #endregion
    #region Get Serial Port Parameters    // Baud rate array    public int[] _BaudRateArray = { 9600, 14400, 19200, 38400, 57600, 115200 };    // Data bits array    public int[] _DataBitArray = { 8, 7, 6, 5 };    public  string[] GetSerialPortArray()    {        return SerialPort.GetPortNames();    }    public Array GetBaudRateArray()    {        return _BaudRateArray;    }    public Array GetDataBitArray()    {        return _DataBitArray;    }    public string[] GetParityArray()    {        return Enum.GetNames(typeof(Parity));    }    public string[] GetStopBitArray()    {        return Enum.GetNames(typeof(StopBits)).Skip(1).ToArray();    }    #endregion
}    

Conclusion

Through this case study, we learn the basic functionalities of Modbus RTU. As a beginner, I do not want to focus on the implementation principles for now, but rather on achieving functionality. By using the built-in controls in WinForms, I have created a simple interface, and through the feedback from completing the case study, I have gained interest in learning. I hope this article can be helpful to you, serving both as a share and a backup.

Project Address:gitee.com/incodenotes/csharp-modbus

For previous case study code links, click the menu 【Case Studies

Implementing Modbus RTU Master Communication in C#ENDIf you find this article helpful, feel free to give it a thumbs up before you go!If you have any other questions, please leave a comment for discussion!You can also join the WeChat public account [Programming Notes in] to exchange and learn together!

❖ Thank you for your attention ❖

Leave a Comment