串口空闲中断+DMA接收不定长数据

  此工程的硬件环境为尚学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);返回当前剩余数据单元的数量

五、工程应用演示

  串口助手发送数据如下:
在这里插入图片描述
  3.5寸TFTLCD显示画面如下:
在这里插入图片描述

发布了20 篇原创文章 · 获赞 48 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/yc5300891/article/details/90545990