测试板卡: 正点原子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
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
- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
n1 r k' |/ i: M5 U0 a - {3 c) Y% Z: R8 q) L( j
- if (huart->Instance == DEBUG_UART_HANDLER.Instance)/ J, J, v* D4 A0 K7 I/ M" y) F
- {
1 A# `6 ^. \4 m% f- L - if ((DEBUG_UART_RX_STA & 0x8000) == 0) // 接收未完毕* R/ d* M5 ]* k7 U& [ b
- {' V; R2 z/ s! t- |) u$ n
- if (DEBUG_UART_RX_STA & 0x4000) // 已经接收到了回车符
4 Y$ `. _8 N, o - {0 z8 y; [7 K! m9 S
- if (DEBUG_UART_RX_BYTE == '\n') DEBUG_UART_RX_STA |= 0x8000; // 本次接收到的是换行符,标记接收到了换行符/ @) U2 C: T! `6 R$ k* c
- else DEBUG_UART_RX_STA = 0; // 本次接收到的不是换行符,视为接收错误,状态置为初始& r* Z" w, u$ b+ p
- }
! k# r& P4 a5 ]. g4 X) z - else" f: Y3 D1 S) s4 |; A
- {
2 s6 P% m' L- e - if (DEBUG_UART_RX_BYTE == '\r') DEBUG_UART_RX_STA |= 0x4000; // 本次接收到的是回车符,标记接收到了回车符$ S" G8 R( x# E1 K! c0 R
- else
& f3 z ?# ~: X& W1 i" s: Z- I7 r - {! p. {. L1 a8 K" ^: ~
- DEBUG_UART_RX_BUF[DEBUG_UART_RX_STA & 0x3FFF] = DEBUG_UART_RX_BYTE; // 将本次接收到的数据保存到接收缓存中: ~& W* ^5 M) I) w- L2 L9 g
- if (DEBUG_UART_RX_STA++ >= DEBUG_UART_RX_LEN) DEBUG_UART_RX_STA = 0; // 连续接收到的数据长度高于最大接收长度,则视为接收错误, {( Z. E v9 C$ s7 v
- }+ l: o1 n; `4 V% x9 f! [: O+ y, I( I5 k
- }+ n9 {5 w1 u+ \3 d6 z. N& Q% ]
- }. [" T' L4 K- |2 }' G
- HAL_UART_Receive_IT(huart, &DEBUG_UART_RX_BYTE, 1); // 再次使能接收中断
+ V* `+ T+ M. \$ d* Z) I/ l - }
4 ?4 l2 e3 j0 P* h - else if (huart->Instance == huart3.Instance)
8 V2 y( V& N; M, s8 v; E - {0 `' l# S/ x; ]$ A+ O f+ ]
- if (MODBUS_RX_STA < MODBUS_RX_LEN)3 D, W4 }2 G l0 t
- {
$ k: U# I$ R" h* E& [$ j - __HAL_TIM_SET_COUNTER(&htim4, 0); // 清除5ms定时器的计数值
9 l9 K8 S, u8 T- E7 | - if (MODBUS_RX_STA == 0) TIM4_Set(TIM4_ENABLE); // 如果接收为初始状态,则启动定时器4 ?$ L/ l3 o2 h$ q2 d) ^
- MODBUS_RX_BUF[MODBUS_RX_STA++] = MODBUS_RX_BYTE; // 接收数据6 c& ?7 U3 V; x( x0 g( r- p
- }
( N @: }2 i0 r' U" f - else
8 v5 @- j- u& g# b i2 F3 }% f - {4 Q) ^/ I& r- z8 w ~+ V; Q, d
- MODBUS_RX_STA |= 0x8000; // 超长,标记接收结束
$ _& G$ M- R1 ^, F - }
- ~3 Z% p/ h; V. X1 |. U - HAL_UART_Receive_IT(huart, &MODBUS_RX_BYTE, 1); // 再次使能接收中断
" `2 F7 V. K! g! p, ^& e1 _' G1 o - }2 \' W, [+ K: `) T' \
- }$ j( B% I. a4 P+ B! a7 e
- " |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- 3 z1 p5 a1 f: W' j- s b
- ( G7 B3 B% s q6 P8 p" y' Y
- // 保持寄存器
. N. p* `1 `8 l - #define REG_HOLD_SIZE 10UL; Q4 L& S) `4 r" t
- uint16_t REG_HOLD[REG_HOLD_SIZE] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
& [4 t7 B# A9 `! i L - ! W& k+ A$ B* h' B
- // 互换两个变量的值9 ~. |, U% g; |) D2 ^
- void swap(unsigned char* byte1, unsigned char* byte2)( u$ H* S. A. d1 q7 Y" M, ]
- {% H6 q: u2 v" m L: l1 ~
- *byte1 ^= *byte2;
/ u. l( A( x' R+ }8 G5 g - *byte2 ^= *byte1;
3 M/ W- J' H# u* l- F* U4 u - *byte1 ^= *byte2;4 j. I+ A, g" W- t7 V; H
- }
* i4 N2 B' Y# I: x! E
4 I I2 A) R9 z' {: Y- inline void modbus_slave_write(const void* buf, uint32_t len)6 ?1 F* F: n, C7 [
- {, ?4 u6 ], \/ c: g I; N
- HAL_UART_Transmit(&huart3, (uint8_t *)buf, len, 1000);6 I/ P: u B4 a7 j: U
- }
3 j9 x9 T, k4 g3 b$ M/ h - + i' B( p. w3 }; c
- /**
/ P' G1 K% E. S4 n* e - * 串口三数据接收处理6 R& k1 T7 ^" _/ [7 o$ c
- */; n9 \5 G% y8 a e" L) n
- void MODBUS_RecvHandler(void)6 h4 s5 h+ z5 w0 N# @& a) C, |
- {: q* W n: P1 K- s. ^
- // 无数据收到' y- r K8 h3 f1 N
- if ((MODBUS_RX_STA & 0x8000) == 0)' h3 I# u n: p
- return;
% h9 t. t7 C- b
1 X7 U# C+ V0 L- w4 |- // 收到的数据有误! S" n2 e4 c; G% l* ^7 z! g
- if (MODBUS_RX_BUF[0] != SLAVE_ADDRESS)# G ?1 O' \7 ?& W4 Q
- goto __exit;) k B7 v) R, s8 ?; o" ?- }
- + x4 K0 z# g7 h: ]. h
- // 选择相应功能码
9 C7 S/ R7 p1 t3 K' _9 O - switch (MODBUS_RX_BUF[1])# i4 v4 Q% o& A2 p
- {
7 J8 ]& r( b9 d$ ]% D - case CMD3:( Z# s/ \6 R9 x! e0 h9 W
- {9 O5 F! ] E% z, ?. Z- |
- int i = 0;4 V# `3 ]5 H+ E7 e* G! K
- int nread = 0;9 \+ _( A2 F+ ] ?; {
- int offset = 0;* o _; _' e( W; @; K- u
- int num = 0;2 I, `, T0 T& a7 |. D* z' k7 Y
9 j, p" J' a4 ^, Q# N: d- // CRC校验+ p* V9 t) k) F9 a$ B
- if (usMBCRC16(MODBUS_RX_BUF, 6) != *((uint16_t *)(MODBUS_RX_BUF + 6)))4 T. j( J5 t, ]) u- E$ I
- goto __exit;; j( e9 D- [% G1 l* h9 ]5 A
+ `1 E9 ^" f+ w) a( r1 [4 Z- // 大端转小端8 U3 V- Z' \% m$ h. R9 w
- swap(MODBUS_RX_BUF + 2, MODBUS_RX_BUF + 3);% p1 X; g1 v5 j3 M" M2 Z9 o
- swap(MODBUS_RX_BUF + 4, MODBUS_RX_BUF + 5);4 o& p5 J3 m2 j
* q1 A% J. k: Y6 u6 ?) |' X3 t/ \ P- // 得到保持寄存器偏移地址' D7 _& Z! w3 P1 a
- offset = *((uint16_t *)(MODBUS_RX_BUF + 2));
. ]% k/ M( F6 I |7 F- ^% e: N
# k- t2 @6 Z% J$ o- // 偏移地址错误
9 [6 C+ f$ R' { - if (offset >= REG_HOLD_SIZE) goto __exit;& D! h( H% e" V& ^9 ^7 e8 a
0 T$ P7 ?) ]6 t: R9 t3 t- // 得到要读取的保持寄存器个数
5 o8 @) T$ I0 K$ w/ A2 S% P% | - num = *((uint16_t *)(MODBUS_RX_BUF + 4));
# B6 V$ C8 q8 N7 s4 L2 f - # Y" q# O/ }& z- W0 \- s c. B
- // 计算实际可读的保持寄存器个数
- A( D8 l* c( c' I6 Y# N* C - nread = REG_HOLD_SIZE - offset >= num ? num : REG_HOLD_SIZE - offset;
, ]( D, {# e. C
# T# N; W+ k z+ c2 {) W- // 实际能够被读取的保持寄存器数量错误
! P3 |3 T2 V# E) g; o% @' t - if (nread <= 0) goto __exit;
. @ M, ?) z* _3 [/ N - - e3 b6 X( v0 v/ O
- // 填充响应数据的字节长度
' C3 _* }7 s! A; f* o! } - MODBUS_RX_BUF[2] = nread * sizeof(uint16_t);
' g3 y; F2 _1 e) i9 y0 h - , c; h3 h$ X2 u2 N# U: u" L
- // 填充保持寄存器的值
' q: f# z9 j( r0 [: n' z - for (i = 0; i != nread; i++)
% E' ^+ b3 ~7 Y5 v# N! f# h8 T8 V - {+ b3 ?; S+ a" ]" D8 ^: N
- *((uint16_t *)(MODBUS_RX_BUF + 3 + i * 2)) = swap_uint16(REG_HOLD[offset + i]);; s8 s& V7 R; N2 |4 ^
- }
" O& z6 R0 [! a6 k: l
) s0 H8 a* @1 Y- // 计算CRC
% p3 c! E0 `. `( d - *((uint16_t *)(MODBUS_RX_BUF + 3 + nread * 2)) = usMBCRC16(MODBUS_RX_BUF, 3 + nread * 2);
* T# v" \3 V4 v1 [ - % q9 Q/ t% v+ K; P* s: e/ z
- // 发送给主机7 A- o, m" M; r$ J5 \
- modbus_slave_write(MODBUS_RX_BUF, 3 + nread * 2 + 2);3 X) { y" ^$ E
- $ v2 g* e+ V' m* |; x# P
- // 等待3.5个字符的时间
* h9 l0 d2 q5 [7 u! n - HAL_Delay(5);" b6 l# d' _, j, |
- : e8 D/ \# _" r# w3 K: W) W ~
- // 改变保持寄存器的值, c4 Q9 y- s( `5 M
- for (i = 0; i != REG_HOLD_SIZE; i++)
9 D# }9 ?7 l0 f2 q* E& K - {( a: _) D* V$ i; M$ N1 L; C
- REG_HOLD<i>++;
* W8 ?3 A U, d. r/ e - }; e6 K: H0 I$ \# ^
- 8 v" w( O4 ]8 m
- // 退出
, U; `5 y4 R; G - goto __exit;
/ ?- F- \4 S; @4 G4 s# G% c- y - }
4 d5 ?, V% }# ]6 I/ j! C5 J - default:
, M: ~5 C, i: C& [& X( f - goto __exit;. \5 L4 B- ~; Y! a
- }
* `% A* L* w& d( [
4 b/ `" j) @, u) u- __exit: w" R+ d1 h7 V
- MODBUS_RX_STA = 0; // 清空接收! B: T+ {5 i- e- O
- return;% @( i3 J; y& @0 V. Y8 V( l
- }% w# W! I; g6 f. A; t3 V6 }
- </i>
复制代码 # y0 c, a" K _# t
在主函数中轮训调用modbus的接收处理函数:
& n* h+ R4 J- f& ~& C( h8 F; h- ^' \! I( a2 G8 Y
& 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
% 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 |
' 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 |