STM32单片机串口空闲中断+DMA接收不定长数据

发布者:数据梦行者最新更新时间:2022-01-27 来源: eefocus关键字:STM32  单片机  串口空闲中断  DMA  不定长数据 手机看文章 扫描二维码
随时随地手机看文章

在上一篇文章STM32单片机串口空闲中断接收不定长数据中介绍了利用串口空闲中断接收不定长数据,这种方式有一个问题就是串口每接收到一个字节就会进入一次中断,如果发送的数据比较频繁,那么串口中断就会不停打断主程序运行,影响系统运行。那么能不能在串口接收数据过程中不要每接收一个数据中断一次,只有在一帧数据接收结束完成后只中断一次? 


用串口的空闲中断加上DMA功能,就可以实现每帧数据接收完成后只中断一次,而在数据接收过程中,由DMA存储串口接收到的每个字节。


关于串口的空闲检测和DMA在STM32参考手册中有详细介绍。

 

下面看如何初始化串口空闲中断和 DMA。


void  uart2_init( u16 baud )

{

    GPIO_InitTypeDef GPIO_InitStructure;

    USART_InitTypeDef USART_InitStructure;

    NVIC_InitTypeDef  NVIC_InitStructure;

 

    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );

    RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART2, ENABLE );

 

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //推挽复用模式

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init( GPIOA, &GPIO_InitStructure );

 

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入模式

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init( GPIOA, &GPIO_InitStructure );

 

    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;

    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;

    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

 

    NVIC_Init( &NVIC_InitStructure );

 

    USART_InitStructure.USART_BaudRate = baud;

    USART_InitStructure.USART_WordLength = USART_WordLength_8b;

    USART_InitStructure.USART_StopBits = USART_StopBits_1;

    USART_InitStructure.USART_Parity = USART_Parity_No;

    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

 

    USART_Init( USART2, &USART_InitStructure );

#if (UART2_DMA == 1)

    USART_ITConfig( USART2, USART_IT_IDLE, ENABLE ); //使能串口空闲中断

    USART_DMACmd( USART2, USART_DMAReq_Rx, ENABLE ); //使能串口2 DMA接收

    uartDMA_Init(); //初始化 DMA

#else

    USART_ITConfig( USART2, USART_IT_RXNE, ENABLE );   //使能串口RXNE接收中断

#endif

    USART_Cmd( USART2, ENABLE ); //使能串口2

//RXNE中断和IDLE中断的区别?

//当接收到1个字节,就会产生RXNE中断,当接收到一帧数据,就会产生IDLE中断。比如给单片机一次性发送了8个字节,就会产生8次RXNE中断,1次IDLE中断。

}

为了方便对比空闲中断和DMA与常规串口设置的差别,这里用了一个宏定义UART2_DMA来设置是否需要开启空闲中断和DMA,如果宏定义UART2_DMA值为1,那么就初始化空闲中断和DMA。这里要将UART2_DMA设置为1,需要开启串口2的IDLE中断,使能串口2的DMA功能,然后初始化 DMA。


 

void uartDMA_Init( void )

{

    DMA_InitTypeDef  DMA_IniStructure;

 

    RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE ); //使能DMA时钟

 

    DMA_DeInit( DMA1_Channel6 );      //DMA1通道6对应 USART2_RX

    DMA_IniStructure.DMA_PeripheralBaseAddr = ( u32 )&USART2->DR; //DMA外设usart基地址

    DMA_IniStructure.DMA_MemoryBaseAddr = ( u32 )dma_rec_buff; //DMA内存基地址

    DMA_IniStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,从外设读取发送到内存

    DMA_IniStructure.DMA_BufferSize = DMA_REC_LEN; //DMA通道的DMA缓存的大小  也就是 DMA一次传输的字节数

    DMA_IniStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变

    DMA_IniStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //数据缓冲区地址递增

    DMA_IniStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设以字节为单位搬运

    DMA_IniStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据缓冲区以字节为单位搬入

    DMA_IniStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式

    DMA_IniStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级

    DMA_IniStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输

 

    DMA_Init( DMA1_Channel6, &DMA_IniStructure );

    DMA_Cmd( DMA1_Channel6, ENABLE );

}

        将 DMA的外设地址设置为串口2的数据寄存器 USART2->DR,DMA的内存地址设置为 DMA数据缓存数数组dma_rec_buff。这样当串口2的数据寄存器中有数据后,DMA就会自动将这个数据存储到dma_rec_buff数组中。当串口2的空闲中断出现后,直接去dma_rec_buff数组中读取接收到的数据就行。


下面看串口2的中断执行过程


void USART2_IRQHandler( void )

{

    u8 tem = 0;

#if (UART2_DMA == 1) //如果使能了 UART2_DMA 则使用串口空闲中断和 DMA功能接收数据

if( USART_GetITStatus( USART2, USART_IT_IDLE ) != RESET )//空闲中断  一帧数据发送完成

    {

        USART_ReceiveData( USART2 ); //读取数据注意:这句必须要,否则不能够清除空闲中断标志位。

        DMA_Cmd( DMA1_Channel6, DISABLE ); //关闭 DMA 防止后续数据干扰

        uart2_rec_cnt = DMA_REC_LEN - DMA_GetCurrDataCounter( DMA1_Channel6 );  //DMA接收缓冲区数据长度减去当前 DMA传输通道中剩余单元数量就是已经接收到的数据数量       

    copy_data( dma_rec_buff, uart2_rec_cnt ); //备份数据

        receiveOK_flag = 1; //置位数据接收完成标志位

        USART_ClearITPendingBit( USART2, USART_IT_IDLE ); //清除空闲中断标志位

        myDMA_Enable( DMA1_Channel6 ); //重新恢复 DMA等待下一次接收

    }

#else //如果未使能 UART2_DMA 则通过常规方式接收数据  每接收到一个字节就会进入一次中断

    if( USART_GetITStatus( USART2, USART_IT_RXNE ) != RESET ) //接收中断

    {

        tem = USART_ReceiveData( USART2 );

        USART_SendData( USART2, tem );

    }

#endif

}

       在串口中断中如果检测到了空闲中断标志,说明串口接收一帧数据结束,这时候就关闭 DMA,然后计算DMA缓冲区中接收到的字节个数,将接收到的数据拷贝到备份数组中,然后置位数据接收成功标志最后清除中断标志位,重新启动 DMA,开始进行下一次数据接收。


       如果串口数据的接收频率不是很高,在这里也可以不用备份DMA缓存区接收到的数据,备份数据主要是防止,数据接收频率很高,当上一笔数据还没处理完成后,又接收到了新的数据,那么数组中的数据就会被覆盖,可能导致程序异常。所以将处理数据的数组和接收数据的数组分开,可以防止在处理数据过程中数据被改变的情况发生。


下面看一下完整代码


#ifndef __UART2_H

#define __UART2_H

#include "sys.h"

 

#define DMA_REC_LEN 50 //DMA数据接收缓冲区

 

void  uart2_init(u16 baud);

void uartDMA_Init( void );

void myDMA_Enable( DMA_Channel_TypeDef*DMA_CHx );

void uart2_Send( u8 *buf, u16 len );

void copy_data( u8 *buf, u16 len );

#endif

 

//串口2空闲中断 + DMA数据传输

#include "uart2.h"

#define UART2_DMA  1 //使用串口2  DMA传输

 

u8 dma_rec_buff[DMA_REC_LEN] = {0};

u16 uart2_rec_cnt = 0; //串口接收数据长度

 

u8 data_backup[DMA_REC_LEN] = {0}; //数据备份

u16 dataLen_backup = 0; //长度备份

_Bool receiveOK_flag = 0; //接收完成标志位

 

/*

空闲中断是什么意思呢?

指的是当总线接收数据时,一旦数据流断了,此时总线没有接收传输,处于空闲状态,IDLE就会置1,产生空闲中断;又有数据发送时,IDLE位就会置0;

注意:置1之后它不会自动清0,也不会因为状态位是1而一直产生中断,它只有0跳变到1时才会产生,也可以理解为上升沿触发。

所以,为确保下次空闲中断正常进行,需要在中断服务函数发送任意数据来清除标志位。

*/

 

void  uart2_init( u16 baud )

{

    GPIO_InitTypeDef GPIO_InitStructure;

    USART_InitTypeDef USART_InitStructure;

    NVIC_InitTypeDef  NVIC_InitStructure;

 

    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );

    RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART2, ENABLE );

 

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //推挽复用模式

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init( GPIOA, &GPIO_InitStructure );

 

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入模式

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init( GPIOA, &GPIO_InitStructure );

 

    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;

    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;

    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

 

    NVIC_Init( &NVIC_InitStructure );

 

    USART_InitStructure.USART_BaudRate = baud;

    USART_InitStructure.USART_WordLength = USART_WordLength_8b;

    USART_InitStructure.USART_StopBits = USART_StopBits_1;

    USART_InitStructure.USART_Parity = USART_Parity_No;

    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

 

    USART_Init( USART2, &USART_InitStructure );

#if (UART2_DMA == 1)

    USART_ITConfig( USART2, USART_IT_IDLE, ENABLE ); //使能串口空闲中断

    USART_DMACmd( USART2, USART_DMAReq_Rx, ENABLE ); //使能串口2 DMA接收

    uartDMA_Init(); //初始化 DMA

#else

    USART_ITConfig( USART2, USART_IT_RXNE, ENABLE );   //使能串口RXNE接收中断

#endif

    USART_Cmd( USART2, ENABLE ); //使能串口2

//RXNE中断和IDLE中断的区别?

//当接收到1个字节,就会产生RXNE中断,当接收到一帧数据,就会产生IDLE中断。比如给单片机一次性发送了8个字节,就会产生8次RXNE中断,1次IDLE中断。

}

 

void uartDMA_Init( void )

{

    DMA_InitTypeDef  DMA_IniStructure;

 

    RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE ); //使能DMA时钟

 

    DMA_DeInit( DMA1_Channel6 );      //DMA1通道6对应 USART2_RX

    DMA_IniStructure.DMA_PeripheralBaseAddr = ( u32 )&USART2->DR; //DMA外设usart基地址

    DMA_IniStructure.DMA_MemoryBaseAddr = ( u32 )dma_rec_buff; //DMA内存基地址

    DMA_IniStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,从外设读取发送到内存

    DMA_IniStructure.DMA_BufferSize = DMA_REC_LEN; //DMA通道的DMA缓存的大小  也就是 DMA一次传输的字节数

    DMA_IniStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变

    DMA_IniStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //数据缓冲区地址递增

    DMA_IniStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设以字节为单位搬运

    DMA_IniStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据缓冲区以字节为单位搬入

    DMA_IniStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式

[1] [2]
关键字:STM32  单片机  串口空闲中断  DMA  不定长数据 引用地址:STM32单片机串口空闲中断+DMA接收不定长数据

上一篇:STM32CubeMX中文用户手册下载方法
下一篇:STM32单片机串口空闲中断接收不定长数据

推荐阅读最新更新时间:2024-11-11 10:14

STM32正交编码器驱动,引入(突变)带进位的位置环和速度环
STM32正交编码器驱动,引入(突变)带进位的位置环和速度环 http://bbs.elecfans.com/jishu_484159_1_1.html (出处: 中国电子技术论坛) #include stm32f10x.h #include stm32f10x_encoder.h #include sys.h #include usart.h #include led.h #define COUNTER_RESET (u16)0 #define ICx_FILTER (u8) 0 // 6 - 670nsec #define TIMx_PRE_EMPTION_PRIORITY 1 #define TIMx_SUB_
[单片机]
基于AVR单片机的医用臭氧治疗仪的设计
概述 臭氧作为一种高效冷杀菌手段,目前已经被广泛应用在各行各业中。具有高效、迅速杀菌作用的臭氧在医院环境消毒、术前消毒等方面应用广泛,其治疗效果优于其它传统杀菌治疗仪。因此研制一种运行稳定、使用方便、便携的臭氧治疗仪产品,为妇科疾病患者提供一种方便有效的在家治疗方式,具有实际意义。 目前市面上的同类型产品都是采用80C51单片机为控制核心的, 虽然也能实现它所需求的功能,但执行速度慢,在长期工作环境中,特别在臭氧治疗仪的内部大功率气泵模块和臭氧发生器的干扰下,系统功耗高和抗干扰性能差,系统性能不稳定等问题便凸现出来。对此本文采用了ATMEL公司的一款AVR高档单片机,对控制系统作了改进,提高了整机的性能。 臭氧产生的原理及方法 臭
[电源管理]
基于AVR<font color='red'>单片机</font>的医用臭氧治疗仪的设计
STC51单片机实例之05数码管的各种显示方式
简介:本文主要是STC51单片机实例之05数码管的各种显示方式的程序代码,希望对你的学习有所帮助。
[单片机]
STC51<font color='red'>单片机</font>实例之05数码管的各种显示方式
STM32串口usart发送数据
主函数请直接关注41行到47行代码!! 1 #include stm32f10x.h // 相当于51单片机中的 #include reg51.h 2 #include stm32f10x_gpio.h 3 #include stm32f10x_usart.h 4 #include led.h 5 #include key.h 6 #include key interrupt.h 7 #include delay.h 8 #include usart1.h 9 #include stdio.h 10 #include usart.h 11 12 int main() 13 { 14 /* 15
[单片机]
AT89C51单片机的红外线遥控信号发送器电路设计
TC9012F是一种通用型红外遥控信号发送用CMOS大规模集成电路,适用于电视(TV),磁带录像机(VTR),激光唱机等设备的遥控操作。市场上,以TC9012F为核心的9012型红外遥控器被广泛使用且价格便宜。将设计的基于单片机AT89C51的9012型红外遥控解码器应用于生产即时显示系统中,作为参数设置和系统控制用红外遥控器,在实际应用中收到了良好效果。 1 红外线遥控信号发送器电路 TC9012F的遥控信号 TC9012F为4位专用微控制器,其内部振荡电路的振荡频率fosc典型值为455 kHz。当不按下操作键时,其内部455 kHz的时钟振荡器停止工作,以减少电池消耗。内部分频电路将振荡频率,fosc进行12分频后,变成
[单片机]
AT89C51<font color='red'>单片机</font>的红外线遥控信号发送器电路设计
ATmega16单片机在实时温度采集与分析系统中的设计
  温度是工农业生产中很重要的的参数,它直接影响到产品的质量与性能。提出了一种基于ATmega16单片机与温度传感器相结合的实时温度采集与分析系统。本系统介绍了以ATmega16单片机为核心,以及自动化控制装置GTJJ4-10A固态继电器和数字温度传感器DS18B20,报警与指示电路等,在分析中基于MFC的软件处理。其中包括温度曲线的绘制,以及温度值的保存,显示历史记录等。在经过烧水温度的测试,该系统稳定可靠便于分析。温度误差0.5℃,可以满足工农业生产的要求。   随着计算机技术尤其是单片微型机技术的发展,温度对人们的生活与工作影响很大,所以要实时采集温度并且对其进行分析。为此,实现实时准确的测量监控。采用串口传送数据并且在PC
[单片机]
ATmega16<font color='red'>单片机</font>在实时温度采集与分析系统中的设计
玩转STM32(7)第一次编译
前面介绍了认识开发环境,当然你学习上面这些知识还不会进行编译一个项目的,那么你也许会急着想知道下一步怎么办?怎么样才可以把这个项目编译出来,或者烧写到开发板里进行测试。下面就来解决编译这个问题,编译原因在前面已经说过,就是把人类理解的语言转换为机器语言。在Keil的集成开发环境里,界面上主要有四个编译选项,如下图所示: 下面来从左到右来介绍这四个编译选项的使用和意义,第一个选项是编译一个文件,并且是代码区里选中的文件,如上图就是main.c是当前活动的、选中的,其它文件不作任何的处理。那么为什么要有这样一个选项,而不是编译所有的文件呢?对于初学者来说,要理解这个区别有点困难,因为他接触到的代码,都是非常少,比如几百行,几千行,
[单片机]
玩转<font color='red'>STM32</font>(7)第一次编译
51单片机(利用return)实现判断数据头来接收
一、使用proteus绘制简单的电路图,用于后续仿真 二、编写程序 /******************************************************************************************************************** ---- @Project: return ---- @File: main.c ---- @Edit: ZHQ ---- @Version: V1.0 ---- @CreationTime: 20200808 ---- @ModifiedTime: 20200808 ----
[单片机]
51<font color='red'>单片机</font>(利用return)实现判断<font color='red'>数据</font>头来接收
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved