直接寄存器访问(DMA)

DMA 即 Direct Memory Access. DMA传输方式无须CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条直接传送数据的通路,能使CPU的效率大为提高。

1. DMA基础知识

STM32 最多有2个DMA控制器(DMA2仅存在大容量产品中),DMA有7个通道,DMA2有5个通道。每个通道专门用来管理来自一个或多个外设对存储器访问的请求。还有一个仲裁器协调各个DMA请求的优先权。

STM32的DMA有以下一些特性:

  1. 每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置。
  2. 在7个请求间的优先权可以通过软件编程来配置,假如在优先权相等时,由硬件决定。
  3. 独立的源和目标数据区的传输宽度(字节,半字,全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
  4. 支持循环的缓冲器管理。
  5. 每个通道都有3个事件标志(DMA半传输,DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。
  6. 存储器和存储器间的传输。
  7. 外设和存储器,存储器和外设的传输。
  8. 闪存,SRAM, 外设的SRAM,APB1,APB2,和AHB外设均可作为访问的源和目标。
  9. 可编程的数据传输数目,最大为65536.
    外设通过逻辑或输入到DMA通道,意味着每个通道只能同时接收一个请求。

2. 用寄存器方式操作DMA

假设为DMA1的通道4,配置步骤如下:

  1. 设置外设地址。 外设地址通过DMA1_CPAR4设置,只要在这个寄存器里面写入&USART1_DR的值就可以。该地址将作为DMA传输的目标地址。
  2. 设置存储器地址。存储器地址通过DMA1_CMAR4来设置,假设要把数组SendBuf作为存储器,那么在该寄存器内写入&SendBuf就可以。该地址将作为DMA传输的源地址。
  3. 设置传输数据量。通过DMA_CNDTR4寄存器,这里面写入此次要传输的数据量,即SendBuf的大小。该寄存器的数值将在DMA启动后自减,每次新的DMA传输,都重新向该寄存器写入要传输的数据量。
  4. 设置通道4的配置信息。设置存储器和外设的数据位宽为8,存储器增量模式。优先级可以随便设置,因为只有一个通道开启。
  5. 使能DMA1通道4,启动传输。以上配置都完成后,就使能DMA1_CCR4的最低位开启DMA传输,注意要设置USART1的使能DMA传输位,通过USART1->CR3的第7位设置。

3. 用库函数的方式操作DMA

总共5步去配置DMA.

  • 使能DMA时钟。
  • 初始化DMA通道4的参数。
  • 使能串口DMA发送。
  • 使能DMA1通道4,启动传输。
  • 查询DMA传输状态。

1. 使能DMA时钟

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);

2. 初始化DMA通道4的参数

DMA的初始化是通过配置函数:

void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct)

第1个参数指定通道,第二个参数用来初始化DMA。
设置具体如下:

DMA_InitTypeDef DMA_InitStructure; 
//DMA外设ADC基地址
DMA_InitStructure.DMA_PeriperalBaseAddr = &USART->DR;
//DMA内存基地址
DMA_InitStructure.DMA_MemoryBaseAddr = cmar;
//从内存读取发送到外设
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripherialDST;
//DMA通道的DMA缓存的大小
DMA_InitStructure.DMA_BufferSize = 64;
//外设地址不变
DMA_InitStructure.DMA_PeripherialInc = DMA_PeripherialInc_Disable;
//内存地址递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
//8位
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
//8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
//工作在正常缓存模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
//DMA通道x拥有中优先级
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
//非内存到内存传输
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
//根据指定的参数初始化
DMA_Init(DMA_CHx, &DMA_InitStructure);

3. 使能串口DMA发送

进行DMA配置后,就要开启串口的DMA发送功能,使用的函数是:

USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);

如果是接受的话,第2个参数修改为USART_DMAReq_Rx即可。

4. 使能DMA1通道4, 启动传输

使能串口DMA发送之后,接着就要使能DMA传输通道。

DMA_Cmd(DMA_CHx, ENABLE);

5. 查询DMA传输状态

用户要查询DMA传输通道的状态,使用的函数是:

FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);

查询 DMA 通道4 是否完成,方法是:

DMA_GetFlagStatus(DMA2_FLAG_TC4);

获取当前剩余数据量大小的函数:

uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);

例如 要获取DMA通道4, 还有多少个数据没有传输:

DMA_GetCurrDataCounter(DMA1_Channel4);

4. DMA操作实例

该例子是利用DMA把数据(数组)从内存转移到外设(串口)。外设工作的时候,除了转移数据,实质是不需要内核干预的,而数据转移的工作现在交给了DMA, 所以在串口发送数据的时候,内核同时还可以进行其他操作,比如点亮LED灯。

#include "stm32f10x.h"
#include "usart1.h"
#include "led.h"
extern uint8_t SendBuff[SENDBUFF_SIZE];
static void Delay(__IO u32 nCount);
int main(void)
{
	USART1_Config();
	USART1_DMA_Config();
	LED_GPIO_Config();
	printf("\r\n usart1 DMA TX Test \r\n");
	{
		uint16_t i;
		/*填充将要发送的数据*/
		for(i=0; i<SENDBUFF_SIZE; i++)
		{
			SendBuff[i] = 'A';
}
}
	/*USART1 向DMA 发出TX请求*/
	USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
	/*此时CPU是空闲的可以干其他事情*/
	//同时控制LED
	for(;;)
	{
		LED1(ON);
		Delay(0xFFFFF);
		LED1(OFF);
		Delay(0xFFFFF);
}
}

static void Delay(__IO uint32_t nCount)//简单的延时函数
{
	for(;nCount != 0; nCount--);
}

DMA传输代码如下:

#include "usart1.h"
uint8_t SendBuff[SENDBUFF_SIZE];
void USART1_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	/*配置USART1时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA,ENABLE);
	/*配置USART1 PA.9(Tx)脚为复用推挽输出*/、
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	/*配置USART1的PA.10(Rx)脚位浮空输入*/
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATTING;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	/*USART1 模式配置*/
	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);
	USART_Cmd(USART1, ENABLE);
}

void USART1_DMA_Config(void)
{
	DMA_InitTypeDef DMA_InitStructure;
	/*开启DMA时钟*/
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	/*设置DMA源:串口数据寄存器地址*/
	DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base;
	/*内存地址(要传输的变量的指针)*/
	DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;
	/*方向:从内存到外设*/
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
	/*传输大小 DMA_BufferSize = SENDBUFF_SIZE*/
	DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;
	/*外设地址不增*/
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	/*内存地址自增*/
	DMA_InitStructure.DMA_MemoryInc = DMA_Memory_Enable;
	/*外设数据单位*/
	DMA_InitStructure.DMA_PeriphaeralDataSize = DMA_PeripheralDataSize_Byte;
	/*内存数据单位*/
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
	/*DMA模式:不断循环*/
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
	/*优先级:中*/
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
	/*禁止内存到内存之间的传递*/
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
	/*配置DMA1的4通道*/
	DMA_Init(DMA1_Channel4, &DMA_InitStructure);
	/*使能DMA*/
	DMA_Cmd(DMA1_Channel4, ENABLE);
}
//重定向c库函数printf到USART1
int fputc(int ch, FILE *f)
{
	/*发送一个字节数据到USART1*/
	USART_SendData(USART1, (uint8_t)ch);
	/*等待发送完毕*/
	while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
	return(ch);
}
//重定向c库函数scanf到USART1
int fgetc(FILE *f)
{
	/*等待串口1输入数据*/
	while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
	return (int)USART_RecieveDat(USART1);
}

版权声明:本文为weixin_38092300原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。