UART空闲中断
在实际项目中经常用到串口接收一些不定长的数据,此时必须面对一个问题:怎么判断这一帧数据接收完成了呢?通常使用UART非空中断配合简单的数据协议,在数据中加入帧头、帧尾,在程序中判断是否接收到帧尾来确定数据接收完毕,因为对每个数据都要进行判断,比较消耗系统资源,尤其是在一些实时性要求较高的场合。而串口空闲中断可以大大简化数据接收过程的判断,在这一块起到非常重要的作用。
空闲中断(IDLE),俗称帧中断,即第一帧数据接收完毕到第二帧数据开始接收期间存在一个空闲状态(每接收一帧数据后空闲标志位置1),检测到此空闲状态后即执行中断程序。空闲中断的优点在于省去了帧头帧尾的检测,进入中断程序即意味着已经接收到一组完整数据,仅需即时对数据处理或将数据转移出缓冲区即可。
串口空闲中断在串口无数据接收的情况下,是不会产生的,产生的条件是当清除空闲标志位后,必须有接收到第一个数据后,才开始触发,一旦接收的数据断流,没有接收到数据,即产生空闲中断。
MM32F0270系列的UART支持空闲中断功能。相关的寄存器包括UART中断状态寄存器(UART\_ISR)、 UART中断使能寄存器(UART\_IER)、 UART中断清除寄存器(UART\_ICR),对应的位如下:
UART中断状态寄存器(UART\_ISR)
UART中断使能寄存器(UART\_IER)
UART中断清除寄存器(UART\\\\\\_ICR)
UART DMA方式
MM32F0270 UART使用DMA方式接收数据可以减小CPU的开销。
对于接收定长数据,可以将DMA接收缓冲区的长度设定为待接收数据的长度,这样利用DMA的传输完成中断就可以知道已经接收了一帧数据。
对于接收不定长数据,由于内核在串口接收数据到空闲这段时间,是不受理串口数据的,所以可以使用DMA来协助我们把数据传送到指定的地方,当数据传输完成后,通知内核去处理。
截取MM32F0270的DMA各通道请求映像表如下,其中UART1\_RX对应通道3。
各通道DMA请求一览表(部分)
实验
本次实验使用UART空闲中断功能,通过DMA方式把数据传送到指定的缓存区。当UART接收完一帧数据后,会产生一个空闲中断。这个中断在UART其他任何状态都不产生,只会在接收完一帧数据后才会产生,一帧数据可以是1个字节或者多个字节。因此,我们可以在这个空闲中断函数中,设置一个接收完成标志位。那么,我们只需要在主程序中检测这个标志位就知道数据是否接收完成了,如果数据接收完成,将数据发送到上位机显示。
程序部分
程序参考官网UART空闲中断代码,并在此基础上修改。
UART初始化
void UART1_NVIC_Init(u32 baudrate)
{
UART_InitTypeDef UART_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2ENR_UART1, ENABLE);
//UART1 NVIC
NVIC_InitStruct.NVIC_IRQChannel = UART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
//Baud rate
UART_StructInit(&UART_InitStruct);
UART_InitStruct.BaudRate = baudrate;
//The word length is in 8-bit data format.
UART_InitStruct.WordLength = UART_WordLength_8b;
UART_InitStruct.StopBits = UART_StopBits_1;
//No even check bit.
UART_InitStruct.Parity = UART_Parity_No;
//No hardware data flow control.
UART_InitStruct.HWFlowControl = UART_HWFlowControl_None;
UART_InitStruct.Mode = UART_Mode_Rx | UART_Mode_Tx;
UART_Init(UART1, &UART_InitStruct);
UART_Cmd(UART1, ENABLE);
UART1_GPIO_Init();
}
DMA初始化
void DMA_Config_Init(DMA_Channel_TypeDef* dam_chx, u32 cpar, u32 cmar, u16 cndtr)
{
DMA_InitTypeDef DMA_InitStruct;
RCC_AHBPeriphClockCmd(RCC_AHBENR_DMA1, ENABLE);
DMA_DeInit(dam_chx);
DMA_StructInit(&DMA_InitStruct);
DMA_InitStruct.DMA_PeripheralBaseAddr = cpar;
DMA_InitStruct.DMA_MemoryBaseAddr = cmar;
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStruct.DMA_BufferSize = cndtr;
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;
DMA_InitStruct.DMA_Priority = DMA_Priority_Low;
DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;
DMA_InitStruct.DMA_Auto_reload = DMA_Auto_Reload_Disable;
DMA_Init(dam_chx, &DMA_InitStruct);
UART_DMACmd(UART1, UART_DMAReq_EN, ENABLE);
DMA_Cmd(dam_chx, ENABLE);
}
在程序中调用DMA初始化函数配置DMA1\_Channel3,将UART1接收寄存器的数据通过DMA搬运到接收缓冲区sRecvBuf[RECVBUFLENGTH],数据单元数目为RECVBUFLENGTH。
DMA_Config_Init(DMA1_Channel3, (u32)&UART1->RDR, (u32)sRecvBuf, RECVBUFLENGTH);
UART中断服务函数
void UART1_IRQHandler(void)
{/* Omit the code related to sending. */
// Recv packet
if (UART_GetITStatus(UARTx, UART_ISR_RX) != RESET) {
UART_ClearITPendingBit(UARTx, UART_ICR_RX);
if(Flag_RecvComplete == 0) {
if(RecvCnt < RECVBUFLENGTH) { //(1)
RecvCnt++;
}
else {
RecvCnt = 0;
Flag_RecvComplete = 1; //(2)
}
}
}
//Idle interrupt
if (UART_GetITStatus(UARTx, UART_ISR_RXIDLE) != RESET) {
receive_len = RECVBUFLENGTH - DMA_GetCurrDataCounter(DMA1_Channel3); //(3)
if(receive_len > 0){
Flag_RecvComplete = 1; //(4)
}
UART_ClearITPendingBit(UARTx, UART_ICR_RXIDLE);
}
}
(1)UART接收到数据进入中断,通过变量RecvCnt累加记录接收的数据量。限制RecvCnt不能超出缓冲区范围。
(2)对超出缓冲区数据处理,此处将超出缓冲区的数据舍去,将Flag\_RecvComplete置1。
(3)进入UART空闲中断,通过receive\_len记录当前DMA传输的数据单元数量。
(4)接收到数据,则将Flag\_RecvComplete置1。
在中断外通过判断Flag\_RecvComplete标志进行数据处理。
UART收发函数:
void UART1_RxTx_Transceiving(void)
{ UART_ITConfig(UART1, UART_IT_RXIEN, ENABLE); //(1)
UART_ITConfig(UART1, UART_IER_RXIDLE, ENABLE); //(2)
while(1) {
if(Flag_RecvComplete == 1) { //(3)
Flag_RecvComplete = 0;
memcpy((u8*)&sSendBuf[0], (u8*)&sRecvBuf[0], receive_len); //(4) //DMA reset to reinitialize
DMA_Cmd(DMA1_Channel3, DISABLE);
DMA_Config_Init(DMA1_Channel3, (u32)&UART1->RDR, (u32)sRecvBuf, RECVBUFLENGTH); //(5)
DMA_Cmd(DMA1_Channel3, ENABLE);
//Send data
UART1_Send_Packet_Interrupt((u8*)&sSendBuf[0], receive_len); //
while(1) {
if((SendLen) == UART1_Check_Send_Finish()) {
break;
}
}
}
}
}
(1)和(2)配置UART\_IT\_RXIEN中断和UART\_IER\_RXIDLE中断。
(3)判断标志位,该标志位在进入空闲中断置1。
(4)将接收缓冲区sRecvBuf的数据复制到发送缓冲区sSendBuf。
(5)复位DMA,重新设置DMA传输的数据单元数目为RECVBUFLENGTH(接收缓冲区范围)。传输数量寄存器DMA\_CNDTRx只能在通道关闭时写入,通道开启后该寄存器变为只读。
其余和发送相关的代码是将数据发送到上位机。
演示
仿真运行程序,可以在程序的①、②位置设置断点,并在Watch窗口观测receive\_len的值。
打开串口调试助手发送一帧数据,如:0123456789,程序运行至断点①,UART中断状态寄存器UART\_ISR的RXIDLE\_INTF位置1,表示进入UART空闲中断。
继续运行至断点②,RXIDLE\_INTF位清0, Watch窗口显示此时receive\_len的值为0x0A,和数据帧长度一致。
继续运行,UART\_RecvComplete置1。
重复进行上述操作,每帧数据发送完成都会进入空闲中断。
串口调试助手显示如下:
实验简单演示了使用MM32F0270的UART空闲中断+ DMA方式接收不定长数据,运行结果和预期一致。
参考Demo程序可登录MindMotion的官网(https://www.mindmotion.com.cn/products/mm32mcu/mm32f/mm32f\_mainstream/MM32F0270/ )下载MM32F0270库函数和例程,工程路径如下:~ MM32F0270\_Samples\LibSamples\UART\UART\_idleframe\_Interrupt\。