此工程的硬件环境为尚学STM32F103ZET6核心板+正点原子3.5寸TFTLCD
工程下载链接:https://download.csdn.net/download/qq_40501580/11203377
一、什么是串口空闲中断,有啥子用?
CSDN上看到的教程大多是直接就编写程序实现空闲中断,但没有对原理性部分阐述清楚,也没有写为什么要这样子写代码,那我就自己来总结一下前人的经验。
在实际做项目的时候,经常需要用串口接收数据,一般是使用串口中断来接收数据。但是用这种方法的话,就要频繁进入串口中断,效率就比较低,裸机(区分系统跑代码)会增加单片机的负荷。于是就想到用DMA来接收串口数据,但是关键的一点,当发送的数据量不定时,如OpenMV发送特征物体中标坐标、接收RM裁判系统回馈数据、Manifold妙算传输控制炮管的位置指令,就需要用到串口空闲中断了。接收不定长度数据是串口空闲中断的重要使用方法。
在STM32的串口控制器中,设置了有串口空闲中断,即如果串口空闲,又开启了串口空闲中断的话,就触发串口空闲中断,然后程序就会跳到串口空闲中断去执行。有了这个,是不是可以判断什么时候串口数据接收完毕了呢?因为串口数据接收完毕后,串口总线肯定是会空闲的嘛,那这个中断肯定是会触发的了。
那么单片机是怎么判断总线空闲的呢?比如一帧数据是18个字节,当在第19个字节的时间里,串口没有接受到数据,即过了一段时间,串口接收的数据帧没有了,即判断为总线空闲。
二、串口空闲中断的配置
void USART3_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);
//USART3_TX GPIOB.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PB.10
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB.11
//USART3_RX GPIOB.11初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;//PB11
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB11
//USART3 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//USART3 初始化设置
USART_InitStructure.USART_BaudRate = 115200;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART3, &USART_InitStructure); //初始化串口3
USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);//开启串口空闲中断
USART_Cmd(USART3, ENABLE); //使能串口3
}
注意这里的配置与配置接收中断类似,但开启的中断要改为USART_IT_IDLE标志符(串口空闲中断)
三、DMA配置
void DMA_config(DMA_Channel_TypeDef* DMA_CHx,s32 peripherals_addr,s32 memory_addr,u16 size)
{
DMA_InitTypeDef DMA_InitTypestruct;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
DMA_DeInit(DMA_CHx);
DMA_InitTypestruct.DMA_BufferSize=size; //通道传输数据量
DMA_InitTypestruct.DMA_DIR=DMA_DIR_PeripheralSRC; //数据传输方向为 外设到存储器
DMA_InitTypestruct.DMA_M2M=DMA_M2M_Disable;//不开启存储器到存储器方式
DMA_InitTypestruct.DMA_MemoryBaseAddr=memory_addr;//存储器基地址
DMA_InitTypestruct.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;//存储器数据宽度(一次传输的数据位数)
DMA_InitTypestruct.DMA_MemoryInc=DMA_MemoryInc_Enable;//开启存储器增量模式(因为存储器被设置为一个数组)
DMA_InitTypestruct.DMA_Mode=DMA_Mode_Normal;//正常模式,数据传输就一次
DMA_InitTypestruct.DMA_PeripheralBaseAddr=peripherals_addr;//外设基地址
DMA_InitTypestruct.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;//外设数据宽度
DMA_InitTypestruct.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//不要外设增量模式
DMA_InitTypestruct.DMA_Priority=DMA_Priority_VeryHigh;//这里设置为最高优先级(一共4个优先级)
DMA_Init(DMA_CHx,&DMA_InitTypestruct);
DMA_Cmd(DMA_CHx,ENABLE);
}
注意,这里配置的模式是DMA_Mode_Normal(正常模式),数据传输就一次。思路如下:初始化时开启DMA,在总线空闲时,收到第一帧数据,之后关闭DMA,处理数据,再重新配置DMA的接收字节数后,开启DMA,准备下一帧数据的接收。所以,这里不需要设置DMA为循环发送模式,正常模式即可。
四、中断程序的编写
这个就是关键的地方了。在这里需要对DMA设置下。当进入这个中断的时候,串口接收的数据,已经在内存的数组中了。通过减去DMA的剩余计数值,就可以知道接收到了多少个数据。然后再把DMA给关掉,重新设置接收数据长度,再开启DMA,接收下一次串口数据。为什么要这么做了,因为在STM32手册中有如下说明:
另外还有一点,串口空闲中断触发后,硬件会自动将串口空闲中断标志位给置1,我们是需要将标志位给置0的,不然又要进中断了,这个在手册中也有说明。
串口空闲中断程序参考如下:
/*------------串口空闲中断程序------------*/
/*------------串口空闲中断程序------------*/
void USART3_IRQHandler(void)
{
u8 num=0;
static u8 last_num=0;
s8 i=0;
if(USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)
{
num=USART3->SR;
num=USART3->DR; //清除USART_IT_IDLE标志位
DMA_Cmd(DMA1_Channel3,DISABLE);
num = BufferSize - DMA_GetCurrDataCounter(DMA1_Channel3); //得到接收的数据个数
receive_data[num] = '\0'; //为字符串加上结束符
if(last_num>num)
{
for(i=last_num-num; i>0; i--)
receive_data[num+i]=0; //清除上一次传输的数据
}
DMA1_Channel3->CNDTR=BufferSize; //设置DMA下一次接收的字节数
DMA_Cmd(DMA1_Channel3,ENABLE);
Send_Counter++; //加满溢出后为1 0000 0000,而Send_Counter只能加载1个字节,所以溢出后自动为0
receive_flag=1;
last_num = num;
}
}
这里要注意的操作是:
1、根据芯片手册,清除空闲中断的标志位是要先读USARTx->SR,再读USARTx->DR。
2、DMA1_Channel3->CNDTR是DMA剩余字节寄存器
3、DMA_GetCurrDataCounter(DMA1_Channel3);返回当前剩余数据单元的数量