使用stm32cubeide stm32f103 freeRTOS 实现Modbus RTU协议寄存器读写过程详解
1前言
项目需要使用MCU实现Modbus RTU协议与PLC通信,STM32作为从机需要将一些传感器信息上报给PLC,有时也需要STM32作为主机采用Modbus RTU协议获取伺服器或者其他设备的一些状态信息。这些信息不需要很多,很多场景下可能就几个地址的寄存器信息。文末给出使用工程的下载地址。
如下图所示,本文主要主要设计使用stm32cubeide stm32f103 freeRTOS 实现Modbus RTU协议对从机寄存器的读写。
2详细设计
采用STM32CubeIDE 1.19.0,其他版本类似,基本上一样,针对硬件配置如下
2.1时钟配置
采用8Mhz外部时钟经过倍频后放大
2.2调试串口1配置
如下图所示
该串口波特率设置为115200,用于调试信息打印,这里在main.c中添加如下代码,实现printf数据打印
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endifPUTCHAR_PROTOTYPE
{while((USART1->SR&0x40)==0){};USART1->DR = ch;return ch;
}
2.3485串口的配置
采用uart2作为485串口,波特率选择9600,打开NVIC Settings,是能接受中断。
打开该中断后会在stm32f4xx_it.c有如下中断处理函数
/*** @brief This function handles USART2 global interrupt.*/
void USART2_IRQHandler(void)
2.4freeRTOS的配置
使用freeRTOS可以简化编程,实现多任务编程。首先配置调试接口,可以根据需要配置,这里配置SW接口,这里需要配置Timebase Source为TIM1,避免和HAL库共用了SysTick时钟源。
添加485串口接受数据处理任务,以及对应的接受数据队列,这里也对默认任务的堆栈做了放大处理。
在Advanced settings中USE_NEWLIB_REENTRANT 需要Enabled,不然会又警告提示,项目实际上芯片资源充分,这里选择了使能。这里是否需要使能 USE_NEWLIB_REENTRANT 取决于应用需求。如果需要线程安全且可以接受额外的内存开销,建议启用此选项。如果不需要线程安全或内存资源有限,可以选择不启用,但需避免使用可能引发线程安全问题的函数。
3关键代码
3.1Modbus RTU 06功能码处理
针对06功能码,主要主机写入寄存器,将寄存器的值保存到本地。
// Modbus RTU 6号功能码函数
// Modbus RTU 主机写入寄存器值
void Modbus_RTU_Func6(u8 *src, u16 len)
{u16 Regadd;u16 val,j;Regadd = src[2] * 256 + src[3]; //得到要修改的地址val = src[4] * 256 + src[5]; //修改后的值setVal2Regadd(Regadd, val);// 开始返回Modbus RTU数据Modbus_RTU_485_TX_Mode;vTaskDelay(5);for(j=0;j<len;j++){Modbus_RTU_Send_Byte(src[j]);}vTaskDelay(5);Modbus_RTU_485_RX_Mode;}
3.2Modbus RTU 03功能码处理
03功能码主要实现主机读取寄存器数据的回复。将本地的数据返回给主机。
// Modbus RTU 3号功能码函数
// Modbus RTU 主机读取寄存器值
void Modbus_RTU_Func3(u8 *src, u16 len)
{u16 Regadd,Reglen,crc,tmp,buf[8];u8 i,j;//要读取寄存器的首地址Regadd = src[2] * 256 + src[3];//要读取寄存器的数据长度Reglen = src[4] * 256 + src[5];//发送回应数据包i = 0;sendbuf[i++] = MY_MODBUS RTU_ADDR; //发送本设备地址sendbuf[i++] = 0x03; //发送功能码//Modbus_RTU.sendbuf[i++] = ((Reglen*2)/256); //返回字节个数sendbuf[i++] = ((Reglen*2)%256); //返回字节个数tmp = getValFromRegadd(Regadd, Reglen, buf);if(tmp == 0){printf("getValFromRegadd tmp = %d,error\r\n", tmp);return;}for(j = 0;j < tmp; j++) //返回数据{sendbuf[i++] = buf[j]/256;//Reg[Regadd+j]/256;sendbuf[i++] = buf[j]%256;//Reg[Regadd+j]%256;}crc = Modbus_RTU_CRC16(sendbuf,i); //计算要返回数据的CRCsendbuf[i++] = crc%256;sendbuf[i++] = crc/256;// 开始返回Modbus_RTU数据Modbus_RTU_485_TX_Mode;vTaskDelay(5);for(j=0;j<i;j++){Modbus_RTU_Send_Byte(sendbuf[j]);}vTaskDelay(5);Modbus_RTU_485_RX_Mode;}
3.3Modbus RTU crc校验函数
针对Modbus RTU crc校验的函数如下,Modbus RTU可以选择的校验很多这里采用CRC16,有时候可能对不上对方校验码,这时可能要考虑更换校验算法。
u16 Modbus_RTU_CRC16(u8 *data, u16 length)//简化CRC校验提升通信速度
{u16 j;u16 reg_crc = 0xffff;while(length--){reg_crc ^= *data++;for(j = 0; j < 8; j++){if(reg_crc & 0x0001)reg_crc = (reg_crc>>1) ^ 0xa001;elsereg_crc = reg_crc>>1;}}return reg_crc;
}
4测试记录
使用电脑模拟485主机进程测试,电脑端运行,sscom5.13.1,打开对485进行电压转换,这里需要借助一根线,连接到电脑。比如USB转485。
这里还需要补充实测记录
这里举例说明,发送指令
01 03 00 01 00 08 15 CC
其中
01 //从机地址
03 //功能码
00 01 //读取地址01
00 08 //读取长度08
15 CC //校验
关于CRC计算可以使用如下网址的在线计算,也可以使用其他软件:
CRC在线计算
返回指令
01 03 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E4 59
01 //地址
03 //功能码
10 //数据长度
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 //数据字节
E4 59//校验
实际测试返回数据截图如下
5本文工程下载地址:
使用stm32cubeidestm32f103freeRTOS实现ModbusRTU协议寄存器读写过程详解,含说明文档、工程代码,测试记录,加速项目开发资源-CSDN下载