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

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

[复制链接]
STMCU小助手 发布时间:2022-4-12 10:00
测试板卡: 正点原子MINISTM32(STM32F103RB)" \+ \3 _2 O- g( D9 B; y) B% P2 e% w
6 U. H; P0 Z' U7 _! o
实现思路: 位机向STM32发送连续数据,STM32串口中断一直接收,期间使用定时器控制接收时间,如果在3.5个时间字符时间内没有接收到任何数据,那么定时器就判定为一帧数据接收完毕,然后根据modbus协议处理接收到的数据就可以了。) q8 Q) D9 I' A4 q
7 F4 e- R3 k4 `  l2 @: K
MODBUS RTU 方式的收发都需要3.5个字符的等待时间,这个时间可以通过定时器控制,两个字符之间的间隔时间按照9600的波特率算,1s可以发9600/8=1200字节,1个字节发送的时间就是1/1200≈833微秒,3.5个字符时间就是1/1200*3.5≈2917微秒,这里我为了方便直接用了5ms,如果你测试不行的话,调小定时器的超时时间。6 k% p7 d5 Q  K; J" _* p# e
3 r3 J' J$ L$ C# q! N( a. a) I! Q

$ M, b( V5 P' |0 n; y# x/ {贴一下modbus的命令码表:
- Y' u$ M! G9 u+ D8 ?9 G. V- X  k+ |9 h: L& v. b/ o
40bc26f33b084f148c0a81da6aa1b023.jpg
6 y) e4 j7 Q4 ?! V# M& D! o9 s. E7 m  o# x2 i! c) X, T) ~
贴一下主要的代码片段:串口接收中断回调函数:% u" k& u2 N: u1 Y
4 W; f, r6 y' {) I/ q: w/ [
串口1为调试串口
! ^" j6 a* m; O& k串口3作为modbus的收发数据端口7 \, n7 W) P' }5 c3 y
  1. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
      n1 r  k' |/ i: M5 U0 a
  2. {3 c) Y% Z: R8 q) L( j
  3.     if (huart->Instance == DEBUG_UART_HANDLER.Instance)/ J, J, v* D4 A0 K7 I/ M" y) F
  4.     {
    1 A# `6 ^. \4 m% f- L
  5.         if ((DEBUG_UART_RX_STA & 0x8000) == 0)                                                  // 接收未完毕* R/ d* M5 ]* k7 U& [  b
  6.         {' V; R2 z/ s! t- |) u$ n
  7.             if (DEBUG_UART_RX_STA & 0x4000)                                                     // 已经接收到了回车符
    4 Y$ `. _8 N, o
  8.             {0 z8 y; [7 K! m9 S
  9.                 if (DEBUG_UART_RX_BYTE == '\n')     DEBUG_UART_RX_STA |= 0x8000;                // 本次接收到的是换行符,标记接收到了换行符/ @) U2 C: T! `6 R$ k* c
  10.                 else                                DEBUG_UART_RX_STA = 0;                      // 本次接收到的不是换行符,视为接收错误,状态置为初始& r* Z" w, u$ b+ p
  11.             }
    ! k# r& P4 a5 ]. g4 X) z
  12.             else" f: Y3 D1 S) s4 |; A
  13.             {
    2 s6 P% m' L- e
  14.                 if (DEBUG_UART_RX_BYTE == '\r')     DEBUG_UART_RX_STA |= 0x4000;                // 本次接收到的是回车符,标记接收到了回车符$ S" G8 R( x# E1 K! c0 R
  15.                 else
    & f3 z  ?# ~: X& W1 i" s: Z- I7 r
  16.                 {! p. {. L1 a8 K" ^: ~
  17.                     DEBUG_UART_RX_BUF[DEBUG_UART_RX_STA & 0x3FFF] = DEBUG_UART_RX_BYTE;         // 将本次接收到的数据保存到接收缓存中: ~& W* ^5 M) I) w- L2 L9 g
  18.                     if (DEBUG_UART_RX_STA++ >= DEBUG_UART_RX_LEN)   DEBUG_UART_RX_STA = 0;      // 连续接收到的数据长度高于最大接收长度,则视为接收错误, {( Z. E  v9 C$ s7 v
  19.                 }+ l: o1 n; `4 V% x9 f! [: O+ y, I( I5 k
  20.             }+ n9 {5 w1 u+ \3 d6 z. N& Q% ]
  21.         }. [" T' L4 K- |2 }' G
  22.         HAL_UART_Receive_IT(huart, &DEBUG_UART_RX_BYTE, 1);                                     // 再次使能接收中断
    + V* `+ T+ M. \$ d* Z) I/ l
  23.     }
    4 ?4 l2 e3 j0 P* h
  24.     else if (huart->Instance == huart3.Instance)
    8 V2 y( V& N; M, s8 v; E
  25.     {0 `' l# S/ x; ]$ A+ O  f+ ]
  26.         if (MODBUS_RX_STA < MODBUS_RX_LEN)3 D, W4 }2 G  l0 t
  27.         {
    $ k: U# I$ R" h* E& [$ j
  28.             __HAL_TIM_SET_COUNTER(&htim4, 0);                                                                // 清除5ms定时器的计数值
    9 l9 K8 S, u8 T- E7 |
  29.             if (MODBUS_RX_STA == 0)   TIM4_Set(TIM4_ENABLE);                                // 如果接收为初始状态,则启动定时器4 ?$ L/ l3 o2 h$ q2 d) ^
  30.             MODBUS_RX_BUF[MODBUS_RX_STA++] = MODBUS_RX_BYTE;                                // 接收数据6 c& ?7 U3 V; x( x0 g( r- p
  31.         }
    ( N  @: }2 i0 r' U" f
  32.         else
    8 v5 @- j- u& g# b  i2 F3 }% f
  33.         {4 Q) ^/ I& r- z8 w  ~+ V; Q, d
  34.             MODBUS_RX_STA |= 0x8000;                                                                                // 超长,标记接收结束
    $ _& G$ M- R1 ^, F
  35.         }
    - ~3 Z% p/ h; V. X1 |. U
  36.         HAL_UART_Receive_IT(huart, &MODBUS_RX_BYTE, 1);                                                // 再次使能接收中断
    " `2 F7 V. K! g! p, ^& e1 _' G1 o
  37.     }2 \' W, [+ K: `) T' \
  38. }$ j( B% I. a4 P+ B! a7 e
  39. " |5 I0 d0 b* Q5 W. l4 v
复制代码
- r- {- m. p9 t; K1 D: w; g
MODBUS RTU数据解析,我只实现了功能码3,其他功能码请根据协议自行进行补充:
0 p, U+ Y5 u8 ?- A6 n0 E% p
  1. 3 z1 p5 a1 f: W' j- s  b
  2. ( G7 B3 B% s  q6 P8 p" y' Y
  3. // 保持寄存器
    . N. p* `1 `8 l
  4. #define REG_HOLD_SIZE        10UL; Q4 L& S) `4 r" t
  5. uint16_t REG_HOLD[REG_HOLD_SIZE] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    & [4 t7 B# A9 `! i  L
  6. ! W& k+ A$ B* h' B
  7. // 互换两个变量的值9 ~. |, U% g; |) D2 ^
  8. void swap(unsigned char* byte1, unsigned char* byte2)( u$ H* S. A. d1 q7 Y" M, ]
  9. {% H6 q: u2 v" m  L: l1 ~
  10.     *byte1 ^= *byte2;
    / u. l( A( x' R+ }8 G5 g
  11.     *byte2 ^= *byte1;
    3 M/ W- J' H# u* l- F* U4 u
  12.     *byte1 ^= *byte2;4 j. I+ A, g" W- t7 V; H
  13. }
    * i4 N2 B' Y# I: x! E

  14. 4 I  I2 A) R9 z' {: Y
  15. inline void modbus_slave_write(const void* buf, uint32_t len)6 ?1 F* F: n, C7 [
  16. {, ?4 u6 ], \/ c: g  I; N
  17.     HAL_UART_Transmit(&huart3, (uint8_t *)buf, len, 1000);6 I/ P: u  B4 a7 j: U
  18. }
    3 j9 x9 T, k4 g3 b$ M/ h
  19. + i' B( p. w3 }; c
  20. /**
    / P' G1 K% E. S4 n* e
  21. * 串口三数据接收处理6 R& k1 T7 ^" _/ [7 o$ c
  22. */; n9 \5 G% y8 a  e" L) n
  23. void MODBUS_RecvHandler(void)6 h4 s5 h+ z5 w0 N# @& a) C, |
  24. {: q* W  n: P1 K- s. ^
  25.     // 无数据收到' y- r  K8 h3 f1 N
  26.     if ((MODBUS_RX_STA & 0x8000) == 0)' h3 I# u  n: p
  27.         return;
    % h9 t. t7 C- b

  28. 1 X7 U# C+ V0 L- w4 |
  29.     // 收到的数据有误! S" n2 e4 c; G% l* ^7 z! g
  30.     if (MODBUS_RX_BUF[0] != SLAVE_ADDRESS)# G  ?1 O' \7 ?& W4 Q
  31.         goto __exit;) k  B7 v) R, s8 ?; o" ?- }
  32. + x4 K0 z# g7 h: ]. h
  33.     // 选择相应功能码
    9 C7 S/ R7 p1 t3 K' _9 O
  34.     switch (MODBUS_RX_BUF[1])# i4 v4 Q% o& A2 p
  35.     {
    7 J8 ]& r( b9 d$ ]% D
  36.     case CMD3:( Z# s/ \6 R9 x! e0 h9 W
  37.     {9 O5 F! ]  E% z, ?. Z- |
  38.         int i      = 0;4 V# `3 ]5 H+ E7 e* G! K
  39.         int nread  = 0;9 \+ _( A2 F+ ]  ?; {
  40.         int offset = 0;* o  _; _' e( W; @; K- u
  41.         int num    = 0;2 I, `, T0 T& a7 |. D* z' k7 Y

  42. 9 j, p" J' a4 ^, Q# N: d
  43.         // CRC校验+ p* V9 t) k) F9 a$ B
  44.         if (usMBCRC16(MODBUS_RX_BUF, 6) != *((uint16_t *)(MODBUS_RX_BUF + 6)))4 T. j( J5 t, ]) u- E$ I
  45.             goto __exit;; j( e9 D- [% G1 l* h9 ]5 A

  46. + `1 E9 ^" f+ w) a( r1 [4 Z
  47.         // 大端转小端8 U3 V- Z' \% m$ h. R9 w
  48.         swap(MODBUS_RX_BUF + 2, MODBUS_RX_BUF + 3);% p1 X; g1 v5 j3 M" M2 Z9 o
  49.         swap(MODBUS_RX_BUF + 4, MODBUS_RX_BUF + 5);4 o& p5 J3 m2 j

  50. * q1 A% J. k: Y6 u6 ?) |' X3 t/ \  P
  51.         // 得到保持寄存器偏移地址' D7 _& Z! w3 P1 a
  52.         offset = *((uint16_t *)(MODBUS_RX_BUF + 2));
    . ]% k/ M( F6 I  |7 F- ^% e: N

  53. # k- t2 @6 Z% J$ o
  54.         // 偏移地址错误
    9 [6 C+ f$ R' {
  55.         if (offset >= REG_HOLD_SIZE)        goto __exit;& D! h( H% e" V& ^9 ^7 e8 a

  56. 0 T$ P7 ?) ]6 t: R9 t3 t
  57.         // 得到要读取的保持寄存器个数
    5 o8 @) T$ I0 K$ w/ A2 S% P% |
  58.         num = *((uint16_t *)(MODBUS_RX_BUF + 4));
    # B6 V$ C8 q8 N7 s4 L2 f
  59. # Y" q# O/ }& z- W0 \- s  c. B
  60.         // 计算实际可读的保持寄存器个数
    - A( D8 l* c( c' I6 Y# N* C
  61.         nread = REG_HOLD_SIZE - offset >= num ? num : REG_HOLD_SIZE - offset;
    , ]( D, {# e. C

  62. # T# N; W+ k  z+ c2 {) W
  63.         // 实际能够被读取的保持寄存器数量错误
    ! P3 |3 T2 V# E) g; o% @' t
  64.         if (nread <= 0)        goto __exit;
    . @  M, ?) z* _3 [/ N
  65. - e3 b6 X( v0 v/ O
  66.         // 填充响应数据的字节长度
    ' C3 _* }7 s! A; f* o! }
  67.         MODBUS_RX_BUF[2] = nread * sizeof(uint16_t);
    ' g3 y; F2 _1 e) i9 y0 h
  68. , c; h3 h$ X2 u2 N# U: u" L
  69.         // 填充保持寄存器的值
    ' q: f# z9 j( r0 [: n' z
  70.         for (i = 0; i != nread; i++)
    % E' ^+ b3 ~7 Y5 v# N! f# h8 T8 V
  71.         {+ b3 ?; S+ a" ]" D8 ^: N
  72.             *((uint16_t *)(MODBUS_RX_BUF + 3 + i * 2)) = swap_uint16(REG_HOLD[offset + i]);; s8 s& V7 R; N2 |4 ^
  73.         }
    " O& z6 R0 [! a6 k: l

  74. ) s0 H8 a* @1 Y
  75.         // 计算CRC
    % p3 c! E0 `. `( d
  76.         *((uint16_t *)(MODBUS_RX_BUF + 3 + nread * 2)) = usMBCRC16(MODBUS_RX_BUF, 3 + nread * 2);
    * T# v" \3 V4 v1 [
  77. % q9 Q/ t% v+ K; P* s: e/ z
  78.         // 发送给主机7 A- o, m" M; r$ J5 \
  79.         modbus_slave_write(MODBUS_RX_BUF, 3 + nread * 2 + 2);3 X) {  y" ^$ E
  80. $ v2 g* e+ V' m* |; x# P
  81.         // 等待3.5个字符的时间
    * h9 l0 d2 q5 [7 u! n
  82.         HAL_Delay(5);" b6 l# d' _, j, |
  83. : e8 D/ \# _" r# w3 K: W) W  ~
  84.         // 改变保持寄存器的值, c4 Q9 y- s( `5 M
  85.         for (i = 0; i != REG_HOLD_SIZE; i++)
    9 D# }9 ?7 l0 f2 q* E& K
  86.         {( a: _) D* V$ i; M$ N1 L; C
  87.             REG_HOLD<i>++;
    * W8 ?3 A  U, d. r/ e
  88.         }; e6 K: H0 I$ \# ^
  89. 8 v" w( O4 ]8 m
  90.         // 退出
    , U; `5 y4 R; G
  91.         goto __exit;
    / ?- F- \4 S; @4 G4 s# G% c- y
  92.     }
    4 d5 ?, V% }# ]6 I/ j! C5 J
  93.     default:
    , M: ~5 C, i: C& [& X( f
  94.         goto __exit;. \5 L4 B- ~; Y! a
  95.     }
    * `% A* L* w& d( [

  96. 4 b/ `" j) @, u) u
  97. __exit:  w" R+ d1 h7 V
  98.     MODBUS_RX_STA = 0;                // 清空接收! B: T+ {5 i- e- O
  99.     return;% @( i3 J; y& @0 V. Y8 V( l
  100. }% w# W! I; g6 f. A; t3 V6 }
  101. </i>
复制代码
# y0 c, a" K  _# t
在主函数中轮训调用modbus的接收处理函数:
& n* h+ R4 J- f& ~& C( h8 F; h- ^' \! I( a2 G8 Y
2019121712534128.png & s1 X) e- N' {9 c- B9 ~" J. P
5 k) Y$ V# i+ n
单片机串口3我设置为9600的波特率,无流控、无奇偶校验、8bit数据、1bit停止位,上位机打开modbus poll软件,串口设置与单片机相同,Steup->Read/Write Definition,然后从机地址为1,功能码为3,偏移地址0,读取10个保持寄存器的值:
1 [2 g9 M5 F9 k  o  L  A* h( r5 i: H" f# y
20191217130008613.png
% T  _9 ^  V" B- D- K+ B0 [5 s
8 i9 \% b; ^5 V4 m: V) H然后收到单片机的modbus rtu的回复:
/ r* i; ~) z$ K8 Z
9 R: o/ o$ F- [, w: T! h9 | 20191217131118918.gif
' o. {% v# N( z6 ]; r: y& W. h' x* s, f" ]8 [4 K" l( o
ends。。。
! C# B# I9 g. X( S" h. c/ ~
" U3 T2 H  Z1 O  J( C
( n8 d: c. ?5 z( T9 K
赞 收藏 评论0 发布时间:2022-4-12 10:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版