首页 > 技术文章 > STM32F10x 串口使用DMA

jzcn 2022-01-17 18:42 原文

一、DMA简介

DMA(Direct Memory Access,直接存储器访问) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载。否则,CPU 需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回到新的地方。在这个时间中,CPU 对于其他的工作来说就无法使用。

二、DMA的工作原理

  DMA 传输将数据从一个地址空间复制到另外一个地址空间。当CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成。
  在实现DMA传输时,是由DMA控制器直接掌管总线,因此,存在着一个总线控制权转移问题。即DMA传输前,CPU要把总线控制权交给DMA控制器,而在结束DMA传输后,DMA控制器应立即把总线控制权再交回给CPU。一个完整的DMA传输过程必须经过DMA请求、DMA响应、DMA传输、DMA结束4个步骤。
  传输的四种情况:

  • 外设到内存
  • 外设到外设
  • 内存到外设
  • 内存到内存
  1. DMA主要特征
  • 12个独立的可配置的通道(请求):DMA1有7个通道,DMA2有5个通道
  • 每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置。
  • 在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推) 。
  • 独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
  • 支持循环的缓冲器管理
  • 每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。
  • 存储器和存储器间的传输
  • 外设和存储器、存储器和外设之间的传输
  • 闪存、SRAM、外设的SRAM、APB1 、APB2和AHB外设均可作为访问的源和目标。
  • 可编程的数据传输数目:最大为65535
  1. DMA框图

  2. DMA硬件优先级

  3. 各个通道的DMA

三、程序分析

  1. 初始化结构体
typedef struct
{
    uint32_t DMA_PeripheralBaseAddr; 
    uint32_t DMA_MemoryBaseAddr;    
    uint32_t DMA_DIR;              
    uint32_t DMA_BufferSize;        
    uint32_t DMA_PeripheralInc;     
    uint32_t DMA_MemoryInc;         
    uint32_t DMA_PeripheralDataSize; 
    uint32_t DMA_MemoryDataSize;     
    uint32_t DMA_Mode;              
    uint32_t DMA_Priority;          
    uint32_t DMA_M2M;                
}DMA_InitTypeDef;

  1. 配置DMA
void MYDMA_Config(DMA_Channel_TypeDef* DMA_CHx, u32 peripheral_base)
{
    DMA_CHxx = DMA_CHx;	
	
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);    //使能DMA传输
    DMA_DeInit(DMA_CHx); //将DMA的通道1寄存器重设为缺省值
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)SendBuff; //DMA内存基地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;  //数据传输方向,从内存读取发送到外设
    DMA_InitStructure.DMA_BufferSize = SEND_BUF_SIZE; //DMA通道的DMA缓存的大小
    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通道 x拥有中优先级 
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
    DMA_Init(DMA_CHx, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Rx_DMA 所标识的寄存器	
} 

注意:需要注意的是以下几点

  • 这里内存的地址可以理解为自己定义的变量地址,对于外设地址不了解的小伙伴可以看我之前的笔记STM32寄存器深入分析
  • DMA缓存长度最大为65535
  • DMA的通道1可以观察上面的DMA的硬件优先级比如USART1_TX使用的是通道4,USART1_RX使用的是通道5,通入如下图所示:

四、试验一

串口通过DMA发送数据,具体源码如下所示:

  1. main.c文件
#include "stm32f10x.h"
#include "usart1.h"

uint8_t buff[] = "STM32F10x DMA 串口实验";

 /***************  延时函数 *******************/
void Delay(__IO u32 nCount)
{
	for(; nCount != 0; nCount--);
} 

int main(void)
{  
	
    SystemInit();	//配置系统时钟为 72M 
   
	USART1_Config(); //USART1 配置 		
	
	MYDMA_Config(DMA1_Channel4, (uint32_t)&USART1->DR);//DMA1通道4,外设为串口1,存储器为SendBuff,长度SEND_BUF_SIZE.
	
	while (1)
	{	 
		MYDMA_Enable((uint8_t *)buff, sizeof(buff));//开始一次DMA传输!
		Delay(1000000);
	}
}

  1. usart.c文件

#include "usart1.h"
#include <stdarg.h>
#include "stm32f10x_dma.h"
#include "string.h"

DMA_InitTypeDef DMA_InitStructure;
DMA_Channel_TypeDef* DMA_CHxx;

#define SEND_BUF_SIZE 1024	//发送数据长度
uint8_t SendBuff[SEND_BUF_SIZE];	//发送数据缓冲区

// 初始化USART1
void USART1_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;

	/* 使能 USART1 时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); 

	/* USART1 使用IO端口配置 */    
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;	// PA.9
	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_10;		// PA.10
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;	//浮空输入
	GPIO_Init(GPIOA, &GPIO_InitStructure);   //初始化GPIOA
	  
	/* USART1 工作模式配置 */
	USART_InitStructure.USART_BaudRate = 115200;	//波特率设置:115200
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;	//数据位数设置:8位
	USART_InitStructure.USART_StopBits = USART_StopBits_1; 	//停止位设置: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);  //初始化USART1
	
	USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);	//使能串口1的DMA发送

	USART_Cmd(USART1, ENABLE);                    //使能串口1
	
}

void MYDMA_Config(DMA_Channel_TypeDef* DMA_CHx, u32 peripheral_base)
{
	DMA_CHxx = DMA_CHx;	
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);    //使能DMA传输
	DMA_DeInit(DMA_CHx); //将DMA的通道1寄存器重设为缺省值
	DMA_InitStructure.DMA_PeripheralBaseAddr = peripheral_base; //DMA外设基地址
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)SendBuff; //DMA内存基地址
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;  //数据传输方向,从内存读取发送到外设
	DMA_InitStructure.DMA_BufferSize = SEND_BUF_SIZE; //DMA通道的DMA缓存的大小
	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通道 x拥有中优先级 
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
	DMA_Init(DMA_CHx, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Rx_DMA 所标识的寄存器
	
} 

uint16_t MYDMA_Enable(uint8_t* buff, uint16_t size)
{
	uint16_t data_size;
	
	if (size <= 0){
		return 0;
	}
	data_size = (size > SEND_BUF_SIZE ? SEND_BUF_SIZE:size);
	
	if (!buff){
		return 0;
	}
	memcpy(SendBuff, buff, data_size);
	DMA_Cmd(DMA_CHxx, DISABLE ); //关闭USART1 TX DMA1 所指示的通道 
	DMA_CHxx->CNDTR = data_size;// 设置发送长度
	DMA_Cmd(DMA_CHxx, ENABLE); //使能USART1 TX DMA1 所指示的通道 
	
	return 0;
}   

参考文献

STM32 DMA工作原理:https://blog.csdn.net/baidu_37366055/article/details/98069744
STM32之DMA原理:https://blog.csdn.net/lushoumin/article/details/78907526

推荐阅读