STM32Cube HAL出来六七年了,还是有很多初学者没有适应,今天就分享一个读者问到的关于中断处理的问题。/ R/ k1 D5 A7 |
很多人都知道STM32CubeMX这套工具的一个目的:减少开发者对STM32底层驱动的开发时间,把重心放在应用代码上。7 S+ G& V- s8 K9 j, Z! C) ^
% @4 L' j" l9 y0 W
但是,STM32CubeMX只是生成了底层驱动的初始化代码。所以,我们还需要掌握:应用层代码如何调用HAL库函数(API接口),以及HAL库中断处理机制等相关知识。7 P# t+ r4 }! Y7 [! X
HAL库牵涉的内容较多,下面简单描述一下HAL库中断处理,以及相关的回调函数。
+ z+ |+ p: ^: Z: f( x" u6 j 1HAL库中断处理机制之前使用标准外设库开发时,中断程序(函数)由我们自己实现。
7 ~( l. ~2 d' s- l1 q而HAL库的中断处理函数是按照HAL处理机制来实现,如USART1,统一由HAL_UART_IRQHandler来进行处理,如下图:2 F. H) t# [' @; K
其它大部分外设(TIM、SPI、CAN...)中断都类似,HAL进行统一处理。% B# O8 g& X6 r" s1 M+ Y1 }. I3 b
. v( v8 `7 W) A. A$ X) j
也就是说,HAL已经帮我们把中断处理函数写好了,我们只需要调用相应函数来编写应用程序就行了。
+ O+ m/ z3 V7 Q' @! _( T' rHAL_xxx_IRQHandler里面做了哪些处理? 我们以STM32F1的HAL_UART_IRQHandler为例:
1 Z9 d3 T4 z1 z3 e2 g6 a# T' [9 ~- void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)) @/ i- F# t/ \7 X0 }9 s( M
- {
* ]. H7 F+ ]8 t, X+ c - uint32_t isrflags = READ_REG(huart->Instance->SR);3 v/ g, x7 H( A8 S
- uint32_t cr1its = READ_REG(huart->Instance->CR1);
3 i f4 j. C& v6 A( [: A+ G - uint32_t cr3its = READ_REG(huart->Instance->CR3);- |. f' s; e; ~
- uint32_t errorflags = 0x00U;
" ^% C; }& \, i% r" f! ? - uint32_t dmarequest = 0x00U;
: S0 f [ d: ] t# T* A
6 S7 w+ D5 G' E" g* F0 L- /* If no error occurs */
2 F+ @+ C# ~+ n - errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));) A, {$ H7 i3 n D' D. z
- if(errorflags == RESET)
3 e3 Y! K0 J. C) g5 n; n* G; |3 j - {
* b `/ ?$ v, b% ]7 |7 o - /* UART in mode Receiver -------------------------------------------------*/! x* D* ?+ S- z* i/ Q) j/ @& Z
- if(((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
. c+ M3 `9 y. ?- j* X* [ - {
P' t! t1 |; b6 D' f. A - UART_Receive_IT(huart);
P/ A' z2 E0 v) m5 N' ] - return;
2 H) [- Z" X% _2 c - }/ p* |, o; z$ k* l/ S' N8 _) W3 u
- }
: p/ o8 s+ T% P- p1 \$ v, c+ F
+ [3 U/ a% U+ T- /* If some errors occur */
! l& p0 ?3 Q, ]* n4 A - if((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET) || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET)))& P/ ]- R, [) j7 S# m: d) c" M+ a
- {
/ e2 g7 S' \) G. | - /*3 z/ I3 W( f. f2 x& v' K# _
- ·
. E6 Z- t( R$ c$ Q" v } - ·删减了部分代码; N3 f- E! G6 ?. X
- ·
: P, v' m+ }( Q3 g; k: Y# L - */. ~2 n, v& f5 L% q, Q8 l6 }
- } /* End if some error occurs */
. H9 m) v6 m1 V9 J; l6 x5 Q - 2 Q) x) p7 h& }( l
- /* UART in mode Transmitter ------------------------------------------------*/& T: h! |% `( D4 L- x" ?
- if(((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
6 f- D# L6 {6 s6 e - {9 n/ S* b* d$ t. t& P5 I4 q
- UART_Transmit_IT(huart);
7 h$ Z1 n# b6 W- p$ y, I - return;/ |/ F3 t. z8 X4 p
- }
: E, \3 @" Z' W - /* UART in mode Transmitter end --------------------------------------------*/
$ |! m) o0 A" X7 ]# w" p - if(((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET)) B5 G% l8 l* O; b' v. l0 `: ?& S
- {
, x3 g# @5 P% D1 J$ j" ` - UART_EndTransmit_IT(huart);9 f. [3 }8 v: [' V" `1 I& t
- return;
$ |1 k5 }2 c0 Y6 Q2 o. c/ [ - }
1 \; u) H- _9 ?3 q' | - }
复制代码
$ r- ]6 ~, K4 V2 l7 L8 f
- D9 {3 r1 R" G( u1 d8 _( j 其实,大家认真看一下代码应该能明白,这些和我们编写的中断处理函数是不是有类似之处?2 b( z: J3 N$ D8 e+ ?8 d
这是无非就是接收中断、发送中断、错误中断等一系列处理。只是这里又进行了再次封装,比如接收中断UART_Receive_IT。7 J, r% }( U8 b
当然,这个UART_Receive_IT接收中断实现方式又可能存在不同。像F0、F1...就是直接调用这个接收中断函数来进一步处理。
& j; `. O& S2 [像L0、G0...是通过执行指针函数RxISR来进一步处理。G0的接收中断处理为:huart->RxISR(huart);( Q/ f& n; v/ M+ A( S5 ^
2 G' ~4 @7 G8 }4 u y$ {6 v8 E! p( ^8 d- void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)! v5 n+ `$ Z2 T+ }( E
- {) W3 m3 ^ C9 r( E1 e' S
- //删除了前面代码
$ K4 @8 C1 n# L4 O2 q - /* If no error occurs */" a# D5 \8 e( Q0 ~' A: G+ z" r
- errorflags = (isrflags & (uint32_t)(USART_ISR_PE | USART_ISR_FE | USART_ISR_ORE | USART_ISR_NE));
5 z- Y! d% g }; n6 j - if (errorflags == 0U)
7 O$ D% O" o9 V3 Y$ Y - {9 ]9 G: i4 h& a; `% D
- /* UART in mode Receiver ---------------------------------------------------*/9 x5 x# S+ @/ z' S: K2 H) y
- if (((isrflags & USART_ISR_RXNE_RXFNE) != 0U)4 i2 ^; ?8 Q l( l$ L! Y2 L3 K
- && (((cr1its & USART_CR1_RXNEIE_RXFNEIE) != 0U)
% I* [# l- b% \* F& {) L! j1 i - || ((cr3its & USART_CR3_RXFTIE) != 0U)))
6 ?: @$ u) \. ], ?4 h T - {- G9 s" S: O: k2 y) y% Q5 d
- if (huart->RxISR != NULL)4 R, u% i" x9 c' i/ R
- {5 a# Q3 X# H9 r2 H. R
- huart->RxISR(huart);
8 g4 T# j* w' y- Y: e. o - }
/ I5 s* Y# t4 ]7 B9 t - return;6 W \ [! ]$ i( o. d2 M
- }
& t& t* [ D- P. J+ m: F6 x( e: u - }0 _/ s" d6 n6 _; q x+ b1 Q
- //删除了后面代码
* c: w$ G4 v( P$ K3 D" k2 f - }
复制代码
+ P& J& K2 P+ p; W2 z7 O( Y$ I
7 G& l$ L% ~7 V" {+ w# R
看了上面USART中断处理的函数,大家有没有得到什么启发?
6 }) B' ~/ P: W. D1 l u
其实,HAL库里面处理机制基本一致,只是实现方式上有所不同。
% j4 Z! u0 |; h! d
如果你摸清楚了HAL库基本原理,相信阅读HAL库源码,或者使用HAL库编写应用代码不是问题。
2 K2 k- e% V! G$ J, J 2回调函数实现原理在HAL库中存在大量类似HAL_XXX_XXXCallback这样的函数,这些都是回调函数。 3 t7 K+ |7 f/ B8 r& L) ^; h
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。" U" N* T1 V! E2 ?) h8 C3 g
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。---来自百度百科
/ w1 E( V) [- ] + L5 X! @; b% z$ p
HAL库中断处理使用了较多的回调函数,还是拿UART接收中断来举例说明。
7 ]( ^* v, o) f2 K
初始化配置好UART中断接收,如果有中断请求,就会执行回调函数HAL_UART_RxCpltCallback。 ' y3 _0 A# }/ G. k/ }
看上面回调函数的定义,通过特定条件调用『回调函数』,这里触发的条件就是中断。 2 g( W D+ f4 v9 _) B
3 扩展说明这里也简单说几点:
. p5 |8 T) ~! e8 S, b
1.初学者想直接使用HAL不是不行,需要有一定C语言功底 针对大部分初学者来说,是不建议直接上手HAL。但是,有部分C功底较好的,还是建议直接上手。 " O+ y! H# l& }8 s
2.学HAL,建议参看官网例程 很多人不知道如何找资源,我不止一次强调,官方的才是最好。在HAL库中Projects目录下就有很多例程Examples。
( X2 j- y; N; J: w; w! M" L+ \6 h
3.我们追求效率,可以HAL库源码2 \) V4 q' y. Y& u- P5 K2 F L& ]9 W
如果你想修改HAL库源码,允许修改少部分。如果要大量修改,还是别折腾了。
) A: \! D' {- s' e2 `0 V
4.实际项目需做一定修改 STM32CubeMX仅仅是生成初始化代码和工程,你实际项目中一般都有自己的软件架构。 特别是项目越大,软件架构就需要更加规范。 ; R( w" B J2 P a
比如:生成的gpio.c文件名,你需要修改成bsp_gpio.c.
+ T" ~# T- V' w4 V- w! P
再比如:函数MX_USART2_UART_Init改成MX_DEBUG_UART_Init.
- ~5 k6 I6 N; `6 Y4 ^2 [2 M0 `: |+ g" T, Z9 z. \ M$ y
|