在学习RS485通信协议之前,首先简单了解一下OSI模型。<这里只做一下简单叙述,我们主要对RS485通信做出介绍,具体了解OSI模型可以搜索相关文章。>


1.OSI七层模型 
OSI(Open System  Interconnect)七层模型是一种将计算机网络通信协议划分为七个不同层次的标准化框架。每一层都负责不同的功能,从物理连接到应用程序的处理。这种模型有助于不同的系统之间进行通信时,更好地理解和管理网络通信的过程。
OSI定义了网络互连的七层框架(物理层、数据链路层 、网络层、传输层、会话层、表示层、应用层),即ISO开放互连系统参考模型。

RS485在OSI模型中处于物理层,只定义电气接口规范:电压电平、差分信号、驱动 / 接收电路。

常见跑在 RS485 上的协议:Modbus-RTU、Modbus-ASCII、自定义串口协议,这些属于第 2 层 数据链路层


2.RS485的简单介绍

RS485的底层依赖串口通信,是由RS232增加485转换芯片,将TTL电平转换成485电平进行通信。

信号:RS485标准规定采用差分信号(差分信号是指用两根线的电平差表示0、1)进行数据传输,两线间的电压差为+2v到+6v表示逻辑“1”两线间的电压差为-2v到-6v表示逻辑“0”.

如:A>B为逻辑0,B>A为逻辑1

使用差分信号能有效地减少噪声信号的干扰(因为如果受到干扰,两根线都会受到相同的干扰,所以受到干扰后这两根线的电平差仍然能正确的表示0或1),延长通信距离,RS485的通信距离可以达到1500m;RS485接口信号的电平比RS232降低了,所以不易损坏接口电路的芯片,且该电平与TTL电平兼容,可方便地与TTL电路连接。如图,差分信号的好处:即使电平受到干扰,但差值基本不变。

接口:采用一条双绞线电缆作总线,将各个节点串接起来,从总线到每个节点的引出线长度应尽量短,以便使引出线中的反射信号对总线信号的影响最低。因为采用两线制(两根双绞线),数据的发送和接收都要使用这对差分信号线,发送和接收不能同时进行,所以只能采用半双工的方式工作。

一主多从的拓扑结构:

半双工方式,不能同时发送和接收:

2.1 RS485的优势
        ①长距离传输与多节点连接:RS485是一种差分传输的通信方式,支持长距离传输,通常可以达到数千米(具体距离取决于电缆类型、质量和环境条件)。

        ②高速数据传输:RS485通讯具有较高的数据传输速率,能够支持实时数据的快速传输,满足工业自动化等应用场景对数据传输速度的要求。

        ③抗干扰能力强:RS485采用差分信号传输方式,能够有效抵抗外界电磁干扰,保证数据通信的稳定性和可靠性。

2.3 TD21S485H

接线方面TX对应TX RX对应RX不再赘述,CON作为使能引脚,需要不断的改变其电平状态来控制数据的收发,如上图,低电平时发送数据,高电平时接收数据。

电气原理图

(EN使能引脚为低电平时,CON引脚为高电平,即是接收功能
   EN使能引脚为高电平时,CON引脚为低电平,即是发送功能)

2.4 代码部分

void Task_485GpioInit(void)//初始化使能引脚
{

    GPIO_InitTypeDef GPIO_InitStruct = {0};
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET);//低电平默认接收数据
    GPIO_InitStruct.Pin =  GPIO_PIN_14 
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    xGpio485_UART3_En.pPort = GPIOB;
    xGpio485_UART3_En.wPin = GPIO_PIN_14;

}
void Task_485Init(void)//485任务初始化
{
    Task_485GpioInit();//使能引脚初始化
    USART485_Server.lBitrate = 115200;//波特率
    USART485_Server.px485En = &xGpio485_UART3_En;
    USART485_Server.pFunc = &Task_485Server_RxData;//回调函数
    Usart_init(&USART485_Server);//配置参数
}
void Task_485Server_TxData(STR_UsartStruct *pxUsart, uint8_t * data, uint16_t wlen)
{

    Usart_txData(pxUsart, data, wlen);
}//485发送数据 底层是串口
void Usart_txData(STR_UsartStruct *pxUsart, uint8_t *data, uint16_t len)
{   
    if(pxUsart->px485En != NULL)
    {
        HAL_GPIO_WritePin(pxUsart->px485En->pPort, pxUsart->px485En->wPin, PIN_SET_HIGH);
    }//EN使能引脚拉高 CON被拉低 切换至发送数据模式

    pxUsart->pxDriver->Send(data, len);

    while(pxUsart->pxDriver->GetStatus().tx_busy == 1);

    if(pxUsart->px485En != NULL)
    {
        HAL_GPIO_WritePin(pxUsart->px485En->pPort, pxUsart->px485En->wPin, PIN_SET_LOW);
    }
}

3.MODBUS RTU协议<以上位机读取MCU从站输入寄存器为例>

3.1标准帧发送结构(固定四段)

字段 长度 (字节) 说明
从站地址 (Slave ID) 1 0~247;0 = 广播地址(从站不回复)
功能码 (Function Code) 1 读 / 写操作;0x80 + 原码 = 异常响应
数据域 (Data) N (0~252) 随功能码变化,请求 / 响应有效载荷
CRC16 校验 2 CRC 低字节在前,高字节在后

总长度计算公式

帧长 = 1(地址) + 1(功能码) + N(数据) + 2(CRC)

功能码 0x04 读输入寄存器(常用)

响应帧结构
字段 长度 说明
从站地址 1 接收的主机请求从站 ID
0x03 1 原功能码不变
数据字节长度 L 1 后续寄存器数据总字节数 = 寄存器数量 × 2
寄存器数据 L 每个寄存器 2 字节,高字节在前
CRC 校验 2 CRC 低字节、CRC 高字节

示例

主机请求:01 04 00 00 00 02 【CRC校验】C4 0B //读ID为1地址从0开始的两个输入寄存器的数据

从站寄存器 0: 0x1234,寄存器 1:0x5678回应帧:01 04 04 12 34 56 78 [CRC低][CRC高]

3.2 代码部分

void Func_mbGetRxData_Rtu(STR_ModBusDataTrans *pData)
{
    uint8_t *pRxData = pData->nRxData;//接收到的上位机的读取输入寄存器的数据帧
    uint16_t wLen = pData->nRxLen;//数据帧长度

    if(bMbInitFlag)
    {
        if(pRxData[0] ==  MODBUS_DATA_ID)//MCU从站ID
        {
            pData->nStartPostion = MB_RTU;

            if(wLen > 3)
            {
                uint16_t wCrcTemp  = (pRxData[wLen - 2] << 8) + pRxData[wLen - 1];//CRC
                uint16_t wCrc  = Func_mbCrc16Check(pRxData, wLen - 2);//计算数据帧的CRC
                eMbError status = MB_NOERR;

                if(wCrc == wCrcTemp)//CRC校验通过
                {
                    switch(pRxData[1])
                    {
                        case MODBUS_COMMAND_RXHOLDREG://读保持寄存器
                            status = Func_mbReadReg(MODBUS_HOLD_REGISTER, pData, xMbConfig_RTU);
                            break;

                        case MODBUS_COMMAND_RXINPUTREG://读输入寄存器
                            status = Func_mbReadReg(MODBUS_INPUT_REGISTER, pData, xMbConfig_RTU);
                            break;

                        case MODBUS_COMMAND_TXONEREG://写单个寄存器
                            status = Func_mbWriteHoldOneReg(pData, xMbConfig_RTU);
                            break;

                        case MODBUS_COMMAND_TXMULREG://写多个寄存器
                            status = Func_mbWriteHoldMulReg(pData, xMbConfig_RTU);
                            break;
                    }
                }

                if(status != MB_NOERR)//数据帧错误 传回错误信息
                {
                    uint8_t nCnt = 0;
                    uint8_t nMbErrorArry[5] = {0};
                    nMbErrorArry[nCnt++] =  0x31;
                    nMbErrorArry[nCnt++] = (pRxData[1] | 0x80);
                    nMbErrorArry[nCnt++] = status;
                    wCrc = Func_mbCrc16Check(nMbErrorArry, nCnt);
                    nMbErrorArry[nCnt++] = wCrc >> 8;
                    nMbErrorArry[nCnt++] = wCrc;
                    Func_mbTxData_Rtu(nMbErrorArry, nCnt); //调用发送函数
                }
                else//校验通过 发送回应报文
                {
                    wCrc = Func_mbCrc16Check(pData->nTxData, pData->nTxLen);
                    pData->nTxData[pData->nTxLen++] = ((wCrc & 0xFF00) >> 8);
                    pData->nTxData[pData->nTxLen++] = (wCrc & 0x00FF);
                    Func_mbTxData_Rtu(pData->nTxData, pData->nTxLen);
                }
            }
        }
    }
}

供学习和理解参考,由于工作原因,代码不能全部提供。其他功能码分析和上面同理,只要直到发送帧格式以及对应的响应帧格式都可以按照以上分析的方式来分析。个人观点,如有错误请指出!



Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐