IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> STM32H743 UART接收中断设置与原理剖析(HAL库结合FreeRTOS操作系统) -> 正文阅读

[嵌入式]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函数是在等待通知,这个通知来自于回调函数。

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

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

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

测试如下

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

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-09-02 11:32:57  更:2021-09-02 11:33:05 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 0:33:07-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码