精华内容
下载资源
问答
  • STM32 HAL库+串口DMA+空闲中断(IDLE)实现不定长数据接收,可以用来参考学习使用,简单易懂。
  • 在上一篇文章STM32单片机串口空闲中断接收不定长数据中介绍了利用串口空闲中断接收不定长数据,这种方式有一个问题就是串口每接收到一个字节就会进入一次中断,如果发送的数据比较频繁,那么串口中断就会不停打断主...

             在上一篇文章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;													 //工作在正常缓存模式
        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指针
    void myDMA_Enable( DMA_Channel_TypeDef*DMA_CHx )
    {
        DMA_Cmd( DMA_CHx, DISABLE );  																				 //关闭DMA1所指示的通道
        DMA_SetCurrDataCounter( DMA_CHx, DMA_REC_LEN );												 //DMA通道的DMA缓存的大小
        DMA_Cmd( DMA_CHx, ENABLE );  																					 //DMA1所指示的通道
    }
    //发送len个字节
    //buf:发送区首地址
    //len:发送的字节数
    void uart2_Send( u8 *buf, u16 len )
    {
        u16 t;
        for( t = 0; t < len; t++ )																						//循环发送数据
        {
            while( USART_GetFlagStatus( USART2, USART_FLAG_TC ) == RESET );
            USART_SendData( USART2, buf[t] );
        }
        while( USART_GetFlagStatus( USART2, USART_FLAG_TC ) == RESET );
    }
    
    //备份接收到的数据
    void copy_data( u8 *buf, u16 len )
    {
        u16 t;
        dataLen_backup = len;																									 //保存数据长度
        for( t = 0; t < len; t++ )
        {
            data_backup[t] = buf[t];																					 //备份接收到的数据,防止在处理数据过程中接收到新数据,将旧数据覆盖掉。
        }
    }
    
    //利用空闲中断接收串口不定长数据
    //串口接收一组数据结束后才会进入中断,在数据发送过程中,已经接收到的数据将会被存入DMA的缓冲区中
    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传输通道中剩余单元数量就是已经接收到的数据数量
            //uart2_Send( dma_rec_buff, uart2_rec_cnt );														//发送接收到的数据
    			  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
    }
    
    
    #include "sys.h"
    #include "delay.h"
    #include "usart.h"
    #include "led.h"
    #include "key.h"
    #include "uart2.h"
    #include "string.h"
    
    extern u8 data_backup[DMA_REC_LEN]; 													//数据备份
    extern u16 dataLen_backup;																			//长度备份
    extern _Bool receiveOK_flag;																		//接收完成标志位
    
    int main(void)
    {
        u8  j = 0;
    	
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    	
        delay_init();       //延时函数初始化
        LED_Init();         //初始化与LED连接的硬件接口
        uart2_init(9600);
        while(1)
        {
          
            if( receiveOK_flag )																		//一帧数据接收完成后开始处理数据
            {
                receiveOK_flag = 0;
                uart2_Send( data_backup, dataLen_backup );					//发送数据
                memset( data_backup, 0, sizeof( data_backup ) );		//清空备份数组
            }
    			
            j++;
            if(j > 50)
            {
                j = 0;
                LED = !LED;
            }
            delay_ms(10);
        }
    }
    
    
    

    下面看一下测试效果

     

    工程下载地址 https://download.csdn.net/download/qq_20222919/12926810

    展开全文
  • STM32串口空闲中断DMA接收不定长数据消息队列。 串口1空闲中断+DMA,接收不定长数据
  • STM32CubeMX—串口空闲中断+DMA接收

    千次阅读 多人点赞 2019-07-30 15:12:36
    实验内容:使用串口空闲中断结合DMA 完成不定长数据接收 STM32的串口接收数据的方式 1、轮询接收   所谓轮询,就是在主函数中判断接收完成的标志位。举个不太恰当例子,就比如,此时你正在考试作弊,手机藏在...

    一、实验说明

    实验平台:STM32F103C8T6
    实验内容:使用串口一空闲中断结合DMA 完成不定长数据接收

    STM32的串口接收数据的方式
    1、轮询接收

      所谓轮询,就是在主函数中判断接收完成的标志位。举个不太恰当例子,就比如,此时你正在考试作弊,手机藏在兜里,你的队友再给你发答案,但是你的手机静音,所以你不得不写一会题看一会手机,有的时候答案已经发来了但是你此时在假装写,没有看,导致你没能及时看到答案浪费了时间(仅仅为了举例而已。。。。)。轮询接收数据也是这样。

    2、中断接收

      串口接收配置为中断模式,当有数据收到时,进入到串口接收中断中读取数据。继续上面的例子(你为了不浪费时间且及时抄到答案,你把手机开了震动,消息一来立马看,这是就比上面好多了,能够及时发现消息。但是又出了一个问题,你的猪队友,写一个选择给你发一次,不停的震动,完全扰乱了你的节奏)。其实也就是,串口接收数据时,一次接收一个字节,当数据量较大时,显然这样频繁的进入中断,打断主程序,严重影响系统性能。

    3、空闲中断接收

      空闲中断接收,当一帧数据接收完成之后,串口会进入到空闲中断中去,然后在空闲中断中处理收到的数据。这种模式对处理不定长数据帧带来很大的便利,我们不必频繁的进入接收中断处理数据,但是弊端也是明显的,由于每次都要接收完一个完整的数据帧后才空闲中断,所以当一帧数据出错时,我们也不得不接收这帧错误的数据。在通讯可靠的场合,使用空闲中断接收模式接收串口数据,将会大大提高系统的性能。

    二、实验步骤

    1、基础配置

    1)、sys中,选好调试方式,例如jtag-4pin。
    2)、RCC时钟,晶振选择。
    3)、时钟树配置。
    4)、中断分组配置。
    以上步骤可以参考串口中断实验。

    串口中断实验配置

    本实验要配置好printf函数便于演示效果

    2、串口和DMA配置

    1)、串口一配置。
    这是

    2)、DMA选择在这里插入图片描述
    在这里插入图片描述
    3)、生成工程代码
    在这里插入图片描述
    然后生成代码。
    4)、添加代码
    在这里插入图片描述

    // 这里没有使用中断回调函数,这样写更直接一点。
    extern volatile uint8_t rx_len;//接收到的数据长度
    extern volatile uint8_t recv_end_flag; //接收完成标志位
    extern uint8_t rx_buffer[200]; //数据缓存数组
    void USART1_IRQHandler(void)
    {
      /* USER CODE BEGIN USART1_IRQn 0 */
        uint32_t tmp_flag = 0;
        uint32_t temp;
        tmp_flag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE); 
        if((tmp_flag != RESET))
        { 
            __HAL_UART_CLEAR_IDLEFLAG(&huart1);
            temp = huart1.Instance->SR;  
            temp = huart1.Instance->DR; 
            HAL_UART_DMAStop(&huart1); 
            temp  = hdma_usart1_rx.Instance->CNDTR;
            rx_len =  200 - temp; 
            recv_end_flag = 1;		
        }
        HAL_UART_IRQHandler(&huart1);	
    }
    

    如下图,在usart.c 文件中先把变量定义上,同时把支持printf的函数添加上。

    //注:**这三个变量 需要在stm32f1xx_it.c和main.c中外部声明**
    volatile uint8_t rx_len=0;  //接收到的数据长度
    volatile uint8_t recv_end_flag=0;//接收成功标志位
    uint8_t rx_buffer[200];//缓存数组
    

    在这里插入图片描述
    在这里插入图片描述

     	__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
     HAL_UART_Receive_DMA(&huart1,rx_buffer,200);
    

    在这里插入图片描述

    int main(void)
    {
    	HAL_Init();
    	SystemClock_Config();
    	MX_GPIO_Init();
    	MX_DMA_Init();
    	MX_USART1_UART_Init();
    	/* USER CODE BEGIN WHILE */
    	printf("DMA_TEST");
    	while (1)
    	{
    		if(recv_end_flag ==1)			
    		{	
    			printf("接收到的数据长度为%d\r\n",rx_len);
    			HAL_UART_Transmit(&huart1,rx_buffer, rx_len,200);
    			for(uint8_t i=0;i<rx_len;i++)
    			{
    				rx_buffer[i]=0;
    			}
    			printf("\r\n");
    			rx_len=0;
    			recv_end_flag=0;
    		}
    		HAL_UART_Receive_DMA(&huart1,rx_buffer,200);	
    		/* USER CODE END WHILE */
    	}
    }
    

    3 烧录调试

    添加链接描述在这里插入图片描述

    代码下载

    代码下载地址 提取码 wqoo

    展开全文
  • 一、什么是串口空闲中断,有啥子用?   CSDN上看到的教程大多是直接就编写程序实现空闲中断,但没有对原理性部分阐述清楚,也没有写为什么要这样子写代码,那我就自己来总结一下前人的经验。   在实际做项目的...

      此工程的硬件环境为尚学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显示画面如下:
    在这里插入图片描述

    展开全文
  • 1,电脑通过串口1给STM32F407芯片发送数据(不定长,按照645格式来),芯片根据串口接收中断接收到数据后,通过DMA将数据存储在内存。当检测到数据接受完毕,产生接收完成标志位置位。当407检测到这个中断标志位后从...

    传统串口接受与发送:
    串口接受一个很长的帧,接受帧时依靠串口中断每次只能传输8位,传一个帧要进入好多次中断,每次进入中断都要判断是否接收完毕。

    DMA串口接收与发送:
    1,电脑通过串口1给STM32F407芯片发送数据(不定长,按照645格式来),芯片根据串口接收中断接收到数据后,通过DMA将数据存储在内存。当检测到数据接受完毕,产生接收完成标志位置位。当407检测到这个中断标志位后从TX端向电脑发送这段数据。

    任务拆分:
    1.检测到key0按下,由TX发送已经存在存储器的数据到电脑(用DMA存储器->外设):
    2.实现RX通过串口中断能正常接受电脑发送的帧,检测到key0按下,由TX发送到电脑用(用DMA存储器->外设):
    3.实现RX通过串口(事件)能正常接受电脑发送的帧(DMA:外设->内存),检测到key0按下,由TX发送到电脑用DMA(存储器->外设):

    设置关注博文即可访问,程序发布到GitHUB

    一、DMA基础(STM32F4芯片)

    DMA(Direct Memory Access)直接存储器访问,当外设到存储器、存储器到外设、存储器到存储器有传输任务时,可以在无需任何 CPU 操作的情况下通过 DMA 快速移动数据。这样节省的 CPU 资源可供其它操作使用。

    • 存储器—>存储器,内存间拷贝
    • 外设—>存储器,如uart、spi、i2c等总线接收数据过程
    • 存储器—>外设,如uart、spi、i2c等总线发送数据过程

    1.STM32407的DMA主要特性

    • 每个 DMA 控制器有 8 个数据流,每个数据流有多达 8 个通道(或称请求)
    • DMA 数据流请求之间的优先级可用软件编程(4 个级别:非常高、高、中、低)
    • 独立的源和目标传输宽度(字节、半字、字):源和目标的数据宽度不相等时,DMA 自动封装/解封必要的传输数据来优化带宽。这个特性仅在 FIFO 模式下可用
    • 对源和目标的增量或非增量寻址
    • 支持 4 个、8 个和 16 个节拍的增量突发传输。
    • 5 个事件标志(DMA 半传输、DMA 传输完成、DMA 传输错误、DMA FIFO 错误、直接模式错误)
    • 这两个DMA控制器支持循环缓冲区管理,还具有双缓冲功能,无需任何特殊代码即可自动使用和切换两个内存缓冲区。

    2.DMA 控制系统框图

    在这里插入图片描述
    注:DMA1 控制器 AHB 外设端口与 DMA2 控制器的情况不同,不连接到总线矩阵,因此,仅 DMA2 数据流能够执行存储器到存储器的传输。关于总线矩阵的相关内容见文末链接。

    3.DMA请求映射表

    在这里插入图片描述
    在这里插入图片描述

    4.可能的 DMA 配置

    在这里插入图片描述

    二、DMA实现串口接收(STM32F4芯片)

    1.DMA替代中断的原因

    因为如果使用串口中断接收一帧很长很长的数据,每传8位都要打断一次CPU,CPU需要不停的进入中断做处理,严重影响CPU的工作效率。如果能通过DMA实现在接收完一帧数据之后再通知CPU处理这些数据,那岂不好处大大滴!
    DMA+IDLE中断是一个常规的串口DMA接收方案,但是也有其局限性,如果数据帧的字节超时时间大于1个字节,那就会导致接收不完整的情况,所以建议在实现DMA接收的时候加入多字节超时的机制.

    3.DMA流配置过程

    1. 确认没有正在进行的数据流(参考手册P218)
    2. 在 DMA_SxPAR 寄存器中设置外设端口寄存器地址
    3. 在 DMA_SxMA0R 寄存器(在双缓冲区模式的情况下还有 DMA_SxMA1R 寄存器)中设置存储器地址
    4. 在 DMA_SxNDTR 寄存器中配置要传输的数据项的总数
    5. 使用 DMA_SxCR 寄存器中的 CHSEL[2:0] 选择 DMA 通道(请求)
    6. 如果外设用作流控制器而且支持此功能,请将 DMA_SxCR 寄存器中的 PFCTRL 位置 1
    7. 使用 DMA_SxCR 寄存器中的 PL[1:0] 位配置数据流优先级
    8. 配置 FIFO 的使用情况(使能或禁止,发送和接收阈值)
    9. 配置数据传输方向、外设和存储器增量 / 固定模式、单独或突发事务、外设和存储器数据宽度、循环模式、双缓冲区模式和传输完成一半和/或全部完成,和/或 DMA_SxCR 寄存器中错误的中断
    10. 通过将 DMA_SxCR 寄存器中的 EN 位置 1 激活数据流

    警告:要关闭连接到 DMA 数据流请求的外设,必须首先关闭外设连接的数据流请求的外设,必须首先关闭外设连接的 DMA 数据流,然后等待 EN 位 = 0 。只有这样才能安全地禁止外设。

    2.DMA传输需要注意的细节

    • 当DMA的数据流被禁止传输后(DMA_S_CR寄存器EN位置零),最好不要直接打开(EN位置1),先清除DMA所有与数据流对应的事件标志标志位,然后检查EN是否已经清零,最后再将EN置1。
    • 循环模式是当&&8*****,突发模式*****
    • 为了使能 FIFO 阈值级别,必须通过将 DMA_SxFCR 寄存器中的 DMDIS 位置 1 来禁止直接模式。啥时候开启呢?
    • 双缓冲模式需要使能两个缓冲区,使能了双缓冲模式会自动使能循环模式,如何编程呢?。

    3.DMA做串口接收数据的方法

    接收定长数据:

    使用DMA传输完成中断,比较简单

    接收不定长数据:

    方法1:用DMA + 串口空闲中断 :利用串口空闲标志位产生中断,在中断中处理接收的数据

    方法2:用DMA+ 定时器:当产生&&&&&*******(((((((

    4.DMA + 串口空闲中断实现串口接收

    串口空闲中断的功能先简单了解一下:注意,空闲中断是接受数据后出现一个Byte的高电平(空闲)状态,就会触发空闲中断,而且清除中断的方式很奇怪,需要通过先读SR寄存器再读DR寄存器来清除该标志,库函数的USART_ClearITPendingBit里头都没有IDLE参数的呦。
    在这里插入图片描述在这里插入图片描述
    程序的大致流程:在dma.h文件中编写DMA初始化配置过程和DMA中断函数,在usart.c文件中编写串口1初始化函数和串口中断函数,在usart1初始化过程中调用DMA接收中断配置函数,串口1的RX接收到串口调试助手发的一帧数据后,进入串口1中断函数进行数据处理,完事儿重新开启DMA传输后退出中断。(下面的程序实现了串口1的DMA收发,仅展示关键步骤)

    dma.h文件中DMA初始化及DMA中断:

    /**********************************************************
    * @brief    串口接收端DMA配置函数(从外设->存储器模式/8位数据宽度/存储器增量模式 )
    * @param	DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
    *			chx:DMA通道选择,@ref DMA_channel DMA_Channel_0~DMA_Channel_7
    *			par:外设地址
    *			mar:存储器地址
    *			ndtr:数据传输量 
    ***********************************************************/
    void u2DMA_RX_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx,u32 par,u32 mar,u16 ndtr)
    {
    	DMA_InitTypeDef  DMA_InitStructure;
    	if((u32)DMA_Streamx>(u32)DMA2)//得到当前stream是属于DMA2还是DMA1
    	{
    	  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);	//DMA2时钟使能 
    	}else 
    	{
    	  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);	//DMA1时钟使能 
    	}
    	DMA_DeInit(DMA_Streamx);
    	while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}		//等待DMA可配置 
    		
    	/*配置 DMA Stream*/
    	DMA_InitStructure.DMA_Channel = chx;  							//通道选择
    	DMA_InitStructure.DMA_PeripheralBaseAddr = par;					//DMA外设地址
    	DMA_InitStructure.DMA_Memory0BaseAddr = mar;					//DMA存储器0地址
    	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;			//外设到存储器
    	DMA_InitStructure.DMA_BufferSize = ndtr;						//数据传输量 
    	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式
    	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;			//存储器增量模式
    	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位
    	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;	//存储器数据长度:8位
    	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; 					//正常模式,即满了就不在接收了,而不是循环存储
    	DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;			//高优先级
    	DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;         
    	DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
    	DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;		//存储器突发单次传输???
    	DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发单次传输???
    	DMA_Init(DMA_Streamx, &DMA_InitStructure);						//初始化DMA Stream
    		
    	DMA_ClearFlag(DMA2_Stream5,DMA_FLAG_TCIF5);						//清除传输完成中断
    	//开启DMA错误和接收完成中断 +(使能接收完成中断是为了防止一次性接收过长数据)
    	DMA_ITConfig(DMA2_Stream5,DMA_IT_TE|DMA_IT_TC, ENABLE);
    	NVIC_Configuration(2,2);										//NVIC 配置
    	
    	DMA_Cmd (DMA2_Stream5,ENABLE);									//使能DMA
    }
    
    /**********************************************************
    * @brief    串口发送端DMA配置函数(从存储器->外设模式/8位数据宽度/存储器增量模式 )
    * @param	DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
    *			chx:DMA通道选择,@ref DMA_channel DMA_Channel_0~DMA_Channel_7
    *			par:外设地址
    *			mar:存储器地址
    *			ndtr:数据传输量 
    ***********************************************************/ 
    void u2DMA_TX_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx,u32 par,u32 mar,u16 ndtr)
    {
    	DMA_InitTypeDef  DMA_InitStructure;
    	if((u32)DMA_Streamx>(u32)DMA2)		//得到当前stream是属于DMA2还是DMA1
    	{
    	  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);	//DMA2时钟使能 
    	}else 
    	{
    	  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);	//DMA1时钟使能 
    	}
    	DMA_DeInit(DMA_Streamx);
    	while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}		//等待DMA可配置 
    		
    	/*配置 DMA Stream*/
    	DMA_InitStructure.DMA_Channel = chx;  							//通道选择
    	DMA_InitStructure.DMA_PeripheralBaseAddr = par;					//DMA外设地址
    	DMA_InitStructure.DMA_Memory0BaseAddr = mar;					//DMA存储器0地址
    	DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;			//存储器到外设模式
    	DMA_InitStructure.DMA_BufferSize = ndtr;						//数据传输量 
    	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式
    	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;			//存储器增量模式
    	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位
    	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;	//存储器数据长度:8位
    	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;					//正常模式,即满了就不在接收了,而不是循环存储
    	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;			//中等优先级
    	DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;         
    	DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
    	DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;		//存储器突发单次传输
    	DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发单次传输
    	DMA_Init(DMA_Streamx, &DMA_InitStructure);						//初始化DMA Stream
    }
    
    /************************************************************
    * @brief    DMA2_Stream5的所有中断,防止接收到的帧过长无法触发串口空闲中断
    * @param	
    ***********************************************************/
    void DMA2_Stream5_IRQHandler(void)
    {
    if(DMA_GetFlagStatus(DMA2_Stream5,DMA_FLAG_TCIF5) != RESET)
    	{
    		DMA_Cmd(DMA2_Stream5, DISABLE); 					//关闭DMA,防止处理其间有数据
    		Rx_DMA_receive_size = RECEIVE_BUF - DMA_GetCurrDataCounter(DMA2_Stream5);
    		if(Rx_DMA_receive_size !=0)
    		{
    //			Receive_DataPack();/*处理接收数据函数*/;
    		}
    		DMA_ClearFlag(DMA2_Stream5,DMA_FLAG_TCIF5 | DMA_FLAG_FEIF5 | DMA_FLAG_DMEIF5 | DMA_FLAG_TEIF5 | DMA_FLAG_HTIF5);//清除DMA2_Steam7传输完成标志
    		while (DMA_GetCmdStatus(DMA2_Stream5) != DISABLE){}	//确保DMA可以被设置
    		DMA_SetCurrDataCounter(DMA2_Stream5, RECEIVE_BUF);
            DMA_Cmd(DMA2_Stream5, ENABLE);    			 		//打开DMA
    	}
    }
    

    usart.c文件中串口1初始化函数和串口中断函数

    #define RECEIVE_BUF_SIZE  100		//定义最大接收字
    extern  u8 Rx_DMA_receive[];  		//100bytes buffer
    /************************************************************
    * @brief    串口 1 初始化函数
    * @param	bound:波特率
    ***********************************************************/
    void uart_init(u32 bound)
    	{
    	//GPIO端口设置
    	GPIO_InitTypeDef GPIO_InitStructure;
    	USART_InitTypeDef USART_InitStructure;
    	//开启GPIO与串口时钟
    	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); 		//使能GPIOA时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);		//使能USART1时钟
     
    	//串口1对应引脚复用映射
    	GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); 	//GPIOA9复用为USART1
    	GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);	//GPIOA10复用为USART1
    	
    	//串口1的GPIO配置
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; 	//GPIOA9,GPIOA10
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;				//复用功能
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;			//速度50MHz
    	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; 				//推挽复用输出
    	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; 				//上拉
    	GPIO_Init(GPIOA,&GPIO_InitStructure); 						//初始化PA9,PA10
    
        //USART1初始化设置
    	USART_InitStructure.USART_BaudRate = bound;					//波特率设置
    	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(USART1, &USART_InitStructure); 					//初始化串口1
    	
    #if USE_USART_DMA_RX	
    	//Usart1 开启中断 + NVIC 配置
    	USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);				//开启串口空闲中断
    	NVIC_Configuration(1,1);									//中断优先级配置
    	
    	USART_DMACmd(USART1,USART_DMAReq_Rx, ENABLE); 				//使能的DMA收
    	//DMA2,STEAM5,CH4,外设为串口1,存储器为SendBuff,方向为外设到存储器,长度为:RECEIVE_BUF_SIZE.
    	u2DMA_RX_Config(DMA2_Stream5,DMA_Channel_4,(u32)&USART1->DR,(u32)Rx_DMA_receive,100);
    #else
    	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);				//开启串口接收中断
    	NVIC_Configuration(3,3);
    #endif
    	
    	
    #if USE_USART_DMA_TX
    	USART_DMACmd(USART1,USART_DMAReq_Tx, ENABLE); 				//使能的DMA发
    	//DMA2,STEAM7,CH4,外设为串口1,存储器为Rxbuf,方向为存储器到外设,长度为:RECEIVE_BUF_SIZE.
     	u2DMA_TX_Config(DMA2_Stream7,DMA_Channel_4,(u32)&USART1->DR,(u32)Rxbuf,0);	
    #endif
    	
    	USART_Cmd(USART1, ENABLE); 					  				//使能串口1,要放最后
    }
    
    u8 dum;
    u8 Rx_DMA_receive[RECEIVE_BUF_SIZE];  //100bytes
    u8 Rx_DMA_receive_size = 0;
    /************************************************************
    * @brief    串口 1 中断函数
    * @param	
    ***********************************************************/
    void USART1_IRQHandler(void)
    {
    #if SYSTEM_SUPPORT_OS 		//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
    	OSIntEnter();    
    #endif
    	
    #if USE_USART_DMA_RX
    	/*空闲中断 */
    	if(USART_GetITStatus(USART1,USART_IT_IDLE) != RESET)
    	{		
    		dum = USART1->SR;
    		dum = USART1->DR;	//读取SR DR数据可清除“空闲中断”标志
    		Rx_DMA_receive_size = RECEIVE_BUF_SIZE - DMA_GetCurrDataCounter(DMA2_Stream5); //本帧数据长度=DMA准备的接收数据长度-DMA已接收数据长度
    		
    		if( 0 != Rx_DMA_receive_size)
    		{
    			Receive_DataPack();/*DMA接收模式时处理接收数据函数*/
    		}
    		
    		DMA_Cmd(DMA2_Stream5,DISABLE);							//暂停DMA传输 
    		// 清除DMA中断标志位
    		DMA_ClearFlag(DMA2_Stream5,DMA_FLAG_TCIF5 | DMA_FLAG_FEIF5 | DMA_FLAG_DMEIF5 | DMA_FLAG_TEIF5 | DMA_FLAG_HTIF5);//清除DMA2_Steam7传输完成标志
    		while (DMA_GetCmdStatus(DMA2_Stream5) != DISABLE){}		//确保DMA可以被设置  
    		DMA_SetCurrDataCounter(DMA2_Stream5,RECEIVE_BUF_SIZE); 	//设置DMA数据传输量 
    		DMA_Cmd(DMA2_Stream5, ENABLE);                     		//开启DMA传输 
    	}
    
    #endif
    	
    #if SYSTEM_SUPPORT_OS 	//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
    	OSIntExit();  											 
    #endif
    } 
    

    其中USE_USART_DMA_RXUSE_USART_DMA_TX两个宏定义放在.h文件作为决定是否开启串口通道DMA功能的开关。这种串口接收方式存在很大的隐患,如果发送端设备发送一帧数据并不是一次性发完的而是分多次发送(期间间隔几毫秒),就会导致收到的数据不完整,如何解决呢?看下面的方法。

    好文链接:

    STM32之串口DMA接收不定长数据

    一个严谨的STM32串口DMA发送&接收(1.5Mbps波特率)机制

    深入解析stm32f407参考手册——总线架构

    STM32的DMA串口直通(外设到外设)

    STM32—无需中断来实现使用DMA接收串口数据

    DMA和UART的深刻认识–串口接收的3种工作方式

    STM32 串口采用DMA方式收发

    STM32使用串口1配合DMA接收不定长数据,大大减轻CPU载荷

    展开全文
  • STM32CubeMX 串口空闲中断+DMA 接收不定长度数据,例程为两个串口透传。对发
  • 在STM32F103C8T6T6单片机上,利用串口空闲中断DMA功能实现Modbus通信时数据检测功能。不再需要利用定时器中断去判断一帧数据是否接收完成。
  • 每个串口DMA收发数据的通道都不一样: 串口配置: u8 receive_data[1280]={0}; u8 RX_data[26] = {0}; void uart_init(u32 bound) { GPIO_InitTypeDef GPIO_InitStruct; USART_InitTypeDef USART_InitStruct; ...
  • STM32 DMA加串口空闲中断接收数据

    千次阅读 2019-12-27 10:42:47
    STM32使用DMA加串口空闲中断接收数据 STM32中,需要用串口接收数据,是使用串口中断来接收数据。但是用这种方法的话,就要频繁进入串口中断,然后处理,效率就比较低。于是就想到用DMA接收串口数据,这个STM32...
  • STM32H750的IDLE串口空闲中断DMA传输UART接收数据、STM32CUBEMX生成MDK5编译
  • 是由空闲中断引起的,因为在空闲中断里面会复位DMA指针,所以说不是所有地方都适合用DMA的,改成查询后我测试过,每秒1K的数据一点丢包都没有
  • STM32F103 串口DMA+空闲中断接收

    热门讨论 2017-11-14 11:54:44
    STM32F103VET6 串口DMA+空闲中断接收接收到的数据用DMA串口实时发送回去。该版本存在一个bug,推荐下载另一个修复后的版本,或者参照我的博文自己进行修复。 修复版本下载链接:...
  • 本文开发环境: MCU型号:STM32F051R8T6 IDE环境: MDK 5.25 代码生成工具:STM32CubeMx 5.2.0 ...串口接收中断的配置 串口接收DMA线的配置 示例程序及起运行流程 附件:代码工程(MDK) 文章...
  • 本文件是使用STM32F103C8T6的串口1的空闲中断+DMA实现接收不定长数据源代码测试文件记录,仅供参考。
  • STM32中,需要用串口接收数据,是使用串口中断接收数据。但是用这种方法的话,就要频繁进入串口中断,然后处理,效率就比较低。于是就想到用DMA接收串口数据,这个STM32也是支持的。但是关键的一点,怎么知道...
  • STM32 串口接收数据 使用串口空闲中断DMA 一. 串口空闲中断 串口空闲中断,是指串口在开始接收到数据后开始检测直到数据接收完为后,串口空闲时产生中断,也就是RXNE(RXNE位在有数据输入时自动置1)被置1开始检测...
  • 一、在底层中初始化DMA接收串口中断 void MX_USART1_UART_Init(void) HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart) &emsp;&emsp;&emsp;&emsp;HAL_UART_MspInit(UART_...
  • 空闲中断+DMA接收数据

    千次阅读 2019-05-26 21:22:52
    目的:使用串口+DMA接收DJI遥控器的数据帧。 (注)Dji的遥控器是每隔7ms返回一帧数据,每一帧数据有18个字节。本文的讲解注重快速使用上手,故细节部分会在后面依次改进,添加。 1.DMA的理解 首先需要讲讲...
  • 串口空闲中断+DMA接收不定长数据

    千次阅读 2019-05-25 13:37:37
      此工程的硬件环境为...一、什么是串口空闲中断,有啥子用?   CSDN上看到的教程大多是直接就编写程序实现空闲中断,但没有对原理性部分阐述清楚,也没有写为什么要这样子写代码,那我就自己来总结一下前人的经...
  • 使用串口空闲中断+DMA接收不定长数据的时候,需要明白,空闲中断是如何产生的,手册上写明了,空闲中断产生的标志是在接收数据后出现的一个byte的高电平。所以这里就有一个问题,当发送端发送数据的时候,一旦出现两...
  • 串口空闲中断+DMA.zip

    2019-05-25 13:18:56
    接收不定长度数据是串口空闲中断的重要使用方法,在裁判系统、OpenMV、 Manifold数据解析的使用中帮助巨大。 1、FSMC为灵活的静态存储控制器,利用控制SRAM的原理控制TFTLCD,方便快捷 2、LCD初始化程序中有厂商...
  • 串口DMA接收+消息队列
  • stm32LL库串口空闲中断+DMA接收

    千次阅读 2021-02-06 09:21:53
    stm32LL库串口空闲中断+DMA接收 俺用的STM32F407VGT6,cubemx生成的代码。 cubemx的串口配置 这里就按默认参数配就行,主要是要打开后面的DMA 在这里点添加就完事了,如果只用串口接收,那只开RX就行,我把TX也开了...
  • ST推的HAL库,在整个接收过程中,是没有用到串口的接收空闲中断,它的处理有三种,分别是轮询,接收完成中断(每一个字节一次),DMA接收。 整个Hal库把接收和发送过程都封装好了,就用最简单的轮询方式看,先看...
  • STM32CubeMX—串口空闲中断+DMA接收-附件资源
  • STM32F103VET6 串口DMA+空闲中断接收接收到的数据用DMA串口实时发送回去。相对于之前上传的版本,修复了一个bug。 关于bug的详细描述,参考我的博文:https://blog.csdn.net/mickey35/article/details/78521311
  • 1.我们知道DMA

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,636
精华内容 1,054
关键字:

串口空闲中断加dma接收