STM32HAL库串口和DMA库函数调用关系
一、串口中断接收(串口3为例)
stm32f1xx.it.c——USART3_IRQHandler(void)
该文件里是中断向量表的函数,当串口3接收完数据后,产生中断,会先调用USART3_IRQHandler(void)函数,函数里再调用HAL_UART_IRQHandler(&huart3);
点击跳转到HAL_UART_IRQHandler函数的具体实现
stm32f1xx_hal_uart.c——HAL_UART_IRQHandler(&huart3)
该函数里通过判断标志位来确定是哪个中断请求,进而再调用不同的中断函数,如UART_Receive_IT(huart);是接收中断
函数中间部分是一些错误判断,以及错误回调函数,这里没贴出
点击进入UART_Receive_IT函数
stm32f1xx_hal_uart.c——UART_Receive_IT(UART_HandleTypeDef *huart)
UART_Receive_IT函数最后会调用HAL_UART_RxCpltCallback接收中断回调函数,在调用中断回调函数之前,前面三句__HAL_UART_DISABLE_IT():作用是关闭串口接收中断,也就是说,在一次串口中断接收过程的最后,即串口接收完一组数据之后会关闭串口接收中断,然后将接收状态重新设为READY,再调用回调函数
stm32f1xx_hal_uart.c——HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
接收中断回调函数就是用_weak修饰的虚函数,用户可以在自己的文件中重写该函数,实现接收数据的处理
总结一下,串口中断接收的流程:
USART3_IRQHandler(void) -> HAL_UART_IRQHandler(&huart3) -> UART_Receive_IT(huart) -> HAL_UART_RxCpltCallback(huart);
Callback函数就是用户要重写在main.c里的回调函数。
再说明一下一个很重要的问题:STM32的每个串口中断有好几个(发送接收等),但只要是与串口相关的中断发生系统都会先调用个函数USART3_IRQHandler,也就是中断向量表中的那个,比如usart3的话就是USART3_IRQHandler(void),然后这个函数再调用
HAL_UART_IRQHandler,在HAL_UART_IRQHandler中去读取寄存器判断究竟是那几个位被置为1,确定好是哪个中断之后,(接收还是发送)再调用不同的回调函数。
如何使用接收中断
在CubeMX中配置完了之后并没有使能串口中断(有一个串口初始化函数,但是在这个函数中并未使能串口中断)需要用户手动使能。使能代码如下:
HAL_UART_Receive_IT(&huart3,(uint8_t *)kRxBuffer,10);
什么意思呢?
HAL库的串口接收思路是这样的:用户你可以随便定义一个缓存区,大小随意,然后通过上边这个函数把这个缓存区对应到串口的接收,上面函数的意思就是把kRxBuffer(这是一个数组)作为缓存区,指定大小为10。然后usart3接收数据的时候就放到kRxBuffer这个数组中,只有当接收到10个数据之后才调用一次callback函数(回调函数)。当然不要忘了该函数的使能串口接收中断功能,在介绍UART_Receive_IT(UART_HandleTypeDef *huart)函数时说到了串口接收完数据后会关闭使能,所以,在回调函数中一定要再写一次HAL_UART_Receive_IT(&huart3,(uint8 *)kRxBuffer,10),使能接收中断。
所以更具体一下串口接收的流程就是这样的:
(1)串口一个接一个的接收到数据填充到缓存区
(2)缓存区满(大小是用户定义的)程序几经辗转最后会调用到回调函数。
(3)执行用户在回调函数中实现的功能。
二、串口中断发送(串口3为例)
stm32f1xx.it.c——USART3_IRQHandler(void)
因为串口中断的入口函数就只有一个,就是USART3_IRQHandler(void)函数,所以这里与接收的一样,都是从这个函数进入
点击跳转到HAL_UART_IRQHandler函数的具体实现
stm32f1xx_hal_uart.c——HAL_UART_IRQHandler(&huart3)
该函数与接收的代码一样,函数体的前部分就是接收模式的
一直往下拉,到函数的最后,就可以看到发送模式了
函数的最后有发送中断函数UART_Transmit_IT(huart);以及发送结束中断函数UART_EndTransmit_IT(huart);
在UART_Transmit_IT函数里会判断发送的数据是否为0,为0则表示发送完成,失能UART_IT_TXE位,再使能UART_IT_TC位,所以在上一张图中就判断UART_IT_TC位,进而调用UART_EndTransmit_IT(huart);发送结束中断函数
UART_Transmit_IT(huart)
进入UART_EndTransmit_IT(huart)函数体内部
stm32f1xx_hal_uart.c——UART_EndTransmit_IT(huart)
第一个红线处清除了发送中断使能(同接收一样,在用完之后就关掉,但是不同于接收,发送完成就不用再在回调函数中使能了,因为在中断发送的时候就会使能)
最后会调用HAL_UART_TxCpltCallbsck(huart)中断回调函数
stm32f1xx_hal_uart.c——HAL_UART_TxCpltCallbsck(huart)
如何使用发送中断
与接收中断一样,发送也要先调用HAL_UART_Transmit_IT函数使能中断发送
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
比如有一个unsigned char数组a[10],HAL_UART_Transmit_IT(&huart3,a,10),这一句的意思是用usart3(串口3)发送a数组10个数据,然后使能发送中断。
当发送完成之后(或者发送一半,发送一半也有个中断)就会执行回调函数。
总结一下发送中断
使用HAL_UART_Transmit_IT函数发送指定长度的数据,并使能发送中断,发送到一半和发送结束会触发中断(相关的回调函数是
HAL_UART_TxHalfCpltCallback()和HAL_UART_TxCpltCcalback())中断触发后发送中断使能会被清除,然后调用回调函数,回调函数执行完成之后结束本次发送。
串口中断与DMA中断的关系
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
串口DMA发送,以DMA方式发送指定长度的数据。
过程是,把发送缓冲区指针指向要发送的数据,设置发送长度,发送计数器初值,设置 DMA传输完成中断的回调函数,使能DMA控制器中断,使能DMA控制器传输,使能UART的DMA传输请求。
然后,UART便会发送数据,直到发送完成,触发DMA中断。
DMA中断处理,如果 DMA模式是循环模式,则直接调用 DMA传输完成中断的回调函数。
如果 DMA模式是正常模式,则先关闭DMA传输完成中断,不再触发DMA中断,再调用DMA传输完成中断的回调函数。
DMA传输完成中断的回调函数处理过程,如果 DMA模式是循环模式,则直接调用串口发送完成回调函数。
如果 DMA模式是正常模式,则先关闭UART的DMA传输请求, 再使能串口传输完成中断,直到传输完成,触发中断。
串口传输完成中断处理,关闭中断,调用串口发送完成回调函数。
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
串口DMA接收,以DMA方式接收指定长度的数据。
过程是,把 接收缓冲区指针 指向 要存放接收数据的数组,设置接收长度,接收计数器初值,设置 DMA传输完成中断的回调函数,使能DMA控制器中断,使能DMA控制器传输,使能UART的DMA传输请求。
然后,UART接收到数据,便会通过DMA把数据存到接收缓冲区,直到接收到指定长度数据,触发DMA中断。
DMA中断处理,如果DMA模式是循环模式,则直接调用 DMA传输完成中断的回调函数。
如果 DMA模式是正常模式,则先关闭DMA传输完成中断,不再触发DMA中断,再调用DMA传输完成中断的回调函数。
DMA传输完成中断的回调函数处理过程,如果DMA模式是循环模式,则直接调用串口接收完成回调函数。
如果 DMA模式是正常模式,则先关闭UART的DMA传输请求, 再调用串口接收完成回调函数。
参考:
https://wenku.baidu.com/view/21f7886030687e21af45b307e87101f69e31fbb1.html