|
前言 对于 STM32 串口的使用,确实很简单使用 STM32CubeMX 做好初始化,就可以直接使用了。 但是最近在某些产品上使用串口同时收发的时候,发现有时候串口会收不到数据了,但是发送正常,而且这个问题再数据量大的时候很容易出现,于是乎进行了好几天的问题测试…… ( S+ h; I1 `0 c, Z# N( d! g
一、 HAL 库串口收发先简单回顾一下 STM32 HAL库串口收发是如何使用的。
. d7 g' r& e" ?0 P, n+ s! Z- i$ L 1.1 串口发送对于 STM32 来说,串口发送有3中方式: - 轮询发送;
- 中断发送;
- DMA发送;4 Z1 g; }6 e2 H; d) I
在实际产品上,大部分项目中都用的是 轮询 方式发送,本次出现接收卡死的问题的产品也是采用的轮询发送,所以我简单的说明一下轮询发送,其他两种方式为 STM32 学习的基础问题,这里就不过多讨论。 发送相对简单,在 HAL 库实际都是使用HAL_UART_Transmit函数:
, M& Z8 U# S2 P+ Y% {6 |2 \
/ Q: Z( w6 Y, z y, k( x; c Q- c" L S. x c7 v' l
不管是哪个串口发送,都做了个简单的发送函数:
; {1 m( }, f2 z1 Z# [! X6 D0 }. y( W/ a9 p$ d
. V& f/ U2 C8 z0 ^$ T( ^# P发送没什么好说的,简单易用。 . w" Q# {: C# K6 N" a6 k+ Z
1.2 串口接收和串口发送一样,串口接收有3中方式: - 轮询接收;
- 中断接收;
- DMA接收;9 W0 t. M9 O7 Y! Z
在我们正常的项目使用中,一般都是 中断接收 或者 DMA 接收,基本上不会使用 轮询接收的方式。 那么对于本次出问题的产品,我采用的是 中断接收的方式。 但是相比较发送,在 HAL 库中 使用中断接收的方式就有点 “五花八门” 的感觉。 $ o: O3 s1 K- f+ m% D0 a3 Y) M
1.2.1 标准库接收在标准库的时候,我们经常这么用,在串口初始化的时候使用下面的语句使能中断: - //在串口初始化代码之后加上中断使能2 Q C7 V/ c, \; s- W' L% z) O
- USART_ITConfig(USART3,USART_IT_RXNE,ENABLE);
复制代码 1 u X/ X- C- h; g. i0 K+ i9 T8 ?
如果需要自动判断一帧数据,我们再开启一下 IDLE 中断: - USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);
复制代码
/ }& B: r: L$ A5 }5 t: o然后在对应的中断处理函数中,直接读取 DR 寄存器(STM32F103 而言为 DR,STM32L051 为RDR),然后当 IDLE 中断产生,也可以处理一下标志位: - //自己用的,用到了 USART_IT_IDLE 标志位,有时候不合适
) ?! K6 |3 k. _3 u( v) A" Q - void USART3_IRQHandler(void) //串口3中断服务程序
* m- S Z3 z' K x* ^) u - {
( ] w6 W% T8 n/ ~ - u8 clear3=clear3; //消除编译器没有用到的提醒; K0 m6 F2 v0 Z4 U
- // u8 Res;
" ? U _3 F3 @ b9 K) K" t - ) H' y' \2 i- C) S! d+ c( I
- if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) $ a! y c. g3 v' B/ T$ [$ K
- {
' ]3 b+ b$ ~4 x; S8 \1 Z6 ? - USART_Enocean_BUF[Enocean_Data++] = USART3->DR;
* Y {& N8 M) r) G - USART3_RX_BUF[USART3_Data++] = USART_ReceiveData(USART3);
% v6 @3 i0 u4 |- Z" O( t8 q - // Res= USART_ReceiveData(USART3);) s/ }4 D2 |; T) T
- // USART_SendData(USART1,Res);while(!(USART1->SR&USART_FLAG_TXE)); $ i5 l$ H8 H8 Y. K6 V8 g: X8 s
- }
5 X$ w/ K8 |8 e" n5 I8 Y3 p0 l# i
7 d& Y0 V' U2 y6 T% ?9 k- else if(USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)
6 l8 m) d, v" r. V - { g1 {0 J% V- w7 V* K
- clear3=USART3->SR; //读SR寄存器 可以清空寄存器) G- I$ b }) H9 l' q0 ?
- clear3=USART3->DR; //读DR寄存器(先读SR寄存器,再读DR,为了清除IDLE中断) 5 T7 J; r9 {" y r0 V1 u
- USART3_RX_STA=1; //标记接收到了一帧数据
( G" a/ |2 `; h n9 r) d+ W. U$ Y0 n - //USART3_Data=0;
3 r' J* R$ f4 Z - } //enocean是不是读不到一帧数据,不用一帧数据测试一下
" R1 L9 r: ^0 c Q8 [ - }
复制代码
/ I) z& h. q# H5 T; S1.2.1 HAL库接收HAL库接收方式一在 HAL 库函数接收的时候,其实也可以使用标准库上一样的中断标志使能: - MX_LPUART1_UART_Init();
2 u0 f( j0 ]2 n2 D - __HAL_UART_ENABLE_IT(&hlpuart1,UART_IT_RXNE);
复制代码 3 a9 i8 {2 \, k4 `. O
IDLE 中断使能: - __HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE);
复制代码 : q' G! X! u5 U3 j0 f: z
这种方式的处理方式,可以和标准库差不多。 在 HAL 库中,外设的中断的入口函数都放在stm32l0xx_it.c 这个文件夹中( 以STM32L051 为例),在这个文件中可以找到和标准库一样的 中断入口函数,我们可以进行如下处理: - void USART2_IRQHandler(void)
7 ]: y8 O9 H! u$ S; ^1 N8 q5 V - {
/ J+ v9 P3 n5 N3 \ - /* USER CODE BEGIN USART2_IRQn 0 */
9 ?$ \* M6 j- l$ ~8 s' |/ } - 0 o5 M- V2 U- F6 n
- /* USER CODE END USART2_IRQn 0 *// _. n1 g7 Z# o- [& f$ {% }, g
- HAL_UART_IRQHandler(&huart2);
. ~% z# s |! K/ y# N Q1 V - /* USER CODE BEGIN USART2_IRQn 1 */5 D# x+ j& j4 f
- if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_RXNE) == SET){$ B, z: N! i* Z3 ?
- // USART_Enocean_BUF[Enocean_Data++] = huart2.Instance->RDR - g) Q |) F* b
- // RXNE 数据处理,直接读取数据 & y4 L6 @* d8 W
- }
$ u# E( R D5 f0 ?5 q - if((__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE) != RESET))
, y/ V7 S+ a* ~3 p0 l- x! c( Q - {
7 |. Z* O( M1 p& F9 V - __HAL_UART_CLEAR_IT(&huart2,UART_CLEAR_IDLEF); //Çå³ýÖжÏ
4 g" E$ K5 s- l. D, Q) h: ^ - // ReceiveState = 1;6 x# E9 G4 H7 b) b
- // IDLE 数据处理,一帧数据接收标志位置位
% t4 K3 Z( r; L6 @9 n% L& ^, V - }, w% E" [- r. Q, B' F7 o
- }
复制代码 + v% o. Q. {. ^6 Y9 E0 t
HAL库接收方式二但是在于 HAL 中,还有一种比较常用的开启中断方法,不是直接使能中断,而是通过调用 HAL 库函数 HAL_UART_Receive_IT :
: `# k" f9 O' B" l
+ a: o, V& s- M
: s5 O: W N [, b$ q# R! Y7 m/ i3 Y0 h
一般的使用方式步骤: ( H4 v+ M9 R' s# c
/ n! k6 `! l9 q! _2 Q
8 ~% R) @1 N( e. [7 E
2 G) x* v$ {1 D+ c2 t5 d6 L3 v! ^% P9 v! @1 U
M& y9 e0 q5 c& e" D
对于本次需要说明的问题,就是使用了 HAL_UART_Receive_IT 函数导致的,下文我们会说明,这里列出了基本的使用步骤。
8 h6 E: O% _2 g 二、 收发同时串口卡死?
) z) S% Y' e7 g# y8 @) H2.1 问题说明最近测试部反馈,产品有些时候的下行没反应,这里所说的下行,其实就是串口接收。 霹雳扒拉一大堆多余的省略 ... ... 只说几个重点: 出问题的最后现象就是串口发送正常,但是永远接收不到数据了,其他程序正常运行。 出问题只存在于串口又有接收,又有发送的产品上。 产品发送一般是周期性的,但是接收是随机的,无线信号串口接收,所以产品的出问题的情况也是随机的,但是数据量大起来肯定就会出现永远接收不到的问题。
_$ R+ \: N' ?2.2 尝试的处理方式因为所有的一些都是按照正常流程设计的,按理来说实在是不知道为什么会这样,所以网上查询测试了好久,现在我把尝试的处理方式以及步骤记录说明一下: ' m# K8 ?3 a a6 H" _
2.2.1 清除错误标志位在使用 HAL 库的时候,有4个错误 flag,如下图: 6 S2 d6 q# K5 R+ h# S8 {
7 ^9 k, A! u/ q" K' c( |6 y
s7 W) V _$ g# F1 a
+ S# L' q. `/ U0 S5 q0 z期初还以为是某些异常错误导致的,经过网上的的一些查询,刚开始是添加了清除错误标志位: - __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_PE);//清标志, J: B2 e, N+ N9 L$ A
- __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_FE);
6 z& S9 Q$ d4 l$ W1 B - __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_NE);) r0 {0 `( m& R; b1 `- I& [
- __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_ORE);
复制代码
/ c7 X( J& ]+ ` C- E在需要的地方加上错误标志位清除,我是在清除串口缓存中处理的: 0 K! c k, Z6 L
6 K9 a( r, @, E+ n
# x4 _, N% D S
( R5 q5 r `1 y/ b r: ]) m: S( g8 e
2.2.2 串口溢出错误其实串口溢出错误在上面的已经清除过标志位,因为这个问题着实搞得我头有点大,所以看到网上前人的处理方式和上面直接清除不一样,还是试了一把。 这里简单说明一下,我还特意去看了下自己的 CubeMX 设置,在设置的时候 有一个 Overrun 错误标志位,平时我们设置都不一 定往下拉着看 = =!:
6 G1 \" E6 C5 C# e/ `
+ p/ R( A0 S! j# L9 `然后确定了开启了串口溢出错误检测以后,我根据网上的方式,加了一个HAL_UART_ErrorCallback函数,其实就类似于HAL_UART_RxCpltCallback 函数:
# t5 ?6 R _/ ^: B! c
- w# }1 f. S5 L: `: E; I
( r' S0 P" r4 t5 |1 R% t) `) `
) G( i' b1 c7 D1 X# c+ S n自己加了一个 出错处理函数,其实现在看来,当然也是没有用的。
9 a, F" m# e2 a4 x5 W2 t, O2.2.3 HAL库的半双工处理?折腾了好长一段时间,其实一开始就知道问题在于 同时收发会出问题的情况,那么继续上网找问题。 最终确定了一个问题就是: 我们都知道 STM32 串口是全双工的, STM32 HAL库在处理接收的时候会锁一下串口一会,导致变成某个短时间的“半双工”。 这个时候如果同时收发就会出现问题,最后解决的办法在这篇文章中看到了:STM32 F103串口同时收发出现死锁问题解决办法 问题在于我们使用的HAL_UART_Receive_IT函数中,有对串口加锁的操作:
, _2 z u2 D) O! E x7 X3 E
3 D }, z8 W9 O) l; q虽然在后面有解锁:
3 K/ g, t- r# _% S
5 Q# N4 u" @" n, K. l6 H
$ w: {2 @: @# L+ g4 Q, L: q0 Y
6 h. K3 s( |7 j但是根据后期的解决方式来说,确实就是这个HAL_UART_Receive_IT函数的问题,最后使用的方式为,在产生一次中断以后开启的时候手动解锁:
( x+ h" S6 G! w5 r0 y: H- K# x9 _1 c
& \ M3 Q! P" c3 _9 T$ Q( f1 F0 }
: u8 H$ J* B: Q: B2 G! ^% t- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
, \/ M+ ^* p9 U+ w f - {
2 d0 k0 G" w6 H - if(huart->Instance == LPUART1){0 B7 d6 G8 M, q4 D$ X
- Enocean_Data++;3 K! o$ p E' [& e5 X6 q
- if(Enocean_Data > 98)Enocean_Data = 0;
& n0 N/ U% e& D. y. U% L - while(HAL_UART_Receive_IT(&hlpuart1, (uint8_t *)&USART_Enocean_BUF[Enocean_Data], 1) != HAL_OK){; t4 i- l2 Y$ R! L9 d
- hlpuart1.RxState = HAL_UART_STATE_READY;7 h( s0 _6 r- m
- __HAL_UNLOCK(&hlpuart1);
) O: `9 U3 A" P- R- b, [ - }
# ?- x2 V5 f/ J: {. O% D - } O4 q1 ^9 J4 d E* m) a
- else if(huart->Instance==USART1)
6 T* R3 B! S* D- ?4 X - {
+ ~) R/ |& F+ C - }
" l |: }$ o* h - }
复制代码
0 L4 q& U5 A3 ~
9 {+ t( e0 ? _* g0 j终于,串口不再卡死 , 成功!
& t: Z# ]3 S6 F5 O/ S* O7 G7 d0 t2 D" i; H) S Y" G+ e/ ~
转载自:矜辰所致/ b2 {2 }0 b* K D' B3 H
如有侵权请联系删除
& ~/ ~, K6 G5 `$ I
6 A% O1 O& R% y! r: ]2 A+ E! j$ c' p0 C7 _4 p4 }7 i i
|
直接使用HAL_UART_Transmit_IT,不会卡死,不过调用的时候要保证上一帧已经发送完成,不然会丢数据。
我觉得吧,一般情况使用HAL库收发函数要成对使用,接收和发送要么都轮询,要么都中断,要么都DMA,这样不用做特殊处理问题不大。
特殊情况下,比如RS485通信,收发不同时发生,轮询发+中断收也没问题。