c# MOdbus rto读写串口,如何不相互影响

在C# Modbus RTU串口通信中,避免读写操作相互影响的关键在于合理的同步机制和资源管理。以下是几种有效的解决方案:

1. 使用锁机制(推荐)

基本锁实现

复制代码
public class ModbusRTUManager
{
    private readonly SerialPort _serialPort;
    private readonly object _lockObject = new object();
    
    public ModbusRTUManager(string portName, int baudRate)
    {
        _serialPort = new SerialPort(portName, baudRate)
        {
            Parity = Parity.None,
            StopBits = StopBits.One,
            DataBits = 8,
            ReadTimeout = 1000,
            WriteTimeout = 1000
        };
    }
    
    public byte[] ReadHoldingRegisters(byte slaveId, ushort startAddress, ushort count)
    {
        lock (_lockObject)
        {
            if (!_serialPort.IsOpen)
                _serialPort.Open();
                
            // 发送读取请求
            var request = CreateReadHoldingRegistersRequest(slaveId, startAddress, count);
            _serialPort.Write(request, 0, request.Length);
            
            // 读取响应
            return ReadResponse(slaveId, 3); // 3是功能码
        }
    }
    
    public void WriteSingleRegister(byte slaveId, ushort address, ushort value)
    {
        lock (_lockObject)
        {
            if (!_serialPort.IsOpen)
                _serialPort.Open();
                
            // 发送写入请求
            var request = CreateWriteSingleRegisterRequest(slaveId, address, value);
            _serialPort.Write(request, 0, request.Length);
            
            // 读取响应(如果需要)
            ReadResponse(slaveId, 6); // 6是功能码
        }
    }
    
    private byte[] ReadResponse(byte expectedSlaveId, byte expectedFunctionCode)
    {
        // 实现响应读取逻辑,包括CRC校验
        // ...
    }
}

2. 使用SemaphoreSlim(支持异步)

复制代码
public class AsyncModbusRTUManager
{
    private readonly SerialPort _serialPort;
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
    
    public async Task<byte[]> ReadHoldingRegistersAsync(byte slaveId, ushort startAddress, ushort count)
    {
        await _semaphore.WaitAsync();
        try
        {
            if (!_serialPort.IsOpen)
                _serialPort.Open();
                
            // 清除输入缓冲区
            _serialPort.DiscardInBuffer();
            
            // 发送请求
            var request = CreateReadHoldingRegistersRequest(slaveId, startAddress, count);
            await WriteAsync(request);
            
            // 读取响应
            return await ReadResponseAsync(slaveId, 3);
        }
        finally
        {
            _semaphore.Release();
        }
    }
    
    private async Task WriteAsync(byte[] data)
    {
        await Task.Run(() => _serialPort.Write(data, 0, data.Length));
    }
    
    private async Task<byte[]> ReadResponseAsync(byte expectedSlaveId, byte expectedFunctionCode)
    {
        // 异步读取实现
        // ...
    }
}

3. 使用读写锁(ReadWriteLockSlim)

复制代码
public class ReadWriteModbusRTUManager
{
    private readonly SerialPort _serialPort;
    private readonly ReaderWriterLockSlim _lockSlim = new ReaderWriterLockSlim();
    
    // 用于读取操作(允许多个读取,但实际串口需要独占)
    public byte[] ReadData(byte slaveId, ushort address, ushort count)
    {
        _lockSlim.EnterWriteLock(); // 串口读取也需要写锁,因为涉及实际的IO操作
        try
        {
            // 读取操作
            return PerformReadOperation(slaveId, address, count);
        }
        finally
        {
            _lockSlim.ExitWriteLock();
        }
    }
    
    // 用于写入操作
    public void WriteData(byte slaveId, ushort address, ushort value)
    {
        _lockSlim.EnterWriteLock();
        try
        {
            // 写入操作
            PerformWriteOperation(slaveId, address, value);
        }
        finally
        {
            _lockSlim.ExitWriteLock();
        }
    }
}

4. 完整的线程安全实现示例

复制代码
public class ThreadSafeModbusRTU : IDisposable
{
    private readonly SerialPort _serialPort;
    private readonly object _syncRoot = new object();
    private bool _disposed = false;
    
    public ThreadSafeModbusRTU(string portName, int baudRate = 9600)
    {
        _serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One)
        {
            ReadTimeout = 1000,
            WriteTimeout = 1000,
            Handshake = Handshake.None
        };
    }
    
    public ModbusResult ReadHoldingRegisters(byte slaveId, ushort startAddress, ushort count)
    {
        lock (_syncRoot)
        {
            EnsureNotDisposed();
            EnsurePortOpen();
            
            try
            {
                _serialPort.DiscardInBuffer();
                _serialPort.DiscardOutBuffer();
                
                // 1. 发送请求
                var request = BuildReadRequest(slaveId, startAddress, count);
                _serialPort.Write(request, 0, request.Length);
                
                // 2. 读取响应
                var response = ReadExpectedBytes(5 + count * 2); // 基础5字节 + 数据
                
                // 3. 验证响应
                if (!ValidateResponse(response, slaveId, 0x03))
                    return ModbusResult.CreateErrorResult("响应验证失败");
                
                // 4. 解析数据
                var data = ParseRegisterData(response, count);
                return ModbusResult.CreateSuccessResult(data);
            }
            catch (TimeoutException ex)
            {
                return ModbusResult.CreateErrorResult($"读取超时: {ex.Message}");
            }
            catch (Exception ex)
            {
                return ModbusResult.CreateErrorResult($"读取失败: {ex.Message}");
            }
        }
    }
    
    public ModbusResult WriteSingleRegister(byte slaveId, ushort address, ushort value)
    {
        lock (_syncRoot)
        {
            EnsureNotDisposed();
            EnsurePortOpen();
            
            try
            {
                _serialPort.DiscardInBuffer();
                _serialPort.DiscardOutBuffer();
                
                // 发送写入请求
                var request = BuildWriteRequest(slaveId, address, value);
                _serialPort.Write(request, 0, request.Length);
                
                // 读取响应
                var response = ReadExpectedBytes(8); // 写入响应固定8字节
                
                if (!ValidateResponse(response, slaveId, 0x06))
                    return ModbusResult.CreateErrorResult("写入响应验证失败");
                
                return ModbusResult.CreateSuccessResult();
            }
            catch (Exception ex)
            {
                return ModbusResult.CreateErrorResult($"写入失败: {ex.Message}");
            }
        }
    }
    
    private void EnsurePortOpen()
    {
        if (!_serialPort.IsOpen)
            _serialPort.Open();
    }
    
    private void EnsureNotDisposed()
    {
        if (_disposed)
            throw new ObjectDisposedException("ModbusRTU已被释放");
    }
    
    public void Dispose()
    {
        lock (_syncRoot)
        {
            if (!_disposed)
            {
                _serialPort?.Close();
                _serialPort?.Dispose();
                _disposed = true;
            }
        }
    }
}

5. 使用建议

  1. 单一职责原则:每个串口由一个管理器实例负责

  2. 超时设置:合理设置读写超时,避免线程长时间阻塞

  3. 缓冲区清理:每次通信前清理缓冲区

  4. 异常处理:确保异常情况下锁能被正确释放

  5. 资源释放:正确实现IDisposable接口

6. 调用示例

复制代码
using (var modbus = new ThreadSafeModbusRTU("COM1", 9600))
{
    // 读取操作 - 自动同步,不会相互干扰
    var result1 = modbus.ReadHoldingRegisters(1, 0, 10);
    
    // 写入操作 - 会等待读取完成后执行
    var result2 = modbus.WriteSingleRegister(1, 5, 100);
    
    // 并发调用也会自动序列化
    Task.WaitAll(
        Task.Run(() => modbus.ReadHoldingRegisters(1, 0, 5)),
        Task.Run(() => modbus.WriteSingleRegister(1, 6, 200))
    );
}

这种设计确保了串口读写的原子性,避免了多个线程同时访问串口导致的通信混乱。

相关推荐
代码雕刻家3 小时前
2.4.蓝桥杯-分巧克力
算法·蓝桥杯
Ulyanov3 小时前
顶层设计——单脉冲雷达仿真器的灵魂蓝图
python·算法·pyside·仿真系统·单脉冲
小酒窝.4 小时前
【多线程】多线程打印1~100
java·多线程
码云数智-园园4 小时前
使用 C# 将 PowerPoint 演示文稿高效转换为 PDF 格式
c#
PfCoder4 小时前
WinForm真入门(23)---PictureBox 控件详细用法
开发语言·windows·c#·winform
智者知已应修善业5 小时前
【查找字符最大下标以*符号分割以**结束】2024-12-24
c语言·c++·经验分享·笔记·算法
91刘仁德5 小时前
c++类和对象(下)
c语言·jvm·c++·经验分享·笔记·算法
diediedei5 小时前
模板编译期类型检查
开发语言·c++·算法
阿杰学AI6 小时前
AI核心知识78——大语言模型之CLM(简洁且通俗易懂版)
人工智能·算法·ai·语言模型·rag·clm·语境化语言模型