本帖最后由 tanic 于 2019-1-17 17:02 编辑
: d% D& Y' _1 C( f+ a7 W8 T3 J+ @8 V- H, L
之前做一个项目,串口收发频繁就老卡死,后来用了保守法度过去,近来有空详细分析,发现了问题,并解决,这里分享一下。
( B/ ?3 ?% o: |' a# a& c
. z- j6 F6 c: h+ e8 `2 X这里不对基本操作分析,仅仅讨论一种方式,能让我们稳定的用串口,本文会对HAL库进行些微的修改。
' [& c2 a. E8 m2 S% OHAL库的串口大致可以分解为2个部分,一个是寄存器控制部分,一个是数据传输部分。我接下来的只修改寄存器控制部分一点点内容,数据传输部分不变,任然采用其回调函数形式,下面具体说明。
- S' X- a4 d3 d. r" c1 W- C: g2 p一般来说最常用的是发送数据HAL_UART_Transmit,中断接收数据HAL_UART_Transmit_IT。串口其余,如DMA什么的不做分析。8 y7 g9 A$ G8 {8 ^; r
研究一下会发现,HAL库中强制对串口进行了半双工限制,其实STM32的串口是全双工的,很多时候卡死,是因为我们做了全双工操作导致的卡死(不是HAL_UART_STATE_READY状态)。" u; H. S5 P' R! q( I
HAL_UART_Transmit修改后如下
$ _; v$ z$ A- m8 m$ l- v- HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
& H4 }' R6 K6 T" }- Y# _0 d# L) s - {
3 }. H O C2 C' H& L - uint16_t* tmp;( X2 Q2 ]' v7 w: }3 b- C8 s
- uint32_t tickstart = 0U;8 a% }; M {- [0 \6 ~7 {/ V
A) q. e8 f# m5 t$ ]& [- /* Check that a Tx process is not already ongoing */
$ A% Y: s2 U. {8 g& \# y - // if(huart->gState == HAL_UART_STATE_READY)# h7 ]! i" Z9 b+ g6 j% K
- {# i U3 @& z+ M0 o
- if((pData == NULL ) || (Size == 0U))+ c7 ^& b* d1 L8 B
- {
# w+ V# t- s A* d* G Z( Y" _ - return HAL_ERROR;2 _3 a6 W! j# r/ v) b3 m( ^1 @- j
- }8 A$ n9 g0 e& S4 m" i3 e4 J8 o
+ K' Y! f5 F0 G1 W" [: ?- /* Process Locked */
" O& j) M! p# p C' a3 { - // __HAL_LOCK(huart);$ t; _3 F3 w( F
- ; w7 h$ s7 B: |/ X/ T1 D' a
- huart->ErrorCode = HAL_UART_ERROR_NONE;
1 U9 x! |, r6 r1 Z; \, I2 Y2 m - huart->gState = HAL_UART_STATE_BUSY_TX;
1 x* u" E7 _; K# J2 o - . H1 V2 x. C- q. j n* \4 J
- /* Init tickstart for timeout managment*/8 Z" I) a1 V: }4 R
- tickstart = HAL_GetTick();9 k6 a7 Q+ e' [. f5 w
- 6 ~; f9 m* I! ~# O
- huart->TxXferSize = Size;, a5 P! \; {7 F, c# g
- huart->TxXferCount = Size;. ^3 t0 h' [4 V* g0 T! W
- while(huart->TxXferCount > 0U)8 z& N8 ], A- [
- {' u+ }- t ^3 b5 ~* R4 s, q8 i: Z
- huart->TxXferCount--;4 z" Z y& m/ z0 R
- if(UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)2 G: L) H/ C4 g. ]' r# q# E- S
- {
/ a( T! e4 I5 [7 q( D3 ^ - return HAL_TIMEOUT;: W/ G' }) E x G* i6 @- V5 A
- }
W2 [# |% y: y/ a1 L - if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
$ k! s- N: m" q5 ]) E2 i* z& Q - {
4 ^8 U" E6 p! z5 A" R - tmp = (uint16_t*) pData;
( Z( i! J7 ^5 H. c9 C6 x - huart->Instance->TDR = (*tmp & (uint16_t)0x01FFU);
0 G" b$ Z# d. f) F7 K - pData += 2;3 A8 k/ @, ]' v- h
- }
5 p. V& _* V1 _3 T a - else
; u. `3 ?, z2 B2 ~ - {9 R V0 j* [5 r( x8 D& _4 e
- huart->Instance->TDR = (*pData++ & (uint8_t)0xFFU);
7 s3 B! j. o) A# t0 K5 {. J - }' v2 u# C5 R" T: d( @% j; X
- }
1 ~+ e) W: g& y0 v8 ~7 d& ^0 G( N - if(UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK)
5 w1 D9 F- c3 u0 s& |, T - {2 ^8 P, N5 o4 l9 X; n: t
- return HAL_TIMEOUT;+ _6 S9 Y" j, N7 x- v
- } r4 E2 t9 Z) \. o( Z" T
- - K3 {5 s3 F6 x
- /* At end of Tx process, restore huart->gState to Ready */7 _7 [! W+ ?: M
- huart->gState = HAL_UART_STATE_READY;+ J) U% K4 O( b* x( u* m* `
- 1 P# |& X+ e1 ~/ ^+ l
- /* Process Unlocked */ T, M8 D) i- @, x- S M; c
- // __HAL_UNLOCK(huart);
7 m" ~4 a( [: d
1 Q5 n; f5 r% Y4 v) p+ Q- return HAL_OK;
/ f1 Z. e+ i- g* F0 t - }/ x* I+ D, J' F4 C5 s/ ~
- // else
6 ?7 [3 h" {9 |$ }6 |* X0 h2 @+ j - // {. M/ i r& H8 t5 F. |
- // return HAL_BUSY;
2 ^: L" g5 n& n* w* N2 F9 T% p - // }
3 E% q$ B$ l2 i, g - }
复制代码 2 v! ]; V/ m5 s8 Z
HAL_UART_Transmit_IT修改如下,除了祛除限制,还屏蔽了中断使能,这个后面再说原因
$ a" f+ T' |. i8 I- HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); a9 k" |: s" U, {' G/ C
- {
' A2 f+ L4 `1 A4 A& B - /* Check that a Rx process is not already ongoing */$ Q# U% v- [2 _, K* X
- // if(huart->RxState == HAL_UART_STATE_READY)" j% ~) G( C$ }, w, ` I
- {% U% h/ b) a" J( u5 `- x: I
- if((pData == NULL ) || (Size == 0U))
* \/ t9 H' j: \ - {6 |, F: x8 ?2 v
- return HAL_ERROR;8 _/ ?2 |/ l1 b' h0 l S
- }2 i0 X( P. _7 q7 q
- " o( I( `: o. ]4 q4 O
- /* Process Locked */
" ?" T7 m- l( N0 O! K - // __HAL_LOCK(huart);! D7 c# ^8 w# s& B8 u3 V
- ; l% e# e4 |' F8 k0 e, h8 h+ X
- huart->pRxBuffPtr = pData;
3 b( a+ ?" z$ i" d8 a - huart->RxXferSize = Size;
5 S* D: n: }8 H9 h! x - huart->RxXferCount = Size;( F+ R' g& k# s% \' I
- 3 H9 k3 h( M) O& X
- /* Computation of UART mask to apply to RDR register */% W/ U* Q& `: B
- UART_MASK_COMPUTATION(huart);/ Y0 u7 B, ?2 ]1 w
) ~* o) h% W; g- huart->ErrorCode = HAL_UART_ERROR_NONE;
( j- j' a8 d, y" H9 Z) o - huart->RxState = HAL_UART_STATE_BUSY_RX;
7 w& V$ R3 o+ q- J2 ^% l
7 x% k N+ Z4 l- /* Process Unlocked */
! w* d/ l* j$ E - // __HAL_UNLOCK(huart);
1 J! w& U8 ^; ~! e: r8 o. k
+ i7 E% A1 z6 O% E. Q3 Z- /* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
: |7 p8 y/ p$ W% p6 ^ - // SET_BIT(huart->Instance->CR3, USART_CR3_EIE);
# |' d* {! Y3 P$ _1 M5 S$ p - : A9 o* _' J% A0 ?/ H& P
- /* Enable the UART Parity Error and Data Register not empty Interrupts */1 @; E# b$ x) S+ ]" U& o
- // SET_BIT(huart->Instance->CR1, USART_CR1_PEIE | USART_CR1_RXNEIE);
4 R! f9 ]4 e) X8 f - & n9 V0 G- d7 M" A: }: f# }. e3 z' [2 p
- return HAL_OK;
* y4 V9 i1 N. Y* v) o - }* K y5 d* s9 o6 V, t, V
- // else
|! [+ F* e. N: H0 z/ F. [0 I - // {; O8 ], U! F# \3 h/ M1 j- q
- // return HAL_BUSY;
5 O$ w8 s6 O$ _. K6 A( `7 i - // }. b0 c1 n5 B- Z" L; E/ V7 I1 \
- }
复制代码
: [- i3 m% V9 l. A接收中断,屏蔽了清除中断的两行,中断开启后就不再停止了' r3 e l( V' l* C9 k
- static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
5 j/ a* d" B5 t0 F3 m% e c2 A- f - {7 M8 V$ ^! x% t
- uint16_t* tmp;: s, J% F1 \0 B( `5 j4 o7 E4 T6 G
- uint16_t uhMask = huart->Mask;0 a5 [" x5 [6 i/ i; U' w6 }
; T" p: u. t0 \! O* _2 z- /* Check that a Rx process is ongoing */0 H' C7 L: l' c; x. p/ x
- if(huart->RxState == HAL_UART_STATE_BUSY_RX): n1 r1 T8 i$ m+ Z
- {
+ p t# K: F* ~9 C - / t2 m O) Y! {( k$ k8 W R
- if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
5 c3 B: e3 i$ u/ l$ H! k - {- e- l# A# ^! h" M6 u' K% S
- tmp = (uint16_t*) huart->pRxBuffPtr ;( U. n8 ?/ ~8 {
- *tmp = (uint16_t)(huart->Instance->RDR & uhMask);
5 _4 y" P" w. q- _ E' o' R2 D: g - huart->pRxBuffPtr +=2;6 A; I" a0 C5 d. Z
- }
$ ]' c% ]" b# A3 Q8 `) p- x - else
8 \6 K' S/ f0 _" p) s% m6 h. F - {
0 B4 T) X9 q1 F - *huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->RDR & (uint8_t)uhMask);
* ^; ?2 G3 T) Y! S8 W - }3 b: V$ n; K% s* m
+ K, A. l4 p: ?; E! G2 m- if(--huart->RxXferCount == 0)5 Q* X" T' E3 T4 D% ?
- {
# x. k. e; u1 | Z) { - /* Disable the UART Parity Error Interrupt and RXNE interrupt*/
" p* y3 w& @1 W" N p6 a8 m - // CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE | USART_CR1_PEIE));
& q2 M4 I( L+ [* L# ?2 V
" C8 h: y# Z( ^) o- /* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
8 ~9 d# T; v) v4 N8 { - // CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);
- s0 D" v: E7 l6 h1 f' s - 7 N1 ~6 B; _$ Q L
- /* Rx process is completed, restore huart->RxState to Ready */
2 w0 T. J a2 K% C \ - huart->RxState = HAL_UART_STATE_READY;
. L# d! ], a1 q" ?4 x; `- k3 d& P - $ H! Y! X, d8 {+ U# W, D. h
- HAL_UART_RxCpltCallback(huart);+ g ~3 {! ~( @. f5 f* a8 l: i9 y T
- B2 C: T. D6 T2 V+ l- n
- return HAL_OK;
2 Q$ `9 x2 o7 L' N( G. c - }# }. b- E6 a8 \6 D3 F
- 9 p0 x% ]" I5 ]. y5 ~( Z
- return HAL_OK;, { z7 P: o5 y3 W# W* Y
- }- E( z- t/ i( A3 {* w
- else
1 F+ M' P- X6 a9 p) }; | - {/ M3 ~8 [1 g4 y; r8 a/ d5 z- q
- /* Clear RXNE interrupt flag */
+ z" ]+ c/ i8 u/ r2 { - __HAL_UART_SEND_REQ(huart, UART_RXDATA_FLUSH_REQUEST);/ J3 L- ~, A% r; V8 r( {
( R! ]; [! \6 g, s- return HAL_BUSY;/ ]# h9 r. j6 l* B
- }) \5 h% X' X% C# W% Z y
- }
复制代码
. T) a1 e$ G$ R: G8 F在usart.c添加如下代码,1 ^1 v5 T \3 N( A
StartUART_Rx:开启中断,同时开启HAL数据流。本来调用HAL_UART_Receive_IT会重新开启中断,不过前面我把它屏蔽了,这样,这里初始化开一次就行了,上面代码中关中断部分已经屏蔽了
- k1 |$ S8 E$ p! B' H0 zHAL_UART_RxCpltCallback:接收中断回调函数. {1 |) u% T5 ?& F$ k8 K
HAL_UART_ErrorCallback:错误中断回调函数
# ~$ v- w' v4 S Y" e& i7 l+ E6 h$ s" M/ h z) D! q0 e' ?4 d; x
- /* USER CODE BEGIN 1 */# S+ W( K8 F; t2 q( O# W J
- uint8_t a=0;
4 S, ?* o% @# A - void (*UART3_Callback)(uint8_t)=NULL;, G) W& F, ^7 q7 y, i3 }$ z
- void StartUART_Rx(UART_HandleTypeDef *uartHandle)
- o) z* V: g% N. ? - { M V/ W k$ k8 d. K" A
- if(uartHandle->Instance==USART3)5 {7 T) T4 V) s% c0 ?# l' W2 q2 A x
- {
- Z; D2 v2 c" k! G. n - if(HAL_OK!=HAL_UART_Receive_IT(uartHandle,&a,1))
- {6 C8 Q' g/ g - {& x1 m3 x0 M# E% \; @: f4 V( }
- assert("HAL_UART_Receive_IT",__FILE__,__LINE__);, C& u3 ~! X+ l. g( M# Y# H, T
- } & T" E! [* [: v+ C( W3 f
- SET_BIT(uartHandle->Instance->CR3, USART_CR3_EIE);
; q# S6 C; z ^: d# w& Z8 z - SET_BIT(uartHandle->Instance->CR1, USART_CR1_PEIE | USART_CR1_RXNEIE); 1 C! B0 ?, i& b6 u+ H- s
- }
; K7 }" B3 ] M; l" h6 ~: D - }
$ k3 O) o D l9 Z7 Q8 [6 b% k8 R
& x; D( s) [5 |' {- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *uartHandle)& @/ H& O. A3 \( `, O( F9 k& ?- y
- {
8 c, m, C2 i, M: _' p - if(uartHandle->Instance==USART3)
! R. [1 d) m r* `$ f" Y - {0 Y3 d/ t+ V" h( f3 @0 a1 [9 k
- if(UART3_Callback!=NULL)
) A& `* Q# N4 k& M5 N% C8 H% [ - {# p) C) g6 W6 S Z% v
- UART3_Callback(a);4 G) M# w. [$ ?, [: b
- }
7 \$ L, @3 j, V9 o/ K+ d - if(HAL_OK!=HAL_UART_Receive_IT(uartHandle,&a,1)). V- f" A+ N$ I% s. n
- {
' a: n7 z& k. {* T1 W - assert("HAL_UART_Receive_IT",__FILE__,__LINE__);
1 e* m' v3 s! y0 U! J: W - }
+ w8 T3 L; o! Q2 y* r8 | - }
2 D; _/ B: j. ?; i! z+ h/ a: W0 ` - } o& b( o5 C j8 R+ I2 K
- void HAL_UART_ErrorCallback(UART_HandleTypeDef *uartHandle)# w' h1 N5 ?. S; {( `# `
- {3 \/ m$ K: L* a
- uartHandle->RxState = HAL_UART_STATE_READY;6 t& q9 v% _' w# c- O
- if(uartHandle->Instance==USART3). n u" Y# Z# A5 H$ b+ o
- {( h$ M. r& p: H. i1 |0 s
- if(HAL_OK!=HAL_UART_Receive_IT(uartHandle,&a,1))5 ]6 u/ i3 f4 O, b; c& P* t; f) J
- {0 _9 S2 X+ p8 @7 x* { Q
- assert("HAL_UART_Receive_IT",__FILE__,__LINE__);
" o' @9 v/ h! X" A - }
" H8 s8 O' s9 Z3 ~4 T - }! I' K1 g1 E/ f# p! t7 R
- }5 @8 R, N0 `) E4 T( d% U
- 2 `, h0 f: x j0 d! M% S6 h; o% K
- % O" d) z% p4 T" u ?8 U6 R; _
- ' F& G, g% Z2 |0 E" z2 _4 [
- /* USER CODE END 1 */
复制代码
! H3 X' F% r2 ]/ t: H
& E6 W9 Z( j. E- t. a0 B基本这样改就能应付大部分应用了, , P9 ?( b& P) k9 l
当然有另外一种方式,就是开一个检测任务,定期检测串口状态,如果发现串口卡死,则重新初始化即可。 亦有人用了DMA+空闲中断的方法,不过感觉是不太稳定的。 估计HAL库其他驱动亦有类似的情况,自己把自己卡死。 改库有风险,跟风需谨慎!
! G# Q$ u8 C. L( v6 S* ~& x3 F! I2 k+ T, U
/ z4 f' L* M3 L% R3 z
# \; I! B6 w- }" P' E- X, I' { |