C#有人IO模块USR-IO808的完整指南

概述

有人USR-IO808是一款8通道数字输入输出模块,支持Modbus RTU协议,通过RS485接口进行通信。本文将介绍如何使用C#语言通过串口操作该模块,实现数字输入(DI)状态的读取和数字输出(DO)的控制。

硬件连接与配置

在开始编程前,请确保:

  1. USR-IO808模块已正确供电

  2. RS485接口与计算机正确连接(通常通过USB转485转换器)

  3. 模块的波特率、数据位、停止位和校验位与软件设置一致

  4. 模块地址已正确设置(默认为0x11)

核心代码解析

1. 数据包构建类(DataPacketIO808)

public class DataPacketIO808
{
    // 模块默认地址
    public const byte AddressDefault = 0x11;
    
    // 功能码定义
    public enum EfunctionNum
    {
        read0x02 = 0x02,  // 读取DI状态
        read0x01 = 0x01,  // 读取DO状态
        read0x05 = 0x05,  // 控制DO状态
    }
    
    // 控制状态定义
    public enum EcontrolStatus
    {
        open = 0xFF00,   // 开启DO
        close = 0x0000,  // 关闭DO
    }
    
    // DI/DO寄存器地址定义
    public enum EDIDOAddress
    {
        DI1 = 0x0020, DI2 = 0x0021, DI3 = 0x0022, DI4 = 0x0023,
        DI5 = 0x0024, DI6 = 0x0025, DI7 = 0x0026, DI8 = 0x0027,
        DO1 = 0x0000, DO2 = 0x0001, DO3 = 0x0002, DO4 = 0x0003,
        DO5 = 0x0004, DO6 = 0x0005, DO7 = 0x0006, DO8 = 0x0007,
    }
    
    // 构建读取DI状态的数据包
    public static DataPacketIO808 GetDIStatus(byte address)
    {
        return BuildReadParam(address, EfunctionNum.read0x02, EDIDOAddress.DI1, 8);
    }
    
    // 构建读取DO状态的数据包
    public static DataPacketIO808 GetDOStatus(byte address)
    {
        return BuildReadParam(address, EfunctionNum.read0x01, EDIDOAddress.DO1, 8);
    }
    
    // 构建开启DO的数据包
    public static DataPacketIO808 OpenDO(byte address, ushort DONum)
    {
        return BuildControlParam(address, EfunctionNum.read0x05, DONum, EcontrolStatus.open);
    }
    
    // 构建关闭DO的数据包
    public static DataPacketIO808 CloseDO(byte address, ushort DONum)
    {
        return BuildControlParam(address, EfunctionNum.read0x05, DONum, EcontrolStatus.close);
    }
    
    // 将数据包转换为字节数组
    public byte[] ToBytes()
    {
        lock (Locker)
        {
            List<byte> bts = new List<byte>
            {
                Serialaddress,
                functionNum,
            };
            
            // 添加地址数据(大端模式)
            byte[] startArr = BitConverter.GetBytes(startAddress);
            Array.Reverse(startArr, 0, startArr.Length);
            bts.AddRange(startArr);
            
            // 添加参数数据(大端模式)
            byte[] controlsta = BitConverter.GetBytes(param);
            Array.Reverse(controlsta, 0, controlsta.Length);
            bts.AddRange(controlsta);
            
            // 计算并添加CRC校验
            byte[] checkList = CalcChecksum(bts);
            bts.AddRange(checkList);
            
            return bts.ToArray();
        }
    }
    
    // CRC校验计算
    public byte[] CalcChecksum(List<byte> content)
    {
        UInt16 wCrc = 0xFFFF;
        for (int i = 0; i < content.Count; i++)
        {
            wCrc ^= Convert.ToUInt16(content[i]);
            for (int j = 0; j < 8; j++)
            {
                if ((wCrc & 0x0001) == 1)
                {
                    wCrc >>= 1;
                    wCrc ^= 0xA001;
                }
                else
                {
                    wCrc >>= 1;
                }
            }
        }
        
        byte[] arr = BitConverter.GetBytes((ushort)wCrc);
        return arr;
    }
}

2. 数据解析类(DataParseIO808)

public class DataParseIO808
{
    // 解析返回数据
    public static bool parseData(byte[] param, byte address, byte funNo)
    {
        if (param[0] == address && param[1] == funNo)
        {
            bool crcStatus = CRC_Check(param);
            if (crcStatus)
            {
                return true;
            }
        }
        return false;
    }
    
    // CRC校验检查
    public static bool CRC_Check(byte[] byteData)
    {
        bool Flag = false;
        byte[] CRC = new byte[2];

        UInt16 wCrc = 0xFFFF;
        for (int i = 0; i < byteData.Length - 2; i++)
        {
            wCrc ^= Convert.ToUInt16(byteData[i]);
            for (int j = 0; j < 8; j++)
            {
                if ((wCrc & 0x0001) == 1)
                {
                    wCrc >>= 1;
                    wCrc ^= 0xA001;
                }
                else
                {
                    wCrc >>= 1;
                }
            }
        }

        CRC[1] = (byte)((wCrc & 0xFF00) >> 8);
        CRC[0] = (byte)(wCrc & 0x00FF);
        if (CRC[1] == byteData[byteData.Length - 1] && CRC[0] == byteData[byteData.Length - 2])
        {
            Flag = true;
        }
        return Flag;
    }
}

3. 应用层方法

// 获取所有DI状态
public void GetALLDIStatus(out bool oneStatus, out bool twoStatus, out bool threeStatus, 
                          out bool fourStatus, out bool fiveStatus, out bool sixStatus, 
                          out bool sevenStatus, out bool eightStatus)
{
    oneStatus = twoStatus = threeStatus = fourStatus = 
    fiveStatus = sixStatus = sevenStatus = eightStatus = false;
    
    try
    {
        oneStatus = GetDIStatus(1);
        twoStatus = GetDIStatus(2);
        threeStatus = GetDIStatus(3);
        fourStatus = GetDIStatus(4);
        fiveStatus = GetDIStatus(5);
        sixStatus = GetDIStatus(6);
        sevenStatus = GetDIStatus(7);
        eightStatus = GetDIStatus(8);
    }
    catch (Exception ex)
    {
        // 异常处理
        Console.WriteLine("获取DI状态时发生错误: " + ex.Message);
    }
}

// 获取所有DO状态
public void GetALLDOStatus(out bool oneStatus, out bool twoStatus, out bool threeStatus, 
                          out bool fourStatus, out bool fiveStatus, out bool sixStatus, 
                          out bool sevenStatus, out bool eightStatus)
{
    oneStatus = twoStatus = threeStatus = fourStatus = 
    fiveStatus = sixStatus = sevenStatus = eightStatus = false;
    
    try
    {
        oneStatus = GetDOStatus(1);
        twoStatus = GetDOStatus(2);
        threeStatus = GetDOStatus(3);
        fourStatus = GetDOStatus(4);
        fiveStatus = GetDOStatus(5);
        sixStatus = GetDOStatus(6);
        sevenStatus = GetDOStatus(7);
        eightStatus = GetDOStatus(8);
    }
    catch (Exception ex)
    {
        // 异常处理
        Console.WriteLine("获取DO状态时发生错误: " + ex.Message);
    }
}

完整使用示例

// 创建串口对象
SerialPort serialPort = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One);
serialPort.Open();

// 读取所有DI状态
bool di1, di2, di3, di4, di5, di6, di7, di8;
GetALLDIStatus(out di1, out di2, out di3, out di4, out di5, out di6, out di7, out di8);

Console.WriteLine("DI状态:");
Console.WriteLine($"DI1: {di1}, DI2: {di2}, DI3: {di3}, DI4: {di4}");
Console.WriteLine($"DI5: {di5}, DI6: {di6}, DI7: {di7}, DI8: {di8}");

// 控制DO1开启
DataPacketIO808 openPacket = DataPacketIO808.OpenDO(0x11, 1);
byte[] openData = openPacket.ToBytes();
serialPort.Write(openData, 0, openData.Length);

// 等待并读取响应
Thread.Sleep(100);
byte[] response = new byte[serialPort.BytesToRead];
serialPort.Read(response, 0, response.Length);

// 验证响应
bool success = DataParseIO808.parseData(response, 0x11, 0x05);
if (success)
{
    Console.WriteLine("DO1开启成功");
}

// 关闭串口
serialPort.Close();

注意事项

  1. 线程安全:使用lock确保在多线程环境下串口操作的线程安全

  2. 异常处理:妥善处理可能出现的串口通信异常

  3. 超时设置:合理设置读取超时,避免程序无响应

  4. CRC校验:务必验证CRC确保数据完整性

  5. 地址匹配:确保程序中使用的模块地址与实际硬件设置一致

常见问题排查

  1. 通信失败:检查硬件连接、串口参数和模块地址

  2. CRC校验错误:检查数据传输过程中是否受到干扰

  3. 无响应:检查模块供电和通信线路

总结

通过上述C#代码,我们可以方便地操作有人USR-IO808模块,实现数字输入状态的读取和数字输出状态的控制。代码采用了面向对象的设计,提供了良好的封装性和可扩展性,可以根据实际需求进一步扩展功能。

希望本文对您使用USR-IO808模块有所帮助!

更多推荐