STM32Cube HAL出来六七年了,还是有很多初学者没有适应,今天就分享一个读者问到的关于中断处理的问题。
0 ^: }2 @0 q; o很多人都知道STM32CubeMX这套工具的一个目的:减少开发者对STM32底层驱动的开发时间,把重心放在应用代码上。
7 x2 @6 |2 v8 s) G* a" y; ]( ?' K7 b- W3 _
但是,STM32CubeMX只是生成了底层驱动的初始化代码。所以,我们还需要掌握:应用层代码如何调用HAL库函数(API接口),以及HAL库中断处理机制等相关知识。
6 e, T0 w1 E: iHAL库牵涉的内容较多,下面简单描述一下HAL库中断处理,以及相关的回调函数。
, E/ v8 O. G, h 1HAL库中断处理机制之前使用标准外设库开发时,中断程序(函数)由我们自己实现。
* Y2 A( Y& M; y# u l1 N+ v而HAL库的中断处理函数是按照HAL处理机制来实现,如USART1,统一由HAL_UART_IRQHandler来进行处理,如下图:6 L) a! X5 z% u- [. Z4 V( Y2 L
其它大部分外设(TIM、SPI、CAN...)中断都类似,HAL进行统一处理。
, q& n+ n! y" ]$ a# _, [- W( u+ M- M+ h( g
也就是说,HAL已经帮我们把中断处理函数写好了,我们只需要调用相应函数来编写应用程序就行了。
+ D; G: x' N* c- Z$ mHAL_xxx_IRQHandler里面做了哪些处理? 我们以STM32F1的HAL_UART_IRQHandler为例:- & J) B6 U0 e3 ~5 m
- void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
/ V T6 R4 r4 ^# A8 Z8 C- [' [8 H; b - {7 s* q3 t. S# z( a
- uint32_t isrflags = READ_REG(huart->Instance->SR);
$ g; }, o1 N6 V ~2 X9 J8 @ - uint32_t cr1its = READ_REG(huart->Instance->CR1);
. r# k# W- M4 [ - uint32_t cr3its = READ_REG(huart->Instance->CR3);
Q. P, i7 `+ L' _# D - uint32_t errorflags = 0x00U;& F2 c9 P+ Q& p8 n: y8 q( D
- uint32_t dmarequest = 0x00U;' W9 t) o1 a! c+ c' C5 G, K, R
- c9 `( o% i9 |# M3 [# q- /* If no error occurs */
$ x. X- I6 P& b: a" A: {$ @ - errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));, D R# ]5 |! ?& D* z8 L' I: Q! n
- if(errorflags == RESET)
G& J" b4 h8 p ~# B - {* m: p7 @$ W% B- n4 w
- /* UART in mode Receiver -------------------------------------------------*/
" }8 o. ]/ r, f6 O1 F - if(((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))" t4 B C# D( D; r# q
- {$ ]/ ^7 v% h' n, \% G4 _, y
- UART_Receive_IT(huart);4 o* F; k g% m- d
- return;
9 r3 J) N1 |9 p3 E - }* I% r4 [: _$ j$ o% c N: J5 h5 O
- }
3 b2 c" m. v5 B& a: g
) w6 B4 V, ?7 ?# E& w) s' I* c- /* If some errors occur */
) r4 x! @# W7 S7 A - if((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET) || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET)))' V. ^" z R5 j4 B# {) {5 Y4 W
- {
9 X- ` I, r7 f6 S: g% Q6 p; _ - /*) A! @5 ^& ^2 c2 w7 n7 t" E, H
- ·
- N% l5 d0 t, r2 k2 T# M4 U - ·删减了部分代码
: q% y/ }' X" ^9 f0 x# w: o - ·- y& U/ z( p. [2 a( ?
- */( b9 }- j$ t$ \2 x S- ~" _/ |, \
- } /* End if some error occurs */6 l$ P U3 j- I/ |+ \" F
5 {/ w- O* v' d: ^$ m( ?' G- /* UART in mode Transmitter ------------------------------------------------*/
- P* W5 A5 @6 s0 O4 A6 d - if(((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))0 g8 `2 ?1 U4 t1 Q7 ]$ |
- {
. T+ d% a' _6 c' c4 E - UART_Transmit_IT(huart);
7 X6 h. c. B" v5 Z0 T' m - return;) J. q& o: O. ?
- }
6 F$ ^3 L7 c, h- _5 P - /* UART in mode Transmitter end --------------------------------------------*/
: R3 |# r+ F& V3 A' k2 A& @9 C - if(((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))( _" r% n! g9 u2 m3 F! P/ \3 k, ]8 p
- {1 E! W9 Y! Q' u5 M" v
- UART_EndTransmit_IT(huart);9 {' H2 x: H' C$ A# {. z9 Z# A7 R
- return;
h- N# k/ W& H: w7 y - }
B% i2 K* _4 _; N - }
复制代码
8 X4 D& V8 |* L
2 \' f# h* V* T9 v: l" W$ ^ 其实,大家认真看一下代码应该能明白,这些和我们编写的中断处理函数是不是有类似之处?
' Y8 k3 O* ]& I+ @这是无非就是接收中断、发送中断、错误中断等一系列处理。只是这里又进行了再次封装,比如接收中断UART_Receive_IT。
/ @# L5 T0 y/ s) C2 A# |( k: X- O9 E当然,这个UART_Receive_IT接收中断实现方式又可能存在不同。像F0、F1...就是直接调用这个接收中断函数来进一步处理。
. h& i0 S$ |- R. A/ v1 T- Z" T像L0、G0...是通过执行指针函数RxISR来进一步处理。G0的接收中断处理为:huart->RxISR(huart);
& R# D: Z# ~ J! s) k
0 t0 W/ k; h) `& d! g, q5 d- void HAL_UART_IRQHandler(UART_HandleTypeDef *huart), e: R* M$ b6 M- W/ x3 h- }5 V
- {& L' n6 \8 i8 k. m
- //删除了前面代码/ e$ r' X, ^/ d1 C# @- A4 o
- /* If no error occurs */* i. a! I$ J2 `/ m, p- x1 F% Q
- errorflags = (isrflags & (uint32_t)(USART_ISR_PE | USART_ISR_FE | USART_ISR_ORE | USART_ISR_NE));
; v. o$ I( i# l. Q e. B$ e$ U - if (errorflags == 0U)
/ g1 D: x( I1 M: u5 g - {* F: K" o4 ]3 V# P
- /* UART in mode Receiver ---------------------------------------------------*/) H* J1 |9 ?: }6 W, j$ L! K
- if (((isrflags & USART_ISR_RXNE_RXFNE) != 0U)
5 I$ }, s3 h# I! k0 e* Q - && (((cr1its & USART_CR1_RXNEIE_RXFNEIE) != 0U)7 D2 y. ^" e/ \. L
- || ((cr3its & USART_CR3_RXFTIE) != 0U)))+ K8 d2 Y W7 \/ D( T
- {1 ]* E! C& y8 Y
- if (huart->RxISR != NULL)+ M, \$ b% \) j" ^7 Y7 a$ J
- {
u6 w1 n& t7 k8 d5 f" f% n - huart->RxISR(huart);, [" x: w* P! t- ?7 X$ h
- }
& ^" c. J% I8 t7 v$ O6 f* g - return;
1 A+ X. _1 S2 f& W& l; R) S; o - }6 J3 E5 m2 s) R3 Y
- }) G! X& C1 U' I
- //删除了后面代码
3 N; u1 G, O0 L# b0 Y - }
复制代码 ( m6 q0 w+ E: A
. ~; k7 \/ d: G: I
看了上面USART中断处理的函数,大家有没有得到什么启发? ( J# y5 L( s9 ^
其实,HAL库里面处理机制基本一致,只是实现方式上有所不同。 , m! |$ C" J% J' \) p$ Q
如果你摸清楚了HAL库基本原理,相信阅读HAL库源码,或者使用HAL库编写应用代码不是问题。 G4 J6 w: u- r
2回调函数实现原理在HAL库中存在大量类似HAL_XXX_XXXCallback这样的函数,这些都是回调函数。
( j! i6 A, I O* u! t0 l# b4 o 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。9 J# Z% A; T& p# ^* O! p7 y6 }
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。---来自百度百科1 F3 S! v6 ]3 c/ x5 X- L2 x
- g7 Z* _1 `1 f' R' W: Y) Z
HAL库中断处理使用了较多的回调函数,还是拿UART接收中断来举例说明。 3 N5 j' [* |( u1 w* ?6 @
初始化配置好UART中断接收,如果有中断请求,就会执行回调函数HAL_UART_RxCpltCallback。 " S; T' G8 E/ k1 Q! K
看上面回调函数的定义,通过特定条件调用『回调函数』,这里触发的条件就是中断。 : J5 \8 L2 H) U
3 扩展说明这里也简单说几点: S9 z3 V; ]% i& u, q# j2 O
1.初学者想直接使用HAL不是不行,需要有一定C语言功底 针对大部分初学者来说,是不建议直接上手HAL。但是,有部分C功底较好的,还是建议直接上手。 6 Y! u, g6 p( ]
2.学HAL,建议参看官网例程 很多人不知道如何找资源,我不止一次强调,官方的才是最好。在HAL库中Projects目录下就有很多例程Examples。
5 [# {4 E& p- B L% j
3.我们追求效率,可以HAL库源码" q" b8 p& D# |: S3 {
如果你想修改HAL库源码,允许修改少部分。如果要大量修改,还是别折腾了。
9 \! T' A; w/ o* K; J9 B
4.实际项目需做一定修改 STM32CubeMX仅仅是生成初始化代码和工程,你实际项目中一般都有自己的软件架构。 特别是项目越大,软件架构就需要更加规范。
2 S' ]$ _" r8 E/ t: t
比如:生成的gpio.c文件名,你需要修改成bsp_gpio.c.
2 o# i! t; V3 G: v8 ^
再比如:函数MX_USART2_UART_Init改成MX_DEBUG_UART_Init.
- m+ C [' {) I0 h2 P8 r+ f3 Q% |( q; J y2 H. m- ^6 B
|