|

我是老温,一名热爱学习的嵌入式工程师
+ m5 C& U5 e, ^4 R. ]2 O关注我,一起变得更加优秀!说起通信,首先想到的肯定是串口,日常中232和485的使用比比皆是,数据的发送、接收是串口通信最基础的内容。这篇文章主要讨论串口接收数据的断帧操作。空闲中断断帧一些mcu(如:stm32f103)在出厂时就已经在串口中封装好了一种中断——空闲帧中断,用户可以通过获取该中断标志位来判断数据是否接收完成,中断标志在中断服务函数中获取,使用起来相对简单。void UART4_IRQHandler(void)4 h: O% N9 n6 |' j! Z! g" r& n
{5 x. U6 x! y7 D( n1 u+ h
uint8_t data = 0;7 g$ J" F% Z( i6 \* N& T8 _
data = data;* H& k- ^% {) \8 }7 v; E& o E
if(USART_GetITStatus(LoraUSARTx, USART_IT_RXNE) == SET)3 ]/ @. i' \) J2 i6 G5 G9 @
{
; k* I$ Z4 |& `% z+ _" d USART_ClearITPendingBit(LoraUSARTx, USART_IT_RXNE);/ f3 a$ J( U9 v
if(Lora_RecvData.Rx_over == 0)3 i. R6 |: Y3 u* _; }- @
Lora_RecvData.RxBuf[Lora_RecvData.Rx_count++] = LoraUSARTx->DR;. l1 ~; S1 [/ \$ b3 A
}' j; M( k" a% [
if(USART_GetITStatus(LoraUSARTx, USART_IT_IDLE) == SET)
% r2 N4 c- h* @3 ]9 Z8 w {
6 @. T/ J6 b( K1 n$ K data = LoraUSARTx->SR;6 }7 i; g5 Y" [" p* i
data = LoraUSARTx->DR;
' b: I4 S/ W1 {+ y7 P
0 j i: R" s8 J/ T" q( Y- k Lora_RecvData.Rx_over = 1; //接收完成# o3 }# H3 M$ }8 Z+ I
}
: b- c) { @/ m# R}例程中,当接收完成标志 Lora_RecvData.Rx_over 为1时,就可以获取 uart4 接收到的一帧数据,该数据存放在 Lora_RecvData.RxBuf 中。
4 ^) B1 |0 Q1 Y超时断帧空闲帧中断的使用固然方便,但是并不是每个mcu都有这种中断存在(只有个别高端mcu才有),那么这个时候就可以考虑使用超时断帧了。
9 @" F( r8 K S* J5 Q& ]Modbus协议中规定一帧数据的结束标志为3.5个字符时长,那么同样的可以把这种断帧方式类比到串口的接收上,这种方法需要搭配定时器使用。+ S: L0 U+ s$ x4 G
其作用原理就是:串口进一次接收中断,就打开定时器超时中断,同时装载值清零(具体的装载值可以自行定义),只要触发了定时器的超时中断,说明在用户规定的时间间隔内串口接收中断里没有新的数据进来,可以认为数据接收完成。
% Z9 \" ]" d+ l$ Muint16_t Time3_CntValue = 0;//计数器初值
% c% E9 u! b, Q. s6 f1 H
( T. [7 z. W- R$ e8 M6 U7 [: N) Y/*******************************************************************************% X& O* u% h3 b
* TIM3中断服务函数7 L6 _8 O. ~5 k; M+ b% N. p5 c" q g
******************************************************************************/
5 R! ^* a4 c" N" R2 H4 ]3 r# }void Tim3_IRQHandler(void)" C3 K9 O& C2 S% {$ a
{& z! }2 {: F! b# r" z& {, @
if(TRUE == Tim3_GetIntFlag(Tim3UevIrq))# [. `* O7 I: I% N4 x
{
. f, s5 W; g% }' L/ @ Tim3_M0_Stop(); //关闭定时器32 e' n6 \$ c1 a4 _" Y. M* X
Uart0_Rec_Count = 0;//接收计数清零& Y/ f8 V8 A: K
Uart0_Rec_Flag = 1; //接收完成标志4 ]$ M5 R( O `0 {, J$ S# ^
Tim3_ClearIntFlag(Tim3UevIrq); //清除定时器中断# e/ ~" ? y2 G: ^& O4 A5 g9 r
}# k: b$ s% j! d; U$ c- t# o) A* Z
}5 } e5 \7 ~5 S3 B- ~
8 x5 i" }, O2 x1 Tvoid Time3_Init(uint16_t Frame_Spacing)
* L: V/ M- m5 t6 I/ @, [& I{
/ q- W' Y& W; k3 f# z; |- } uint16_t u16ArrValue;//自动重载值
& V: X+ M ~/ o) B4 X uint32_t u32PclkValue;//PCLK频率
( n9 X/ o( R' Y! m( P9 W, T
% I% ^) ]1 s ~5 y8 ?1 s+ c stc_tim3_mode0_cfg_t stcTim3BaseCfg;
) F3 Y4 k _& i6 G Y
& T( m, A7 ?9 T d. l* r //结构体初始化清零) z9 C$ I3 h9 O2 O" J
DDL_ZERO_STRUCT(stcTim3BaseCfg);/ f2 W ?+ x( V
# s2 S5 E( A9 C& D" r/ L Sysctrl_SetPeripheralGate(SysctrlPeripheralTim3, TRUE); //Base Timer外设时钟使能# q; [: {; M! z7 h
8 {0 L( L# t7 n* {; P' b stcTim3BaseCfg.enWorkMode = Tim3WorkMode0; //定时器模式% h1 C+ J. q9 ~; Q0 J
stcTim3BaseCfg.enCT = Tim3Timer; //定时器功能,计数时钟为内部PCLK
0 U2 u6 r8 R' A4 O stcTim3BaseCfg.enPRS = Tim3PCLKDiv1; //不分频! M& E, p" H9 f
stcTim3BaseCfg.enCntMode = Tim316bitArrMode; //自动重载16位计数器/定时器
3 S* u) b6 v3 O* W, R2 a stcTim3BaseCfg.bEnTog = FALSE;2 Q$ `" c: `( B3 o, m1 ^
stcTim3BaseCfg.bEnGate = FALSE;- Q7 H; n, c. W1 T! Z* P* `8 e+ d
stcTim3BaseCfg.enGateP = Tim3GatePositive;; b9 K2 w$ c1 l6 M% z( D9 c
9 S0 U. ~: O9 }1 p7 |- x+ q
Tim3_Mode0_Init(&stcTim3BaseCfg); //TIM3 的模式0功能初始化
# B. H6 H9 w* n; Y # c, f3 m9 v0 v0 z; u
u32PclkValue = Sysctrl_GetPClkFreq(); //获取Pclk的值; @, [6 i, R5 N2 s1 o+ Q7 ]' B
//u16ArrValue = 65535-(u32PclkValue/1000); //1ms测试
, i" n% o' t& ~ u16ArrValue = 65536 - (uint16_t)((float)(Frame_Spacing*10)/RS485_BAUDRATE*u32PclkValue);//根据帧间隔计算超时时间/ ^4 ]5 }- \8 m* N6 e. N' M
Time3_CntValue = u16ArrValue; //计数初值; d: e1 a( l2 K5 h) [ x
Tim3_M0_ARRSet(u16ArrValue); //设置重载值
8 q/ g1 f' C7 N Tim3_M0_Cnt16Set(u16ArrValue); //设置计数初值
2 k$ S j$ n. i2 d d: h: D 9 a& B9 L; V+ N; ]9 Z; q2 t! i
Tim3_ClearIntFlag(Tim3UevIrq); //清中断标志+ U$ d A% l( O% T& s
Tim3_Mode0_EnableIrq(); //使能TIM3中断(模式0时只有一个中断)
( w3 }8 \, l6 n1 o) p EnableNvic(TIM3_IRQn, IrqLevel3, TRUE); //TIM3 开中断
* f1 v- }8 G7 K P; k# P}
, z+ Q6 _' _3 n& h# i; D
4 X# @: u- [ O/**************************此处省略串口初始化部分************************/
& g3 N6 h a: y5 o6 ?* Z+ y- g//串口0中断服务函数
. Z+ ], Y& f5 k0 Jvoid Uart0_IRQHandler(void)
) |. C4 S3 z( b' F- L{
6 k) T5 p+ M$ `8 l uint8_t rec_data=0;& G4 D, A4 x+ l3 E* }
3 z! _! \$ b t# m3 D/ x' V0 p if(Uart_GetStatus(M0P_UART0, UartRC))
& Q% g. R2 U4 n1 d {$ M* m. ]6 l G v$ e& P
Uart_ClrStatus(M0P_UART0, UartRC);
3 h; D& ^% R' \2 x0 U8 ~' h* [ rec_data = Uart_ReceiveData(M0P_UART0); ! _7 ]$ T6 o: q0 M% Z# v# Q: a$ q% c
if(Uart0_Rec_Count[U]//帧长度
( B& ^- f) r6 G0 U4 G. ^ {
& a# t4 D! r d& C8 T Uart0_Rec_Buffer[Uart0_Rec_Count++] = rec_data; F# Y6 M T5 W- ?& P# b* w5 c
}$ _4 u n: F/ ?4 W1 l
Tim3_M0_Cnt16Set(Time3_CntValue);//设置计数初值
7 J2 D$ B' E3 s# \$ i. O7 @% B Tim3_M0_Run(); //开启定时器3 超时即认为一帧接收完成3 R% H4 {3 T& M, u* Z
}
6 L1 z! E; d3 R4 `8 f/ n9 i}例程所用的是华大的hc32l130系列mcu,其它类型的mcu也可以参考这种写法。其中超时时间的计算尤其要注意数据类型的问题,u16ArrValue = 65536 - (uint16_t)((float)(Frame_Spacing * 10)/RS485_BAUDRATE * u32PclkValue);其中Frame_Spacing为用户设置的字符个数,uart模式为一个“1+8+1”共10bits。
" j. o& U6 X9 T+ w" z% N# l6 L状态机断帧状态机,状态机,又是状态机,没办法!谁让它使用起来方便呢?其实这种方法我用的也不多,但是状态机的思想还是要有的,很多逻辑用状态机梳理起来会更加的清晰。
4 V1 c' b1 D# B a: j( K2 }相对于超时断帧,状态机断帧的方法节约了一个定时器资源,一般的mcu外设资源是足够的,但是做一些资源冗余也未尝不是一件好事,万一呢?对吧。( ~$ ^# J K' X! }( N5 Z3 L2 |
//状态机断帧. o" u; W8 l, A2 k5 S& y
void UART_IRQHandler(void) //作为485的接收中断
* h; A6 E3 K; I. g7 p{% ~; J6 u; a ~) Z; `7 a* I! x
uint8_t count = 0;+ g: d$ ?8 q- |7 o9 X/ \2 Z6 `
unsigned char lRecDat = 0;
0 X) Q$ {4 g+ p% M, i& c
8 }7 a! e! U# A4 ?1 [, ` if(/*触发接收中断标志*/) # x* {8 R) O' r* Y
{1 U/ `) z( S6 [' ]3 ^
//清中断状态位
& n# ?& d% K% n7 W rec_timeout = 5;
* T8 c1 w3 L* h S5 K) B if((count == 0)) //接收数据头,长度可以自定义
1 V X1 ], M9 ]! ]' m {' u0 A+ w* O. w" t7 D. Q, ^
RUart0485_DataC[count++] = /*串口接收到的数据*/;8 S+ U& Z3 {! t: Z- V' A
gRecStartFlag = 1;/ m! P3 L. |0 {! J6 S
return;% E3 Y7 \, j* E4 v" N
}, A# S' S. w! l$ x. e& {
if(gRecStartFlag == 1)
. d) w% j3 I0 G& ~- t7 P {
1 ]3 w: v6 @2 [2 I4 p, _' X( z RUart0485_DataC[count++] = /*串口接收到的数据*/;
) R B! g4 A( b8 X/ R 0 d. F7 I* a9 F3 I! w( H3 F' G
if(count > MAXLEN) //一帧数据接收完成! n8 v) } I- L6 Z1 S' P5 g5 c z
{
) P/ X6 v' t8 Y1 {% ~1 d count=0;( \. }- O- G8 p! V8 D0 }0 `) ~9 f
gRecStartFlag = 0;
( G1 p! g1 v) e1 j3 A7 ^" w : _! Z) @) e7 |0 ~0 Z: s
if(RUart0485_DataC[MAXLEN]==CRC16(RUart0485_DataC,MAXLEN))
; ^. U/ M, O: f) Y! x {
5 |7 N+ e0 c' f, U8 ] memcpy(&gRecFinshData,RUart0485_DataC,13);
/ E7 e5 Y J; Q gRcvFlag = 1; //接收完成标志位
( o! h+ j: G T, I }
# A- G) H" A$ A* ?7 C5 j }
) T# [9 \. x ?2 l' T: Y! j7 h }
0 M0 ?1 ^' n4 u/ l' m. e% |3 p return; p( b3 g! ?& y2 B1 ^! U
}
2 h8 C. Y( K+ X4 }/ x9 y0 V6 ^8 S return ;
4 i6 L' [5 M1 e/ C8 I! Q9 C2 h}这种做法适合用在一直有数据接收的场合,每次接收完一帧有效数据后就把数据放到缓冲区中去解析,同时还不影响下一帧数据的接收。4 R. M! _, L' v& x' |( M; d$ [
整个接收状态分为两个状态——接收数据头和接收数据块,如果一帧数据存在多个部分的话还可以在此基础上再增加几种状态,这样不仅可以提高数据接收的实时性,还能够随时看到数据接收到哪一部分,还是比较实用的。
6 V. s$ z) u* y! e- O& H. M"状态机+FIFO"断帧记得刚毕业面试的时候,面试官还问过我一个问题:如果串口有大量数据要接收,同时又没有空闲帧中断你会怎么做?/ G4 H+ ?) c2 r4 ?
没错,就是FIFO(当时并没有回答上来,因为没用过),说白了就是开辟一个缓冲区,每次接收到的数据都放到这个缓冲区里,同时记录数据在缓冲区中的位置,当数据到达要求的长度的时候再把数据取出来,然后放到状态机中去解析。当然FIFO的使用场合有很多,很多数据处理都可以用FIFO去做,有兴趣的可以多去了解一下。/********************串口初始化省略,华大mcu hc32l130******************/
* d( T& ?1 I- f% Cvoid Uart1_IRQHandler(void)( X# X1 q) p3 Y" q
{
1 P& Z% r4 W1 W$ ~3 A uint8_t data;
3 ~- }7 x4 }1 W if(Uart_GetStatus(M0P_UART1, UartRC)) //UART0数据接收
* _' w: H7 \' C3 |4 P {, J7 G- Z4 s' ]; {' g& N0 m
Uart_ClrStatus(M0P_UART1, UartRC); //清中断状态位3 @3 ^$ J% X9 W
data = Uart_ReceiveData(M0P_UART1); //接收数据字节: p( I; t# @# @% O$ t- p) A
comFIFO(&data,1);
; F, w5 z$ y( m% ?7 A2 a$ d }
8 v, J# J7 G4 x, i8 n1 Q}, S$ S* g- V" g7 |+ n5 v
6 L% I& j h) x6 H- j5 e
/******************************FIFO*******************************/
. }/ T% V3 H/ Pvolatile uint8_t fifodata[FIFOLEN],fifoempty,fifofull;( a9 o3 d7 V) P4 o: m; p( d2 E; H
volatile uint8_t uart_datatemp=0;
3 G; i# g7 U9 E: o9 s& F9 }' @ , W; o: P& ~ t; _
uint8_t comFIFO(uint8_t *data,uint8_t cmd)" d1 b4 E" \' [8 S K
{
) f6 z5 ?! z. E- a; c5 o/ X( Q0 U* t static uint8_t rpos=0; //当前写的位置 position 0--99 y8 \2 H0 ^" H# T
static uint8_t wpos=0; //当前读的位置
1 d0 M% g- |; W8 k( ~" j/ J6 t, { 5 ]2 j$ G7 O( v" b& _) \
if(cmd==0) //写数据; F# q0 ~7 z+ c$ P
{. [2 Y9 d; ^# m2 f; G
if(fifoempty!=0) //1 表示有数据 不为空,0表示空
5 {$ E3 B+ C0 g* o9 B/ L8 U: B) } {5 r7 {# A+ Y- Q& a( r3 j
*data=fifodata[rpos];$ H$ I, U* S+ X9 \! Y
fifofull=0;
8 E" y8 F) F, h! P0 B rpos++;( _$ l( K8 P& t' B( p" t2 n- }0 m# X
if(rpos==FIFOLEN) ; Q3 \( h' S& W& b) g. ?- J
rpos=0;
7 F8 U# |3 z0 a+ b- \+ r: u* u. D if(rpos==wpos)
1 B8 ?% I+ ]5 V4 N. g2 w fifoempty=0;6 W( v1 G5 X6 {2 S' M
return 0x01;
1 P, N# I9 s. ~& k7 @4 w } & S" l+ I% e7 K# m, T- w
else
) r- N% G* K1 \( e5 x5 F return 0x00;
7 G$ l1 s6 ^' S& Z8 Q3 c
' v: s/ Y3 J0 H5 r8 Y0 ? } / Q% e. p: [- @3 y
else if(cmd==1) //读数据
. i" s' e) E1 u( J { g- g+ H* |( {# c+ }6 p& l' x& L
if(fifofull==0)( a, v4 D% I- t3 j7 J3 K8 d9 G
{+ y* R. t' U) D6 N
fifodata[wpos]=*data;
9 y; L% O# L& g% q, v fifoempty=1;
" O. A6 Q5 ?) A* I7 |3 ~5 F wpos++;" j" G6 X& ?4 ^- o
if(wpos==FIFOLEN) 5 @' j. `* B/ T
wpos=0;
9 m2 U4 N9 Q! A6 Y4 t if(wpos==rpos)
; `5 K( f. a3 S+ S2 X$ J3 c- |* I fifofull=1;
. y/ n ~, q* u4 j8 ~ return 0x01;
2 y. h" |' @2 H9 Y! C } else0 [0 H6 p; \& H# u2 e7 B, r
return 0x00;3 Q' U( \. t8 i, d, L& V7 N& F
}
( ~( _, O9 v6 n8 C2 E) N return 0x02;6 `" f7 |- }! v9 R0 D) n* R
}
A ^# t8 Z. m- X / W1 Z: w8 X+ R5 m, z
/********************************状态机处理*******************************/8 l- v; C5 l- H, _8 R0 n
void LoopFor485ReadCom(void)
( o: K9 n2 @/ V, o. U{
4 r2 H% y& j# b uint8_t data;; B, b6 {5 {1 _9 F1 v
. Q* E: J3 G# F) {( V while(comFIFO(&data,0)==0x01)% w8 C, ^. t% n! J
{
. `0 E3 w4 _2 u3 U* |! x if(rEadFlag==SAVE_HEADER_STATUS) //读取头
! G- z2 Q8 H1 @6 T {& t' y9 v; m, T; G5 N
if(data==Header_H)' Y* |9 }' F: d9 ?
{
5 q4 d x( m) y# B" D3 t) l* X buffread[0]=data;+ f" r; I, Y# X) L7 f
continue; `1 h& d; [, E; x5 N5 e- e
}
; A" b+ n( ?. Y. w0 I M) s! W if(data==Header_L)
" M; K( l7 S1 k- Y0 e {' H0 ^1 I& e( i# [( T
buffread[1]=data;
8 b* F( Z* c3 g% t3 { if(buffread[0]==Header_H)) O7 T- ^8 w C! e$ G( j6 ^: N
{. Z+ `' M% Z# f' F5 M+ e
rEadFlag=SAVE_DATA_STATUS;1 A& r3 n3 ]1 u, w- z1 i3 q
}: i( x4 b3 w$ B8 g; N
} $ B3 G- l, c5 Q' e- m: z4 S0 j, v
else
; X% m" S6 n+ Z1 ^7 ? {8 Q: L; F! e, A: l
memset(buffread,0,Length_Data);, c$ }: w1 R- @3 Q+ G: U. Y* f# G
}
' H/ t1 J: C) I. v0 b } 9 |5 `) H0 _; M: B/ w$ D
else if(rEadFlag==SAVE_DATA_STATUS) //读取数据
, c+ q; e& g; g, E {" T; o7 P& S5 R, k( z
buffread[i485+2]=data;. V6 A2 p/ k4 b$ J
i485++;; F* B' g5 \' T1 E5 T t
if(i485==(Length_Data-2)) //数据帧除去头 q6 \0 v3 M+ r5 z! H0 g" f
{
! F4 J7 ^ H/ Y* C3 @* x6 s6 W unsigned short crc16=CRC16_MODBUS(buffread,Length_Data-2);& ^8 {; t# O W9 R, q+ D9 `( R, s
if((buffread[Length_Data-2]==(crc16>>8))&&(buffread[Length_Data-1]==(crc16&0xff)))# ?- W: f, a) f. O) b7 [
{0 i- E2 e& }9 p2 A- ]+ Z
rEadFlag=SAVE_OVER_STATUS;1 Q2 T" p* m; l; \* S" p( \; O7 P
memcpy(&cmddata,buffread,Length_Data); //拷贝Length_Struct个字节,完整的结构体4 ^. m1 T1 P: \" {
} 5 J; ]2 i }" u) g3 h4 j
else
# S, h; P# V; i" z+ ]9 u. \ {* }+ M/ G8 W* y' [
rEadFlag=SAVE_HEADER_STATUS;7 Q# g# E" i5 u/ ^3 g
}& k) {2 j/ r" o0 T
0 n( N& ]' q4 W' ?7 p memset(buffread,0,Length_Data);
# H% N: R6 H# |4 U0 ~ i485=0;0 F/ ^* d# U4 g% o
break;
6 J. i% Y7 R$ v/ D; U }5 m7 U& _6 e/ `( o2 V) H, F
} Z- }0 b) X/ D5 X/ u8 B8 d
}
! Z( m3 H: V- G; m2 f7 ]}原文:https://blog.csdn.net/qq_56527127/article/details/123018515
/ p. f; N$ p, \" |$ P
ohb1aidbpas64054373034.png
' T$ H6 g% e z; r6 B
-END-2 Q6 Q0 ~2 [- t& I& }
往期推荐:点击图片即可跳转阅读% t ^7 f/ N9 O9 a* j7 @
: |3 T: o* l) w+ h1 o6 m' b 8 k( z' s* V( X, q! S2 }
8 y7 b+ X% Z% {+ k
- ]0 d; _7 I0 o, L. O: g
) y3 N9 s1 C/ F/ Y; k9 E/ P% s
ptzib2nqqkv64054373134.jpg
- O. T# N% Z! P( r7 c) e( R ) e, ~% Q6 I) U
嵌入式软件调试,如何计算任务的运行周期?6 L# C b; L; M+ g" L% y
/ C, ]% g, o' G
+ I( z6 `& f o2 U " F+ A+ m# g% u+ A, p! ?
% K r' B- h0 J& T! V1 ^ 9 Y/ D& b, N" Z
) m, [8 q H. j
" [' U, p& [# D1 Q4 @, D
) m/ m; ], U* n1 Z
* x: o( z1 y, ^& K3 S
/ k. q& N2 r2 S
iarkvaxoix564054373235.jpg
, {: j& d9 ]6 W) {
3 B5 |; k# N% U- F7 K% R7 ~ 嵌入式软件,如何把编译时间加入到bin文件,进行版本管理?( M- Z# e, |4 D2 B3 b
/ x; y! Y2 r1 Z4 I6 V7 b6 p
: `# Y4 C, e& ^: P2 L 9 r f* ^* n) ~! m: `. B3 t9 I
8 x9 L# J) f/ h
# R* V3 A+ ^) R5 V! [) M8 u1 W. q! d% M' j; N
& w* v5 v j f+ l" |/ _# p& {
) z* }# A$ o; x5 X# y T z& ]. ]: r. y3 b+ F) J
0 i4 [0 L& f5 A, x7 j( A
e31rvw53ost64054373335.jpg
( Q0 |/ y+ V8 W7 h
- K9 j4 K% B$ v
嵌入式初学者入门后,应该如何开始进阶学习?2 v* O2 `+ | @6 g# v0 v
$ A0 N, y, }( o, L( z: g
$ w, D7 {* @) S- {3 `
6 `6 z3 p; p$ {4 q
, M* o) `3 @# a , N. V9 p0 }0 W' I
我是老温,一名热爱学习的嵌入式工程师0 J5 F; y% ]% q
关注我,一起变得更加优秀! |
|