写在前面
最近需要使用一款 STM32L4 系列的芯片进行开发, 需要学习使用 HAL 库. 在进行串口中断使用的时候遇到了一些小麻烦, 写下解决方案供大家参考.
1.UART 相关的头文件引用错误
由于本人直接使用 MDK 进行开发, 没有使用 CubeMX, 所以一些初始化需要手动进行. 在引用 UART 相关的头文件时, 记得将 "stm32l4xx_hal_conf.h" 文件中的相关引用取消注释, 如下图:
2. 如何接收字符串(多次进入中断)
接收字符串主要有两种方法, 一种是对中断函数进行改造, 另一种是对接收回调函数进行改造.
在阐述这两种方法之前, 需要介绍函数 "HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)". 该函数的作用是用户自定义一个缓冲区 (即参数 pData), 接受一定数量(由参数 Size 决定) 的字符存入缓冲区中. 同时, 参数 Size 还决定着进入回调函数的频率, 即每接收 Size 个字符, 就进入一次回调函数. 需要注意的是, Size 只决定进入回调函数的频率, 而不能影响进入接收中断的频率, 无论 Size 是多少, 每接收完成一个字符都会进入一次接收中断.
方法 1: 改造回调函数
1首先在主函数中进入主循环前的位置调用一次 HAL_UART_Receive_IT 函数, 定义一个字符数组 getBuffer[]作为缓冲区, 参数 Size 设定为 10. 即每接收 10 个字符, 就进入一次回调函数.
2注册中断函数
- void USART1_IRQHandler(void)
- {
- HAL_UART_IRQHandler(&UartHandle); // 该函数会清空中断标志, 取消中断使能, 并间接调用回调函数
- }
3在文件 "stm32l4xx_hal_uart.h" 中, 我们可以看到串口接收回调函数的定义. 使用 "_weak" 关键字定义的函数, 其具有如下特性: 一般情况下和一般函数相同. 但是当有一个同名函数但是不带__weak 被定义时, 所有对这个函数的调用都是指向后者(不带__weak 那个). 也就是说, ST 官方提供的这个回调函数需要我们自己进行改写.
- /**
- * @brief Rx Transfer completed callback.
- * @param huart UART handle.
- * @retval None
- */
- __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 can be implemented in the user file.
- */
- }
我们在主函数所在的文件中对回调函数进行改写:
- uint8_t myBuffer[] = "I have gotten your message:"; // 用户提示信息
- uint8_t Enter[] = "\r\n"; // 回车换行
- uint8_t getBuffer[100]; // 用户自定义的缓冲区
- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
- {
- while(HAL_UART_Transmit(UartHandle, (uint8_t*)myBuffer, COUNTOF(myBuffer), 5000)!= HAL_OK); // 发送字符串, 用户提示信息
- while(HAL_UART_Transmit(UartHandle, (uint8_t*)getBuffer, 10, 5000)!= HAL_OK); // 发送用户自定义缓冲区中的数据
- while(HAL_UART_Transmit(UartHandle, (uint8_t*)Enter, COUNTOF(Enter), 5000)!= HAL_OK); // 发送回车换行
- }
以上代码的作用是把用户发送给单片机数据再返回给用户. 运行效果如下图:
我们可以看到, 用户向单片机发送了 10 个字符, 单片机向串口助手返回了这 10 个数据. 但是以上程序只能实现一次, 当我们再次向单片机发送数据时, 单片机却不再返回数据. 这是因为我们在中断函数中取消了中断使能, 所以导致了进入一次中断后, 中断被关闭, 无法再次进入中断的现象. 为了实现多次数据返回, 我们要在中断处理函数中添加一行代码:
- void USART1_IRQHandler(void)
- {
- HAL_UART_IRQHandler(&UartHandle); // 该函数会清空中断标志, 取消中断使能, 并间接调用回调函数
- HAL_UART_Receive_IT(&UartHandle, (uint8_t *)&value,1); // 添加的一行代码
- }
这样就可以实现多次数据返回了, 新的执行结果如下图:
可见, 函数 HAL_UART_Receive_IT 还有中断使能的作用. 这一功能的实现我们可以在 HAL_UART_Receive_IT 函数中找到.
方法 2: 改造中断处理函数
1首先在主函数中进入主循环前的位置调用一次 HAL_UART_Receive_IT 函数, 定义一个字符 value 作为缓冲区, 参数 Size 设定为 1. 即每接收 1 个字符, 就进入一次回调函数. 使得进入回调函数的频率与进入中断处理函数的频率相同. 这样, 我们就可以直接在中断函数中对接收的数据进行处理了.
2注册中断函数
- uint8_t myBuffer[] = "I have gotten your message:";
- uint8_t getBuffer[10];
- uint8_t Enter[] = "\r\n";
- void USARTx_IRQHandler(void)
- {
- HAL_UART_IRQHandler(&UartHandle); // 该函数会清空中断标志, 取消中断使能, 并间接调用回调函数
- getBuffer[countOfGetBuffer++] = value;
- if(countOfGetBuffer == 10)
- {
- while(HAL_UART_Transmit(&UartHandle, (uint8_t*)myBuffer, COUNTOF(myBuffer), 5000)!= HAL_OK);
- while(HAL_UART_Transmit(&UartHandle, (uint8_t*)getBuffer, countOfGetBuffer, 5000)!= HAL_OK);
- while(HAL_UART_Transmit(&UartHandle, (uint8_t*)Enter, COUNTOF(Enter), 5000)!= HAL_OK);
- countOfGetBuffer = 0;
- }
- HAL_UART_Receive_IT(&UartHandle, (uint8_t *)&value,1); // 由于接收中断是每接收一个字符便进入一次, 所以这一行代码必须添加, 否则只能接收一个字符, 而无法接收整个字符串
- }
以上代码的作用是接收每个来自用户的字符, 并依次存入用户自定义的缓冲区中, 数量达到 10 个后, 将缓冲区中的所有数据返回给用户, 同时清空计数, 准备接下来 10 个字符的接收. 运行效果如下图:
写在最后
看完本文, 大家可能对回调函数和中断处理函数的关系产生了疑问. 其实是这样的, 单片机每完成接收一个字符, 就会进入一次中断处理函数, 而在中断处理函数中, 我们又调用了函数 "void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)", 该函数会间接调用回调函数, 也就是说回调函数是由中断处理函数间接调用的. 而函数 "HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)" 决定了中断处理函数调用回调函数的频率, 若 Size 为 1, 则每进入一次中断处理函数都会调用一次回调函数; 若 Size 为 10, 则每第十次进入中断处理函数时, 才会调用回调函数.
来源: https://www.cnblogs.com/UnfriendlyARM/p/10321838.html