此工程的硬件环境为尚学STM32F103ZET6核心板+正点原子3.5寸TFTLCD
工程下载链接:https://download.csdn.net/download/qq_40501580/11203377
一、什么是串口空闲中断,有啥子用? 上看到的教程大多是直接就编写程序实现空闲中断,但没有对原理性部分阐述清楚,也没有写为什么要这样子写代码,那我就自己来总结一下前人的经验。
在实际做项目的时候,经常需要用串口接收数据,一般是使用串口中断来接收数据。但是用这种方法的话,就要频繁进入串口中断,效率就比较低,裸机(区分系统跑代码)会增加单片机的负荷。于是就想到用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);返回当前剩余数据单元的数量
五、工程应用演示 串口助手发送数据如下: 3.5寸TFTLCD显示画面如下:
