STM32 F1 串口空闲中断 + DMA实现数据发送

DMA实现数据发送


前言

当你遇到通信数据量大的时候,可以使用 空闲中断 + 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);
        }
	}	 
 }


在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_51963216/article/details/134627378