DMA简介
- 直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。换而言之就是当外设有数据发送给mcu,此时可以使用DMA接收到用户定义空间(不占用cpu),接收完成在产生中断发给mcu(才占用CPU)反正一样。
- 当CPU和DMA同时访问相同的目标(RAM或外设)时,DMA请求会暂停CPU访问系统总线达若干个周期,总线仲裁器执行循环调度,以保证CPU至少可以得到一半的系统总线(存储器或外设)带宽。
- 两个DMA控制器有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自于一个或多个外设对寄存器访问。
DMA 主要特性
● 12个独立的可配置的通道(请求):DMA1有7个通道,DMA2有5个通道
● 每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过
软件来配置。
● 在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、
中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推) 。
● 独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
● 支持循环的缓冲器管理
● 每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志
逻辑或成为一个单独的中断请求。
● 存储器和存储器间的传输
● 外设和存储器、存储器和外设之间的传输
● 闪存、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标。
● 可编程的数据传输数目:最大为65535
处理的方式
每次DMA传送由3个操作组成:
● 从外设数据寄存器或者从当前外设/存储器地址寄存器指示的存储器地址取数据,第一次传
输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元。
● 存数据到外设数据寄存器或者当前外设/存储器地址寄存器指示的存储器地址,第一次传输
时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元。
● 执行一次DMA_CNDTRx寄存器的递减操作,该寄存器包含未完成的操作数目。
DMA 的配置步骤
1. 使能DMA时钟
(是必不可少的)
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMAx,ENABLE); //使能 DMA 时钟
2. 初始化DMA通道参数
(具体某个通道,根据上面的图可以知道)
DMA 通道配置参数种类比较繁多,在"stm32f10x_dma.h""stm32f10x_dma.c"都封装好了的;说白了就是配置DMA_InitTypeDef结构体的参数。
//初始化
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx,DMA_InitTypeDef* DMA_InitStruct);
typedef struct//结构体
{
uint32_t DMA_PeripheralBaseAddr;//设置DMA传输的外设基地址
/*要进行串口DMA传输,那么外设基地址为串口接受收发送数据存储器USART1->DR的地址*/
uint32_t DMA_MemoryBaseAddr;//定义 DMA 内存基地址(我们定义存放DMA传输数据的内存地址)
uint32_t DMA_DIR;//作为数据传输的目的地还是来源(传输方向)
/* DMA_DIR_PeripheralDST 外设作为数据传输的目的地
DMA_DIR_PeripheralSRC 外设作为数据传输的来源*/
uint32_t DMA_BufferSize;//指定 DMA 通道的 DMA 缓存的大小,单位为数据单位
uint32_t DMA_PeripheralInc;//设定外设地址寄存器递增与否
/*DMA_PeripheralInc_Enable 外设地址寄存器递增
DMA_PeripheralInc_Disable 外设地址寄存器不变*/
uint32_t DMA_MemoryInc;//设定内存地址寄存器递增与否
/*DMA_PeripheralInc_Enable 内存地址寄存器递增
DMA_PeripheralInc_Disable 内存地址寄存器不变*/
uint32_t DMA_PeripheralDataSize;// 设定了外设数据宽度
/*DMA_PeripheralDataSize_Byte 数据宽度为 8 位
DMA_PeripheralDataSize_HalfWord 数据宽度为 16 位
DMA_PeripheralDataSize_Word 数据宽度为 32 位*/
uint32_t DMA_MemoryDataSize;//定了外设数据宽度
/*DMA_MemoryDataSize_Byte 数据宽度为 8 位
DMA_MemoryDataSize_HalfWord 数据宽度为 16 位
DMA_MemoryDataSize_Word 数据宽度为 32 位*/
uint32_t DMA_Mode;//工作模式
/*DMA_Mode_Circular 工作在循环缓存模式
DMA_Mode_Normal 工作在正常缓存模式*/
uint32_t DMA_Priority;//通道 x 的软件优先级
/*DMA_Priority_VeryHigh DMA 通道 x 拥有非常高优先级
DMA_Priority_High DMA 通道 x 拥有高优先级
DMA_Priority_Medium DMA 通道 x 拥有中优先级
DMA_Priority_Low DMA 通道 x 拥有低优先级*/
uint32_t DMA_M2M;//使能 DMA 通道的内存到内存传输
/*DMA_M2M_Enable DMA 通道 x 设置为内存到内存传输
DMA_M2M_Disable DMA 通道 x 没有设置为内存到内存传输*/
}DMA_InitTypeDef;
//后面完整程序有更清楚配置说明
3 )使能串口DMA发送或接收
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);//发送
4 ) 使能DMA 通道x,启动传输。
DMA_Cmd(DMA_CHx, ENABLE);
注:配置好了1 2 3步骤,在使能MDA通道就可以成功发送或接收一次数据。反而言之决定“DMA_Cmd()”函数的位置就能决定发送数据的时间。
5)查询 DMA 传输状态
就比如在中断的时候,我们要判断相对应的标志位。
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
/*
DMA_FLAG_GL1 通道 1 全局标志位
DMA_FLAG_TC1 通道 1 传输完成标志位
DMA_FLAG_HT1 通道 1 传输过半标志位
DMA_FLAG_TE1 通道 1 传输错误标志位
DMA_FLAG_GL2 通道 2 全局标志位
DMA_FLAG_TC2 通道 2 传输完成标志位
DMA_FLAG_HT2 通道 2 传输过半标志位
DMA_FLAG_TE2 通道 2 传输错误标志位
DMA_FLAG_GL3 通道 3 全局标志位
DMA_FLAG_TC3 通道 3 传输完成标志位
DMA_FLAG_HT3 通道 3 传输过半标志位
DMA_FLAG_TE3 通道 3 传输错误标志位
DMA_FLAG_GL4 通道 4 全局标志位
DMA_FLAG_TC4 通道 4 传输完成标志位
DMA_FLAG_HT4 通道 4 传输过半标志位
DMA_FLAG_TE4 通道 4 传输错误标志位
DMA_FLAG_GL5 通道 5 全局标志位
DMA_FLAG_TC5 通道 5 传输完成标志位
DMA_FLAG_HT5 通道 5 传输过半标志位
DMA_FLAG_TE5 通道 5 传输错误标志位
DMA_FLAG_GL6 通道 6 全局标志位
DMA_FLAG_TC6 通道 6 传输完成标志位
DMA_FLAG_HT6 通道 6 传输过半标志位
DMA_FLAG_TE6 通道 6 传输错误标志位
DMA_FLAG_GL7 通道 7 全局标志位
DMA_FLAG_TC7 通道 7 传输完成标志位
DMA_FLAG_HT7 通道 7 传输过半标志位
DMA_FLAG_TE7 通道 7 传输错误标志位
*/
获取当前剩余数据量大小的函数:(有时候还是可以用到)
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
DMA实现USART1收发送数据程序
通过查资料,多次调试,最总完成了这次任务。
使用USART1中断+MDA实现接收数据,PE4接的按键,按下一次通过MDA发送数据一次到USART1。
#include "stm32f10x.h"
#include "stdio.h"
#include "string.h"
int fputc(int ch, FILE *f)
{
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
USART_SendData(USART1,(uint8_t)ch);
return ch;
}
#define UART_RX_LEN 128
/*串口发送DMA缓存*/
uint8_t SendBuff[UART_RX_LEN]={"zxcvbnm,asdfghjkl"};
/*串口接收DMA缓存*/
uint8_t Uart_Rx[UART_RX_LEN] = {0};
uint8_t Data_Receive_Usart=0;
void delay_ms(u16 time)
{
u16 i = 0;
while(time--)
{
i = 12000;
while(i--);
}
}
//串口初始化
void Usart_Init(void)
{
GPIO_InitTypeDef GPIO_ITDef1;
GPIO_InitTypeDef GPIO_ITDef;
USART_InitTypeDef USART_ITDef;
//挂载时钟(复用PA) 串口时钟使能,GPIO 时钟使能,复用时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA,ENABLE);
//PA9 TXD初始化
GPIO_ITDef.GPIO_Pin = GPIO_Pin_9;//PA9 TXD
GPIO_ITDef.GPIO_Mode = GPIO_Mode_AF_PP;////复用推挽输出
GPIO_ITDef.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_ITDef);
//PA10 TXD初始化
GPIO_ITDef1.GPIO_Pin = GPIO_Pin_10;//PA10 RXD
GPIO_ITDef1.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA,&GPIO_ITDef1);
//USART初始化
USART_ITDef.USART_BaudRate = 115200;//波特率
USART_ITDef.USART_WordLength = USART_WordLength_8b;//发送数据长度
USART_ITDef.USART_StopBits = USART_StopBits_1; //一个停止位
USART_ITDef.USART_Parity = USART_Parity_No; //无奇偶校验位
USART_ITDef.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_ITDef.USART_Mode = USART_Mode_Tx| USART_Mode_Rx ;//发送模式
USART_Init(USART1,&USART_ITDef);
/*中断配置*/
USART_ITConfig(USART1,USART_IT_TC,DISABLE);
USART_ITConfig(USART1,USART_IT_RXNE,DISABLE);
USART_ITConfig(USART1,USART_IT_IDLE,ENABLE);
NVIC_InitTypeDef NVIC_InitStructure;
//配置UART1中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //通道设置为串口1中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //中断占先等级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //中断响应优先级0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //打开中断
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1, ENABLE);//使能串口
}
void MDA_USART1_Init(void)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能 DMA 时钟
DMA_Cmd(DMA1_Channel4,DISABLE);
DMA_DeInit(DMA1_Channel4); //将 DMA 的通道 1 寄存器重设为缺省值
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART1->DR); //DMA外设基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff; //DMA内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向,从内存读取发送到外设
DMA_InitStructure.DMA_BufferSize = UART_RX_LEN; //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_VeryHigh; //DMA通道 x拥有中优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
DMA_Init(DMA1_Channel4, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器
/*DMA1通道5配置接收*/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
DMA_DeInit(DMA1_Channel5);
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART1->DR);
//串口的发送和接收stm32都存在同一寄存器的;51和cc2530是发送和接收用单独的寄存器存数据;我们要其他内设使用DMA,就得清楚当前设备在stm32存数据的寄存器。(stm32数据手册都有介绍)
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Uart_Rx;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = UART_RX_LEN;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel5,&DMA_InitStructure);
//主要通过按键来发送数据,所以这里没有使能通道4(一旦使能就将发送一次数据)
/*使能接收通道5*/
DMA_Cmd(DMA1_Channel5,ENABLE);
//采用DMA方式发送
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //采用DMA方式接收
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
//启动串口
USART_Cmd(USART1, ENABLE);
}
/*
* 启动DMA数据发送功能
*size表示需要发送的DMA中数据的个数
*/
void uart_dma_send_enabl(uint16_t size)
{
DMA1_Channel4->CNDTR = (uint16_t)size;
DMA_Cmd(DMA1_Channel4, ENABLE);
}
void USART1_IRQHandler(void)
{
memset(SendBuff,0,UART_RX_LEN);//清空发送数组
uint32_t temp = 0;
uint16_t i = 0;
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
{
temp = USART1->DR;
DMA_Cmd(DMA1_Channel5,DISABLE);
temp = UART_RX_LEN - DMA_GetCurrDataCounter(DMA1_Channel5);
printf("\r\nReceived data : ");
for (i = 0;i < temp;i++)
{
//在发送数据的函数,我这是发送SendBuff[]的数据,所以这里把数据从Uart_Rx[]传给SendBuff[]
SendBuff[i] = Uart_Rx[i];
uart_dma_send_enabl(temp);//发送收到数据
}
//设置传输数据长度
DMA_SetCurrDataCounter(DMA1_Channel5,UART_RX_LEN);
//打开DMA就意味着开始发送数据
DMA_Cmd(DMA1_Channel5,ENABLE);
}
}
//按键初始化
void KEY0_Init_PE4(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
GPIO_InitTypeDef GPIO_ITDef_PE4;
GPIO_ITDef_PE4.GPIO_Pin = GPIO_Pin_4;
GPIO_ITDef_PE4.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
GPIO_Init(GPIOE,&GPIO_ITDef_PE4);
}
int main(void)
{
Usart_Init();
KEY0_Init_PE4();
MDA_USART1_Init();
while(1)
{
if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)==0x00)//已经按下了
{
delay_ms(10);
if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)==0x00)
{
printf("\r\nDMA DATA:\r\n");
//使能串口1的DMA发送
uart_dma_send_enabl(UART_RX_LEN);//开始一次DMA传输
}//其实在发送的时候mcu可以做其他事,因为在发送的时候是不使用CPU的。
}
//等待发送完成
if(DMA_GetITStatus(DMA1_FLAG_TC4)==SET)
{
DMA_ClearFlag(DMA1_FLAG_GL4);//清除标志位
DMA_Cmd(DMA1_Channel4, DISABLE); //关闭发送通道使能
}
}
}
常用资料:
STM32F10x_StdPeriph_Lib_V3.5.0(官方固件库)
链接:STM32固件库使用手册的中文翻译版 提取码:4lkx