前两天在研究STM32F103单片机的串口空闲中断时,突然想起来Modbus通信非常适合用空闲中断来处理。先看看Modbus RTU模式下的通信规范。
& ?* \6 t9 S, _7 c3 V% M
8 s$ a" q% N) G
% g# Z$ y* c: ?- k1 u4 P
* i; c K# _& @4 K/ z7 y% i- f: X
可以看到Modbus RTU通信模式下,数据的开始和结束是由空闲字符间隔时间来区分的,而STM32F103单片机自带串口空闲模式检测。
" R) }1 X% v9 X6 c, ]# y$ p0 D3 X+ N& f+ v
3 `1 a! n* q2 V+ a' i
; q, a# H) m q' a9 @ 在通常情况下Modbus通信一帧数据的检测可以用时间判断,不停的去读取接收数据的长度是否发送变化,如果在一定时间内,接收数据的长度没有发生变化,就认为一帧数据结束完毕。% V" X k% [% n+ I. X/ I
: l" u0 C- y6 g' M' U3 C
通过时间来判断一帧数据是否接收完成,会导致单片机不停的进入中断。而STM32F103单片机自带了空闲数据检测功能,这个功能是由单片机内部的硬件完成的,不需要程序去处理,这样单片机的就有机会去执行更多了任务了。为了让单片机在处理Modbus RTU通信时更简单快速,可以用 串口DMA加上空闲中断的方式来实现。
* C$ q2 Q; G) M7 `6 _
- P/ f3 ^8 h$ l4 A 下面直接用代码说明具体的实现过程,首先初始化串口。
U8 @8 L8 g7 ]3 ]5 ~) E( h+ m
+ @& W% b, [" B$ q4 I+ D5 @- #define UART2_DMA 1 //使用串口2 DMA传输- d g; h0 O& A; J) k8 O1 N; u
- #define CHECK_NONE_ONE_STOP 1 //无校验位 1个停止位 1有效 0 无效
% C0 k. q' ^- f1 O( T7 T - #define CHECK_NONE_TWO_STOP 0 //无校验位 2个停止位 1有效 0 无效
1 \/ G7 D h3 x& \ - #define CHECK_EVEN 0 //偶数校验 1有效 0 无效
5 n" i, e/ b( n5 o - #define CHECK_ODD 0 //奇数校验 1有效 0 无效
/ B' P! b$ F2 w9 W, g) } - 9 k5 A4 _. G$ g) X
- + X& O2 S0 e$ J# p% m5 A( ~
- void uart2_init( u16 baud )
' f' T4 _* ^+ d9 M0 @ - {
+ O4 V' R1 g+ \) m7 F# o- H - GPIO_InitTypeDef GPIO_InitStructure;
1 |) t! h' h' \, X- r - USART_InitTypeDef USART_InitStructure;
# b0 M) ? J; w1 g - NVIC_InitTypeDef NVIC_InitStructure;5 a+ L J7 o/ A' G
- * i: V0 N; x( S: B% v7 c5 k
- RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );
7 Q% j5 R) L3 K2 O, o) p1 W - RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART2, ENABLE );1 _' N& s) v/ r8 l5 m m3 e; c
- u- B/ t6 y, e. S( P
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
6 p: R ]+ L7 y6 r H; | - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //推挽复用模式
7 Q3 S, r3 l# A; K8 V - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
* s% E0 ^, d1 |9 N* C) l# O5 O - GPIO_Init( GPIOA, &GPIO_InitStructure );, x; R, _0 D. o* k' d
( R% G0 J; a, r; o# B- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
1 H2 c( d& `4 t* m - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入模式. ?, }5 m3 X, [2 u
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;4 Q2 n$ W5 j+ l5 b8 i5 P+ {
- GPIO_Init( GPIOA, &GPIO_InitStructure );
2 s' k$ e* L" g9 }! s. E
1 j; J2 r; h. V- T9 M+ N3 B9 Q- NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
6 |; m/ P {7 I; h, h" p# ^ - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;+ b, N4 ~: A* w6 G2 M
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;
! b' W1 u% a3 ~+ ~0 B" M/ U - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
' m7 D) p5 t! k1 R k - $ T/ i9 t I% x& U
- NVIC_Init( &NVIC_InitStructure );
9 r" A1 }: r3 @0 Q/ a! h( A - 4 N A5 ^$ s+ ?% `( o9 A2 m; e; M
- USART_InitStructure.USART_BaudRate = baud;
' \# u7 r. u4 h' b& U$ F% E - USART_InitStructure.USART_WordLength = USART_WordLength_8b;! j* c8 ^( S; _ t" n; s3 d5 ]; r
- USART_InitStructure.USART_StopBits = USART_StopBits_1;/ N- G% a0 p( o& P5 W
- USART_InitStructure.USART_Parity = USART_Parity_No;0 V8 O" R1 t3 B4 O& L
-
0 [6 H! U5 F" b9 O - #if(CHECK_EVEN == 1) //如果定义了偶校验 数据位长度要改为9位
! K1 \$ L6 M$ S ~9 X+ E - USART_InitStructure.USART_WordLength = USART_WordLength_9b;
. f: k8 l: B3 Q, u - USART_InitStructure.USART_Parity = USART_Parity_Even;
; g3 H9 H; c1 G2 s5 ]. X% R: {* p% X - #endif: O# {$ o$ G4 X$ E
& g1 R5 s6 k5 a9 V- #if(CHECK_ODD == 1) //如果定义了奇校验 数据位长度要改为9位
: d$ P/ b* W, h* N4 Z - USART_InitStructure.USART_WordLength = USART_WordLength_9b;" O/ O& ?6 t9 U1 W
- USART_InitStructure.USART_Parity = USART_Parity_Odd;
% `6 d( \' y3 r - #endif7 D( j7 G4 m5 T
& M9 a! j) k, X- #if(CHECK_NONE_ONE_STOP==1) //停止位为 一位
$ E( \$ \3 d# H' w0 v - USART_InitStructure.USART_StopBits = USART_StopBits_1;2 E( y! [! d0 v; x% t; x
- #endif
" y& G: X! X& x9 @
( `1 i; Z' \4 a v7 L1 }* m6 ?9 z- #if(CHECK_NONE_TWO_STOP==1) //停止位为 两位 1 U& |" Y3 Y+ q- B
- USART_InitStructure.USART_StopBits = USART_StopBits_2;
% ?/ n! m8 P7 ]+ X7 z - #endif / C0 F1 l% ]) S: c; C- F( [8 d2 X
-
' |: j( b. j- a2 |6 R - ( _% p F, D1 c: t
- USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;2 w' |4 J$ e" U6 e# @0 V
- USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;1 U0 Z2 t7 j# ~3 h
- : @$ Z' `) p" `, }" a: f6 C
- USART_Init( USART2, &USART_InitStructure );# O) r( k- J& \. l0 @
-
9 R8 a* ^8 o9 l. x4 @$ p( l V - #if (UART2_DMA == 1)
6 G# j/ e& J ]: [2 P9 x - USART_ITConfig( USART2, USART_IT_IDLE, ENABLE ); //使能串口空闲中断9 @/ n# ~7 h# t* C' l9 W6 |
- USART_DMACmd( USART2, USART_DMAReq_Rx, ENABLE ); //使能串口2 DMA接收
# T0 Z6 B) u% {# r( w* \& u9 E - uartDMA_Init(); //初始化 DMA
% p9 ]/ p2 R( Q - #else# |. e8 ~8 w& i" u4 Q& S5 R
- USART_ITConfig( USART2, USART_IT_RXNE, ENABLE ); //使能串口RXNE接收中断; Z5 q% u' h: k$ a
- #endif
Y% T" y! L* j! I3 `9 ^ - USART_Cmd( USART2, ENABLE ); //使能串口2( L$ S9 M k/ r) M' L+ t" Q. R
- & Q1 _& t5 b8 E2 I: D4 W/ U
- //RXNE中断和IDLE中断的区别?6 |! V! m3 _7 q
- //当接收到1个字节,就会产生RXNE中断,当接收到一帧数据,就会产生IDLE中断。比如给单片机一次性发送了8个字节,就会产生8次RXNE中断,1次IDLE中断。
0 ?+ h2 h& s* t, G Q) O# d, G7 |+ t - }
复制代码 ) Q& d( {' d; j
在Modbus通信时,需要对每个字节的数据进行校验,所以这里使用宏定义来初始化串口数据使用的是那种校验方式。如果需要偶校验就将CHECK_EVEN 设置为1,如果需要奇校验就将CHECK_ODD设置为1,如果不需要校验位,就将这两个都设置为0。在串口初始化的时候,需要开启空闲中断和DMA功能。& N( V) e: G( j3 U j& B2 @1 C
3 b. Y9 G- b. ] 下来初始化 串口2的DMA功能
2 p M, C" W0 g6 X4 ]5 ^' q' [7 ]+ j" t* G4 x! ]0 B3 b0 k
- void uartDMA_Init( void )
% C _4 {. ~0 ]! d' l2 s - {) g5 E! W' \8 W I8 l4 `$ H
- DMA_InitTypeDef DMA_IniStructure;) `& D6 {% r; U4 Y" S) P
- 4 b* I! V) N3 ^% G) @1 T# Z
- RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE ); //使能DMA时钟
4 S) w* |1 @' A% O9 \# e: P7 \
& ~. g1 \ d, f9 a9 c! b- K- DMA_DeInit( DMA1_Channel6 ); //DMA1通道6对应 USART2_RX% a: e+ K$ [+ c( ] h/ ~
- DMA_IniStructure.DMA_PeripheralBaseAddr = ( u32 )&USART2->DR; //DMA外设usart基地址2 `' |* K" E# u& V3 y
- DMA_IniStructure.DMA_MemoryBaseAddr = ( u32 )dma_rec_buff; //DMA内存基地址. U$ ]; S7 b- s1 W
- DMA_IniStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,从外设读取发送到内存
& z" s6 x- J9 ~+ Z- k - DMA_IniStructure.DMA_BufferSize = DMA_REC_LEN; //DMA通道的DMA缓存的大小 也就是 DMA一次传输的字节数
1 S+ [6 x! O/ N. M2 } - DMA_IniStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变( k+ p( l/ ^8 N
- DMA_IniStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //数据缓冲区地址递增# Y1 y/ q" l5 H
- DMA_IniStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设以字节为单位搬运9 B. z; R7 g# M/ r% c
- DMA_IniStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据缓冲区以字节为单位搬入) @5 t5 {) Q# D
- DMA_IniStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式
! C# t4 Z' q) \& Q - DMA_IniStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级
- p& X' L% j Q# f& @* M - DMA_IniStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
' S+ |! \. A# s
5 o' |4 Q9 j$ N/ @! I3 P- DMA_Init( DMA1_Channel6, &DMA_IniStructure );
1 G1 S" g( |' ~, o" @# K - DMA_Cmd( DMA1_Channel6, ENABLE );( | W3 d; d+ [5 @1 X$ ?
- }
复制代码 $ s- K: U9 |9 x- w# H, B. m
1 L& x; B' G7 n C
0 K! ^. x# k g. i/ M# B7 [ 串口2的接收对应的是DMA的通道6,所以DMA的通道选择通道6,然后将串口2的数据寄存器作为外设基地址,将数组dma_rec_buff作为DMA的内存地址。外设地址不自增,内存地址自增。这样串口2每接收一个字节的数据,DMA就将接收到的数据存入数组dma_rec_buff中,同时指向数组的地址增加一位,这样串口2接收到的数据就会被一直存放在数组dma_rec_buff中。
: m0 g% d9 T5 Y$ G6 S4 x0 O# r' t
0 @7 Z2 @* F$ W3 t 这里要注意一点,数组的缓存区要大于串口2接收到的最长数据个数,否则如果接收到串口数据比较长,而数组比较小,数组中的数据就会被覆盖,导致接收到的数据错误。2 G& O6 P k" O7 K
* C0 x. _& R+ a0 s( f9 T 接下来就是串口中断的实现了
4 |4 }" ?9 k3 F) Y7 h9 D
8 w2 V3 @2 u& H. |" r- void USART2_IRQHandler( void )
1 Q+ { }/ z6 f - {& k( I, m L& i6 }! Y' K" I
- u8 tem = 0;- y+ {7 C8 s4 }: j G8 V4 c
- #if (UART2_DMA == 1) //如果使能了 UART2_DMA 则使用串口空闲中断和 DMA功能接收数据
' o! n$ |, j/ o$ a - if( USART_GetITStatus( USART2, USART_IT_IDLE ) != RESET ) //空闲中断 一帧数据发送完成0 J) ~+ Z# i1 C% Z) N8 e
- {' P# G, Q M% O# @
- USART_ReceiveData( USART2 ); //读取数据注意:这句必须要,否则不能够清除空闲中断标志位。
' K" \' _1 e' C$ B - DMA_Cmd( DMA1_Channel6, DISABLE ); //关闭 DMA 防止后续数据干扰- y) W; m c9 a9 q
- uart2_rec_cnt = DMA_REC_LEN - DMA_GetCurrDataCounter( DMA1_Channel6 ); //DMA接收缓冲区数据长度减去当前 DMA传输通道中剩余单元数量就是已经接收到的数据数量
: q1 ^4 K9 o9 |/ I, C+ {7 | - # N0 R+ a; M) l h
- copy_data( dma_rec_buff, uart2_rec_cnt ); //备份数据
3 E$ ^0 O& C; K W, P" t! _3 I. Z8 l( T - receiveOK_flag = 1; //置位数据接收完成标志位8 N9 j2 o/ F/ @* v8 b. |
- USART_ClearITPendingBit( USART2, USART_IT_IDLE ); //清除空闲中断标志位
8 B+ w3 ^: J5 P F2 T \8 s - myDMA_Enable( DMA1_Channel6 ); //重新恢复 DMA等待下一次接收
3 R/ v. C4 k& k- i- F* |3 }. L4 K - }
% @ i5 L$ M' z3 n, P4 |0 O# s: K - #else //如果未使能 UART2_DMA 则通过常规方式接收数据 每接收到一个字节就会进入一次中断
+ S2 ^$ {& Z2 V* M4 H% P+ u - if( USART_GetITStatus( USART2, USART_IT_RXNE ) != RESET ) //接收中断
/ ~1 T! l5 j2 a. M- J5 A - {
7 s. f0 N$ o/ Z7 {6 h) f: q& F - tem = USART_ReceiveData( USART2 );
8 e. O. J1 c, h( U5 ]) s0 T - USART_SendData( USART2, tem );
U0 V) A+ x, \' B - }* `, @" q/ ^( _: z6 w5 E2 h7 D r
- #endif; {1 {! A4 b% N/ D) h
- }
复制代码 $ M! [( \+ A$ e( s: d
串口进入空闲中断后,说明一帧数据已经接收完成。关闭DMA通道,开始处理数据。获取DMA中接收到的数据长度,然后将数据拷贝到备份数组中,然后置接收完成标志位。开启 DMA进行下一次数据接收。
0 l: r( i7 K" C" `0 C" v3 {& a6 x9 u! ~0 z$ t/ z7 e- R- X7 @% n6 @
在主程序中如果检测到了数据接收完成标志时,就开始处理接收到的数据。这样就可以将串口接收数据和处理数据分开,在处理上一笔数据的时候,就可以继续接收下一笔数据。提高了系统的实时性。
W- j; w2 T2 S( u4 {; b
, s- |/ w$ p, }1 W8 j, L 这里如果直接使用DMA接收数组去处理数据的话,有可能会出现,旧的一笔数据未处理完成时,DMA接收到了新的一笔数据,然后存储到了 DMA缓存数组中。这样在处理数据过程中,旧的数据就会被新的数据覆盖掉,导致系统异常。如果Modbus的通信速率没有那么快,在下一次数据来临之前,系统完全有时间处理完上一笔数据,那么直接使用DMA的缓存数据也是可以的。1 f8 D' i0 q+ G: ~6 N# b1 N
: ]7 t5 g2 r% k& M8 H. y
不过上面这种将数据拷贝倒其他数据中处理的方法也是有风险的。比如第一笔数据还没处理完成,第二笔数据已经接收完成,此时进入串口空闲中断,系统依然会将第二笔数据拷贝到备份数组中去。这样备份数组中的数据依然会被覆盖。但是这种情况如果出现的话,就说明接收数据的频率高于系统处理数据的频率,系统本身就存在风险,需要重新设计。
' q- N7 \+ R( X6 q" N! ?; t9 N$ F- p' t; W8 A& L4 M
接收数据完成后,就需要在主程序中调用数据处理函数。
/ D6 i1 T2 L6 @+ [7 ?6 m6 D, [
- int main(void)4 t3 O3 {8 w& D6 Q7 M$ p; z
- {" m4 Z/ M( f; d7 O, s! |# T
- u8 j = 0;/ _) n2 T- y# a: [& R; f% p
-
9 u4 G5 P! F2 g2 N - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
( f1 [" I/ j$ Z; p% ]" I -
# Q1 L y- c H3 _& [- E - delay_init(); //延时函数初始化
4 q, X- D# X' q' D% N6 ] - LED_Init(); //初始化与LED连接的硬件接口
4 n, M. h% s- r: t& d: C/ g - uart2_init(9600);# a6 Z9 G! A' W/ J
- while(1)
) h! O5 {7 d7 n/ t* B* O1 q - {
2 I* _* R2 A; }: _& e3 v4 N) a - * A' B- v' y, P/ I
- if( receiveOK_flag ) //一帧数据接收完成后开始处理数据& y, u5 E( M7 y
- {
% Y/ J( t, `5 H+ m m" Z+ X% T - receiveOK_flag = 0;0 \1 W7 p$ W( }; U: ] O
- DisposeReceive(); //处理modbus通信
' E) Q; c1 I b1 m1 h5 Z0 @ - }' I$ M+ f a, r+ c3 V& H( @
-
: B; a: S' t. t5 M* ` - j++;! @ b% S" \ S
- if(j > 50)2 R& P! D# O* J$ G$ ]4 h
- {4 ^) K8 B; V) a) Y
- j = 0;- |7 g# f. Z" N$ z
- LED = !LED;* }3 l# m) B/ i, W: i9 s! t- X
- } H3 w0 K/ w7 H5 J9 N
- delay_ms(10);
! _8 A* N* t+ N4 o* j# ?: k c+ d - }
. R6 Y' A) h, S# E/ b3 o, u( U& F- G - }
复制代码 ! z, _5 z ? W. M' E
最后是Modbus的协议解析7 L( T8 ^& F7 l& E6 \( c, d
- void DisposeReceive( void )
! m. [6 I0 ?% G/ A5 m7 r. ~2 ] - {7 }9 ]: q7 D6 [6 _
- u16 CRC16 = 0, CRC16Temp = 0;
: U/ q1 U7 E0 ~( n" W. M - if( data_backup[0] == SlaveID ) //地址等于本机地址 地址范围:1 - 32) e. Z, F7 L; e9 A7 O7 v
- {
. x4 p$ r# [* u2 ^2 g - CRC16 = App_Tab_Get_CRC16( data_backup, dataLen_backup - 2 ); //CRC校验 低字节在前 高字节在后 高字节为报文最后一个字节
; \- W% o; K4 H - CRC16Temp = ( ( u16 )( data_backup[dataLen_backup - 1] << 8 ) | data_backup[dataLen_backup - 2] );, a, n# @* c2 S* h0 ]
- if( CRC16 != CRC16Temp ), a ~- b# m0 T! T4 f/ V r0 a
- {% w/ D- v4 X5 T* L8 G; o' z' K
- err = 4; //CRC校验错误2 o& p, P' h% D7 x8 K
- }
6 ^' s0 u( B1 {4 q2 z9 z - StartRegAddr = ( u16 )( data_backup[2] << 8 ) | data_backup[3];8 C" @1 q* Z& Y# z
- if( StartRegAddr > (HoldRegStartAddr + HoldRegCount - 1) ) : r* z. @" J7 s s; N; N
- {
. }4 d4 {' t6 G) h8 e. \8 \* a9 H* Z - err = 2; //起始地址不在规定范围内 00 - 07 1 - 8号通道6 w8 l& V$ b/ y$ ]1 `) i
- }
l m$ H5 _2 i. X$ o9 x - if( err == 0 )% k2 n+ N: t+ _$ ^1 `
- {
2 h% D/ f7 [( D3 F - switch( data_backup[1] ) //功能码8 t+ o1 W8 ^8 j. Y S) x: T- y
- {
7 w% J1 t4 u6 h8 A2 Q - case 3: //读多个寄存器
! @, K2 j8 Q+ M1 S. j( d - {
; q$ h9 \6 A% s2 b6 o* f - Modbus_03_Slave();" `; t" Q! T% A
- break;0 T7 P( J8 b% t% S0 X* O
- }. [# M) Z5 p: D& z4 c6 c7 M' X: y4 l
- case 6: //写单个寄存器
- O: s, p- U6 J# L - {9 ~: L) N2 M# \$ q3 {
- Modbus_06_Slave();
4 N3 r, Y* Y; ]. [3 n7 R! F5 a, j - break;
6 h: \1 u& U" Q; J0 \+ W - }" Q; l& @9 W' ]. ^! @
- case 16: //写多个寄存器7 ?# R! @, a! O8 K6 x
- {8 O L" E7 k$ e
- Modbus_16_Slave();
8 n- S# w. V# A9 A; j - break;' J6 X: c, Q7 M2 y
- }* u/ W' }! u+ l& B" o2 I0 @: V) I
- default:/ p+ _& L6 f9 f: @
- {
. s6 U g; Z- ?, p y - err = 1; //不支持该功能码+ c3 j& B+ O3 U+ w9 g o& K
- break;) u9 ~9 j ]( Y9 K$ Y: G- }$ R
- }5 `" Q9 J" }. V
- }
$ Y7 S- n0 c$ h y( E" r( \3 } - }- z* ]/ Z/ l0 g/ W1 T0 O) R
- if( err > 0 )
. ?( C J) W0 N- } - {
# I y1 a4 b) m! I, v% h - SendBuf[0] = data_backup[0];
2 h9 U( f3 b6 L - SendBuf[1] = data_backup[1] | 0x80;& _$ p8 ?3 @, W3 T
- SendBuf[2] = err; //发送错误代码
( d, c/ T6 t8 q# c) K - CRC16Temp = App_Tab_Get_CRC16( SendBuf, 3 ); //计算CRC校验值
' i4 o+ ]+ p& \6 Q; `, W - SendBuf[3] = CRC16Temp & 0xFF; //CRC低位
, q0 {. X& m. f4 S2 ~" z* T - SendBuf[4] = ( CRC16Temp >> 8 ); //CRC高位
) N0 U9 V% p$ ]4 A# H - uart2_Send( SendBuf, 5 ); 4 O! A4 p4 \7 s0 a. t k' b4 H
- err = 0; //发送完数据后清除错误标志* T- m$ ]7 H5 Q' f2 k
- }0 K! h0 `2 O) `& a/ k
- }
2 I3 J2 ^( L [* n3 ? N - }" w# x4 M' e: ` Z
9 j& {8 [7 }) j" k( t5 x. W' V) D- /*4 q! g; d1 x6 K T$ K4 p4 Y4 ]) p) k
- 函数功能:读保持寄存器 03+ s, M! m1 a) r; [
- 主站请求报文: 0x01 0x03 0x0000 0x0001 0x840A 读从0开始的1个保持寄存器. P; m2 O4 O7 ?+ |. v' f% ?
- 从站正常响应报文: 0x01 0x03 0x02 0x09C4 0xBF87 读到的2字节数据为 0x09C4
) b$ z' S& i/ N; { - */" h8 w: C3 \9 Q, B: m0 |" o
- void Modbus_03_Slave( void )1 p- M6 W+ E# ~+ l7 w7 k. Y
- {0 h8 w6 l+ c) c2 n6 r1 Y/ \. v$ r
- u16 RegNum = 0;
4 I. U9 L/ P/ h2 d: l. z - u16 CRC16Temp = 0;
! M; C3 B: n7 S- L - u8 i = 0;1 `! j- U+ _& A" _) }
- RegNum = ( u16 )( data_backup[4] << 8 ) | data_backup[5]; //获取寄存器数量 _: S8 u5 w5 Q$ }4 P k
- if( ( StartRegAddr + RegNum ) <= (HoldRegStartAddr + HoldRegCount) ) //寄存器地址+寄存器数量 在规定范围内 <=8& Z/ B$ O0 [& L N. E6 d) Z
- {
) ^; K6 ]+ J4 I, Q - SendBuf[0] = data_backup[0]; //地址
# N! E8 d2 e* j- \1 X& C - SendBuf[1] = data_backup[1]; //功能码
( t( T- b- @2 s L0 C- u - SendBuf[2] = RegNum * 2; //返回字节数量
+ Q; v8 R, g+ l; b0 Y9 f. u - for( i = 0; i < RegNum; i++ ) //循环读取保持寄存器内的值
9 J1 ~+ ]0 c( r7 m - {
6 C8 P" X3 R" t- t9 ~/ D - SendBuf[3 + i * 2] = HoldReg[StartRegAddr * 2 + i * 2];
! n/ G# h7 E8 w) A3 c& m! a# I+ P - SendBuf[4 + i * 2] = HoldReg[StartRegAddr * 2 + i * 2 + 1];
: W8 ~8 p& d6 Y. s6 \ - }. ?3 s( u* [" U5 i9 R
- CRC16Temp = App_Tab_Get_CRC16( SendBuf, RegNum * 2 + 3 ); //获取CRC校验值- P, Y& q5 V2 q8 {
- SendBuf[RegNum * 2 + 3] = CRC16Temp & 0xFF; //CRC低位
4 n3 S6 C/ h g ^ - SendBuf[RegNum * 2 + 4] = ( CRC16Temp >> 8 ); //CRC高位+ }. v0 O I! F# }& O
- uart2_Send( SendBuf, RegNum * 2 + 5 );9 ?% P% Y# w8 [2 Y( U" A; [3 [% T" l
- }
5 t: p i4 Z: x {3 ^. R - else
; j8 o2 r# l- {) X) i - {" m9 Z( g* c. e. b
- err = 3; //寄存器数量不在规定范围内9 p+ p% Z6 L; {$ ^5 r/ b
- }% n5 g# d# [. q- e
- }
* T8 K, X( M; t/ e - /*
/ j7 Z( R" s6 u* D: T - 函数功能:写单个保持寄存器 06
& p/ K% ^9 i4 X# Y+ [ - 主站请求报文: 0x01 0x06 0x0000 0x1388 0x849C 写0号寄存器的值为0x1388& M5 m. |2 A: c+ a6 Y: C
- 从站正常响应报文: 0x01 0x06 0x0000 0x1388 0x849C 0号寄存器的值为0x13881 i- ^; A' y. }$ `2 B+ H! Q. P
- */
) `2 _2 R8 a* Q - void Modbus_06_Slave( void )
( l! B4 m2 m+ s - {# I, { j0 j/ b9 B8 e- o' V
- u16 RegValue = 0;7 A. u' U% [7 Q! W% z$ [3 c; g, y
- u16 CRC16Temp = 0;
: ?" m; v! K* G - RegValue = ( u16 )( data_backup[4] << 8 ) | data_backup[5]; //获取寄存器值
5 V4 d# I, _0 R% H - if( RegValue <= HoldMaxValue ) //寄存器值不超过1000% [ p& o! w+ s% y F6 o: h# w
- {
2 m& l) m3 ?) c& P, U& U# m8 j - HoldReg[StartRegAddr * 2] = data_backup[4]; //存储寄存器值
+ K/ u/ V' Q; t0 W - HoldReg[StartRegAddr * 2 + 1] = data_backup[5];
/ g2 t" z! N$ o - SendBuf[0] = data_backup[0]; //地址
# w4 u6 Q A# J6 a( t - SendBuf[1] = data_backup[1]; //功能码
! A" Y: ~4 M+ H$ ? - SendBuf[2] = data_backup[2]; //地址高位
- x, H( n! y# e" Y- ~3 S - SendBuf[3] = data_backup[3]; //地址低位$ L5 J. U" q& w8 Z) K6 t, v
- SendBuf[4] = data_backup[4]; //值高位
3 t: h: D% G9 U) M' P' w - SendBuf[5] = data_backup[5]; //值低位
* B! u" R/ G; f - CRC16Temp = App_Tab_Get_CRC16( SendBuf, 6 ); //获取CRC校验值% i$ |- [+ K. h
- SendBuf[6] = CRC16Temp & 0xFF; //CRC低位' b. e" F" z2 z3 f; ^
- SendBuf[7] = ( CRC16Temp >> 8 ); //CRC高位% {# a3 I# p* z9 _; P/ v
- uart2_Send( SendBuf, 8 );
& }, }5 O8 z k - }1 \# B5 Y, H: f% m$ E1 F6 K0 A( r
- else4 _: e; x3 L& I8 A
- {; G8 e" F& z! Y0 d* _! P* @
- err = 3; //寄存器数值不在规定范围内, J3 ^ @3 c. H. K
- }' s; ~& S+ [/ Q% R1 ^3 g8 n S
- }
- Y5 q; N, \5 y' V$ B3 w - /* `2 Q0 ^; J" y
- 函数功能:写多个连续保持寄存器值 16! ~: L3 J$ {. Y( S! X f
- 主站请求报文: 0x01 0x10 0x7540 0x0002 0x04 0x0000 0x2710 0xB731 写从0x7540地址开始的2个保持寄存器值 共4字节1 s% a7 T: _, L2 z' r1 L5 Z% [. n
- 从站正常响应报文: 0x01 0x10 0x7540 0x0002 0x5A10 写从0x7540地址开始的2个保持寄存器值
5 |% x y2 n* s9 m - */
* a% t1 o8 \8 ]! | - void Modbus_16_Slave( void )% ^) T3 k( Y6 A+ D( w$ `9 S* q
- {
9 Y. P4 Y9 b3 O/ ? K: f- C - u16 RegNum = 0;: C& X! U7 w2 c8 @$ a1 d' ]/ l
- u16 CRC16Temp = 0;
4 s( y& w& W' q; l, r) ` - u8 i = 0;/ y: U( m, r) z1 n* e0 X
- RegNum = ( u16 )( data_backup[4] << 8 ) | data_backup[5]; //获取寄存器数量
9 R" ^& V, j8 o( j9 r: @ - if( ( StartRegAddr + RegNum ) <= (HoldRegStartAddr + HoldRegCount) ) //寄存器地址+寄存器数量 在规定范围内 <=8
* y1 | o. d* ? - {
- ~ ]4 p2 r) [# Q; z% u! G$ c - for( i = 0; i < RegNum; i++ ) //存储寄存器设置值
) p" o% s; |7 ~0 ~ L - {
/ t* V M: s7 N& D5 M1 w9 u - HoldReg[StartRegAddr * 2 + i * 2] = data_backup[i * 2 + 7];( I) y: V" O( L3 i' R" |
- HoldReg[StartRegAddr * 2 + 1 + i * 2] = data_backup[i * 2 + 8];
1 v! R- `( }! N# P, E" [" L - }
# j8 ~0 {) F4 K/ J% K - SendBuf[0] = data_backup[0]; //起始地址* h1 ?- @% E7 x4 o
- SendBuf[1] = data_backup[1]; //功能码
. G9 j/ w( u. t - SendBuf[2] = data_backup[2]; //地址高位
- S+ v! P8 X8 K - SendBuf[3] = data_backup[3]; //地址低位
' I* I% a* w7 d- I: b8 m - SendBuf[4] = data_backup[4]; //寄存器数量高位
8 t" Q( {1 A$ g+ _+ c ` - SendBuf[5] = data_backup[5]; //寄存器数量低位6 E1 `! M& L& h
- CRC16Temp = App_Tab_Get_CRC16( SendBuf, 6 ); //获取CRC校验值2 N7 e, C$ r" i/ @; @, c" G
- SendBuf[6] = CRC16Temp & 0xFF; //CRC低位
2 k6 k- F5 ?2 ~3 }, ~ - SendBuf[7] = ( CRC16Temp >> 8 ); //CRC高位' {8 P( }' M& N
- uart2_Send( SendBuf, 8 );% R5 i% J! m$ @7 V8 b' P5 o
- }
4 v; V7 M3 c, M - else. d1 }; R) g) m& j% f' {
- {
. A/ r# l. P! u* j: L" \ - err = 3; //寄存器数量不在规定范围内/ J% ]/ p( T1 d3 u: V: c; g
- }; F9 P! E# E( C1 a
- }
复制代码
. e; J& K# `4 p8 }- LModbus的执行在这里只实现了最常用的三个。
! o( y9 I6 {/ A# I0 t+ N G: Y, ~5 i3 u3 O
) G/ A5 j; D( `( s L2 A
4 I$ K, }3 ?* x$ W7 k各个功能码的实现原理在这里就不说了,直接通过代码中和注释就可以看明白了。
$ j3 Z' X' g1 n$ Z# I4 v; _% P; O* _0 x% h
下面贴出串口和modbus的完整代码
9 [/ ]: j6 Q8 T- r
/ R& H. r, n8 B) i0 ]0 X3 p Suart2.h3 S) `+ S& o4 ]$ O2 h5 Y, P
; q3 A! ?- E* t/ |- #ifndef __UART2_H( s1 m# i8 Q5 N2 c
- #define __UART2_H0 h, Z# F5 h# V7 i# R
- #include "sys.h"
* [* Q) {7 }( Q - ) t- Y" O1 \5 R' `) u* K. C
- #define DMA_REC_LEN 50 //DMA数据接收缓冲区) h/ t+ t) ` H
: t' X/ l; {0 _) l: F4 V$ H, X- void uart2_init(u16 baud);1 g% U m( [# o- T% E
- void uartDMA_Init( void );
/ y5 V' E4 B( _+ H - void myDMA_Enable( DMA_Channel_TypeDef*DMA_CHx );& z4 q3 J5 R% a8 K$ Y* A( |
- void uart2_Send( u8 *buf, u16 len );# e8 l5 v# @# Y/ ?; H
- void copy_data( u8 *buf, u16 len );/ ?- J: d& N- k" n+ y$ F
- #endif* i; {$ V% B% i
复制代码
$ U0 A) G( U: M# l+ z' p# M7 C' Juart2.c$ v1 U9 |# b6 k3 i: Y' @1 k
1 y b8 q# u+ x& l( L; L$ O
- //串口2空闲中断 + DMA数据传输: q2 R. _ S6 c# N% X# v: ]1 O
- #include "uart2.h"
4 k+ L) S; |1 n - #define UART2_DMA 1 //使用串口2 DMA传输
6 E U, _8 k, u7 p9 O# Z - #define CHECK_NONE_ONE_STOP 1 //无校验位 1个停止位 1有效 0 无效 1 P q' y& A* z5 K6 ^0 h$ ?
- #define CHECK_NONE_TWO_STOP 0 //无校验位 2个停止位 1有效 0 无效 ! z2 l+ k3 e8 y# [* ~
- #define CHECK_EVEN 0 //偶数校验 1有效 0 无效
% v& {; [/ R2 ^ | - #define CHECK_ODD 0 //奇数校验 1有效 0 无效
0 H( P9 ?5 i# b0 L! F& Q - + `8 S& v" p1 J
- u8 dma_rec_buff[DMA_REC_LEN] = {0};
3 M) `) J! |3 q/ l o! x - u16 uart2_rec_cnt = 0; //串口接收数据长度! B, m# ?. |5 v% i
- 1 z3 x* s' ~/ z/ a/ ]
- u8 data_backup[DMA_REC_LEN] = {0}; //数据备份. S7 g. v& u! L4 ]. Z8 a9 X
- u16 dataLen_backup = 0; //长度备份
/ Z* H: p ^" `) w4 |/ p, a - _Bool receiveOK_flag = 0; //接收完成标志位
6 z! [. m3 V" M4 P - " l- N) ^: U, J6 U( p
- /** A* r. v( F; p# U R
- 空闲中断是什么意思呢?, e0 M8 h. I! z4 d! k0 T# D
- 指的是当总线接收数据时,一旦数据流断了,此时总线没有接收传输,处于空闲状态,IDLE就会置1,产生空闲中断;又有数据发送时,IDLE位就会置0; E( o# D. l- ^5 [9 W
- 注意:置1之后它不会自动清0,也不会因为状态位是1而一直产生中断,它只有0跳变到1时才会产生,也可以理解为上升沿触发。
- R9 m6 K3 v. I" r0 H - 所以,为确保下次空闲中断正常进行,需要在中断服务函数发送任意数据来清除标志位。
" z( e P, g6 N2 n - */- R# H- n6 E) i% \' J; u( Y* K
- * d7 u6 Z: z; _2 A. |2 t
- void uart2_init( u16 baud )
' y+ F: ~2 e6 b, W" x9 Q - {
8 X# S5 k. t* A' l3 c - GPIO_InitTypeDef GPIO_InitStructure;
" q5 F, l" e6 f7 d - USART_InitTypeDef USART_InitStructure;9 W3 Q9 d! x1 v d
- NVIC_InitTypeDef NVIC_InitStructure;
/ F+ O* g+ B1 ?" A - 6 x/ c% p2 V; L% Q' r8 r
- RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );
3 g6 P+ K5 w! f' _4 n- C - RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART2, ENABLE );
4 Z2 p# ^2 ] ]) v2 [8 y5 S) | - $ [" y* D7 A) ~6 d6 Y
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;2 l- @+ O. ]1 U/ t2 D
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //推挽复用模式; r7 ?3 s9 J! W* n, Z2 V7 H' ?( W+ T
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;4 i `: r' N- q" y) T3 |4 R8 f9 ]" d
- GPIO_Init( GPIOA, &GPIO_InitStructure );
j/ y; I+ E+ B; o - , }# z4 {3 X* e3 V! } r/ Y
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;! y) Y3 [/ e3 ]% l2 {$ W
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入模式
% Z) E* S) B7 V/ W - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
; ^9 f# {. r/ X. P; t - GPIO_Init( GPIOA, &GPIO_InitStructure );
3 e+ F6 r* N; r" D - - o% X6 I, q) K
- NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;- C' s; m8 h# E
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
4 T( C$ U$ e) @, V5 r7 m% C - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;
% f3 n" \+ E. y6 t: p - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;- S0 B! {; ^: J4 @
- 8 K6 A% w6 ]) }( o- g
- NVIC_Init( &NVIC_InitStructure );
' [% W; g h+ {( s& X
1 a- k# Y/ i: }- USART_InitStructure.USART_BaudRate = baud;
% v- w$ q# k3 @1 | - USART_InitStructure.USART_WordLength = USART_WordLength_8b;
/ A; w. C( ]5 P6 O6 d" v3 i - USART_InitStructure.USART_StopBits = USART_StopBits_1;6 M* ]4 k# U8 ^1 X8 l' R/ ^+ {; J! L
- USART_InitStructure.USART_Parity = USART_Parity_No;4 ^2 V0 o: H' u5 E2 s, i* D
- 4 ?1 G( X% Q5 a* R/ @
- #if(CHECK_EVEN == 1) //如果定义了偶校验 数据位长度要改为9位
* |. l* Q, G4 Y* W - USART_InitStructure.USART_WordLength = USART_WordLength_9b;, k5 {8 `" `; u
- USART_InitStructure.USART_Parity = USART_Parity_Even;
* ?4 j. X" G/ S$ H- m5 @ - #endif
* d4 J* u$ X7 S5 s - * Z! _. U4 G" ~* ~4 o' v
- #if(CHECK_ODD == 1) //如果定义了奇校验 数据位长度要改为9位4 z: O- w) g, u |2 s
- USART_InitStructure.USART_WordLength = USART_WordLength_9b;# h4 ^3 w# n/ r* |# ?9 W4 S2 _8 ^
- USART_InitStructure.USART_Parity = USART_Parity_Odd;
' Z8 I: ]9 V/ s/ [) g7 E+ @2 O# S - #endif7 \4 i1 |6 P/ I* F# _
) C6 w) q: j \! C# U- #if(CHECK_NONE_ONE_STOP==1) //停止位为 一位% w" G- [2 o. I* f' H/ ]: z
- USART_InitStructure.USART_StopBits = USART_StopBits_1;
) l; \/ N f+ b' b: ]3 [3 K) N - #endif2 S& B/ C( M" C, p5 G, j! D
- 9 N/ x1 t: V! X7 v2 B# j5 n. ~3 X D
- #if(CHECK_NONE_TWO_STOP==1) //停止位为 两位 * H+ _0 k9 m3 Z$ p
- USART_InitStructure.USART_StopBits = USART_StopBits_2;+ ]) x7 M9 l7 n2 s) j% ]
- #endif " P: i$ {) W0 P- a n
-
5 Z) G, y. g2 i o) o1 I+ K -
. x9 ]5 n$ i8 [) t - USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
; B% L4 [9 O' c6 _ - USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
" c6 W3 H: s# B4 z - 0 i8 j1 l" c$ X2 z
- USART_Init( USART2, &USART_InitStructure );$ @8 G$ _5 z% K
- 6 h. L, m" D7 D: B! X$ R9 n8 A- q6 O
- #if (UART2_DMA == 1)
7 @5 u# @! U! ]8 r0 m3 l4 c' Z - USART_ITConfig( USART2, USART_IT_IDLE, ENABLE ); //使能串口空闲中断
9 [* A" a( R, l - USART_DMACmd( USART2, USART_DMAReq_Rx, ENABLE ); //使能串口2 DMA接收+ r6 N1 n# D+ e, T4 H
- uartDMA_Init(); //初始化 DMA
- c3 m1 I4 K" \- H9 ]: @/ J. O - #else
5 p! ?1 u- b! q% j0 a" e) T0 @9 C - USART_ITConfig( USART2, USART_IT_RXNE, ENABLE ); //使能串口RXNE接收中断
' m; J7 ^8 d0 A- N8 Z# o8 A5 Q% V8 w - #endif) X( j# B# M4 ]" B$ m
- USART_Cmd( USART2, ENABLE ); //使能串口24 c! t; V/ J0 ~7 s1 x% x
- 5 m5 l5 B! t1 m! J0 ?
- //RXNE中断和IDLE中断的区别? @3 ?+ Y- ]8 L) v; U
- //当接收到1个字节,就会产生RXNE中断,当接收到一帧数据,就会产生IDLE中断。比如给单片机一次性发送了8个字节,就会产生8次RXNE中断,1次IDLE中断。( l) e% ^& A+ ~+ F
- }
8 [$ V. n& W) i5 i* j" t# I* x. d - 9 Z5 t) o: k: ?. _; Y; i
- void uartDMA_Init( void ) i( `0 \* c/ q/ l( \% S/ d
- {
) s. G) n0 R* x - DMA_InitTypeDef DMA_IniStructure;% @, X8 Z& }* r2 l/ Z4 x2 }% T: I
* J; S3 d* K) N0 `' }4 t6 `% g+ u- RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE ); //使能DMA时钟" i$ V% g( E& W" u1 E, i1 q4 r
- t' R7 F0 H/ Y
- DMA_DeInit( DMA1_Channel6 ); //DMA1通道6对应 USART2_RX
+ ^+ ^, k/ p- ~" @7 N - DMA_IniStructure.DMA_PeripheralBaseAddr = ( u32 )&USART2->DR; //DMA外设usart基地址
5 j" x( c: N8 J - DMA_IniStructure.DMA_MemoryBaseAddr = ( u32 )dma_rec_buff; //DMA内存基地址1 G5 t/ H# D4 P) o8 i% L
- DMA_IniStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,从外设读取发送到内存
8 n' l% H1 P9 [' F! X! N - DMA_IniStructure.DMA_BufferSize = DMA_REC_LEN; //DMA通道的DMA缓存的大小 也就是 DMA一次传输的字节数, @3 q5 A H( y- P
- DMA_IniStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变( g' K. l/ A* C" ?- |0 x
- DMA_IniStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //数据缓冲区地址递增) T0 M' |. ^2 X
- DMA_IniStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设以字节为单位搬运8 `. \' @* E* u8 M
- DMA_IniStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据缓冲区以字节为单位搬入
c9 x. ~" x' R$ H, E2 `4 l. @. { - DMA_IniStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式6 s9 U" B! m( \0 X
- DMA_IniStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级
D- m% [4 v! h; [; b - DMA_IniStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输* U- E& P; G- [* c6 [7 w( G5 d% ~
- d$ G$ N: P q, d2 a! _; [- DMA_Init( DMA1_Channel6, &DMA_IniStructure );
; e* c0 F* o! d. c! j0 Y( g5 v - DMA_Cmd( DMA1_Channel6, ENABLE );& b& i! \5 A2 g" _0 ^
- }
0 Y% y4 o/ d. y' A6 D9 N. q - & j" |7 t. A7 Y- |: C$ H4 V
- //重新恢复DMA指针& x# J) D/ L5 m3 B# |- T
- void myDMA_Enable( DMA_Channel_TypeDef*DMA_CHx )+ [' l, x/ g i5 \+ |. `
- {
# z/ P/ v4 m% H3 L/ @ - DMA_Cmd( DMA_CHx, DISABLE ); //关闭DMA1所指示的通道6 Z R5 N, b2 d( M. W7 s
- DMA_SetCurrDataCounter( DMA_CHx, DMA_REC_LEN ); //DMA通道的DMA缓存的大小
( U' @. l. v0 J# ~) Q. P+ \+ g$ U3 m - DMA_Cmd( DMA_CHx, ENABLE ); //DMA1所指示的通道' c: o5 S9 C7 y$ e
- }. Y" h& k0 g$ b. Q& F$ f
- //发送len个字节
& X$ L" O$ P9 W6 C; y - //buf:发送区首地址5 B+ ~( `+ S: o( X
- //len:发送的字节数, C0 J. D6 k7 l0 m7 Y3 ?8 y
- void uart2_Send( u8 *buf, u16 len )" `6 g5 R* |4 b/ u, j3 K1 A1 Y' q
- {
" t8 K& w4 A9 T/ ^. T' S - u16 t;
6 m6 G; n5 ]7 ?6 ~1 {/ O8 } - for( t = 0; t < len; t++ ) //循环发送数据$ X% G9 K2 }7 c) n5 ^
- {2 r& T+ x8 J, H: h9 ?: [
- while( USART_GetFlagStatus( USART2, USART_FLAG_TC ) == RESET );
: M' a, A9 J9 r7 p% Q5 s$ y A - USART_SendData( USART2, buf[t] );. M! t$ I: I/ z1 }/ m
- }& y, z6 ?- L( B6 W8 C% A
- while( USART_GetFlagStatus( USART2, USART_FLAG_TC ) == RESET );+ X/ u% P0 X0 _" }3 ]1 Z2 h) y
- }) L3 h+ ~, O }: i% h+ Y2 n
- 8 t% L3 X& F' j
- //备份接收到的数据0 r2 |# x6 m% F& r
- void copy_data( u8 *buf, u16 len )
0 {% n( X' Q1 I - {/ P; l/ j4 O! s9 z: ]2 {
- u16 t;
% O+ l' E$ @) _! w4 c0 r - dataLen_backup = len; //保存数据长度
* q3 F. f, j/ G8 ]# ^ - for( t = 0; t < len; t++ ), v7 k$ u9 t& o- f; ?
- {# {) k0 c% I; `! R) P# ]( W- Y
- data_backup[t] = buf[t]; //备份接收到的数据,防止在处理数据过程中接收到新数据,将旧数据覆盖掉。" t( @& p$ K( B* S3 E
- }
2 V8 E; u$ O2 ?$ \ - }
# t, F/ \; {; Y6 l, f! t9 J' n - // Modbus RTU 通信协议5 Q/ D$ v& m4 s7 ?2 W
- //地址码 功能码 寄存器高位 寄存器低位 数据高位 数据低位 CRC高位 CRC低位3 e3 n8 W" ~4 j7 ?0 I [& v
- //01 03 00 00 03 e8 xx xx
7 @& T5 }2 N; B- J( F - //利用空闲中断接收串口不定长数据& Q( C$ ~; j5 R# O# e' n3 S2 h
- //串口接收一组数据结束后才会进入中断,在数据发送过程中,已经接收到的数据将会被存入DMA的缓冲区中: T$ ?/ f- Q: I5 F4 G% T
- void USART2_IRQHandler( void )5 o, P2 A D& [
- {$ {/ q: F4 {, s- }: v
- u8 tem = 0;
, a; r5 w. O" m! {: b - #if (UART2_DMA == 1) //如果使能了 UART2_DMA 则使用串口空闲中断和 DMA功能接收数据
% D! l# A) a* b. a* k. ` - if( USART_GetITStatus( USART2, USART_IT_IDLE ) != RESET ) //空闲中断 一帧数据发送完成
* S6 H$ ?, \$ `) j+ {& t: ~ - {
! K, n( v7 y8 M9 x - USART_ReceiveData( USART2 ); //读取数据注意:这句必须要,否则不能够清除空闲中断标志位。
6 M3 o/ [5 k2 V4 j - DMA_Cmd( DMA1_Channel6, DISABLE ); //关闭 DMA 防止后续数据干扰( ]2 j( P# [; r2 J; z( @
- uart2_rec_cnt = DMA_REC_LEN - DMA_GetCurrDataCounter( DMA1_Channel6 ); //DMA接收缓冲区数据长度减去当前 DMA传输通道中剩余单元数量就是已经接收到的数据数量4 Y' k7 E. L$ Z3 V
- //uart2_Send( dma_rec_buff, uart2_rec_cnt ); //发送接收到的数据6 G2 f, D( _* g4 r
- copy_data( dma_rec_buff, uart2_rec_cnt ); //备份数据 \$ h' v3 f8 }6 u( G. d
- receiveOK_flag = 1; //置位数据接收完成标志位) ^( |/ V3 U- u8 s' f3 G8 k8 v
- USART_ClearITPendingBit( USART2, USART_IT_IDLE ); //清除空闲中断标志位
) Q, g$ z% i6 b9 N: { - myDMA_Enable( DMA1_Channel6 ); //重新恢复 DMA等待下一次接收% g# ?: ?: w% y) A6 L1 z/ i; l9 E
- }7 `) m; F6 {; H& }
- #else //如果未使能 UART2_DMA 则通过常规方式接收数据 每接收到一个字节就会进入一次中断1 _' H$ `7 c; g+ n# ]. B6 V
- if( USART_GetITStatus( USART2, USART_IT_RXNE ) != RESET ) //接收中断
' `1 z; V" }0 O% l* w - {
( i3 r7 M% s) G7 }0 }- O' {1 m - tem = USART_ReceiveData( USART2 );# ?9 b0 E" R( |0 n# m+ ]' }! X
- USART_SendData( USART2, tem );
" P$ Q6 V/ ]) ?8 D - }* b* _8 Z/ V7 y0 W. ?* C
- #endif
& k: I" J8 R) Y; O0 r- `+ h9 |6 ^ - }
复制代码 & J& b0 p7 W% |% l" v1 J8 J
modbus.h
" W( b+ w$ o4 |, g* o0 ?; h# v, N6 Q) i. F0 l' W, w7 M2 g
- #ifndef __MODBUS_H
: {3 F- _# y! W- R. ^6 C - #define __MODBUS_H% M9 d+ e5 w3 P
- #include "sys.h"; U# Z' \7 ?5 j
- 4 o, j6 A" l" w
- - x2 b( M8 ]& N' L; [+ i* d
- #define SlaveID 0x01 //从机地址: ?- y) A7 R* h, s! R p
+ v0 k) [( d* t$ y9 O$ X. _# s- void DisposeReceive( void );
% l4 ]: T& W9 ~3 v `8 g - void Modbus_03_Slave(void);0 e9 x3 A# x) s( |" D: I
- void Modbus_06_Slave(void);
. m( |1 @; x7 p% w/ _, S - void Modbus_16_Slave(void);
5 Y& B- h" ^ y% ~ - #endif
复制代码
) L) s% @% T4 B' @8 h, T k; w5 D- Amodbus.c' L( n& I7 s+ x' K+ V# e
' @& ]4 U" y- M B
- #include "modbus.h"" u, r) b/ f- ], }$ g* t
- #include "crc16.h"' b" x9 H8 E# i h+ J$ Z6 r/ R
- #include "uart2.h"$ X4 C$ F {; O% I$ R* _$ K
3 T' {8 ?* \' W7 E3 H% i# G- #define HoldRegStartAddr 0x0000 //保持寄存器起始地址
, @ B4 y' K) u# T1 a9 h, @" _7 y - #define HoldRegCount 8 //保持寄存器数量8 }+ ^: S3 q2 N. ^% N% E8 A! ~
- #define HoldMaxValue 1000 //寄存器最大值# w* z( B+ B5 a8 I: B7 g
- ; O/ g/ @: ?, W
- extern u8 data_backup[DMA_REC_LEN]; //modbus接收到数据备份/ O- D8 q% ]2 b- e( a8 J$ A
- extern u16 dataLen_backup; //modbus接收数据长度备份: H0 A0 P+ y) @ c2 O9 o
- . z- Z( A! j5 p" Q+ W' H; y
- 0 Q+ G |3 U7 O, G8 i- s8 z& f
- u8 SendBuf[DMA_REC_LEN] = {0};/ g( e% ^- y- T$ E. G0 n
- u16 StartRegAddr = HoldRegStartAddr;
. j; \/ X3 [$ [6 J - u8 err = 0; //错误代码
# B# U" g4 P$ e9 `* h4 a) o
* {8 O9 T( _) _! V- u8 HoldReg[HoldRegCount*2] = {1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}; //存储8路数据值
4 B( [: I( h" `
9 v7 [2 Y) R( M0 X( h- 7 a [/ V2 ]' S
- /*
5 D9 [! I4 E6 G8 g# F - 错误代码说明:
5 h# s- H$ y+ n - 0x01 不是支持的功能码% ]) \$ p* q! S3 m
- 0x02 起始地址不在规定范围内- O; m: G. E, t4 Y5 [! n3 q+ ` R
- 0x03 寄存器数量不在规定范围内2 s1 t6 ?9 o. R5 p# _7 N3 H; |
- 0x04 数据校验错误4 f7 B3 f0 }: e
- */6 X. V% E, X4 D$ `
- //处理接收到的数据
+ B$ k4 f2 |! T, }4 o( M - // 接收: [地址][功能码][起始地址高][起始地址低][总寄存器数高][总寄存器数低][CRC低][CRC高] |: J1 Q5 G6 z4 B% q7 O. i0 R
- void DisposeReceive( void )/ H8 n+ _6 O8 A- x
- {: r1 ^0 W/ @9 i6 Y
- u16 CRC16 = 0, CRC16Temp = 0;, [4 s' P" K- s0 z3 a
- if( data_backup[0] == SlaveID ) //地址等于本机地址 地址范围:1 - 32
. d! }5 {+ f8 Z - {
" V% G5 t; D4 | - CRC16 = App_Tab_Get_CRC16( data_backup, dataLen_backup - 2 ); //CRC校验 低字节在前 高字节在后 高字节为报文最后一个字节
$ f# i, i( C" c - CRC16Temp = ( ( u16 )( data_backup[dataLen_backup - 1] << 8 ) | data_backup[dataLen_backup - 2] );5 e( q, p' c0 P5 c" k
- if( CRC16 != CRC16Temp )
- Y" h' s0 `' r2 k - {
& T4 [+ a% X: H0 R; K" R- F7 r - err = 4; //CRC校验错误
! g) A0 ?4 A; U/ I( d6 H* l - }: q# i* _! m' ~; E6 e8 |& q7 a
- StartRegAddr = ( u16 )( data_backup[2] << 8 ) | data_backup[3];
6 u# Z* B& c: T - if( StartRegAddr > (HoldRegStartAddr + HoldRegCount - 1) )
2 \! U4 G; _, L3 h0 Q% O$ z# A - {, z0 a v7 E n, T. C, s
- err = 2; //起始地址不在规定范围内 00 - 07 1 - 8号通道8 o4 Q1 I' O. ^0 {! A& ]3 F
- }
0 ]7 {. D3 {4 S9 Y2 J/ b+ A0 B! q - if( err == 0 )! Y- c( a, b, F8 X8 X
- {. k% m& R' L, S6 ~% X& @+ K( l
- switch( data_backup[1] ) //功能码 s; y, Z2 g& s2 n) c% Z
- {
0 u8 Y( a% I' X8 I9 z - case 3: //读多个寄存器+ x ~0 N. Y8 e) v( E" ~: c
- {
6 @# B7 C8 D" J4 h; x! u5 ^ - Modbus_03_Slave();( Z5 I2 y# D' @; e6 n6 L, f8 N- z
- break;
% N0 v/ \; @( H J - }
# W5 ~: c( F" F. X$ W- y& \, b8 H - case 6: //写单个寄存器2 S; C- w5 \. v8 x: R
- {' {. P0 d0 c6 k0 Q/ O1 `* v) B, H+ ^
- Modbus_06_Slave();0 Z( \2 S* h+ e# d
- break;: C3 v# r4 a- }! [% c
- }
+ w6 C4 }% F9 G0 C& P - case 16: //写多个寄存器
, Q/ w g( N; s- C - {
?+ b2 C! s% W! P - Modbus_16_Slave();
' r0 m$ [7 C, z ^0 C1 a - break;( k: d& ^0 ^' v# c4 ` s' y8 i
- }
3 W5 m8 B: S1 O' \* { - default:7 N9 {: P% ]- i& D5 D- U+ Z/ }
- { t+ t; [8 U ]" w6 A
- err = 1; //不支持该功能码9 L6 v4 a5 U7 j/ t% R
- break;
' T8 Z. O" b4 s1 @0 r - }" F, O! {/ K- W& b# {" X* L6 d
- }
% Y1 X0 S% u: @ } - }, c' ~$ F1 x6 n
- if( err > 0 )* X9 M" l. x& }8 ]' t2 L$ W6 C
- {: E* `% I' p. E* E
- SendBuf[0] = data_backup[0];
) ^5 E" _% S% s2 {9 Q: S - SendBuf[1] = data_backup[1] | 0x80;' Q/ _0 o6 H4 D7 ?$ A$ B
- SendBuf[2] = err; //发送错误代码- r7 z+ N* ~& P! i8 K6 b, f
- CRC16Temp = App_Tab_Get_CRC16( SendBuf, 3 ); //计算CRC校验值
y0 ?* J2 S* |5 e/ o - SendBuf[3] = CRC16Temp & 0xFF; //CRC低位
: O( o- a3 s: q' ]# j2 U% k - SendBuf[4] = ( CRC16Temp >> 8 ); //CRC高位+ R4 y+ t+ j" U% ^
- uart2_Send( SendBuf, 5 );
: P6 h8 }3 P$ Z- y% h - err = 0; //发送完数据后清除错误标志- H. }# P _7 W2 i5 A; d( z
- }! F( f0 Z. ]+ E# o" d& Z! E8 R. ~5 M
- }7 y" n9 Y1 [" I: J2 \# D
- }/ @8 `% T9 D) N
2 c8 G6 J- G: U, t m* E0 a; ?7 k- /*
2 ~! z' R1 K! a8 V2 M$ M. Y - 函数功能:读保持寄存器 03
% f. ?- x. n' v$ n' j - 主站请求报文: 0x01 0x03 0x0000 0x0001 0x840A 读从0开始的1个保持寄存器
7 [- w- M# w! `$ Q" k2 s - 从站正常响应报文: 0x01 0x03 0x02 0x09C4 0xBF87 读到的2字节数据为 0x09C42 k) l* X. X( w+ G
- */
( R3 z2 e6 ^) w# i" d9 g; g - void Modbus_03_Slave( void )! }- Q3 n! h3 u. G2 V0 z
- {
0 A/ m* O, V: f0 a j - u16 RegNum = 0;
" F2 p; b) {: _- [ - u16 CRC16Temp = 0;
! }" [6 s4 x+ F5 z8 I0 V! G - u8 i = 0;
# I5 o g8 K& c+ W( d - RegNum = ( u16 )( data_backup[4] << 8 ) | data_backup[5]; //获取寄存器数量 ?- e# b( V3 I' X
- if( ( StartRegAddr + RegNum ) <= (HoldRegStartAddr + HoldRegCount) ) //寄存器地址+寄存器数量 在规定范围内 <=8/ c5 \/ |2 G T P
- {% y2 x# f% W" |0 y) ]
- SendBuf[0] = data_backup[0]; //地址 9 I9 n; {$ G$ h3 G* B
- SendBuf[1] = data_backup[1]; //功能码
1 M' h! h$ L2 Q9 K6 H6 A( a - SendBuf[2] = RegNum * 2; //返回字节数量
h% T8 h9 `) [' o# H7 b8 W0 i - for( i = 0; i < RegNum; i++ ) //循环读取保持寄存器内的值
+ n: ?! r3 q# S- K4 O- L4 | - {% m5 K: _; J* ^, \3 U
- SendBuf[3 + i * 2] = HoldReg[StartRegAddr * 2 + i * 2];
. [* U" b( h$ P4 M7 D0 T `0 B - SendBuf[4 + i * 2] = HoldReg[StartRegAddr * 2 + i * 2 + 1];
8 J; [3 [' @8 F - }8 D0 G. E5 Y2 U3 ^8 t
- CRC16Temp = App_Tab_Get_CRC16( SendBuf, RegNum * 2 + 3 ); //获取CRC校验值) c/ X* n0 O+ m
- SendBuf[RegNum * 2 + 3] = CRC16Temp & 0xFF; //CRC低位
m: i0 G8 A7 C- x+ F/ k. O/ K" H - SendBuf[RegNum * 2 + 4] = ( CRC16Temp >> 8 ); //CRC高位; Q4 c5 W3 k+ ]1 r* o
- uart2_Send( SendBuf, RegNum * 2 + 5 );
) m: B- l9 M, N; X# s$ ~ - }1 b; ?" u" k4 q! S1 f3 Y
- else
, \6 |- Q9 h8 X0 X& t! u1 g - {
. n: e- B( h# {/ }3 P% O; s" m2 y) B - err = 3; //寄存器数量不在规定范围内
, ?! J7 P- {/ ^+ [2 g - }5 q! @3 b5 @# c" e
- }: m; e m/ F5 X5 \8 K: m8 O
- /*3 t" ~2 P9 P* g
- 函数功能:写单个保持寄存器 06
# b' a0 `1 p( \5 F* O# {. o! x - 主站请求报文: 0x01 0x06 0x0000 0x1388 0x849C 写0号寄存器的值为0x1388# f) l7 s& i( u9 c* f
- 从站正常响应报文: 0x01 0x06 0x0000 0x1388 0x849C 0号寄存器的值为0x13883 [5 S6 Q: F. g2 l
- */
, n3 c6 G8 }' e& u- W1 d - void Modbus_06_Slave( void )
* I* ]# ?0 q, ]: S+ j - {
* }0 f( v4 {$ B/ w) B - u16 RegValue = 0;, H, \& ~5 W* \4 _
- u16 CRC16Temp = 0;6 ~5 q/ F* D) h5 U, Z& ]0 d
- RegValue = ( u16 )( data_backup[4] << 8 ) | data_backup[5]; //获取寄存器值
9 d6 t( Z: q- l& l* ^ - if( RegValue <= HoldMaxValue ) //寄存器值不超过1000+ O+ F' g. P! B9 x7 [' Y
- {
7 z8 l# d; P1 S2 A; {8 a4 m - HoldReg[StartRegAddr * 2] = data_backup[4]; //存储寄存器值
8 }2 [5 ]( h/ \. H7 n! G9 ? - HoldReg[StartRegAddr * 2 + 1] = data_backup[5];
( r2 u9 O' ] g0 @8 ]6 }$ B: m - SendBuf[0] = data_backup[0]; //地址& u! p$ ^! m) q3 M
- SendBuf[1] = data_backup[1]; //功能码/ E' J8 T# w7 N
- SendBuf[2] = data_backup[2]; //地址高位6 T2 v- |- r, v6 P
- SendBuf[3] = data_backup[3]; //地址低位
' r# h+ n! h1 G6 `1 k - SendBuf[4] = data_backup[4]; //值高位4 o4 H5 J( x1 H7 |7 S
- SendBuf[5] = data_backup[5]; //值低位$ H" r5 }6 ?9 E% W. V6 d1 Q
- CRC16Temp = App_Tab_Get_CRC16( SendBuf, 6 ); //获取CRC校验值" s) N% A/ ^1 {4 T4 x
- SendBuf[6] = CRC16Temp & 0xFF; //CRC低位5 r, D3 r& m, R, }) K7 Q. V7 w4 C
- SendBuf[7] = ( CRC16Temp >> 8 ); //CRC高位1 j, C) j. C$ R* J Y- N' c8 T
- uart2_Send( SendBuf, 8 ); q# a- ]0 w; B0 \
- }9 Z) h! K6 V! [/ u9 m- H
- else5 h8 j+ R5 z6 A, q+ v2 T8 ?, U6 R
- {
! \( X; I# L- i; m - err = 3; //寄存器数值不在规定范围内! T7 Q$ i( a" |5 Q- D7 y$ @
- }
; q- K) }8 B* h3 c* \ - }
3 M. J$ E z# j. B/ T7 k - /*" u9 l8 Y1 `$ I! J* H
- 函数功能:写多个连续保持寄存器值 16; p. Z/ J; N5 m3 i. z
- 主站请求报文: 0x01 0x10 0x7540 0x0002 0x04 0x0000 0x2710 0xB731 写从0x7540地址开始的2个保持寄存器值 共4字节
0 G4 {9 l7 ~+ H x - 从站正常响应报文: 0x01 0x10 0x7540 0x0002 0x5A10 写从0x7540地址开始的2个保持寄存器值
6 p. P$ A- r( Q7 Q8 s1 S - */
5 A5 j+ J- w2 {' d1 F - void Modbus_16_Slave( void )
: ?- M2 x. O, T! h4 N4 X# A/ j - {& w1 L2 ~$ n, o* Z7 G2 F" D8 L3 b) ?
- u16 RegNum = 0;
0 N5 y& R: [& Y- A0 h: _1 m - u16 CRC16Temp = 0;
/ |$ ~- D6 `- f0 N - u8 i = 0;
( g( m9 H( e, ?9 P5 ] - RegNum = ( u16 )( data_backup[4] << 8 ) | data_backup[5]; //获取寄存器数量
! t$ ~1 |& N! ]0 V" ~# B - if( ( StartRegAddr + RegNum ) <= (HoldRegStartAddr + HoldRegCount) ) //寄存器地址+寄存器数量 在规定范围内 <=8
) G ^2 ^% q' r C& q8 Z% V - {
3 q! ~. w( [* `% S4 v9 J - for( i = 0; i < RegNum; i++ ) //存储寄存器设置值/ h8 V) y9 V Y7 ~2 L1 C
- {
/ G: G* l8 }- g$ s" z; E$ @ - HoldReg[StartRegAddr * 2 + i * 2] = data_backup[i * 2 + 7];
7 r" R: N+ u! N2 l& U! b - HoldReg[StartRegAddr * 2 + 1 + i * 2] = data_backup[i * 2 + 8];
4 y( B3 A0 f* B6 y; ^4 ^ - }
% ^& t g7 J' B9 a8 z - SendBuf[0] = data_backup[0]; //起始地址, Z6 p k. K6 A$ Z b4 E
- SendBuf[1] = data_backup[1]; //功能码
3 i t# L0 a8 e; w - SendBuf[2] = data_backup[2]; //地址高位
2 V4 ?6 j. r7 l; g0 T - SendBuf[3] = data_backup[3]; //地址低位
. M' v( I5 u- s0 ? - SendBuf[4] = data_backup[4]; //寄存器数量高位! m, F5 B* s3 U+ h9 X, s
- SendBuf[5] = data_backup[5]; //寄存器数量低位; d: B/ k$ Y M0 [. N
- CRC16Temp = App_Tab_Get_CRC16( SendBuf, 6 ); //获取CRC校验值3 n1 k: q! h6 e+ N3 x* z" E( i l
- SendBuf[6] = CRC16Temp & 0xFF; //CRC低位
) `/ ~" z* d0 x; P- o; c - SendBuf[7] = ( CRC16Temp >> 8 ); //CRC高位
) b+ Q; K! H% I1 N- ~0 {, |; u - uart2_Send( SendBuf, 8 );: F3 q1 P3 W: p) ]4 ], B
- }; w0 c5 v# d' J8 c' k
- else8 `1 l K% }/ K
- {
/ D( W! \ |4 ?( x( G! z, t+ D; Z( Y - err = 3; //寄存器数量不在规定范围内/ O9 }) z" B2 F& a Q
- }) \ q6 n7 w6 v" F/ h% d6 A! E
- }
复制代码 . B4 J2 O5 l) j
测试效果. U) a; ]3 u9 d% x5 G1 m! k: |
5 U3 _% e( c3 m1 V
测试功能码 0x03 读一个或者多个保持寄存器值) D" K' Q8 x' V% N6 [5 g) r
- w- E. W- t: P7 z. k# \( J! Q! Y
; n4 d M. r2 \2 {
' I! B; Y8 g5 P& [$ y, H- b" G测试功能码 0x06 写单个保持寄存值. n, Q9 {+ p: G" }1 Q: z) J2 M
+ |6 {% }% N: g- z
3 |* B3 U" I- A. v, l0 m& |) E1 z
测试功能码 0x10 写多个保持寄存器值- I( l& H. y2 A4 h I6 T: r% ?3 d
& Q+ w6 a) F) k! P9 y* X* r
, r, n& B! j+ |( v/ |
7 c$ c) c; |- w$ P
再次读取所有寄存器值
8 W! J" w" j: G# A3 d9 y1 w! I+ A, D9 @3 u" O
( M4 d, v8 G" P3 T) K& Z" @( Q& P& M9 Z: f3 d2 f# l2 C
说明寄存器值修改成功。$ q% t: B1 w4 y, ~+ Q. p
% A m& ?3 j! {" }5 D# f
; B8 t5 ?4 {% v4 F' M9 b- n3 S; P- Y |