尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

深入解析STM32 HAL库UART中断处理机制与实战应用

深入解析STM32 HAL库UART中断处理机制与实战应用 1. 项目概述从CubeMX到中断处理如果你用过STM32CubeMX肯定对那个一键生成代码的便利性印象深刻。点几下鼠标时钟树、外设、引脚分配就全搞定了最后生成一个完整的工程里面main.c、stm32fxxx_hal_msp.c等文件一应俱全。但很多朋友尤其是刚从标准库转过来的在第一次看到生成的UART中断代码时往往会愣一下那个在stm32fxxx_it.c文件里被自动调用的HAL_UART_IRQHandler(huart1)它到底是怎么工作的我该怎么用它来处理我自己的串口数据这其实是一个典型的“知其然不知其所以然”的困惑。CubeMX和HAL库把底层硬件中断的注册、使能、清除标志位这些脏活累活都包揽了留给我们的是一个看似简单的“处理函数”。但正是这个封装让不少开发者感到迷茫我的数据接收逻辑该写在哪里发送完成中断又该如何利用这个函数内部到底发生了什么今天我就结合自己这些年踩过的坑把HAL_UART_IRQHandler从里到外掰开揉碎了讲清楚让你不仅能“用上”更能“用好”它。简单来说HAL_UART_IRQHandler是HAL库为你提供的UART中断服务程序ISR的“总管家”。当中断发生时CPU会跳转到USART1_IRQHandler以USART1为例而这个函数内部直接调用了HAL_UART_IRQHandler(huart1)。这个“管家”的工作是检查是哪种中断触发了接收完成、发送完成、帧错误、溢出错误等等然后调用HAL库内部对应的回调函数并清除相应的中断标志位。你的任务就是去“定制”这些回调函数实现你自己的业务逻辑。听起来简单但里面的门道可不少。2. 核心思路拆解HAL库的中断处理哲学在深入代码之前我们必须先理解HAL库设计中断处理时的核心思路。这不同于标准库那种“直接在中断函数里写你的代码”的直白方式。HAL库采用了一种“分层”和“状态机”的设计理念目的是提高代码的模块化、可移植性和安全性。2.1 为什么是回调函数而不是直接写ISR标准库时代我们可能在USART1_IRQHandler里直接判断USART_GetITStatus然后操作数据寄存器。这种方式直接高效但缺点也很明显业务逻辑和硬件中断耦合紧密代码复用性差且容易因忘记清除标志位而导致中断卡死。HAL库的解决方案是引入“弱定义”Weak的回调函数。HAL_UART_IRQHandler这个“管家”在判断出中断类型后会去调用一个对应的函数比如接收完成就调用HAL_UART_RxCpltCallback。关键来了这个回调函数在HAL库的.c文件里已经被定义好了但它的函数体是空的或者只有简单处理并且被标记为__weak。这意味着如果你在自己的工程里比如main.c或user_uart.c重新定义一个同名、同参数的函数编译器就会链接你的这个“强”版本覆盖掉库里的“弱”版本。这样做的好处是分离关注点中断管理的硬件细节标志位检查与清除由库函数负责稳定可靠业务逻辑数据存到哪个数组、要不要启动下一次发送由你的回调函数负责灵活可变。安全库函数确保了中断标志位被正确、及时地清除避免了常见的中断死锁问题。可移植性你的业务逻辑代码回调函数几乎可以不加修改地移植到另一个使用HAL库的STM32项目甚至另一个型号的MCU上。2.2 HAL_UART_IRQHandler 内部探秘光讲理念不够我们得看看这个“管家”到底干了什么。虽然我们不需要修改它但理解其内部流程对于调试和解决问题至关重要。它的工作流程可以概括为以下几步参数检查首先检查传入的UART句柄指针huart是否有效以及该句柄的状态是否正常。这是防止空指针或硬件错误状态下误操作的第一道防线。中断源判断这是核心步骤。函数会依次检查UART状态寄存器中的各种中断标志位。检查顺序通常是先检查错误中断溢出ORE、噪声NE、帧错误FE、校验错误PE再检查接收中断RXNE接收寄存器非空或IDLE空闲线路中断最后检查发送中断TXE发送寄存器空或TC发送完成。调用回调与清除标志如果发现是接收完成RXNE它会从数据寄存器读出数据存放到你通过HAL_UART_Receive_IT函数指定的缓冲区里然后递减接收计数。当接收计数减到0即收到了你期望的字节数后它会禁用RXNE中断将句柄状态改为HAL_UART_STATE_READY然后调用HAL_UART_RxCpltCallback。如果发现是发送完成TC它会将句柄状态改为HAL_UART_STATE_READY然后调用HAL_UART_TxCpltCallback。对于错误中断它会调用对应的错误回调函数HAL_UART_ErrorCallback并清除错误标志。状态管理整个过程中UART句柄UART_HandleTypeDef中的gState和RxState等状态变量被严格维护。这是HAL库状态机的体现确保了像“在发送未完成时又启动一次发送”这类非法操作会被库函数拒绝返回HAL_BUSY。注意一个极其关键的细节是在非FIFO模式下HAL_UART_IRQHandler在处理完单个字节的接收后就会退出。它不会一直循环读取直到缓冲区满。填满缓冲区的过程是由多次进入中断、多次调用该函数累积完成的。这就是为什么我们说“中断接收”是“字节中断”模式。3. 实战配置从CubeMX到代码生成理解了原理我们来看看如何从零开始配置一个使用中断接收的UART工程。3.1 CubeMX图形化配置打开CubeMX创建一个新工程选择你的MCU型号。引脚分配在Pinout Configuration标签页找到你需要使用的USART/UART。比如USART1。将其模式Mode设置为“Asynchronous”异步通信。此时对应的TX和RX引脚如PA9和PA10会自动被配置为复用功能。参数配置在Configuration标签页点击USART1进入参数设置。Baud Rate设置波特率如115200 Bits/s。Word Length数据位通常8 Bits。Parity校验位通常None。Stop Bits停止位通常1。Over Sampling过采样通常16倍。其他高级功能如硬件流控RTS/CTS根据实际需要启用。开启中断这是最关键的一步在参数配置页切换到NVIC Settings选项卡。你会看到USART1 global interrupt选项。务必勾选它并设置合适的抢占优先级和响应优先级。这样就使能了USART1的全局中断。生成代码转到Project Manager标签页设置好工程名称、路径、IDE如MDK-ARM或STM32CubeIDE。在Code Generator部分我强烈建议勾选“Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral”这会把每个外设的初始化代码独立成文件结构更清晰。最后点击GENERATE CODE。3.2 生成代码结构解析代码生成后我们重点关注以下几个文件Core/Src/main.cmain函数在这里。你会看到MX_USART1_UART_Init()被调用它完成了UART的硬件初始化。Core/Src/stm32fxxx_hal_msp.c这里包含了HAL_UART_MspInit函数。它是HAL_UART_Init内部调用的一个弱函数被重写以初始化与MCU相关的底层资源GPIO引脚配置和NVIC嵌套向量中断控制器配置。CubeMX生成的NVIC中断使能代码就在这里。Core/Src/stm32fxxx_it.c这是中断服务程序文件。你会找到USART1_IRQHandler函数其内部只有一行代码HAL_UART_IRQHandler(huart1);。这就是我们讨论的“总管家”的入口。Drivers/STM32Fxxx_HAL_Driver/Src/stm32fxxx_hal_uart.cHAL库的UART驱动源码。HAL_UART_IRQHandler、HAL_UART_Receive_IT等函数的实现都在这里。不建议直接修改这个文件。至此硬件和中断框架已经由CubeMX和HAL库搭建完毕。接下来就需要我们注入“灵魂”——也就是我们自己的数据处理逻辑。4. 核心应用实现中断收发我们现在进入实战环节看看如何利用这个框架完成最常见的串口中断接收和发送。4.1 中断接收数据这是使用频率最高的功能。你不能在回调函数里原地等待或进行长时间操作正确的做法是“快进快出”。步骤一启动接收在main函数的初始化部分比如在MX_USART1_UART_Init()之后你需要启动一次中断接收。这相当于告诉“管家”“请帮我监听串口收到数据就存到这个缓冲区里”。// 定义接收缓冲区 uint8_t rx_buffer[100]; uint8_t rx_len 10; // 本次希望接收10个字节 // 启动非阻塞式中断接收 if (HAL_UART_Receive_IT(huart1, rx_buffer, rx_len) ! HAL_OK) { // 处理错误可能是UART正忙状态不对 Error_Handler(); }调用HAL_UART_Receive_IT后库函数会检查UART句柄状态如果空闲则设置为HAL_UART_STATE_BUSY_RX。将你提供的缓冲区地址和期望接收长度保存到句柄中。使能RXNE接收寄存器非空中断。注意不是在这里使能全局中断全局中断已在CubeMX中使能。这里是使能UART本身这个特定事件的中断。步骤二编写接收完成回调函数当接收满你指定的字节数这里是10个后HAL_UART_IRQHandler会调用你的回调函数。// 在main.c或你自己的uart.c文件中重写这个弱函数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { // 首先判断是哪个UART触发的回调因为多个UART可能共用此回调函数 if (huart-Instance USART1) { // 1. 处理数据此时rx_buffer里已经有了10个字节的新数据 // 例如可以将其拷贝到另一个安全区域或者设置一个标志位通知主循环 // memcpy(processed_data, rx_buffer, rx_len); // uart1_rx_done_flag 1; // 2. 重要重新启动接收为下一次数据到来做准备 // 如果不重启UART将不再触发接收中断后续数据会丢失。 if (HAL_UART_Receive_IT(huart1, rx_buffer, rx_len) ! HAL_OK) { // 重启接收失败处理 } } }核心技巧务必在回调函数内重新启动接收。这是一个非常容易遗漏的点会导致“只收到第一包数据后面就没反应了”。因为HAL_UART_Receive_IT在接收完成后会自动关闭RXNE中断。重启它就是重新“订阅”这个中断事件。步骤三利用空闲中断实现不定长接收上面的例子是定长接收。但串口通信中数据包长度经常是不固定的。这时就需要用到空闲中断IDLE。空闲中断是指在RX线路上检测到一帧数据后如果出现一个字节的高电平空闲时间就会产生中断。这个空闲时间通常可配置为1个或几个字符时间。配置方法CubeMX配置在USART参数配置的Advanced Features里通常可以找到Receiver Timeout或IDLE Interrupt的使能选项。对于某些系列可能需要直接在代码中使能。代码使能与处理更通用的做法是在初始化后通过__HAL_UART_ENABLE_IT(huart1, UART_IT_IDLE)来使能空闲中断。处理逻辑需要修改不再使用HAL_UART_Receive_IT来指定长度而是开启一个持续的单字节接收循环。在USART1_IRQHandler对应的处理流程中实际上还是在HAL_UART_IRQHandler里HAL_UART_IRQHandler会检查IDLE标志。我们需要重写HAL_UART_IRQHandler吗不更好的方法是在使能空闲中断后我们仍然用HAL_UART_Receive_IT启动一个单字节接收。然后在HAL_UART_RxCpltCallback中我们并不认为一包数据结束而是将字节存入缓冲区并再次启动单字节接收。关键点我们需要自定义一个空闲中断回调。因为HAL库标准的HAL_UART_IRQHandler虽然会检测IDLE标志并清除它但它没有调用一个像HAL_UART_IdleCallback这样的标准弱函数。因此常见的做法是 a. 使能空闲中断。 b. 在USART1_IRQHandler中先调用HAL_UART_IRQHandler。 c. 然后我们自己检查IDLE标志位是否被置位并且确保是在接收状态下。 d. 如果置位则清除标志并调用我们自定义的空闲处理函数在这个函数里我们认为一帧不定长数据接收完成了。// 在stm32fxxx_it.c中修改USART1_IRQHandler void USART1_IRQHandler(void) { /* 调用库函数处理标准中断 */ HAL_UART_IRQHandler(huart1); /* 自定义空闲中断处理 */ if((__HAL_UART_GET_FLAG(huart1, UART_FLAG_IDLE) ! RESET) (__HAL_UART_GET_IT_SOURCE(huart1, UART_IT_IDLE) ! RESET)) { // 清除空闲中断标志方法因系列而异常见的是先读SR再读DR __HAL_UART_CLEAR_IDLEFLAG(huart1); // 调用自定义的空闲处理函数 UART1_IdleCallback(huart1); } } // 在main.c中定义 void UART1_IdleCallback(UART_HandleTypeDef *huart) { // 计算本次接收到的数据长度 // 当前缓冲区指针 - 起始位置 已接收长度 uint16_t received_len uart1_rx_buffer_ptr - uart1_rx_buffer; if(received_len 0) { // 设置标志通知主循环处理数据 uart1_idle_flag 1; // 可以在这里将数据拷贝走或者主循环根据标志来处理 } // 重置缓冲区指针准备下一次接收 uart1_rx_buffer_ptr uart1_rx_buffer; }这是一个更高级但也更实用的模式它结合了HAL库的便利性和对特定中断的灵活处理。4.2 中断发送数据中断发送通常用于提高发送效率避免HAL_UART_Transmit的轮询等待阻塞CPU。步骤一启动发送uint8_t tx_data[] Hello, UART!\r\n; if (HAL_UART_Transmit_IT(huart1, tx_data, sizeof(tx_data)-1) ! HAL_OK) { // 发送启动失败可能是上一次发送还未完成 }HAL_UART_Transmit_IT会启动发送使能TXE发送数据寄存器空中断然后立即返回。数据会在后台通过中断一个个字节发送出去。步骤二编写发送完成回调函数当最后一个字节从移位寄存器发送出去TC发送完成标志置位会触发中断最终调用你的回调函数。void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { // 发送完成可以在这里做一些后续工作 // 例如点亮一个LED指示发送完毕或者启动下一次发送 // tx_complete_flag 1; } }重要心得HAL_UART_Transmit_IT发送的数据缓冲区tx_data必须保证在发送完成前是有效的。你不能使用函数栈上的局部数组除非是静态局部变量然后在函数返回后启动发送这会导致发送数据是随机值或程序崩溃。通常需要将发送数据定义为全局数组或静态数组或者使用动态内存但需谨慎管理。5. 深入调试与问题排查即使理解了原理和步骤在实际项目中依然会遇到各种问题。下面是一些常见坑点及排查方法。5.1 中断根本进不去检查NVIC配置首先确认CubeMX中是否勾选了对应UART的全局中断并且生成了代码。检查stm32fxxx_hal_msp.c中的HAL_UART_MspInit函数里面应该有HAL_NVIC_SetPriority和HAL_NVIC_EnableIRQ的调用。检查中断优先级如果中断优先级设置得太低且被更高优先级的中断长时间阻塞也可能无法进入。确保没有其他中断服务程序执行时间过长。检查UART时钟确认USART的外设时钟来自APB1或APB2已经使能。CubeMX的时钟配置图Clock Configuration要确保正确并且MX_USART1_UART_Init被成功调用。使用调试器在USART1_IRQHandler函数入口处设置断点。发送数据看程序是否能停在此断点。如果不能说明中断未触发。5.2 只能接收一次数据后续数据丢失忘记重启接收这是最常见的原因。务必在HAL_UART_RxCpltCallback回调函数中再次调用HAL_UART_Receive_IT来重启接收。缓冲区溢出如果数据来得太快主循环或回调函数处理数据太慢导致新的数据覆盖了还未处理完的旧数据。可以考虑使用双缓冲区Ping-Pong Buffer或环形缓冲区Ring Buffer来提高数据吞吐能力和安全性。中断被意外禁用检查代码中是否有其他地方错误地调用了__disable_irq()或HAL_NVIC_DisableIRQ。5.3 发送数据不全或乱码缓冲区生命周期问题如前所述确保发送数据缓冲区的内存空间在发送完成前一直有效。波特率不匹配检查发送端和接收端的波特率、数据位、停止位、校验位是否完全一致。哪怕有微小误差长时间传输也会导致错位。硬件问题检查TX/RX线是否接反电平是否匹配如3.3V与5V系统互连需要电平转换线路是否有干扰。5.4 如何高效处理大量数据对于高速或大数据量串口通信仅靠“接收完成回调重启接收”的模式可能会丢数据因为处理回调函数和重启接收本身需要时间。此时可以采用DMA直接存储器访问模式。CubeMX配置在UART的DMA Settings选项卡中为RX和TX分别添加DMA通道并配置为循环模式或正常模式。代码调用使用HAL_UART_Receive_DMA和HAL_UART_Transmit_DMA函数。优势DMA可以在不占用CPU的情况下自动将接收到的数据搬运到你指定的大缓冲区中只在半满或全满时通过中断通知CPU一次极大降低了中断频率和CPU开销。处理HAL_UART_RxHalfCpltCallback半满回调和HAL_UART_RxCpltCallback全满回调即可。5.5 状态机与错误处理HAL_UART_IRQHandler 也处理错误中断。你应该实现HAL_UART_ErrorCallback回调函数来捕获和处理通信错误。void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { uint32_t error_code huart-ErrorCode; if(error_code HAL_UART_ERROR_PE) { // 奇偶校验错误 } if(error_code HAL_UART_ERROR_FE) { // 帧错误 } if(error_code HAL_UART_ERROR_ORE) { // 溢出错误数据丢失最常见也最需关注 // 通常需要重新初始化接收 __HAL_UART_CLEAR_OREFLAG(huart); HAL_UART_Receive_IT(huart, rx_buffer, rx_len); } if(error_code HAL_UART_ERROR_NE) { // 噪声错误 } if(error_code HAL_UART_ERROR_DMA) { // DMA传输错误 } // 清除错误标志 huart-ErrorCode HAL_UART_ERROR_NONE; } }实现一个健壮的错误处理回调是产品稳定性的重要保障。特别是溢出错误ORE在数据流量突发时很容易发生必须在回调中清除标志并重新启动接收流程。6. 进阶技巧与最佳实践掌握了基本用法后一些进阶技巧能让你的串口驱动更加稳健和高效。6.1 使用环形缓冲区解耦这是提升中断服务程序效率的黄金法则。不要在中断回调函数里做复杂的、耗时的数据处理如解析协议、大量计算。正确的做法是在HAL_UART_RxCpltCallback或自定义的空闲中断处理函数中仅将数据快速存入一个环形缓冲区并更新写指针。设置一个“有新数据”的标志位或者通过判断读写指针位置差。在主循环的while(1)中不断检查这个标志位。如果有新数据则从环形缓冲区读取数据进行处理。这样做实现了“生产者中断-消费者主循环”的模型中断服务时间极短避免了因处理数据而阻塞其他中断或丢失后续数据。6.2 管理多个UART端口当项目中有多个串口如USART1用于调试打印USART2连接模块UART4连接蓝牙时如何优雅地管理回调函数区分所有UART共用HAL_UART_RxCpltCallback等回调函数。因此在回调函数内部第一件事就是通过huart-Instance判断是哪个UART触发的然后分派到不同的处理逻辑或缓冲区。独立的句柄与缓冲区为每个UART定义独立的UART_HandleTypeDef句柄CubeMX会自动生成如huart1huart2和独立的接收/发送缓冲区。统一的接口封装可以创建一个uart_manager.c/.h文件为每个物理UART端口封装一组初始化、发送、接收接口内部管理各自的缓冲区和状态对外提供统一的API这样主业务逻辑会更加清晰。6.3 与RTOS如FreeRTOS配合使用在RTOS环境中中断服务程序ISR的设计原则是“快进快出”的极致体现。避免在ISR或回调中使用阻塞API绝对不要在HAL_UART_RxCpltCallback中调用vTaskDelay、osDelay或者试图获取一个可能被其他任务持有的信号量除非是专门给ISR用的xSemaphoreGiveFromISR。使用RTOS的通信机制在中断回调中使用xQueueSendFromISR向一个队列发送消息例如发送“收到数据”的事件或直接发送数据指针或者使用xSemaphoreGiveFromISR释放一个二进制信号量。然后在RTOS中创建一个专用的任务如UART_Process_Task来阻塞等待这个队列或信号量。当等待到数据时该任务被唤醒进行耗时的数据处理。这完美地将中断响应与业务处理解耦。注意任务优先级处理UART数据的任务优先级需要合理设置既要保证及时处理数据又不能影响更关键的系统任务。6.4 低功耗模式下的UART中断在设备需要进入低功耗模式如Stop模式时UART中断可以用来唤醒MCU。配置唤醒源需要将UART的RX引脚配置为唤醒引脚Wake-up pin并使能相应的唤醒中断。进入低功耗前确保UART中断通常是RXNE是使能的。中断处理当RX线有数据到来时产生的中断会将MCU从低功耗模式唤醒。唤醒后程序会从进入低功耗的语句后继续执行并立即进入UART中断服务程序。此时要特别注意系统时钟的稳定性因为从某些低功耗模式唤醒后HSI/HSE可能还未稳定需要等待时钟就绪或使用MSI等内部时钟先进行初步处理。7. 从原理到实践一个完整的示例框架最后我将给出一个综合性的示例框架它包含了不定长接收空闲中断、环形缓冲区、以及主循环处理这是一个在实际项目中经过验证的稳定结构。/* uart_app.h */ #ifndef __UART_APP_H #define __UART_APP_H #include main.h #define UART1_RX_BUFFER_SIZE 256 typedef struct { UART_HandleTypeDef *huart; uint8_t rx_buffer[UART1_RX_BUFFER_SIZE]; volatile uint16_t rx_read_pos; volatile uint16_t rx_write_pos; volatile uint8_t idle_flag; } uart_app_t; void UART1_App_Init(void); void UART1_App_Process(void); void UART1_Send_String(uint8_t *str); #endif /* uart_app.c */ #include uart_app.h uart_app_t uart1_app {.huart huart1}; // 自定义空闲中断回调 void UART1_IdleCallback(UART_HandleTypeDef *huart) { if(huart-Instance USART1) { __HAL_UART_CLEAR_IDLEFLAG(huart); uart1_app.idle_flag 1; // 设置空闲标志 } } // 重写接收完成回调用于单字节接收循环 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart-Instance USART1) { // 获取刚收到的字节存储在句柄的pRxBuffPtr指向的位置的前一个字节但更安全的方式是使用预定义的缓冲区 // 这里我们用一个静态变量来演示单字节接收的循环 static uint8_t temp_byte; // 实际上我们通常用HAL_UART_Receive_IT(huart, temp_byte, 1)启动接收 // 回调时temp_byte就是收到的数据 // 假设我们通过以下方式启动接收并在回调中处理 // 将收到的字节存入环形缓冲区 uint16_t next_write_pos (uart1_app.rx_write_pos 1) % UART1_RX_BUFFER_SIZE; if(next_write_pos ! uart1_app.rx_read_pos) // 缓冲区未满 { uart1_app.rx_buffer[uart1_app.rx_write_pos] temp_byte; uart1_app.rx_write_pos next_write_pos; } else { // 缓冲区溢出处理可以记录错误 } // 重新启动单字节接收维持循环 if(HAL_UART_Receive_IT(huart1, temp_byte, 1) ! HAL_OK) { // 错误处理 } } } void UART1_App_Init(void) { // 使能空闲中断 __HAL_UART_ENABLE_IT(huart1, UART_IT_IDLE); // 启动第一次单字节中断接收 static uint8_t first_byte; HAL_UART_Receive_IT(huart1, first_byte, 1); // 初始化环形缓冲区指针和标志 uart1_app.rx_read_pos 0; uart1_app.rx_write_pos 0; uart1_app.idle_flag 0; } void UART1_App_Process(void) { // 在主循环中定期调用此函数 if(uart1_app.idle_flag) { uart1_app.idle_flag 0; // 计算本次空闲中断前接收到的数据长度 uint16_t len 0; if(uart1_app.rx_write_pos uart1_app.rx_read_pos) { len uart1_app.rx_write_pos - uart1_app.rx_read_pos; } else { len UART1_RX_BUFFER_SIZE - uart1_app.rx_read_pos uart1_app.rx_write_pos; } if(len 0) { // 这里可以解析协议处理数据包 // 例如判断是否是完整的帧校验等 // process_uart1_frame(uart1_app.rx_buffer[uart1_app.rx_read_pos], len); // 处理完后更新读指针 uart1_app.rx_read_pos (uart1_app.rx_read_pos len) % UART1_RX_BUFFER_SIZE; } } // 也可以在这里处理其他非空闲触发的业务逻辑 } void UART1_Send_String(uint8_t *str) { uint16_t len strlen((char*)str); // 使用中断发送避免阻塞 HAL_UART_Transmit_IT(huart1, str, len); // 注意实际项目中需要考虑发送缓冲区管理和发送完成回调 }这个框架将中断处理数据快速存入缓冲区和业务处理解析数据包分离通过空闲中断标志和环形缓冲区进行通信是一个可靠且高效的架构。你可以根据具体的通信协议如Modbus自定义帧头帧尾等在UART1_App_Process函数中完善数据处理逻辑。
返回列表