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

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

相关推荐
小蒋学算法1 小时前
算法-交替删除操作后最后剩下的整数-跟着灵茶山艾府学算法
数据结构·算法
荆棘鸟智能1 小时前
林业遥感长势评估怎么做?从NDVI时序分析到LiDAR蓄积量回归
人工智能·算法·智慧林业
hhb_6182 小时前
AIRAGDebug:一键定位RAG链路异常
人工智能·python·算法
朝朝辞暮i3 小时前
C++ 第 6 课:switch —— 多状态选择
开发语言·c++·算法
苏打豆3 小时前
Pinocchio源码阅读——刚体运动物理量空间变换及求导
c++·线性代数·算法·矩阵·机器人
波力海苔夹心脆6753 小时前
C# 值类型与引用类型详解:存储位置、赋值机制、参数传递、相等比较、装箱拆箱与常见陷阱
开发语言·jvm·经验分享·笔记·c#·.net
FluxArt3 小时前
GPT Image 2.5 中文提示词教程:Flux Art 可复用模板
前端·gpt·算法
估值探索者4 小时前
【Python量化策略实战 #02】多因子合成mom和vol两因子打分合成
开发语言·c++·python·数据挖掘·c#
思麟呀4 小时前
位运算:从一个整数到一个集合
c语言·数据结构·c++·算法
阜阳山君GEO4 小时前
豆包 SEED 新版本 GEO 优化:信源金字塔、打分体系与避坑落地指南
人工智能·算法