基于标准库实现的Stm32407串口1空闲中断DMA收发详解

文章目录

  • 1 简介
  • 1.1 什么是串口空闲中断
  • 1.2 DMA简介
  • 1.3 DMA模式
  • 1.4 DMA请求映射
  • 1.4 DMA配置简述
  • 2 DMA收发代码实现
  • 2.1 定义收发结构体
  • 2.2 DMA配置
  • 2.3 串口配置
  • 2.4 中断配置
  • 2.5 DMA发送
  • 1 简介

    stm32串口的配置很简单,这里就不赘述了,使用USART_SendData() 阻塞模式发送数据,或是接收中断配置 “接收缓冲区非空” USART_IT_RXNE,这种做法效率很低,而且来一个数据中断一次数据处理起来也麻烦。
    这里基于STM32F407提供一种串口空闲中断+DMA接收的方式,通过库函数编程实现。

    1.1 什么是串口空闲中断

    初学者一开始学习配置串口中断时经常将中断条件配置为USART_IT_RXNE,也就是接收缓冲区非空,每接收一个U8数据便产生一次中断,非常消耗系统资源。
    而空闲中断USART_IT_IDLE,俗称帧中断,即第一帧数据接收完毕到第二帧数据开始接收期间存在一个空闲状态(就是没数据接收的状态),检测到此空闲状态后即执行中断程序。进入中断程序即意味着已经接收到一组完整帧数据,仅需及时对数据处理或将数据转移出缓冲区即可。

    1.2 DMA简介

    DMA–DirectMemoryAccess(存储器直接访问)是指一种高速的数据传输操作,允许在外部设备和存储器之间直接读写数据,既不通过CPU,也不需要CPU干预。整个数据传输操作在一个称为“DMA控制器”的控制下进行。CPU除了在数据传输开始和结束时做一点处理外,在传输过程中还可以进行其他的工作。这样,在大部分时间里,CPU和输入输出都处于并行操作,因此使整个计算机系统的效率大大提高。

    1.3 DMA模式

    STM32中DMA有3种传输模式,如下表所示
    STM32F407
    其中DMA2支持上表3种传输模式,而DMA1只支持外设到存储器和存储器到外设两种模式。

    1.4 DMA请求映射

    每一个外设请求都会占用一个数据流通道,同一个外设请求可以占用不同的数据流通道,如下表所示
    STM32F407

    例如本次使用的是STM32F407的USART1的,查上表发现串口1的发送USART1_TX是通道4数据流7,USART1_RX是通道4数据流5或通道4数据流2

    1.4 DMA配置简述

    1.配置DMA结构体 DMA_InitTypeDef ,使用DMA_Init()初始化DMA配置
    2.启用外设(串口)DMA接口 USART_DMACmd()
    3.使能DMA DMA_Cmd()
    DMA结构体 DMA_InitTypeDef 具体成员含义自行参考库函数源代码说明。

    2 DMA收发代码实现

    2.1 定义收发结构体

    为了方便管理,定义了收发缓冲区的结构体。

    #define RECEIVE_BUF_MAX_SIZE 	100   //DMA单次最大搬运数据量
    #define TRANSMIT_BUF_MAX_SIZE 50   //DMA单次最大搬运数据量
    ///定义数据接收结构体
    typedef struct _ReceiveBuffer{
    	uint8_t Buffer[RECEIVE_BUF_MAX_SIZE];//用于接收DMA搬运的接收数据
    	uint16_t Lenth;//接收的数据长度
    }ReceiveBuffer_t;
    ///定义数据发送结构体
    typedef struct _TransmitBuffer{
    	uint8_t Buffer[TRANSMIT_BUF_MAX_SIZE];//用于接收DMA搬运的发送数据
    	uint16_t Lenth;//发送的数据长度
    }TransmitBuffer_t;
    
    static ReceiveBuffer_t ReceiveBuffer;//数据接收结构体
    static TransmitBuffer_t TransmitBuffer;//数据发送结构体
    /*DMA接收配置结构体*/
    static DMA_InitTypeDef DMA_TransmitInitStruct;
    

    2.2 DMA配置

    1.DMA接收配置
    配置根据上表查询的数据流通道,设置方向为外设到存储器,配置外设地址及存储器地址,设置最大搬运数量。

    static void USART_ReceiveDMA_init(void){
    	DMA_InitTypeDef DMA_ReceiveInitStruct;
    	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
    	DMA_ReceiveInitStruct.DMA_Channel = DMA_Channel_4;	//设置DMA通道
        DMA_ReceiveInitStruct.DMA_PeripheralBaseAddr = (uint32_t) &USART1->DR;//设置外设地址
      	DMA_ReceiveInitStruct.DMA_Memory0BaseAddr = (uint32_t) ReceiveBuffer.Buffer;//设置内存地址
      	DMA_ReceiveInitStruct.DMA_DIR =  DMA_DIR_PeripheralToMemory;//设置搬运方向:外设到内存
      	DMA_ReceiveInitStruct.DMA_BufferSize = RECEIVE_BUF_MAX_SIZE;//搬运数据长度
      	DMA_ReceiveInitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址在搬运过程中是否自增(设置不自增)
      	DMA_ReceiveInitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;//内存地址在搬运过程中是否自增(设置自增)
      	DMA_ReceiveInitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//数据大小为一个字节
      	DMA_ReceiveInitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//数据大小为一个字节
    	DMA_ReceiveInitStruct.DMA_Mode = DMA_Mode_Normal;//无需循环
    	DMA_ReceiveInitStruct.DMA_Priority = DMA_Priority_Medium;
     	DMA_ReceiveInitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable;
      	DMA_ReceiveInitStruct.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
        DMA_ReceiveInitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single;
        DMA_ReceiveInitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
      	DMA_Init(DMA2_Stream5, &DMA_ReceiveInitStruct);
    	USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
    	DMA_Cmd(DMA2_Stream5, ENABLE);//默认开启DMA接收
    }
    

    2.DMA发送配置
    与DMA接收类似,只是现在是从存储器到外设。

    static void USART_TransmitDMA_init(void){
    	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
    	DMA_TransmitInitStruct.DMA_Channel = DMA_Channel_4;
      	DMA_TransmitInitStruct.DMA_PeripheralBaseAddr = (uint32_t) &USART1->DR;
      	DMA_TransmitInitStruct.DMA_Memory0BaseAddr = (uint32_t) TransmitBuffer.Buffer;
      	DMA_TransmitInitStruct.DMA_DIR = DMA_DIR_MemoryToPeripheral;
      	DMA_TransmitInitStruct.DMA_BufferSize =TRANSMIT_BUF_MAX_SIZE;
      	DMA_TransmitInitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
      	DMA_TransmitInitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
      	DMA_TransmitInitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
      	DMA_TransmitInitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
      	DMA_TransmitInitStruct.DMA_Mode = DMA_Mode_Normal;
      	DMA_TransmitInitStruct.DMA_Priority = DMA_Priority_Medium;
      	DMA_TransmitInitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable;
      	DMA_TransmitInitStruct.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
      	DMA_TransmitInitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single;
      	DMA_TransmitInitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
      	DMA_Init(DMA2_Stream7, &DMA_TransmitInitStruct);
    	USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);
    	DMA_Cmd(DMA2_Stream7, DISABLE);//默认不开启DMA发送,需要后续触发
    }
    

    2.3 串口配置

    串口配置很简单,这里不赘述。

    static void USART_init(void){
    
      	GPIO_InitTypeDef GPIO_InitStructure;
    	USART_InitTypeDef USART_InitStructure;
    	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); 
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
    //GPIO端口设置
    	//串口1对应引脚复用映射
    	GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIO复用
    	GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIO复用
    	
      	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; 
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;	//速度50MHz
    	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //开漏
    	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
    	GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化管脚
       //USART1 初始化设置
    	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(USART1, &USART_InitStructure); //初始化串口
    	USART_Cmd(USART1, ENABLE);  //使能串口
    //收发DMA配置	
    	BLE_USART_ReceiveDMA_init();//接收DMA配置
    	BLE_USART_TransmitDMA_init();//发送DMA配置
    //配置中断
    	BLE_USART_NVIC_Config();
    }
    

    2.4 中断配置

    1.中断配置

    static void USART_NVIC_Config(void){
    	//配置中断
    	NVIC_InitTypeDef NVIC_InitStructure;
    	//USART 空闲NVIC
    	NVIC_InitStructure.NVIC_IRQChannel =USART1_IRQn;//串口1中断通道
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;		//子优先级3
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
    	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
    	USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//开启串口空闲中断
    	//DMA NVIC
      NVIC_InitStructure.NVIC_IRQChannel =DMA2_Stream5_IRQn;
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;		
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			
    	NVIC_Init(&NVIC_InitStructure);	
    	DMA_ITConfig(DMA2_Stream5,DMA_IT_TC,ENABLE);  
    }
    
    1. 串口中断函数
      串口中断配置为空闲中断,当一帧数据接收完毕后与下一帧数据到达之前有一段空闲,系统会在这段空闲时进入中断,我们通过DMA搬运计数可以计算出DMA搬运了多少数据(即一帧数据的大小),并在该中断中处理这个数据帧。
    void USART1_IRQHandler(void)                
    {
    	//空闲中断
    	if(USART_GetITStatus(USART1,USART_IT_IDLE) != RESET){
    		uint8_t clear;
    		DMA_Cmd(DMA2_Stream5, DISABLE);  //关闭接收DMA,防止处理其间有数据
        	clear = USART1->SR;
       		clear = USART1->DR;  		//清除IDLE标志			
    		USART_ClearITPendingBit(USART1,USART_IT_IDLE);//清除标志位	
    		ReceiveBuffer.Lenth=RECEIVE_BUF_MAX_SIZE - DMA_GetCurrDataCounter(DMA2_Stream5);
    		/*
    		此处添加处理数据的代码
    		*/
    		DMA_ClearFlag(DMA2_Stream5,DMA_FLAG_TCIF5 | DMA_FLAG_FEIF5 | DMA_FLAG_DMEIF5 | DMA_FLAG_TEIF5 | DMA_FLAG_HTIF5);//清除DMA2_Steam5传输完成标志  
        DMA_SetCurrDataCounter(DMA2_Stream5, RECEIVE_BUF_MAX_SIZE);
        DMA_Cmd(DMA2_Stream5, ENABLE);
    	}
    }
    
    1. DMA中断函数
    void DMA2_Stream5_IRQHandler(void)  
    {  
        //清除标志  
        if(DMA_GetFlagStatus(DMA2_Stream5,DMA_FLAG_TCIF5)!=RESET)//等待DMA2_Steam7传输完成  
        {   
            DMA_Cmd(DMA2_Stream5, DISABLE); //关闭DMA,防止处理期间有数据  
      
            ReceiveBuffer.Lenth =RECEIVE_BUF_MAX_SIZE - DMA_GetCurrDataCounter(DMA2_Stream5);  
            if(ReceiveBuffer.Lenth !=0)  
            {  
    			/*RC_Process();//处理缓冲区ReceiveBuffer的数据*/
            }            
            DMA_ClearFlag(DMA2_Stream5,DMA_FLAG_TCIF5 | DMA_FLAG_FEIF5 | DMA_FLAG_DMEIF5 | DMA_FLAG_TEIF5 | DMA_FLAG_HTIF5);//清除DMA2_Steam7传输完成标志  
            DMA_SetCurrDataCounter(DMA2_Stream5, RECEIVE_BUF_MAX_SIZE);  
            DMA_Cmd(DMA2_Stream5, ENABLE);     //打开DMA 
        }  
    }
    

    2.5 DMA发送

    void DMA_Send(char* data){
    	uint16_t datalenth=strlen(data);
    	memcpy(TransmitBuffer.Buffer,data,datalenth);
    	DMA_TransmitInitStruct.DMA_BufferSize=datalenth;
    	DMA_DeInit(DMA2_Stream7);//先关闭DMA
    	while (DMA_GetCmdStatus(DMA2_Stream7) != DISABLE){}//等待DMA可配置 
    	DMA_Init(DMA2_Stream7, &DMA_TransmitInitStruct);
    	DMA_Cmd(DMA2_Stream7, ENABLE);                      //开启DMA传输 
    }
    
    物联沃分享整理
    物联沃-IOTWORD物联网 » 基于标准库实现的Stm32407串口1空闲中断DMA收发详解

    发表评论