问题背景
开发环境
- 硬件:GD32F407VET6开发版
- IDE:STM32CubeMX + Clion + ARM GNU
- 烧录:OpenOCD
实验目标
使用Cube配置串口USART1及其中断,引脚复用PA9/PA10,通过使能接收中断 Receive_IT
,实现接收7字节时,在中断回调中处理该7个字节数据(简单回传,即echo),并再次开启接收中断,从而实现不断接收7字节、回传7字节的功能。
遇到问题
发送字节数为7时,能够正常echo;但是不为7时,本来期望是单次发送字节数:
- 大于7字节时,应该也能每次触发中断,只是多余的字节数被丢弃了而已
- 少于7字节时(例如5),则通过再次发送也能触发中断
但实验过程中发现这两种情况下,会发送echo了一两次之后,后面无论发送多少数据都无法再echo的情况
情景再现
Cube配置
导入Clion
编译配置-交叉编译
Cmake配置
OpenOCD烧录配置
daplink.cfg
:
# choose st-link/j-link/dap-link etc.
adapter driver cmsis-dap
transport select swd
# 0x10000 = 64K Flash Size
# set FLASH_SIZE 0x20000
# 512KB Flash
set FLASH_SIZE 0x80000
source [find target/stm32f4x.cfg]
# download speed = 10MHz
# adapter speed 10000
代码流程
- 上电后启用接收中断
- 在接收完毕回调中echo,并再次启用接收中断
main.c
/* USER CODE BEGIN 0 */
uint8_t rxbuf[7] = {0};
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
HAL_UART_Transmit(&huart1, rxbuf, 7, 100);
HAL_UART_Receive_IT(&huart1, rxbuf, 7);
}
/* USER CODE END 0 */
int main(void) {
/* USER CODE BEGIN 1 */
__HAL_RCC_HSI_ENABLE();
__HAL_RCC_SYSCLK_CONFIG(RCC_SYSCLKSOURCE_HSI);
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, rxbuf, 7);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) {
// HAL_UART_Transmit_IT(&huart1, (uint8_t *)"Hello, World!\r\n", 14);
// HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
源码分析
启用接收中断HAL_UART_Receive_IT
uint8_t rxbuf[7] = {0};
HAL_UART_Receive_IT(&huart1, rxbuf, 7);
HAL_UART_Receive_IT -> UART_Start_Receive_IT
huart->pRxBuffPtr = pData;
huart->RxXferSize = Size;
huart->RxXferCount = Size;
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->RxState = HAL_UART_STATE_BUSY_RX;
...
/* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
__HAL_UART_ENABLE_IT(huart, UART_IT_ERR);
/* Enable the UART Data Register not empty Interrupt */
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);
pRxBuffPtr
:保存我们传入的buf指针RxXferSize
:保存我们传入的buf大小RxXferCount
: 初始化待接收数据数量ErrorCode
:初始化错误码为 no errorRxState
:标识UART处于接收状态(Data Reception process is ongoing)- 开启UART异常中断
- 帧异常(Frame error)
- 噪声异常(noise error)
- 接收缓冲区溢出异常(overrun error)
- 开启接收寄存器非空中断(Enable the UART Data Register not empty Interrupt)
UART中断向量表
startup_stm32f407vetx.s
.word USART1_IRQHandler /* USART1 */
stm32f4xx_it.c
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
数据接收处理
stm32f4xx_it.c
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart) {
/* If no error occurs */
errorflags = (isrflags & (uint32_t) (USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
if (errorflags == RESET) {
/* UART in mode Receiver -------------------------------------------------*/
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET)) {
UART_Receive_IT(huart);
return;
}
}
...
执行接收数据逻辑(UART_Receive_IT)的前置条件:
- 没有UART相关的异常(errorflags)
- 接收寄存器非空(USART_SR_RXNE)
- 接收寄存器非空中断使能是开启的(USART_CR1_RXNEIE)
逐字接收数据(Data Register)
stm32f4xx_hal_uart.c/UART_Receive_IT
/* Check that a Rx process is ongoing */
if (huart->RxState == HAL_UART_STATE_BUSY_RX) {
...
*pdata8bits = (uint8_t) (huart->Instance->DR & (uint8_t) 0x00FF);
huart->pRxBuffPtr += 1U;
...
if (--huart->RxXferCount == 0U) {
/*Call legacy weak Rx complete callback*/
HAL_UART_RxCpltCallback(huart);
- 判断UART是不是处于接收进程中
- 我们之前调用过
HAL_UART_Receive_IT
,里面会置位这个状态huart->RxState = HAL_UART_STATE_BUSY_RX
- 我们之前调用过
- 从DR(UART数据寄存器)中读取一个字节到用户自定义缓冲区中(我们调用
HAL_UART_Receive_IT
时传入过一个7字节的buf
) - 递减剩余待接收数据数量
huart->RxXferCount
(之前被初始化为7)- 如果递减为0,则说明接收的字节数填满了用户指定缓冲区大小
- 然后调用
HAL_UART_RxCpltCallback
,这是一个weak
函数(默认是一个空实现),用户可以声明一个对应的非weak
版以实现回调处理。这个理念是经典的hook钩子函数。
重写缓冲区接收完毕回调
原型:
stm32f4xx_hal_uart.c
__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
/* Prevent unused argument(s) compilation warning */
UNUSED(huart);
/* NOTE: This function should not be modified, when the callback is needed,
the HAL_UART_RxCpltCallback could be implemented in the user file
*/
}
重写:
main.c
/* USER CODE BEGIN 0 */
uint8_t rxbuf[7] = {0};
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
HAL_UART_Transmit(&huart1, rxbuf, 7, 100);
HAL_UART_Receive_IT(&huart1, rxbuf, 7);
}
/* USER CODE END 0 */
- 将缓冲区7字节数据通过UART TX发送,采用轮询方式(每发一个字节轮询发送状态),超时时间100ms
- 重新开启接收中断,以实现下一次的echo
定长7字节echo测试
至此,定长7字节数据echo功能就实现了
超7字节echo测试
为什么超过我们指定的缓冲区大小(HAL_UART_Transmit
的入参 Size
)后,功能就不整行了呢?
UART中断ISR-异常流程
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart) {
/* If no error occurs */
errorflags = (isrflags & (uint32_t) (USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
if (errorflags == RESET) {
UART_Receive_IT(huart);
/* If some errors occur */
if ((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET)
|| ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET))) {
/* UART Over-Run interrupt occurred
huart->ErrorCode |= HAL_UART_ERROR_ORE;
- 没有异常时,会执行
UART_Receive_IT
逐字接收数据并在填满缓冲区后触发接收完毕回调HAL_UART_RxCpltCallback
- 但是当我们发送8字节时,会触发UART的接收溢出错误,继而转向异常处理流程
/* Call UART Error Call back function if need be --------------------------*/
if (huart->ErrorCode != HAL_UART_ERROR_NONE) {
/*Call legacy weak error callback*/
HAL_UART_ErrorCallback(huart);
因为异常处理流程不会执行我们自定义的 HAL_UART_RxCpltCallback
,也就没有重新开启接收中断 HAL_UART_RxCpltCallback
,所以就出现了发送8字节时,后续没有回传的现象:
思考/遗留问题
USART_SR_ORE是如何被设置的
为什么发送8字节时会触发USART_SR_ORE错误?