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))
    );
}

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

相关推荐
会编程的土豆4 分钟前
【数据结构与算法】 树
数据结构·算法
LSL666_12 分钟前
Redis值数据类型——hash
redis·算法·哈希算法·数据类型
喵喵蒻葉睦14 分钟前
力扣 hot100 滑动窗口最大值 单调双端队列 java 简单题解
java·数据结构·算法·leetcode·双端队列·滑动窗口·队列
样例过了就是过了16 分钟前
LeetCode热题100 搜索二维矩阵
数据结构·c++·算法·leetcode·矩阵
2401_8319207421 分钟前
C++与Qt图形开发
开发语言·c++·算法
Shining059624 分钟前
AI 编译器系列(四)《AI 编译器中的后端优化》
linux·服务器·人工智能·线性代数·算法·triton·ai编译器
像污秽一样41 分钟前
算法设计与分析-习题8.1
数据结构·算法·dfs·dp
飞天狗11143 分钟前
最小生成树算法
算法
H_老邪1 小时前
贪心算法的应用
算法·ios·贪心算法
葳_人生_蕤1 小时前
Hot100——739.每日温度
数据结构·算法