STM32H743 UART接收中断设置与原理剖析(HAL库结合FreeRTOS操作系统)

硬件平台:STM32H743
软件平台:Keil 5 采用HAL库+FreeRTOS系统

初始化UART配置

void DEBUG_USART_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    RCC_PeriphCLKInitTypeDef RCC_PeriphClkInit;
        
    /* 配置串口2时钟源*/
	RCC_PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART2;
	RCC_PeriphClkInit.Usart234578ClockSelection = RCC_USART234578CLKSOURCE_D2PCLK1;
	HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphClkInit);
    
    /* 使能串口时钟 */
    __USART2_CLK_ENABLE();
    
    /* 使能GPIO口时钟 */
	__HAL_RCC_GPIOD_CLK_ENABLE();

    /* 配置USART2------D5、D6 */
    GPIO_InitStruct.Pin = GPIO_PIN_5;        
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);  
    
    GPIO_InitStruct.Pin = GPIO_PIN_6;
    GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); 

    /* 配置USART2 */
    Uart2Handle.Instance = USART2;
    Uart2Handle.Init.BaudRate = 115200;
    Uart2Handle.Init.WordLength = UART_WORDLENGTH_8B;    
    Uart2Handle.Init.StopBits = UART_STOPBITS_1;         
    Uart2Handle.Init.Parity = UART_PARITY_NONE;          
    Uart2Handle.Init.Mode = UART_MODE_TX_RX;
    Uart2Handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    Uart2Handle.Init.OverSampling = UART_OVERSAMPLING_16;
    Uart2Handle.Init.OneBitSampling = UART_ONEBIT_SAMPLING_DISABLED;
    Uart2Handle.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
    HAL_UART_Init(&Uart2Handle);

    /*串口2中断初始化--连接ROS */
    HAL_NVIC_SetPriority(USART2_IRQn, 6, 0);
    HAL_NVIC_EnableIRQ(USART2_IRQn);
    
    /*配置串口接收中断 */
    __HAL_UART_ENABLE_IT(&Uart2Handle,UART_IT_RXNE);     
}

上述代码也可以用STM32CubeMX自动生成,或者野火例程里面也可以直接复制。如果发现串口HAL函数不可用,很可能并没有引入该库函数。需要在stm327xx_hal_conf.h把注释去掉。
在这里插入图片描述

新建任务

此次工程主要使用UART2与工控机的ROS系统进行通信,所以需要新建一个单独的任务完成通信任务。在main.c文件创建任务,在bsp_debug_usart.h函数实现任务。
在这里插入图片描述
在main.c先声明ROS通信任务句柄。
在这里插入图片描述
利用函数创建任务,任务栈设置为最小空间512字节,任务UartRos_Task的实现不在main.c,所以需要引用头文件#include "./usart/bsp_debug_usart.h" ,不然它找不到对应函数会报错。
在这里插入图片描述
在bsp_debug_usart.h需要声明任务函数,这也是为了方便main.c的引用。
在这里插入图片描述
在bsp_debug_usart.c实现任务函数的编写,目前还是空的,待会再填充代码。至此,任务创建完成。

中断程序原理剖析

前面我们已经开启了UART2的读取中断。在stm32h7xx_it.c保存着所有外设的中断入口函数,我们需要在这里添加UART2的重点函数。函数不需要声明,HAL库已经声明过了。所以当中断发生后,程序会通过USART2_IRQHandler函数进入到HAL_UART_IRQHandler中断处理函数。
在这里插入图片描述
而Uart2Handle不是在该文件声明的,所以需要在文件前面加上extern关键词声明。告诉编译器,这里没有,你去其他地方找找。
在这里插入图片描述
HAL_UART_IRQHandler函数位于stm32h7xx_hal_uart.c文件。进入到HAL_UART_IRQHandler函数后,看看内部是如何起作用的。实际上在真正应用中,是不需要理会HAL驱动函数是怎么写的,直接编写回调函数就行了。可是为了更好了解底层驱动是如何运作,还是需要多看看大佬写的东西。
在这里插入图片描述
进来之后之后使用READ_REG函数,这个函数看名字就知道是读取寄存器的值。ISR是中断和状态寄存器,也就是中断发生了没?哪个中断发生了?CR1和CR3都是控制寄存器,都是使能一些功能和中断的。
在这里插入图片描述
第一句代码就是通过逻辑与、或,看看ISR的PE位、FE位、ORE位、NE位有没有置1,置1代表发生错误了。所以这些位就叫状态标志位。
如果没有发生错误,首先看ISR的RXNE/EXFNE标志位,查看一下数据手册这个标志位干嘛用的。这个标志位主要是表示数据寄存器或RXFIFO里面是否有数据的,由硬件置1,读取操作直接清零,意味着不需要软件清零。
在这里插入图片描述
然后看CR1的RXNEIE/RXFNEIE标志位。就是看程序有没有使能接收中断,如果没有使能,那就跳过不处理了。
在这里插入图片描述
最后看CR3的RXFTIE位,这个位表示RXFIFO阈值中断使能。
在这里插入图片描述
综上,判断语句的内容是,1.使能读取中断或者阈值中断;2.中断标志位置1;3.两个条件同时满足。这次使用的是普通中断,没有涉及到RXFIFO,主要看接收数据寄存器。
判断成功后,利用RxISR函数进行处理。接下来探索一下这个函数来自何方?
在这里插入图片描述
这个RxISR看串口结构体声明,这是一个函数指针。
在这里插入图片描述
但是在前面设置很明显,并没有对RxISR进行赋值。看一下HAL_UART_Init函数源码也没有发现对RxISR进行赋值。
在这里插入图片描述
最后在HAL_UART_Receive_IT函数里面发现了以下代码。根据之前设置的数据长度8位,赋值的是UART_RxISR_8BIT函数。
在这里插入图片描述

/**
  * @brief RX interrrupt handler for 7 or 8 bits data word length .
  * @param huart UART handle.
  * @retval None
  */
static void UART_RxISR_8BIT(UART_HandleTypeDef *huart)
{
  uint16_t uhMask = huart->Mask;
  uint16_t  uhdata;

  /* Check that a Rx process is ongoing */
  if (huart->RxState == HAL_UART_STATE_BUSY_RX)
  {
    uhdata = (uint16_t) READ_REG(huart->Instance->RDR);
    *huart->pRxBuffPtr = (uint8_t)(uhdata & (uint8_t)uhMask);
    huart->pRxBuffPtr++;
    huart->RxXferCount--;

    if (huart->RxXferCount == 0U)
    {
      /* Disable the UART Parity Error Interrupt and RXNE interrupts */
      CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE_RXFNEIE | USART_CR1_PEIE));

      /* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
      CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);

      /* Rx process is completed, restore huart->RxState to Ready */
      huart->RxState = HAL_UART_STATE_READY;

      /* Clear RxISR function pointer */
      huart->RxISR = NULL;

#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
      /*Call registered Rx complete callback*/
      huart->RxCpltCallback(huart);
#else
      /*Call legacy weak Rx complete callback*/
      HAL_UART_RxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
    }
  }
  else
  {
    /* Clear RXNE interrupt flag */
    __HAL_UART_SEND_REQ(huart, UART_RXDATA_FLUSH_REQUEST);
  }
}

看了以上函数代码,就会恍然大悟。原来回调函数的是在这里引用的。weak意味着这是个弱定义函数,意思即我帮你声明定义了这个函数,但是函数内部什么内容都没有。对于使用者来说,需要重新编写这个回调函数,然后就会覆盖掉之前的定义。
在这里插入图片描述
可以看到这个弱定义函数里面就一句没啥作用的代码。这是为了防止编译器报错。
在这里插入图片描述

特别说明

前面提到每一次中断都会调用RxISR函数,而这个函数就是UART_RxISR_8BIT函数。这个函数有一个小坑。就是它每一次进去都会把中断使能给关掉(为啥这么坏),所以特别注意就是在等待新一次中断之前还得引用HAL_UART_Receive_IT函数重新使能中断。
在这里插入图片描述

整体逻辑

整体逻辑如下图所示,只有第四个函数需要重新定义,其他函数都只需要引用即可。
![在这里插入图片描述](https://img-blog.csdnimg.cn/6997ca5d40074610a4ec7d923b3cf50c.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA56CU56m25YOnLeW9rOW9rA==,size_20,color_FFFFFF,t_70,g_se,x_1

配置函数修改

在这里插入图片描述
在上面的配置函数有配置串口接收中断函数,也就是__HAL_UART_ENABLE_IT(&Uart2Handle,UART_IT_RXNE);。这句代码在很多教程都会存在。实际上这段代码注释掉也是可以运行的。原因在HAL_UART_Receive_IT函数当中,最后也会开启这个接收中断,所以这句代码是多余的。
在这里插入图片描述

任务函数编写

前面我们已经创建了任务,现在弄懂了原理。就可以开始编写。

extern TaskHandle_t UartRos_Task_Handle; //任务手柄

/* ROS通信任务实现 */
void UartRos_Task(void* parameter)
{
   uint8_t aRxBuffer;			    //接收中断缓冲
    while(1)
    {
     
        HAL_UART_Receive_IT(&Uart2Handle, (uint8_t *)&aRxBuffer, 1);    //触发接收中断

        /*获取任务通知,没获取则一直等待;pdTRUE代表退出后通知值执行清0操作*/
       ulTaskNotifyTake(pdTRUE,portMAX_DELAY);    
        
        HAL_UART_Transmit(&Uart2Handle, (uint8_t *)&aRxBuffer, 1,0xFFFF); //将收到的信息发送出去    
            
    }
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) 
{  
    
    BaseType_t pxHigherPriorityTaskWoken = pdFALSE;
    
    /* 发送通知给Uart_ROS任务 */	
    vTaskNotifyGiveFromISR((TaskHandle_t)UartRos_Task_Handle, &pxHigherPriorityTaskWoken);
    
    /*如果被唤醒的任务被现在执行的任务优先级高,则需要进行一次任务切换*/
    portYIELD_FROM_ISR(pxHigherPriorityTaskWoken); 
     
}

在任务当中,需要存在while(1)死循环,这是FreeRTOS规定的。每一次在初始都调用HAL_UART_Receive_IT函数,这是为了重新触发接收中断。

ulTaskNotifyTake函数是在等待通知,这个通知来自于回调函数【HAL_UART_RxCpltCallback】。

而下面回调函数则负责发送通知给该任务,即回调函数不负责处理具体事情,只负责发通知,具体事情在任务当中完成。这也是FreeRTOS建议的,数据处理尽量不要在中断当中去做,因为这会堵塞整个系统的运行,有可能导致系统崩溃。

在该次示例当中,HAL_UART_Receive_IT只接受1个字节数据,然后就进入回调函数。这个可以根据实际情况更改,填写x字节,那就接收x字节后再进入回调函数。

当任务在等待通知时,处于堵塞状态。CPU不会停下来,而是去完成其他任务,这样不会造成CPU资源的浪费。当回调函数发来通知,任务继续运行下去,把收到的数据发送出去。

测试如下

在这里插入图片描述
发送的test2,因为每次只接受1个字节数据,所以这里其实是进入了5次回调函数,一次次把接收到的数据发出来,只是速度很快,就像一起发出来的。

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值