测试板卡: 正点原子MINISTM32(STM32F103RB)
实现思路: 位机向STM32发送连续数据,STM32串口中断一直接收,期间使用定时器控制接收时间,如果在3.5个时间字符时间内没有接收到任何数据,那么定时器就判定为一帧数据接收完毕,然后根据modbus协议处理接收到的数据就可以了。
MODBUS RTU 方式的收发都需要3.5个字符的等待时间,这个时间可以通过定时器控制,两个字符之间的间隔时间按照9600的波特率算,1s可以发9600/8=1200字节,1个字节发送的时间就是1/1200≈833微秒,3.5个字符时间就是1/1200*3.5≈2917微秒,这里我为了方便直接用了5ms,如果你测试不行的话,调小定时器的超时时间。
贴一下modbus的命令码表:
贴一下主要的代码片段:串口接收中断回调函数:
串口1为调试串口
串口3作为modbus的收发数据端口
- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
- {
- if (huart->Instance == DEBUG_UART_HANDLER.Instance)
- {
- if ((DEBUG_UART_RX_STA & 0x8000) == 0) // 接收未完毕
- {
- if (DEBUG_UART_RX_STA & 0x4000) // 已经接收到了回车符
- {
- if (DEBUG_UART_RX_BYTE == '\n') DEBUG_UART_RX_STA |= 0x8000; // 本次接收到的是换行符,标记接收到了换行符
- else DEBUG_UART_RX_STA = 0; // 本次接收到的不是换行符,视为接收错误,状态置为初始
- }
- else
- {
- if (DEBUG_UART_RX_BYTE == '\r') DEBUG_UART_RX_STA |= 0x4000; // 本次接收到的是回车符,标记接收到了回车符
- else
- {
- DEBUG_UART_RX_BUF[DEBUG_UART_RX_STA & 0x3FFF] = DEBUG_UART_RX_BYTE; // 将本次接收到的数据保存到接收缓存中
- if (DEBUG_UART_RX_STA++ >= DEBUG_UART_RX_LEN) DEBUG_UART_RX_STA = 0; // 连续接收到的数据长度高于最大接收长度,则视为接收错误
- }
- }
- }
- HAL_UART_Receive_IT(huart, &DEBUG_UART_RX_BYTE, 1); // 再次使能接收中断
- }
- else if (huart->Instance == huart3.Instance)
- {
- if (MODBUS_RX_STA < MODBUS_RX_LEN)
- {
- __HAL_TIM_SET_COUNTER(&htim4, 0); // 清除5ms定时器的计数值
- if (MODBUS_RX_STA == 0) TIM4_Set(TIM4_ENABLE); // 如果接收为初始状态,则启动定时器
- MODBUS_RX_BUF[MODBUS_RX_STA++] = MODBUS_RX_BYTE; // 接收数据
- }
- else
- {
- MODBUS_RX_STA |= 0x8000; // 超长,标记接收结束
- }
- HAL_UART_Receive_IT(huart, &MODBUS_RX_BYTE, 1); // 再次使能接收中断
- }
- }
复制代码
MODBUS RTU数据解析,我只实现了功能码3,其他功能码请根据协议自行进行补充:
- // 保持寄存器
- #define REG_HOLD_SIZE 10UL
- uint16_t REG_HOLD[REG_HOLD_SIZE] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
- // 互换两个变量的值
- void swap(unsigned char* byte1, unsigned char* byte2)
- {
- *byte1 ^= *byte2;
- *byte2 ^= *byte1;
- *byte1 ^= *byte2;
- }
- inline void modbus_slave_write(const void* buf, uint32_t len)
- {
- HAL_UART_Transmit(&huart3, (uint8_t *)buf, len, 1000);
- }
- /**
- * 串口三数据接收处理
- */
- void MODBUS_RecvHandler(void)
- {
- // 无数据收到
- if ((MODBUS_RX_STA & 0x8000) == 0)
- return;
- // 收到的数据有误
- if (MODBUS_RX_BUF[0] != SLAVE_ADDRESS)
- goto __exit;
- // 选择相应功能码
- switch (MODBUS_RX_BUF[1])
- {
- case CMD3:
- {
- int i = 0;
- int nread = 0;
- int offset = 0;
- int num = 0;
- // CRC校验
- if (usMBCRC16(MODBUS_RX_BUF, 6) != *((uint16_t *)(MODBUS_RX_BUF + 6)))
- goto __exit;
- // 大端转小端
- swap(MODBUS_RX_BUF + 2, MODBUS_RX_BUF + 3);
- swap(MODBUS_RX_BUF + 4, MODBUS_RX_BUF + 5);
- // 得到保持寄存器偏移地址
- offset = *((uint16_t *)(MODBUS_RX_BUF + 2));
- // 偏移地址错误
- if (offset >= REG_HOLD_SIZE) goto __exit;
- // 得到要读取的保持寄存器个数
- num = *((uint16_t *)(MODBUS_RX_BUF + 4));
- // 计算实际可读的保持寄存器个数
- nread = REG_HOLD_SIZE - offset >= num ? num : REG_HOLD_SIZE - offset;
- // 实际能够被读取的保持寄存器数量错误
- if (nread <= 0) goto __exit;
- // 填充响应数据的字节长度
- MODBUS_RX_BUF[2] = nread * sizeof(uint16_t);
- // 填充保持寄存器的值
- for (i = 0; i != nread; i++)
- {
- *((uint16_t *)(MODBUS_RX_BUF + 3 + i * 2)) = swap_uint16(REG_HOLD[offset + i]);
- }
- // 计算CRC
- *((uint16_t *)(MODBUS_RX_BUF + 3 + nread * 2)) = usMBCRC16(MODBUS_RX_BUF, 3 + nread * 2);
- // 发送给主机
- modbus_slave_write(MODBUS_RX_BUF, 3 + nread * 2 + 2);
- // 等待3.5个字符的时间
- HAL_Delay(5);
- // 改变保持寄存器的值
- for (i = 0; i != REG_HOLD_SIZE; i++)
- {
- REG_HOLD<i>++;
- }
- // 退出
- goto __exit;
- }
- default:
- goto __exit;
- }
- __exit:
- MODBUS_RX_STA = 0; // 清空接收
- return;
- }
- </i>
复制代码
在主函数中轮训调用modbus的接收处理函数:
单片机串口3我设置为9600的波特率,无流控、无奇偶校验、8bit数据、1bit停止位,上位机打开modbus poll软件,串口设置与单片机相同,Steup->Read/Write Definition,然后从机地址为1,功能码为3,偏移地址0,读取10个保持寄存器的值:
然后收到单片机的modbus rtu的回复:
ends。。。
|