使用STM32 F1的DMA实现串口空闲中断数据发送

DMA实现数据发送

文章目录

  • DMA实现数据发送
  • 前言
  • 一、DMA
  • 二、代码编写
  • 1.DMA
  • 2.USART
  • 3.main

  • 前言

    当你遇到通信数据量大的时候,可以使用 空闲中断 + DMA 的方案来减轻 CPU 的压力。
    或者
    在进行stm32开发时,有时会遇到这种情况:需要在设备间进行数据传输,由于stm32串口RDR和TDR寄存器都是8位有效的,我们往往需要定义传输协议(如一帧数据中,包含包含帧头、帧ID、数据帧、校验帧等若干8位数据)。我们希望可以一次收到一帧数据,并进行解码操作。利DMA+串口空闲中断可以有效完成上述任务。

    一、DMA

    1、简介
    DMA(直接存储器访问)是一种数据传输方法,利用DMA控制器,将数据直接从一个地址空间复制到另一个地址空间。
    DMA在硬件ROM和IO设备间开辟直接传输数据的通道,不需要CPU主控芯片控制,也不需要类似中断处理那种保留现场&恢复现场的操作。这大大减小了CPU的负担。
    2、使用场景
    DMA用在只需要传输数据,不需要处理数据的地方,有三种传输方式:

    外设→存储器(例:从串口RDR寄存器写入某数据buf)
    存储器→外设(例:从某数据buf写入串口TDR寄存器)
    存储器→存储器(例:复制某特别大的数据buf)

    DMA相关的参数:1 数据的源地址、2 数据传输的目标地址 、3 传输宽度,4 传输多少字节,5 传输模式。

    传输宽度是指一次传输数据的的大小,可以为字节(8b)、半字(16b)、字(32b)

    传输模式分为正常模式(一次结束)和循环模式

    DMA通道

    STM32 最多有 2 个 DMA 控制器(DMA2 仅存在大容量产品中), DMA1 有 7 个通道。 DMA2 有 5个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁起来协调各个 DMA 请求的优先权。每个通道都直接连接专用的硬件 DMA 请求,每个通道都同样支持软件触发。这些功能通过软件来配置。

    FIFO介绍:
    DMA接收有两种模式,一种为直接模式,另一种为FIFO模式
    FIFO为缓存区,大小为32位(16个字节,8个半字,4个字),独立的源和目标传输宽度(字节、半字、字),在单位上分为三种读取FIFO的方式,为(字节、半字、字)每个数据流都有一个独立的 4 字 FIFO,阈值级别可由软件配置为 1/4、1/2、3/4 或满。

    二、代码编写

    1.DMA

    #include "DMA.h"
     
    u16 DMA1_MEM_LEN;//保存DMA每次数据传送的长度         
    //DMA1的各通道配置
    //这里的传输形式是固定的,这点要根据不同的情况来修改
    //从存储器->外设模式/8位数据宽度/存储器增量模式
    //DMA_CHx:DMA通道CHx
    //cpar:外设地址
    //cmar:存储器地址
    //cndtr:数据传输量 
    void MyDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
    {
        DMA_InitTypeDef DMA_InitStructure;
        
        DMA1_MEM_LEN=cndtr;
        
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);    //使能DMA传输
        
        DMA_DeInit(DMA_CHx);   //将DMA的通道1寄存器重设为缺省值
        
        DMA_InitStructure.DMA_BufferSize=cndtr;//DMA通道的DMA缓存的大小(转运的数据量)
        DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//数据传输方向,从外设读取发送到内存
        DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;//DMA通道x没有设置为内存到内存传输
        DMA_InitStructure.DMA_MemoryBaseAddr=cmar;//DMA内存基地址
        DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;//数据宽度为8位
        DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//内存地址寄存器递增
        DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;//工作在正常模式
        DMA_InitStructure.DMA_PeripheralBaseAddr=cpar;//DMA外设基地址
        DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;//数据宽度为8位
        DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//外设地址寄存器不变
        DMA_InitStructure.DMA_Priority=DMA_Priority_Medium; //DMA通道 x拥有中优先级 
        DMA_Init(DMA_CHx,&DMA_InitStructure);
        
        USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);//使能外设的DMA通道,这句可以放在对应的外设里
    }
     
    //开启一次DMA传输
    void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
    { 
        DMA_Cmd(DMA_CHx, DISABLE );  //关闭所指示的通道      
         DMA_SetCurrDataCounter(DMA_CHx,DMA1_MEM_LEN);//重新设定DMA通道的DMA缓存的大小
         DMA_Cmd(DMA_CHx, ENABLE);  //使能所指示的通道 
    }      
     
    

    首先,定义了全局变量DMA1_MEM_LEN用于保存每次数据传输的长度。然后,使用MyDMA_Config函数配置DMA通道的参数,包括DMA缓存大小、数据传输方向、内存地址寄存器递增等。其中,cpar表示外设地址,cmar表示存储器地址,cndtr表示数据传输量。在配置完成后,通过USART_DMACmd函数使能外设的DMA通道。

    接着,使用MYDMA_Enable函数开启一次DMA传输。该函数首先关闭所指示的DMA通道,然后重新设定DMA缓存的大小,并使能DMA通道。

    #ifndef __DMA_H
    #define __DMA_H
     
    #include "sys.h"
     
    #define rx_buff_maxlen 200//定义接受缓存区最长长度
    void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx);
    void MyDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr);
     
    #endif
    

    2.USART

    #include "sys.h"
    #include "usart.h"	  
    #include "DMA.h"	  
     u8 len,Flag=0;
    // 	 
    //如果使用ucos,则包括下面的头文件即可.
    #if SYSTEM_SUPPORT_OS
    #include "includes.h"					//ucos 使用	 
    
    #endif
    //	 
    //本程序只供学习使用,未经作者许可,不得用于其它任何用途
    //ALIENTEK STM32开发板
    //串口1初始化		   
    //正点原子@ALIENTEK
    //技术论坛:www.openedv.com
    //修改日期:2012/8/18
    //版本:V1.5
    //版权所有,盗版必究。
    //Copyright(C) 广州市星翼电子科技有限公司 2009-2019
    //All rights reserved
    //********************************************************************************
    //V1.3修改说明 
    //支持适应不同频率下的串口波特率设置.
    //加入了对printf的支持
    //增加了串口接收命令功能.
    //修正了printf第一个字符丢失的bug
    //V1.4修改说明
    //1,修改串口初始化IO的bug
    //2,修改了USART_RX_STA,使得串口最大接收字节数为2的14次方
    //3,增加了USART_REC_LEN,用于定义串口最大允许接收的字节数(不大于2的14次方)
    //4,修改了EN_USART1_RX的使能方式
    //V1.5修改说明
    //1,增加了对UCOSII的支持
    // 	  
     
    
    //
    //加入以下代码,支持printf函数,而不需要选择use MicroLIB	  
    #if 1
    #pragma import(__use_no_semihosting)             
    //标准库需要的支持函数                 
    struct __FILE 
    { 
    	int handle; 
    
    }; 
    
    FILE __stdout;       
    //定义_sys_exit()以避免使用半主机模式    
    _sys_exit(int x) 
    { 
    	x = x; 
    } 
    //重定义fputc函数 
    int fputc(int ch, FILE *f)
    {      
    	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
        USART1->DR = (u8) ch;      
    	return ch;
    }
    #endif 
    
    /*使用microLib的方法*/
     /* 
    int fputc(int ch, FILE *f)
    {
    	USART_SendData(USART1, (uint8_t) ch);
    
    	while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) {}	
       
        return ch;
    }
    int GetKey (void)  { 
    
        while (!(USART1->SR & USART_FLAG_RXNE));
    
        return ((int)(USART1->DR & 0x1FF));
    }
    */
    #if EN_USART1_RX   //如果使能了接收
    //串口1中断服务程序
    //注意,读取USARTx->SR能避免莫名其妙的错误   	
    u8 USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.
    //接收状态
    //bit15,	接收完成标志
    //bit14,	接收到0x0d
    //bit13~0,	接收到的有效字节数目
    u16 USART_RX_STA=0;       //接收状态标记	  
      
    void uart_init(u32 bound){
      //GPIO端口设置
      GPIO_InitTypeDef GPIO_InitStructure;
    	USART_InitTypeDef USART_InitStructure;
    	NVIC_InitTypeDef NVIC_InitStructure;
    	 
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟
      
    	//USART1_TX   GPIOA.9
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
      GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
       
      //USART1_RX	  GPIOA.10初始化
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
      GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  
    
      //Usart1 NVIC 配置
      NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
    	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
      
       //USART 初始化设置
    
    	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
     // USART_ITConfig(USART1, USART_IT_RXE, ENABLE);//开启串口接受中断
    	 USART_ITConfig(USART1,USART_IT_IDLE,ENABLE);    //开启USART1的空闲中断
      USART_Cmd(USART1, ENABLE);                    //使能串口1 
    
    }
    
    
    void USART1_IRQHandler(void)                 //串口1中断服务程序,当接受完毕后便会触发空闲中断
     {
         u8 clear=0;
         if(USART_GetITStatus(USART1, USART_IT_IDLE) == SET)  //接收中断
          {
            clear=USART1->DR;//清楚中断标志位
            Flag=1;//标志一次接受完毕,在main函数中读取flag来判断是否接收完毕,并在主函数中清零
            len=DMA_GetCurrDataCounter(DMA1_Channel5)-sizeof(rx_buff);//读取剩余未转运的长度
        }
    }
    
    	
    void MyUSART_SendByte(u8 Byte)
    {
        USART_SendData(USART1,Byte);
        while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);//防止发送过快,前一个数据还没发送出去就别覆盖的情况
    }
     
    void MyUSART_SendString(char* str)//发送字符串
    {
        u16 i;
        for(i=0;str[i]!='\0';i++)
        {
            MyUSART_SendByte(str[i]);
        }
        
    }
    #endif
    
    

    要注意的是这里开启的空闲中断

    这里做中断标志位清0

    #ifndef __USART_H
    #define __USART_H
    #include "stdio.h"	
    #include "sys.h" 
    #define USART_REC_LEN  			200  	//定义最大接收字节数 200
    #define EN_USART1_RX 			1		//使能(1)/禁止(0)串口1接收
    
    extern u8 len;
    extern u8 Flag;
    extern char rx_buff[200];
    extern u8  USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 
    extern u16 USART_RX_STA;         		//接收状态标记	
    void uart_init(u32 bound);
    #endif
    

    3.main

    #include "led.h"
    #include "delay.h"
    #include "key.h"
    #include "sys.h"
    #include "usart.h"
     #include "DMA.h"
     int main(void)
     {		
     	u16 t;  
    	 char rx_buff[200]={'\0'};
    	u16 len;	
    	u16 times=0;
    	delay_init();	    	 //延时函数初始化	  
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
    	uart_init(115200);	 //串口初始化为115200
     	LED_Init();			     //LED端口初始化
    	KEY_Init();          //初始化与按键连接的硬件接口
    	  MyDMA_Config(DMA1_Channel5,(u32)&USART1->DR,(u32)rx_buff,rx_buff_maxlen);
       MYDMA_Enable(DMA1_Channel5);
    
     	while(1)
    	{
    	    	if(Flag)
            {
                Flag=0;
                MyUSART_SendString(rx_buff);
                MyUSART_SendString("\r\n");
                printf("len=%d\r\n",len);
                MYDMA_Enable(DMA1_Channel5);
            }
    	}	 
     }
    
    
    

    作者:我与nano

    物联沃分享整理
    物联沃-IOTWORD物联网 » 使用STM32 F1的DMA实现串口空闲中断数据发送

    发表评论