精华内容
下载资源
问答
  • STM32F103 串口DMA+空闲中断接收

    热门讨论 2017-11-14 11:54:44
    STM32F103VET6 串口DMA+空闲中断接收,接收到的数据用DMA串口实时发送回去。该版本存在一个bug,推荐下载另一个修复后的版本,或者参照我的博文自己进行修复。 修复版本下载链接:...
  • STM32 HAL库+串口DMA+空闲中断(IDLE)实现不定长数据接收,可以用来参考学习使用,简单易懂。
  • 本例程实现STM32F103ZET6的串口DMA发送与串口DMA接收,配合串口空闲中断可以接收不定长数据。可以参照本人的文章:https://blog.csdn.net/qq_30267617/article/details/118877845。
  • STM32串口空闲中断DMA接收不定长数据消息队列。 串口1空闲中断+DMA,接收不定长数据
  • STM32F103C8T6T6单片机上,利用串口空闲中断DMA功能实现Modbus通信时数据检测功能。不再需要利用定时器中断去判断一帧数据是否接收完成。
  • stm32h743串口DMA+空闲中断,可接受不定长数据,代码中对容易出问题的地方做了完善处理。可
  • STM32CubeMX 串口 DMA+空闲中断实现不定长接收 利用hal库进行修改实现不定长的uart接收,应用于不定长帧的传输
  • ST推的HAL库,在整个接收过程中,是没有用到串口的接收空闲中断,它的处理有三种,分别是轮询,接收完成中断(每一个字节一次),DMA接收。 整个Hal库把接收和发送过程都封装好了,就用最简单的轮询方式看,先看...
  • STM32f0HAL库,STM32CubeMX创建,串口DMA空闲中断接收fifo数据处理程序,实测可用
  • STM32H750的IDLE串口空闲中断DMA传输UART接收数据、STM32CUBEMX生成MDK5编译
  • STM32CubeMX 串口空闲中断+DMA 接收不定长度数据,例程为两个串口透传。对发
  • STM32L4系列 串口通信 空闲中断+DMA 实现任意长度的数据接收 包含两种方式 1:不用在主函数调用DMA接收函数(推荐,非常实用) 2:许多教程的写法,需要在主函数调用判断空闲中断状态再调用DMA接收函数
  • 按照你的《STM32CubeMX 串口空闲中断DMA 实现多串口不定长度收发数据,且不影响收发》一文,进行实验,因我是刚开始学 STM32,所以完全照搬代码和设置,实验成功了,但只要单片机复位或上电后,第一次用串口调试...
  • STM32F103通过串口DMA空闲中断的方式进行数据接收,该代码已经经过项目验证。
  • 在上一篇文章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

    展开全文
  • STM32F103单片机串口DMA+空闲中断接收DEMO源程序。 。
  • 使用STM32cube创建的stm32f407的工程,使用UART1串口DMA 串口空闲中断实现串口收发不定长度数据,节省CPU资源。
  • STM32F103 串口DMA + 空闲中断 实现不定长数据收发

    万次阅读 多人点赞 2020-11-12 15:03:52
    空闲中断1.1 uart_dma.c1.2 uart_dma.h1.3 main.c1.4 stm32f10x_it.c1.5 效果演示1.6 知识补充1.6.1 外设的基地址     DMA(Direct Memory Access,直接存储器访问) 是所有现代电脑的重要特色,...

        串口DMA系列文章一共两篇,分别是利用DMA + 空闲中断实现不定长数据的收发,另一篇是使用DMA中断实现定长数据的收发,文章链接如下:
        01 STM32F103 串口DMA + 空闲中断 实现不定长数据收发
        02 STM32F103 串口 +DMA中断实现数据收发


      2021-06-02 很多小伙伴想要串口空闲中断+DMA完成不定长数据收发的例子,由于写这篇博客时的例程找不到了,特此又写了一遍例程,代码已上传gitee,有需要的小伙伴可以下载查看。Gitee 代码



        DMA(Direct Memory Access,直接存储器访问) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载。否则,CPU 需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回到新的地方。在这个时间中,CPU 对于其他的工作来说就无法使用。
        关于DMA的基础知识请参考文章 https://blog.csdn.net/gdjason/article/details/51019219 ,个人觉得博主的文章最后代码部分写的有点乱,再次做整理记录。串口DMA可以有两种中断触发方式,一种是使用STM32的 IDLE 空闲中断方便接收不定长的数据,使用中也经常采用这种方式,第二种是使用DMA自身的传输完成中断,这两种方式在发送完成后均能产生发送完成中断,不同的地方是空闲中断方便接收不定长的数据,而DMA传输完成中断只有接收到定义好的长度的数据后才会产生接收中断。
     

    1. 空闲中断

        本文采用485总线来实验串口的DMA空闲中断,实现数据的收发测试。485的不同之处是多了一个使能引脚,该引脚是高电平使能发送,低电平使能接收,因此是半双工的通信,其他的和串口一致。本例使用STM32F103的串口1,TX引脚是PA9;RX引脚是PA10,485的使能引脚是PD1。大家在使用时板子上的串口功能测试时无需考虑485的使能引脚,本文中关于485的部分都用 /**** RS485 ****/标识出来,下面是代码部分。

    1.1 uart_dma.c

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    #include "stm32f10x_gpio.h"
    #include "stm32f10x_rcc.h"
    #include "stm32f10x_usart.h"
    #include "stm32f10x_dma.h"
    #include "misc.h"
    
    #include "systick.h"	// 利用嘀嗒计时器实现了ms级的死等延时,用于切换485收发功能使用,实际项目中不能用死等延时
    #include "uart_dma.h"
    
    uint8_t uart1RecvData[32] = {0};    // 接收数据缓冲区
    uint8_t uart1RecvFlag = 0;          // 接收完成标志位
    uint8_t uart1RecvLen = 0;           // 接收的数据长度
    
    uint8_t uart1SendData[32] = {0};    // 发送数据缓冲区
    uint8_t uart1SendFlag = 0;          // 发送完成标志位
    
    /* 串口1 GPIO引脚初始化 */
    void Uart1GpioInit(void)
    {
        GPIO_InitTypeDef GPIO_InitStruct;
    
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   // 使能GPIOA时钟
    
    	/************ ↓ RS485 相关 ↓ ************/
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);   // 使能GPIOD时钟
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;    // 输入输出使能引脚 推挽输出
        GPIO_InitStruct.GPIO_Pin = UART1_EN_PIN;
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(UART1_EN_PORT, &GPIO_InitStruct);     // PD1
        Uart1RxEnable();    // 初始化接收模式
        /************ ↑ RS485 相关 ↑ ************/
        
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;    // TX 推挽输出
        GPIO_InitStruct.GPIO_Pin = UART1_TX_PIN;
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(UART1_TX_PORT, &GPIO_InitStruct);     // PA9
        
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;      // RX上拉输入
        GPIO_InitStruct.GPIO_Pin = UART1_RX_PIN;
        GPIO_Init(UART1_RX_PORT, &GPIO_InitStruct);     // PA10
    }
    
    /************ ↓ RS485 相关 ↓ ************/
    /* 使能485发送 */
    void Uart1TxEnable(void)
    {
        GPIO_WriteBit(UART1_EN_PORT, UART1_EN_PIN, Bit_SET);    // 485的使能引脚,高电平为使能发送
        Delay_ms(5);
    }
    
    /* 使能485接收 */
    void Uart1RxEnable(void)
    {
        GPIO_WriteBit(UART1_EN_PORT, UART1_EN_PIN, Bit_RESET);  // 485的使能引脚,低电平为使能发送
        Delay_ms(5);
    }
    /************ ↑ RS485 相关 ↑ ************/
     
    /* 串口1配置 9600 8n1 */
    void Uart1Config(void)
    {
        USART_InitTypeDef USART_InitStruct;		// 串口配置
        NVIC_InitTypeDef NVIC_InitStructure;	// 中断配置
        DMA_InitTypeDef DMA_InitStruct;			// DMA 配置
        
        USART_DeInit(USART1);   // 寄存器恢复默认值
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);  // 使能串口时钟
        
        /* 串口参数配置 */
        USART_InitStruct.USART_BaudRate = BAUD_RATE;            // 波特率:9600
        USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;    // 无流控
        USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;    // 收发
        USART_InitStruct.USART_Parity = USART_Parity_No;                // 无校验位 
        USART_InitStruct.USART_StopBits = USART_StopBits_1;             // 1个停止位
        USART_InitStruct.USART_WordLength = USART_WordLength_8b;        // 8个数据位
        USART_Init(USART1, &USART_InitStruct);
        USART_Cmd(USART1, ENABLE);  // 使能串口
        
        /* 串口中断配置 */
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;             // 使能
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;   // 抢占优先级
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;          // 子优先级
        NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;           // 串口1中断
        NVIC_Init(&NVIC_InitStructure);     // 嵌套向量中断控制器初始化
    
        USART_ITConfig(USART1, USART_IT_TC,   ENABLE);  // 使能串口发送中断,发送完成产生 USART_IT_TC 中断
        USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);  // 使能串口空闲中断,接收一帧数据产生 USART_IT_IDLE 空闲中断
        
        /* 串口DMA配置 */
        DMA_DeInit(DMA1_Channel4);  // DMA1 通道4,寄存器复位
        DMA_DeInit(DMA1_Channel5);  // DMA1 通道5,寄存器复位
        
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);  // 使能 DMA1 时钟
        
        // RX DMA1 通道5
        DMA_InitStruct.DMA_BufferSize = sizeof(uart1RecvData);      // 定义了接收的最大长度
        DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;             // 串口接收,方向是外设->内存
        DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;                   // 本次是外设到内存,所以关闭内存到内存
        DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)uart1RecvData;// 内存的基地址,要存储在哪里
        DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;// 内存数据宽度,按照字节存储
        DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;        // 内存递增,每次串口收到数据存在内存中,下次收到自动存储在内存的下一个位置
        DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;                  // 正常模式
        DMA_InitStruct.DMA_PeripheralBaseAddr = USART1_BASE + 0x04; // 外设的基地址,串口的数据寄存器
        DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;    // 外设的数据宽度,按照字节存储,与内存的数据宽度一致
        DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;   // 接收只有一个数据寄存器 RDR,所以外设地址不递增
        DMA_InitStruct.DMA_Priority = DMA_Priority_High;            // 优先级
        DMA_Init(DMA1_Channel5, &DMA_InitStruct);
        
        // TX DMA1 通道4  
        DMA_InitStruct.DMA_BufferSize = 0;                          // 发送缓冲区的大小,初始化为0不发送
        DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST;             // 发送是方向是外设到内存,外设作为目的地
        DMA_InitStruct.DMA_MemoryBaseAddr =(uint32_t)uart1SendData; // 发送内存地址,从哪里发送
        DMA_Init(DMA1_Channel4, &DMA_InitStruct);
         
        USART_DMACmd(USART1, USART_DMAReq_Tx | USART_DMAReq_Rx, ENABLE);// 使能DMA串口发送和接受请求
        DMA_Cmd(DMA1_Channel5, ENABLE);     // 使能接收
        DMA_Cmd(DMA1_Channel4, DISABLE);    // 禁止发送
    }
    
    /* 清除DMA的传输数量寄存器 */
    void uart1DmaClear(void)
    {
        DMA_Cmd(DMA1_Channel5, DISABLE);    // 关闭 DMA1_Channel5 通道
        DMA_SetCurrDataCounter(DMA1_Channel5, sizeof(uart1RecvData));   // 重新写入要传输的数据数量
        DMA_Cmd(DMA1_Channel5, ENABLE);     // 使能 DMA1_Channel5 通道
    }
    
    /* 串口1发送数组 */
    void uart1SendArray(uint8_t *arr, uint8_t len)
    {
        if(len == 0)	// 判断长度是否有效
          return;
    	
    	uint8_t sendLen = len>sizeof(uart1SendData) ? sizeof(uart1SendData) : len;	// 防止越界
    
        /************ ↓ RS485 相关 ↓ ************/ 
        Uart1TxEnable();    // 使能发送
        /************ ↑ RS485 相关 ↑ ************/
        
        while (DMA_GetCurrDataCounter(DMA1_Channel4));  // 检查DMA发送通道内是否还有数据
        if(arr) 
          memcpy(uart1SendData, arr, sendLen);
        
        // DMA发送数据-要先关 设置发送长度 开启DMA
        DMA_Cmd(DMA1_Channel4, DISABLE);
        DMA_SetCurrDataCounter(DMA1_Channel4, sendLen);   // 重新写入要传输的数据数量
        DMA_Cmd(DMA1_Channel4, ENABLE);     // 启动DMA发送  
    }
    

     

    1.2 uart_dma.h

    #ifndef _UART_DAM_H_
    #define _UART_DMA_H_
    
    #include <stdint.h>
    
    #define UART1_TX_PORT   GPIOA
    #define UART1_TX_PIN    GPIO_Pin_9
    #define UART1_RX_PORT   GPIOA
    #define UART1_RX_PIN    GPIO_Pin_10
    #define UART1_EN_PORT   GPIOD
    #define UART1_EN_PIN    GPIO_Pin_1
    #define BAUD_RATE       (9600)
    
    extern uint8_t uart1RecvData[32];
    extern uint8_t uart1RecvFlag;
    extern uint8_t uart1RecvLen;
    extern uint8_t uart1SendFlag;
    
    void Uart1GpioInit(void);
    void Uart1Config(void);
    void uart1DmaClear(void);
    void uart1SendArray(uint8_t *arr, uint8_t len);
    
    /************ ↓ RS485 相关 ↓ ************/ 
    void Uart1RxEnable(void);
    void Uart1TxEnable(void);
    /************ ↑ RS485 相关 ↑ ************/
    
    #endif  /* uart_dma.h */
    

     

    1.3 main.c

    #include "uart_dma.h"
    #include "misc.h"
    
    int main()
    { 
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  // 设置中断优先级分组
        
        /************ ↓ RS485 相关 ↓ ************/ 
        SysTickInit();          // 嘀嗒计时器初始化,没用485可以省去
        /************ ↑ RS485 相关 ↑ ************/
        
        Uart1GpioInit();	// 串口GPIO初始化
        Uart1Config();		// 串口和DMA配置
    
        while(1)
        {     
            if(uart1RecvFlag == 1)	// 接收到数据
            {
                uart1RecvFlag = 0;  // 接收标志清空
                uart1DmaClear();    // 清空DMA接收通道
                uart1SendArray(uart1RecvData, uart1RecvLen);        // 使用DMA发送数据
                memset(uart1RecvData, '\0', sizeof(uart1RecvData)); // 清空接收缓冲区
            }
            
            if(uart1SendFlag == 1)
            {
                uart1SendFlag = 0;  // 清空发送标志   
                Uart1RxEnable();    // 发送完成打开接收
            }
        }
    }
    

     

    1.4 stm32f10x_it.c

    #include "stm32f10x_it.h"
    #include "stm32f10x_usart.h"
    #include "stm32f10x_dma.h"
    
    #include "uart_dma.h"
    
    void USART1_IRQHandler(void)    // 串口1 的中断处理函数
    {
        uint8_t clear;
    
        if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)   // 空闲中断
        {
            clear = USART1->SR; // 清除空闲中断
            clear = USART1->DR; // 清除空闲中断
            
            uart1RecvFlag = 1;  // 置接收标志位
            uart1RecvLen = sizeof(uart1RecvData) - DMA_GetCurrDataCounter(DMA1_Channel5);// 总的buf长度减去剩余buf长度,得到接收到数据的长度
        }   
        
        if(USART_GetITStatus(USART1, USART_IT_TC) != RESET)     // 发送完成
        {
            USART_ClearITPendingBit(USART1, USART_IT_TC);       // 清除完成标记
            DMA_Cmd(DMA1_Channel4, DISABLE);                    // 关闭DMA
            uart1SendFlag = 1;                                  // 设置发送完成标志位
        }
    }
    

     

    1.5 效果演示

        本文使用的是串口采用DMA空闲中断接收不定长的数据,收到后将收到的数据使用DMA发送出去,采用串口调试助手进行调试,效果如下:可以看到当数据超过定义的最大数据(本例是32个字节)后,接收只能接收到32个数据,其他的数据将被丢弃。
    在这里插入图片描述
     

    1.6 知识补充

    1.6.1 外设的基地址

        本文外设到基地址定义的是 USART1_BASE + 0x04,为什么是这个呢?查看STM32的参考手册,找到串口章节,查找USART寄存器地址映象如下图:可以看到数据寄存器是串口基地址寄存器偏移 0x04 后的位置。串口1基地址的宏定义可以在 stm32f10x.h 中找到,如下图。
    在这里插入图片描述
    串口1基地址宏定义
     

    1.6.2 空闲中断清除

        产生空闲中断后,先读SR,再读DR可以清除空闲中断标志位,见下图(手册串口章节 25.6.1 状态寄存器(USART_SR))。
    在这里插入图片描述
     

    1.6.3 DMA传输数量

        查看手册,DMA传输数量寄存器里的值表示剩余待传输的字节数,因此在 it.c 中使用 定义的总数-寄存器中的数值=表示收到的个数。这个地方需要特别注意,否则会计算错收到的字节数。
    在这里插入图片描述
        还有一点,每次接收完成之后为了使下次的接收是从内存中的下标0还是存储,需要重新写入要传输的数据数量,否则下次直接接着上次传输的位置开始接收存储,发送时也一样,见代码 uart_dma.c 中的127–129行和149–151行。
        如果注释掉128行,每次不重新写入传输数量寄存器,演示结果如下:从结果可以看出如果注释掉128行,那么DMA往接收缓冲区搬运数据时下标将从上次结束的位置作为起始位置【因为此时传输数量寄存器的值不是0,是32-7】,前面是’/0’ 是因为每次接收完成后都将接收缓冲区清空。所以每次接收一帧数据之后都要将 传输数量寄存器清0,
    在这里插入图片描述

        在DMA学习过程中有什么问题,欢迎一起交流。

    展开全文
  • STM32使用DMA串口空闲中断接收数据,比较实用的一种串口通信操作!
  • 我做这个串口数据接收 dma+空闲中断 加fifo 实现串口的高效收发 ,主要是串口接收的数据长度不定长,时间超时也不好做,还要串口收发的效率要高,采用串口数据的接收 dma+空闲中断+fifo的方式 速度快和效率高,不...

    我做这个串口数据接收 dma+空闲中断 加fifo  实现串口的高效收发  ,主要是串口接收的数据长度不定长,时间超时也不好做,还要串口收发的效率要高,采用串口数据的接收 dma+空闲中断+fifo的方式  速度快和效率高,占用cpu的时间短

    对比了其他几种方式

    1:采用串口中断的话,每接收1byte就得中断一次。这样太消耗CPU资源! 频繁进中断,占用中断,特别是对时间和时序要求比较严格的时候 串口频繁进入中断导致其他中断时序有影响

     

    2:采用DMA方式接收数据,接收的数据长度必须是固定的  对于接收数据长度不固定就不怎么好弄了,特别像gprs通信,接收长度不固定,这些都是困扰我

     

    3:采用dma方式接收数据+定时器超时中断,这样来确定一帧数据完成,需要开关定时器,操作比较复杂,超时时间还不太好设置,stm32f1和f4 没有超时中断还只能采用定时器或者把rxd引脚接到stm32定时器触发引脚上来实现超时, 

    4:stm32串口dma方式接收数据+空闲中断或者超时中断+fifo 这种分内事来实现不定长的数据和高效的串口数据接收   效率比其他的方式要快,消耗cpu的时间比较少,这样应用可以做数据超时

    所以我采用了stm32串口dma方式接收数据+空闲中断或者超时中断+fifo方式来实现

    像stm32f103和stm32f407芯片没有时间超时中断  需要定时器来做超时 比较麻烦,nxp的部分芯片串口有超时中断,atmel的部分芯片串口有超时中断,stm32的h7和f7系列才有超时中断

    要实现stm32串口dma方式接收数据+空闲中断或者超时中断+fifo方式来实现

    第一步 stm32的串口dma配置 ,串口的初始化,还有串口的空闲中断

    1,通过stm32的cubemx软件来生成串口+dma配置的初始化

    cubemx生成代码都是HAL库的模式,我就以stm32f4的HAL库的方式实现

    UART_HandleTypeDef   huart1={0}; 
    
    huart1.Instance = USART1;
      huart1.Init.BaudRate = 115200;
      huart1.Init.WordLength = UART_WORDLENGTH_8B;
      huart1.Init.StopBits = UART_STOPBITS_1;
      huart1.Init.Parity = UART_PARITY_NONE;
      huart1.Init.Mode = UART_MODE_TX_RX;
      huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
      huart1.Init.OverSampling = UART_OVERSAMPLING_16;
      if (HAL_UART_Init(&huart1) != HAL_OK)
      {
      }

    dma的配置可以通过cubemx软件串口+dma方式来生成代码

      /* USER CODE BEGIN USART1_MspInit 0 */
    
      /* USER CODE END USART1_MspInit 0 */
        /* Peripheral clock enable */
        __HAL_RCC_USART1_CLK_ENABLE();
      
        __HAL_RCC_GPIOA_CLK_ENABLE();
        /**USART1 GPIO Configuration    
        PA9     ------> USART1_TX
        PA10     ------> USART1_RX 
        */
        GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
        GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
        /* USART1 DMA Init */
        /* USART1_RX Init */
        hdma_usart1_rx.Instance = DMA2_Stream2;
        hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4;
        hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
        hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
        hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
        hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
        hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
        hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;
        hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW;
        hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
        if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)
        {
          Error_Handler();
        }
    
        __HAL_LINKDMA(huart,hdmarx,hdma_usart1_rx);
    
        /* USART1 interrupt Init */
        HAL_NVIC_SetPriority(USART1_IRQn, 5, 0);
        HAL_NVIC_EnableIRQ(USART1_IRQn);

    2,stm32生成的代码是不支持空闲中断的,需要自己增加空闲中断和空闲中断的处理

        __HAL_UART_ENABLE_IT(uart->h, UART_IT_IDLE);//使能空闲中断HAL代码
        __HAL_UART_CLEAR_IDLEFLAG(uart->h);//使能空闲中断HAL代码

    串口中断的处理

    void USART1_IRQHandler(void)
    { 
    
     HAL_UART_IRQHandler(&huart1); 
    
    if((__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) ? SET : RESET) == SET)
    
    {
    
    //串口空闲中断数据处理 
    
    }
    
    }

    第二步 了解环形fifo的buff特点,通过分析环形buff的特点,其实串口的dma接收 dma模式设置循环模式 就是环形buff

    环形buff的说明讲网站:https://blog.csdn.net/jiejiemcu/article/details/80563422

    转载环形fifo说明出处:    STM32进阶之串口环形缓冲区实现

    以下内容是引用了原文的内容 

    转载原文链接:https://blog.csdn.net/jiejiemcu/article/details/80563422 

     

    实现的原理:初始化的时候,列队头与列队尾都指向0,当有数据存储的时候,数据存储在‘0’的地址空间,列队尾指向下一个可以存储数据的地方‘1’,再有数据来的时候,存储数据到地址‘1’,然后队列尾指向下一个地址‘2’。当数据要进行处理的时候,肯定是先处理‘0’空间的数据,也就是列队头的数据,处理完了数据,‘0’地址空间的数据进行释放掉,列队头指向下一个可以处理数据的地址‘1’。从而实现整个环形缓冲区的数据读写。

      看图,队列头就是指向已经存储的数据,并且这个数据是待处理的。下一个CPU处理的数据就是1;而队列尾则指向可以进行写数据的地址。当1处理了,就会把1释放掉。并且把队列头指向2。当写入了一个数据6,那么队列尾的指针就会指向下一个可以写的地址。

    如果你懂了环形队列,那就跟着一步步用代码实现吧:

    是不是跟dma的circular的模式跟这个环形队列非常相似呢

    第3步就开始写串口dma+空闲中断或超时中断+fifo的代码啦

    重要的代码 串口dma的fifo的数据长度的处理

    //假设dma的接收缓存大小为512       环形buff的位置为 500    
    //接收到数据长度为48 这个时候的环形buff的位置为548                        
    // 548-500=48      但是dma环形buff的特性,
    //dma实际的长度为36 这个时候如何计算长度            
    // 这个算法如512-500 12      548-512       36
    rev_dma_lens =  DMA接收的数据长度
    //buff_index-----串口接收和app的buff的序列号
    //环形数组
    if (puart->ndtr_last != rev_dma_lens) //上次与这次不同,表示有新数据
    {
    
            start_addr = start_addr + len;//环形数据地址偏移量
            //
            if (start_addr >= 512)
            {
                puart->start_addr = start_addr - 512;
            }
            //
            if (rev_dma_lens > ndtr_last)
            {
                len = rev_dma_lens - ndtr_last; //接收数据长度=上次长度-这次长度
            }
            else
            {
                len =  512 - ndtr_last + rev_dma_lens; //环形数据到头后,总数-这次剩余+上次剩余
            }
            //
    
           ndtr_last = rev_dma_lens;
    
    }

    但是这种方式会2个问题:

    1,串口一直有数据接收 不产生空闲中断 这个时候会有问题  解决的方法采用超时方法来解决  或者采用一个一个的数据处理方式采用每来一个数据产生一个中断方式

    2, 一个数据包超过dma的最大buff长度  已经越界 导致了部分数据被覆盖啦   解决方法就是把DMA的接收buff定义更大一点 最好定义为2倍的一个最大数据包 考虑资源也可以考虑1.5或1倍的一个最大数据包 或者采用一个一个的数据处理方式  采用每来一个数据产生一个中断方式

    展开全文
  • 本例程实现STM32F103ZET6的串口DMA发送与串口DMA接收,配合串口空闲中断可以接收不定长数据。讲解内容可以参照本人的文章:https://blog.csdn.net/qq_30267617/article/details/118877845。
  • STM32 DMA串口空闲中断接收数据

    千次阅读 2019-12-27 10:42:47
    STM32使用DMA串口空闲中断接收数据 STM32中,需要用串口接收数据,是使用串口中断来接收数据。但是用这种方法的话,就要频繁进入串口中断,然后处理,效率就比较低。于是就想到用DMA来接收串口数据,这个STM32...

    STM32使用DMA加串口空闲中断接收数据

     

    STM32中,需要用串口接收数据,是使用串口中断来接收数据。但是用这种方法的话,就要频繁进入串口中断,然后处理,效率就比较低。于是就想到用DMA来接收串口数据,这个STM32也是支持的。但是关键的一点,怎么知道数据接收完毕了呢?如果接收的数据长度固定,那就好办,直接设置DMA的接收数据个数就行了。但是如果长度不固定了,那应该怎么办了?

    这个时候,就要用到STM32在串口中提供的另一个好用的东西了,就是串口空闲中断。在STM32的串口控制器中,设置了有串口空闲中断,即如果串口空闲,又开启了串口空闲中断的话,就触发串口空闲中断,然后程序就会跳到串口中断去执行。有了这个,是不是可以判断什么时候串口数据接收完毕了呢?因为串口数据接收完毕后,串口总线肯定是会空闲的嘛,那这个中断肯定是会触发的了。

    还有一个问题,这串口空闲中断是只要串口空闲就会产生吗?其实不是的,串口空闲中断要触发的话,是要RXNE位被置位后,串口总线空闲才会触发的。所以我们不用担心,串口数据发送完毕后,会不会触发串口空闲中断了。

    下面用代码来说明。

    1、配置串口。包括设置串口的引脚配置,串口的配置,串口中断的配置,串口的接收DMA的配置

    void USART_init(void)
    {
       GPIO_InitTypeDef   GPIO_InitStructure;
        USART_InitTypeDef  USART_InitStructure;
        NVIC_InitTypeDef   NVIC_InitStructure;  
     
        //开启时钟
        RCC_APB2PeriphClockCmd(USART_RCC,ENABLE);
        //配置TX端口
        GPIO_InitStructure.GPIO_Pin = GPIO_USART_TX;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIO_USART_TYPE,&GPIO_InitStructure);
        //配置RX端口
        GPIO_InitStructure.GPIO_Pin = GPIO_USART_RX;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_Init(GPIO_USART_TYPE,&GPIO_InitStructure);
       
        //配置串口模式
        USART_InitStructure.USART_BaudRate = 115200;
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;
        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);
        
        //中断配置
        NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);
     
        /* 若总线空闲,产生中断 */
        USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);  
     
        /*开启串口DMA接收*/
        USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
        USART_Cmd(USART1,ENABLE);
    }

    代码比较简单,一看就明白了,这就是使用库函数开发的好处,代码易懂。这里,关键的是要开启总线空闲中断,并且开启串口DMA接收。注意,不要开启串口接收中断,不然接收数据就会一直产生中断了。

    2、DMA配置

    DMA配置,要先查看串口接收是使用的哪个DMA的哪个通道,对于USART1_RX使用的是DMA1的5通道。

    然后就是代码配置DMA了。

    void DMA_init(void)
    {
       DMA_InitTypeDef    DMA_Initstructure;
    //   NVIC_InitTypeDef   NVIC_Initstructure;
        
       /*开启DMA时钟*/
       RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
       
    //   /* Enable the DMA1 Interrupt */
    //   NVIC_Initstructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;       //通道设置为串口1中断
    //   NVIC_Initstructure.NVIC_IRQChannelSubPriority = 1;     //中断响应优先级0
    //   NVIC_Initstructure.NVIC_IRQChannelPreemptionPriority=1;
    //   NVIC_Initstructure.NVIC_IRQChannelCmd = ENABLE;        //打开中断
    //   NVIC_Init(&NVIC_Initstructure);  
     
       /*DMA配置*/
       DMA_Initstructure.DMA_PeripheralBaseAddr =  (u32)(&USART1->DR);;
       DMA_Initstructure.DMA_MemoryBaseAddr     = (u32)receive_data;
       DMA_Initstructure.DMA_DIR = DMA_DIR_PeripheralSRC;
       DMA_Initstructure.DMA_BufferSize = 128;
       DMA_Initstructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
       DMA_Initstructure.DMA_MemoryInc =DMA_MemoryInc_Enable;
       DMA_Initstructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
       DMA_Initstructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
       DMA_Initstructure.DMA_Mode = DMA_Mode_Normal;
       DMA_Initstructure.DMA_Priority = DMA_Priority_High;
       DMA_Initstructure.DMA_M2M = DMA_M2M_Disable;
       DMA_Init(DMA1_Channel5,&DMA_Initstructure);
       
       //启动DMA
       DMA_Cmd(DMA1_Channel5,ENABLE);  
     
       //开启DMA发送发成中断
       //DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE); 
    }

    因为这里,不需要用到DMA中断,所以DMA中断就不要使能了。因此DMA中断配置也就不需要了。这里,关键的是要设置DMA_DIR为DMA_DIR_PeripheralSRC,表示数据是从外设到内存。这里设定的DMA_Mode是普通模式,即数据传输就只能一次。

    3、串口中断程序编写

    这个就是关键的地方了。在这里,需要做什么了。需要对DMA设置下。当进入这个中断的时候,串口接收的数据,已经在内存的数组中了。通过读取DMA的计数值,就可以知道接收到了多少个数据。然后再把DMA给diable掉,重新设置接收数据长度,在开启DMA,接收下一次串口数据。为什么要这么做了,因为在STM32手册中有如下说明:

    另外还有一点,串口空闲中断触发后,硬件会自动将串口空闲中断标志位给置1,我们是需要将给标志位给置0的,不然又要进中断了,这个在手册中也有说明。

    代码就如下了:

    void USART1_IRQHandler(void)
    {
        unsigned char num=0;
        if(USART_GetITStatus(USART1,USART_IT_IDLE) == SET)
        {
           num = USART1->SR;
           num = USART1->DR; //清USART_IT_IDLE标志
           DMA_Cmd(DMA1_Channel5,DISABLE);    //关闭DMA
           num = 128 -  DMA_GetCurrDataCounter(DMA1_Channel5);      //得到真正接收数据个数  
           receive_data[num] = '\0';
           DMA1_Channel5->CNDTR=128;       //重新设置接收数据个数        DMA_Cmd(DMA1_Channel5,ENABLE);  //开启DMA
           receive_flag = 1;           //接收数据标志位置1
        }
    }

    关键的一点,就是要读取SR,DR,将USART_IT_IDLE标志给清掉,然后DMA设置要注意下。

    在主函数中,使用下面代码测试:

     int main()
    {
        periph_init();
        printf("hello world\n");
        while(1)
        {
           while(receive_flag == 0);
           receive_flag = 0;
           printf("%s",receive_data);
        }
    }

    当串口接收数据后,中断程序会使receive_flag为1,然后就跳出while循环。打印接收到的数据。

    测试结果:

    发送什么,就接收什么。

    还测试了下,在波特率460800下,都还是能正常的工作的。

    展开全文
  • 最近没事瞎研究起来了STM32串口接收功能,按照以前我的解决方案是 使用串口中断接收一个字符然后进中断处理,然后再手动写代码实现空闲读取,代码如下 // 全局数据定义 char rxBuff[64], *pRxBuff; char rxData...
  • STM32 LL库 串口接收空闲中断接收数据 , STM32 cubemx生成工程
  • stm32串口通过DMA数据传输和空闲中断可以增加mcu的利用率。
  • 本文件是使用STM32F103C8T6的串口1的空闲中断+DMA实现接收不定长数据源代码测试文件记录,仅供参考。
  • 一、STM32CubeMX开发 1.1 新建工程 file -> new project 选择芯片-> stm32 core内核 stm32 series 系列 stm32 line stm32 package 选择芯片根据自身需求去选择,目前该项目是stm32f0系列 stm32 ...
  • ## 阅读须知 阅读本文需要有一定的...我们都知道,在用STM32串口的时候,使用DMA传输和串口空闲中断很香。 原理大家都懂,就是发生串口空闲中断的时候,就表示数据传输完成。然后就执行数据处理或者使用就好了。...
  • STM32CubeMX—串口空闲中断+DMA接收

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

空空如也

空空如也

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

stm32串口dma空闲中断