打印
[MM32硬件]

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

[复制链接]
694|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 穿西装的强子 于 2025-6-20 09:55 编辑

1、ModbusRTU协议帧格式
  字段  
长度
说明
  从机地址  
1Byte
1-247,0作为广播
  功能码  
1Byte
0x03读单个或多个数寄存器据,0x06写单个寄存器数据,0x10写多个寄存器数据
  数据位  
NByte
寄存器数据,和读写数据长度有关
  CRC校验  
2Byte
CRC-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
主机->从机数据:
  
报文
  
01
03
01 91
00 01
D4 1B
说明
地址
功能码
寄存器地址
读寄存器个数
CRC校验
从机->主机数据:
  
报文
  
01
03
02
00 19
79 8E
说明
地址
功能码
返回字节数
寄存器值
CRC校验

写入单个数据 0x06
发送报文:01 06 01 91 0C 80 DD 7B 反馈报文:01 06 01 91 0C 80 DD 7B
主机->从机数据:
  
报文
  
01
06
01 91
0C 80
DD 7B
说明
地址
功能码
寄存器地址
写入数据
CRC校验
从机->主机数据:
  
报文
  
01
06
01 91
0C 80
DD 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
主机->从机数据:
  
报文
  
01
10
01 46
00 04
08
00 00
00 28
00 00
00 29
1C 14
说明
地址
功能码
寄存器地址
寄存器数
字节数
写入内容
写入内容
写入内容
写入内容
CRC校验
从机->主机数据:
  
报文
  
01
06
01 91
0C 80
DD 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[USART_RxStruct.Length++] = RxData;
//                USART_SendData(USART1, RxData);
        
    }
        if (RESET != USART_GetITStatus(USART1, USART_IT_TXE))
    {
        if (1 == USART_TxStruct.CompleteFlag)
        {
            USART_SendData(USART1, USART_TxStruct.Buffer[USART_TxStruct.CurrentCount++]);

            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[0];
        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[usart->Length-1]<<8)|(usart->Buffer[usart->Length-2]);
        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[1];
        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[2]<<8)|(usart->Buffer[3]);// 寄存器地址
        uint16_t register_len = (usart->Buffer[4]<<8)|(usart->Buffer[5]);// 寄存器数据长度
        if( (register_addr + register_len) >= 128)
        {
               
        } else {
                tx->Length = 0;
                tx->Buffer[tx->Length] = usart->Buffer[tx->Length++];
                tx->Buffer[tx->Length] = usart->Buffer[tx->Length++];
                tx->Buffer[tx->Length++] = register_len*2;
                for( uint16_t i = 0; i < register_len;i++)
                {
                        tx->Buffer[tx->Length++] = (modbus_data[register_addr+i]>>8);
                        tx->Buffer[tx->Length++] = modbus_data[register_addr+i];
                }
                uint16_t crc = calculate_crc_direct(tx->Buffer,tx->Length);
                tx->Buffer[tx->Length++] = crc>>8;
                tx->Buffer[tx->Length++] = 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] = i+1;
        }


















使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

57

主题

252

帖子

3

粉丝