DMA 即 Direct Memory Access. DMA传输方式无须CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条直接传送数据的通路,能使CPU的效率大为提高。
1. DMA基础知识
STM32 最多有2个DMA控制器(DMA2仅存在大容量产品中),DMA有7个通道,DMA2有5个通道。每个通道专门用来管理来自一个或多个外设对存储器访问的请求。还有一个仲裁器协调各个DMA请求的优先权。
STM32的DMA有以下一些特性:
- 每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置。
- 在7个请求间的优先权可以通过软件编程来配置,假如在优先权相等时,由硬件决定。
- 独立的源和目标数据区的传输宽度(字节,半字,全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
- 支持循环的缓冲器管理。
- 每个通道都有3个事件标志(DMA半传输,DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。
- 存储器和存储器间的传输。
- 外设和存储器,存储器和外设的传输。
- 闪存,SRAM, 外设的SRAM,APB1,APB2,和AHB外设均可作为访问的源和目标。
- 可编程的数据传输数目,最大为65536.
外设通过逻辑或输入到DMA通道,意味着每个通道只能同时接收一个请求。
2. 用寄存器方式操作DMA
假设为DMA1的通道4,配置步骤如下:
- 设置外设地址。 外设地址通过DMA1_CPAR4设置,只要在这个寄存器里面写入&USART1_DR的值就可以。该地址将作为DMA传输的目标地址。
- 设置存储器地址。存储器地址通过DMA1_CMAR4来设置,假设要把数组SendBuf作为存储器,那么在该寄存器内写入&SendBuf就可以。该地址将作为DMA传输的源地址。
- 设置传输数据量。通过DMA_CNDTR4寄存器,这里面写入此次要传输的数据量,即SendBuf的大小。该寄存器的数值将在DMA启动后自减,每次新的DMA传输,都重新向该寄存器写入要传输的数据量。
- 设置通道4的配置信息。设置存储器和外设的数据位宽为8,存储器增量模式。优先级可以随便设置,因为只有一个通道开启。
- 使能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版权协议,转载请附上原文出处链接和本声明。