穿西装的强子 发表于 2025-6-19 23:47

【灵动微电子MM32F0121测评】6、手搓modbus协议从机(一)

本帖最后由 穿西装的强子 于 2025-6-20 09:55 编辑

1、ModbusRTU协议帧格式
字段长度说明
从机地址1Byte1-247,0作为广播
功能码1Byte0x03读单个或多个数寄存器据,0x06写单个寄存器数据,0x10写多个寄存器数据
数据位NByte寄存器数据,和读写数据长度有关
CRC校验2ByteCRC-16,低位在前,高位在后

2、寄存器格式
功能码 0x01:读取线圈数据;0x02: 读取离散输入数据;
0x03:读取N个保持寄存器数据;
0x04: 读取N个输入寄存器;
0x05: 写入单个线圈;
0x06:写入单个数据;
0x0F: 写入多个线圈;
0x10:写入多个数据
一般只用到:0x03/0x06/0x10这三个寄存器比较多,暂时以这三个寄存器进行手搓;
读写指令的数据格式与返回格式数据举例
读取 N个数据0x03发送报文:01 03 01 91 00 01 D4 1B 反馈报文:01 03 02 00 3C B8 55主机->从机数据:
报文010301 9100 01D4 1B
说明地址功能码寄存器地址读寄存器个数CRC校验
从机->主机数据:
报文01030200 1979 8E
说明地址功能码返回字节数寄存器值CRC校验

写入单个数据 0x06发送报文:01 06 01 91 0C 80 DD 7B 反馈报文:01 06 01 91 0C 80 DD 7B主机->从机数据:
报文010601 910C 80DD 7B
说明地址功能码寄存器地址写入数据CRC校验
从机->主机数据:
报文010601 910C 80DD 7B
说明地址功能码寄存器地址写入数据CRC校验

写入多个数据 0x10发送报文:01 10 01 46 00 04 08 00 00 00 28 00 0000 29 1C 14 反馈报文:01 10 01 46 00 04 21 E3主机->从机数据:
报文011001 4600 040800 0000 2800 0000 291C 14
说明地址功能码寄存器地址寄存器数字节数写入内容写入内容写入内容写入内容CRC校验
从机->主机数据:
报文010601 910C 80DD 7B
说明地址功能码寄存器地址写入数据CRC校验



3、CRC校验用直接计算法


#include <stdint.h>



uint16_t calculate_crc_direct(uint8_t *data, uint16_t len) {

    uint16_t crc = 0xFFFF;// 初始值

    uint16_t poly = 0xA001;// Modbus CRC多项式

    while (len--) {

      crc ^= *data++;// XOR输入数据与当前CRC的最低字节

      for (uint8_t i = 8; i != 0; i--) {// 对每个位进行处理

            if ((crc & 0x0001) != 0) {// 如果最低位是1,则右移一位并与多项式XOR

                crc >>= 1;

                crc ^= poly;

            } else {// 如果最低位是0,则只右移一位

                crc >>= 1;

            }

      }

    }

    return crc;

}

uint8_t data[] = {0x01, 0x03, 0x00, 0x13, 0x00, 0x02};// Modbus数据帧示例(设备地址,功能码,起始地址,数量)

    uint16_t crc;

    crc = calculate_crc(data, sizeof(data));// 使用计算CRC
串口不定长数据接收,使用串口idle功能,这个功能是在串口空闲的时候一直触发,所以要做处理,不能直接在中断检测后直接发信号量,判定数据长度>0和处理已经处理完成标志的时候传输信号量串口发送中断不占用任务运行时间
void USART1_IRQHandler(void)
{
    uint8_t RxData = 0;

      if ((RESET != USART_GetITStatus(USART1, USART_IT_PE)) ||
      (RESET != USART_GetITStatus(USART1, USART_IT_ERR)))
    {
      USART_ReceiveData(USART1);
    }

    if (RESET != USART_GetITStatus(USART1, USART_IT_RXNE))
    {
                USART_ClearITPendingBit(USART1, USART_IT_RXNE);
      RxData = USART_ReceiveData(USART1);
                USART_RxStruct.Buffer = RxData;
//                USART_SendData(USART1, RxData);
      
    }
      if (RESET != USART_GetITStatus(USART1, USART_IT_TXE))
    {
      if (1 == USART_TxStruct.CompleteFlag)
      {
            USART_SendData(USART1, USART_TxStruct.Buffer);

            if (USART_TxStruct.CurrentCount == USART_TxStruct.Length)
            {
                USART_TxStruct.CompleteFlag = 0;
                              USART_TxStruct.CurrentCount = 0;

                USART_ITConfig(USART1, USART_IT_TXE, DISABLE);
            }
      }
    }
      if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
      {
                              
                // 检测到空闲中断,表示一帧数据接收完成

                USART_ReceiveData(USART1); // 读取数据寄存器清除中断
               
                USART_ClearITPendingBit(USART1, USART_IT_IDLE);
                if( USART_RxStruct.Length > 0 && USART_RxStruct.CompleteFlag == 0)
                {
                        USART_RxStruct.CompleteFlag = 1;
                        xSemaphoreGive(xBinarySemaphore);
                }

      
      }


}



先创建一个modbus的任务,用来处理modbus协议

xTaskCreate( (TaskFunction_t) Task_modbus,                              // 任务函数
                                 (const char *) "Task_modbus",                        // 任务名
                                 (uint16_t                ) MODBUS_STK_SIZE,                        // 任务堆栈大小
                                 (void*                        ) NULL,                                                // 传递给任务函数的参数
                                 (UBaseType_t   ) MODBUS_TASK_PRIO,                        // 任务优先级
                                 (TaskHandle_t* ) PrintfTask_Handler);                // 任务句柄         modbus的crc校验功能函数,检查到非本id的和长度低于6的直接返回
对于crc校验错误的,是需要返回错误码的,这里暂时没处理,后面再统一处理
void ModBus_Protocol_Process(USART_RxTx_TypeDef *usart,USART_RxTx_TypeDef *tx)
{
      uint8_t id = usart->Buffer;
      if( id != DEVICES_ID || usart->Length < 6)
                return;
      uint16_t crc_check = calculate_crc_direct(usart->Buffer,usart->Length-2);
      uint16_t crc_data = (usart->Buffer<<8)|(usart->Buffer);
      if( crc_check == crc_data)      // 校验成功
      {
                ModBus_Register_Process(usart,tx);
               
      }
      else// 校验错误返回
      {
               
      }
}
寄存器功能选择函数
void ModBus_Register_Process(USART_RxTx_TypeDef *usart,USART_RxTx_TypeDef *tx)
{
      uint8_t cmd = usart->Buffer;
      switch(cmd){
                case 0x03:
                        ModBus_Register_MultiRead(usart,tx);
                        break;
                case 0x06:
                        ModBus_Register_SingleWrite(usart,tx);
                        break;
                case 0x10:
                        ModBus_Register_MultiWrite(usart,tx);
                        break;
                default:
                        break;
      }
}


0x03读寄存器处理函数
对寄存器地址的获取,寄存器长度的获取
判定长度是否超过了buffer空间,超过要返回错误码,此处也暂时未处理
未超过的时候返回寄存器数据,将modbus的id,0x03寄存器,数据长度,数据和校验一起上报
将tx->CompleteFlag 置1并使能TX中断发送,通过中断将数据发生出去
void ModBus_Register_MultiRead(USART_RxTx_TypeDef *usart,USART_RxTx_TypeDef *tx)
{
      uint16_t register_addr = (usart->Buffer<<8)|(usart->Buffer);// 寄存器地址
      uint16_t register_len = (usart->Buffer<<8)|(usart->Buffer);// 寄存器数据长度
      if( (register_addr + register_len) >= 128)
      {
               
      } else {
                tx->Length = 0;
                tx->Buffer = usart->Buffer;
                tx->Buffer = usart->Buffer;
                tx->Buffer = register_len*2;
                for( uint16_t i = 0; i < register_len;i++)
                {
                        tx->Buffer = (modbus_data>>8);
                        tx->Buffer = modbus_data;
                }
                uint16_t crc = calculate_crc_direct(tx->Buffer,tx->Length);
                tx->Buffer = crc>>8;
                tx->Buffer = crc;
                tx->CompleteFlag = 1;
                USART_ITConfig(USART2, USART_IT_TXE, ENABLE);
               
      }
      
      
      
}以下是测试结果
从地址为100的寄存器开始读20个,modbus寄存器数据初始化是128个,读出数据与内容一致,验证通过
for(uint16_t i = 0; i< 128; i++)
      {
                modbus_data = i+1;
      }

















AdaMaYun 发表于 2025-7-31 17:54

ModbusRTU协议帧格式是基础
页: [1]
查看完整版本: 【灵动微电子MM32F0121测评】6、手搓modbus协议从机(一)