串口数据的发送接收可以藉由串口发送中断、接收中断实现。串口发送中断、接收中断可以同时开启。UART发送中断可以分为两种,UART发送缓冲区为空中断,数据从UART引脚发送出去中断。串口中断模式参见FM33L023D串口中断例程,该例程在初始化阶段开启UART0发送、接收中断,串口0收到数据后,会进入串口中断服务处理程序。串口发送中断、接收中断共用一个中断服务处理程序,发送中断触发需要修改中断服务例程。串口中断操作相对于轮询方式,操作更复杂。开发者需要先配置串口功能,再开启总中断,开启UART发送中断、接收中断。
中断使能
串口中断例程先设置中断触发模式,串口发送中断选择BUFFER空且移位寄存器空,相对于串口发送缓冲区为空,这种模式触发时机晚,以前用STM32系列芯片好像没有注意到这个细节。针对这个问题官方论坛有给出明确解释。
deepseek针对这两种模式给出了使用场景,我就直接搬运了。
-
追求最高发送吞吐量(使用TXE/THRE):如果需要连续、大量地发送数据,且对CPU效率要求高,通常建议使用仅"发送缓冲区空"中断。因为可以在上一个字节还在"路上"(移位发送)时,CPU就提前把下一个字节填入缓冲区,充分利用硬件流水线,实现最高效的连续发送,中间几乎没有空闲间隔。
-
需要精确控制RS485收发切换或流控(使用TC) :如果涉及RS485通信,需要在发送完成后立即释放总线(将RE/DE引脚拉低),或者在半双工模式下切换方向,必须使用"BUFFER空且移位寄存器空"中断(即发送完成中断)。只有在TC标志置位时,才能确保总线上所有数据已发送完毕,此时切换收发状态才安全,否则会造成数据丢失或总线冲突。
接收中断选择接收缓冲区满,而不是数据接收完成产生中断。接收缓冲区,即RXREG寄存器有效长度1Byte,接收1Byte触发一次中断,这是最常见的模式。数据接收完成,更像是一帧接收完成触发一次中断。前者更常见,后者估计还要配置数据帧格式(帧头、帧尾、校验和等),暂时没看到明确的解释。接着通过FL_NVIC_Init函数设置中断优先级,底层实际调用NVIC库函数。接着就是配置UARTIE寄存器,使能发送中断、接收中断。
objectivec
FL_UART_SetTxInterruptSelect(UART0, FL_UART_INTSEL_TXBE_TXSE); //发生中断选择BUFFER空且移位寄存器空
FL_UART_SetRxInterruptSelect(UART0, FL_UART_INTSEL_RX_FULL); //接收Buffer满中断
InterruptConfigStruct.preemptPriority = 0x02;
FL_NVIC_Init(&InterruptConfigStruct, UART0_IRQn);
FL_UART_ClearFlag_TXIF(UART0);
FL_UART_ClearFlag_RXIF(UART0);
FL_UART_EnableIT_TXIE(UART0);
FL_UART_EnableIT_RXIE(UART0);
/**
* @brief Configure NVIC for specified Interrupt.
* @param configStruct NVIC configuration.
* @param irq Interrupt number.
* @retval None
*/
void FL_NVIC_Init(FL_NVIC_ConfigTypeDef *configStruct, IRQn_Type irq)
{
/* Check parameter */
if(configStruct->preemptPriority > 3)
{
configStruct->preemptPriority = 3;
}
NVIC_DisableIRQ(irq);
NVIC_ClearPendingIRQ(irq);
NVIC_SetPriority(irq, configStruct->preemptPriority);
NVIC_EnableIRQ(irq);
}



中断接收
中断接收功能,相信使用过串口接收中断的开发人员都有一定的了解。UART0接收到1Byte数据后,进入中断接收逻辑。由于串口一直不断的接收到数据,程序将不断触发串口接收逻辑。开发者可以将接收到的数据存入数组。然后,在主循环中,对接收数据进行解析,如果符合数据帧格式,就执行相应的操作。为了防止数据溢出,可以使用环形数组。也就是记录接收数据的开始位置、结束位置,数据索引循环处理,例如((index++)%arrRecvLen)。
objectivec
int indexTx = 0; //发送索引
int indexRx= 0;
void UART0_IRQHandler(void)
{
if(FL_SET == FL_UART_IsEnabledIT_TXIE(UART0) &&
FL_SET == FL_UART_IsActiveFlag_TXIF(UART0))
{
FL_UART_ClearFlag_TXIF(UART0);
/*中断发送测试代码
for(indexTx = 0;indexTx < 9;indexTx++)
{
UART0->TXREG = arrSend[i];
}
*/
}
if(FL_SET == FL_UART_IsEnabledIT_RXIE(UART0) &&
FL_SET == (FL_UART_IsActiveFlag_RXIF(UART0)))
{
arrRecv[indexRx++] = (uint8_t)FL_UART_ReadRXBuff(UART0);
if(indexRx > 10)
{
}
}
}
中断发送
中断发送功能我了解的不多,只能给出自己的见解。我为了验证这个功能,就在中断使能函数的末尾添加了"UART4->TXREG = 'S'",这样,系统初始化执行到末尾,便会主动发送'S'字符,接着进入串口发送中断,不断向串口发送缓冲区填充数据,直至数组内容发送完毕。至此,中断发送功能得到验证。但是真正使用的时候,还要检查发送缓冲区是否为空,不能直接发送。
最后,再给出中断发送与轮询发送的对比表格。
|---------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 轮询发送(for循环) | 中断发送(中断驱动) |
| for(i=0; i<13; i++) { UART5->TXBUF = commSendBufi; while(TXE==0); } | 主程序 : start_index0 = 0; end_index = 13; UART5->TXBUF = commSendBuf0; 开启中断使能; 中断服务程序(自动触发13次) : if(start_index0 < end_index) UART5->TXBUF = commSendBufstart_index0++; else 关闭中断使能; |
|-----------------|---------------------|--------------------------|
| 维度 | 轮询发送 | 中断发送 |
| 发送循环位置 | 主程序(for循环) | 中断服务程序(ISR) |
| 如何检测TXE | while(TXE==0); 主动等待 | 由硬件中断自动触发,无需显式等待 |
| 是否有检测代码 | 有(显式 while) | 有(ISR中的 if 判断索引,及硬件自动触发) |
| CPU占用 | 高(等待期间100%占用) | 低(只在ISR中占用几微秒) |
| 代码结构 | 简单、直观 | 复杂、需要管理状态和中断使能 |