你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32 HAL库实现MODEBUS RTU从机与上位机通信

[复制链接]
STMCU小助手 发布时间:2022-4-12 10:00
测试板卡: 正点原子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的命令码表:

40bc26f33b084f148c0a81da6aa1b023.jpg

贴一下主要的代码片段:串口接收中断回调函数:

串口1为调试串口
串口3作为modbus的收发数据端口
  1. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
  2. {
  3.     if (huart->Instance == DEBUG_UART_HANDLER.Instance)
  4.     {
  5.         if ((DEBUG_UART_RX_STA & 0x8000) == 0)                                                  // 接收未完毕
  6.         {
  7.             if (DEBUG_UART_RX_STA & 0x4000)                                                     // 已经接收到了回车符
  8.             {
  9.                 if (DEBUG_UART_RX_BYTE == '\n')     DEBUG_UART_RX_STA |= 0x8000;                // 本次接收到的是换行符,标记接收到了换行符
  10.                 else                                DEBUG_UART_RX_STA = 0;                      // 本次接收到的不是换行符,视为接收错误,状态置为初始
  11.             }
  12.             else
  13.             {
  14.                 if (DEBUG_UART_RX_BYTE == '\r')     DEBUG_UART_RX_STA |= 0x4000;                // 本次接收到的是回车符,标记接收到了回车符
  15.                 else
  16.                 {
  17.                     DEBUG_UART_RX_BUF[DEBUG_UART_RX_STA & 0x3FFF] = DEBUG_UART_RX_BYTE;         // 将本次接收到的数据保存到接收缓存中
  18.                     if (DEBUG_UART_RX_STA++ >= DEBUG_UART_RX_LEN)   DEBUG_UART_RX_STA = 0;      // 连续接收到的数据长度高于最大接收长度,则视为接收错误
  19.                 }
  20.             }
  21.         }
  22.         HAL_UART_Receive_IT(huart, &DEBUG_UART_RX_BYTE, 1);                                     // 再次使能接收中断
  23.     }
  24.     else if (huart->Instance == huart3.Instance)
  25.     {
  26.         if (MODBUS_RX_STA < MODBUS_RX_LEN)
  27.         {
  28.             __HAL_TIM_SET_COUNTER(&htim4, 0);                                                                // 清除5ms定时器的计数值
  29.             if (MODBUS_RX_STA == 0)   TIM4_Set(TIM4_ENABLE);                                // 如果接收为初始状态,则启动定时器
  30.             MODBUS_RX_BUF[MODBUS_RX_STA++] = MODBUS_RX_BYTE;                                // 接收数据
  31.         }
  32.         else
  33.         {
  34.             MODBUS_RX_STA |= 0x8000;                                                                                // 超长,标记接收结束
  35.         }
  36.         HAL_UART_Receive_IT(huart, &MODBUS_RX_BYTE, 1);                                                // 再次使能接收中断
  37.     }
  38. }

复制代码

MODBUS RTU数据解析,我只实现了功能码3,其他功能码请根据协议自行进行补充:


  1. // 保持寄存器
  2. #define REG_HOLD_SIZE        10UL
  3. uint16_t REG_HOLD[REG_HOLD_SIZE] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

  4. // 互换两个变量的值
  5. void swap(unsigned char* byte1, unsigned char* byte2)
  6. {
  7.     *byte1 ^= *byte2;
  8.     *byte2 ^= *byte1;
  9.     *byte1 ^= *byte2;
  10. }

  11. inline void modbus_slave_write(const void* buf, uint32_t len)
  12. {
  13.     HAL_UART_Transmit(&huart3, (uint8_t *)buf, len, 1000);
  14. }

  15. /**
  16. * 串口三数据接收处理
  17. */
  18. void MODBUS_RecvHandler(void)
  19. {
  20.     // 无数据收到
  21.     if ((MODBUS_RX_STA & 0x8000) == 0)
  22.         return;

  23.     // 收到的数据有误
  24.     if (MODBUS_RX_BUF[0] != SLAVE_ADDRESS)
  25.         goto __exit;

  26.     // 选择相应功能码
  27.     switch (MODBUS_RX_BUF[1])
  28.     {
  29.     case CMD3:
  30.     {
  31.         int i      = 0;
  32.         int nread  = 0;
  33.         int offset = 0;
  34.         int num    = 0;

  35.         // CRC校验
  36.         if (usMBCRC16(MODBUS_RX_BUF, 6) != *((uint16_t *)(MODBUS_RX_BUF + 6)))
  37.             goto __exit;

  38.         // 大端转小端
  39.         swap(MODBUS_RX_BUF + 2, MODBUS_RX_BUF + 3);
  40.         swap(MODBUS_RX_BUF + 4, MODBUS_RX_BUF + 5);

  41.         // 得到保持寄存器偏移地址
  42.         offset = *((uint16_t *)(MODBUS_RX_BUF + 2));

  43.         // 偏移地址错误
  44.         if (offset >= REG_HOLD_SIZE)        goto __exit;

  45.         // 得到要读取的保持寄存器个数
  46.         num = *((uint16_t *)(MODBUS_RX_BUF + 4));

  47.         // 计算实际可读的保持寄存器个数
  48.         nread = REG_HOLD_SIZE - offset >= num ? num : REG_HOLD_SIZE - offset;

  49.         // 实际能够被读取的保持寄存器数量错误
  50.         if (nread <= 0)        goto __exit;

  51.         // 填充响应数据的字节长度
  52.         MODBUS_RX_BUF[2] = nread * sizeof(uint16_t);

  53.         // 填充保持寄存器的值
  54.         for (i = 0; i != nread; i++)
  55.         {
  56.             *((uint16_t *)(MODBUS_RX_BUF + 3 + i * 2)) = swap_uint16(REG_HOLD[offset + i]);
  57.         }

  58.         // 计算CRC
  59.         *((uint16_t *)(MODBUS_RX_BUF + 3 + nread * 2)) = usMBCRC16(MODBUS_RX_BUF, 3 + nread * 2);

  60.         // 发送给主机
  61.         modbus_slave_write(MODBUS_RX_BUF, 3 + nread * 2 + 2);

  62.         // 等待3.5个字符的时间
  63.         HAL_Delay(5);

  64.         // 改变保持寄存器的值
  65.         for (i = 0; i != REG_HOLD_SIZE; i++)
  66.         {
  67.             REG_HOLD<i>++;
  68.         }

  69.         // 退出
  70.         goto __exit;
  71.     }
  72.     default:
  73.         goto __exit;
  74.     }

  75. __exit:
  76.     MODBUS_RX_STA = 0;                // 清空接收
  77.     return;
  78. }
  79. </i>
复制代码

在主函数中轮训调用modbus的接收处理函数:

2019121712534128.png

单片机串口3我设置为9600的波特率,无流控、无奇偶校验、8bit数据、1bit停止位,上位机打开modbus poll软件,串口设置与单片机相同,Steup->Read/Write Definition,然后从机地址为1,功能码为3,偏移地址0,读取10个保持寄存器的值:

20191217130008613.png

然后收到单片机的modbus rtu的回复:

20191217131118918.gif

ends。。。


收藏 评论0 发布时间:2022-4-12 10:00

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版