记录一下,方便以后翻阅~
主要内容:
1) DMA基本原理;
2) 相关寄存器及库函数介绍;
3) 相关实验代码解读。
实验功能:系统启动后,通过按键KEY0控制串口1以DMA方式发送数据,按下KEY0,就开始DMA传送,同时,串口调试助手可以收到DMA发送的内容。
官方资料:《STM32中文参考手册V10》第10章——DMA控制器
1. DMA(Direct MemoryAccess-直接存储器访问)基本原理
1.1 DMA传输将数据从一个地址空间复制到另一个地址空间。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。
DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。
简要总结:提高数据传输的速度,为CPU减负。
1.2 STM32最多有2个DMA控制器(DMA2仅存在大容量产品中),DMA1有7个通道。DMA2有5个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。
还有一个仲裁起来协调各个DMA请求的优先权。
1.3 DMA框图如下所示
DMA1有7个通道,DMA2有5个通道,仲裁器来处理优先级,DMA1和DMA2的请求来自APB1和APB2的外设。DMA总线用来访问存储器。
1.4 DMA特点
1.4.1 每个通道都直接连接专用的硬件DMA请求,都支持软件触发,这些通过软件来配置;
1.4.2 在七个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),假如在相等优先权时由硬件决定(请求0优先于请求1,依此类推) ;
1.4.3 独立的源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐;
1.4.4 支持循环的缓冲器管理;
1.4.5 每个通道都有3个事件标志(DMA半传输,DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求;
1.4.6 外设和存储器,存储器和外设的传输 ,存储器和存储器间的传输;
1.4.7 闪存、SRAM、外设的SRAM、APB1 APB2和AHB外设均可作为访问的源和目标;
1.4.8 可编程的数据传输数目:最大为65536。
2. DMA控制器
3. DMA处理
针对DMA_CCRx寄存器,位4,数据传输方向:
4. 仲裁器
针对DMA_CCRx寄存器,位[12~13],PL通道优先级:
5. 通道
6. 指针增量
7. 循环模式
8. 通道传输数据量
9. 中断
10. 通道配置过程
11. DMA配置参数
1.1 通道;
1.2 优先级;
1.3 数据传输方向;
1.4 存储器/外设,数据宽度;
1.5 存储器/外设,地址是否增量;
1.6 循环模式;
1.7 数据传输量。
12. 相关库函数
12.1 官方库函数为stm32f10x_dma.c和tm32f10x_dma.h;
12.2 常用库函数
1) void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
2) void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
3) void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);
4) void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);
5) uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
6) FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
7) void DMA_ClearFlag(uint32_t DMAy_FLAG);
8) ITStatus DMA_GetITStatus(uint32_t DMAy_IT);
9) void DMA_ClearITPendingBit(uint32_t DMAy_IT);
12.3 常用的外设DMA使能库函数
1) void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState);
2) void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);
3) void DAC_DMACmd(uint32_t DAC_Channel, FunctionalState NewState);
4) void I2C_DMACmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
5) void SDIO_DMACmd(FunctionalState NewState);
6) void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);
7) void TIM_DMAConfig(TIM_TypeDef* TIMx, uint16_t TIM_DMABase, uint16_t TIM_DMABurstLength);
8) void TIM_DMACmd(TIM_TypeDef* TIMx, uint16_t TIM_DMASource, FunctionalState NewState);
12.4 void DMA_Init(DMA_CHx, &DMA_InitStructure)函数第二个入口参数结构体解读
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;
13. DMA程序配置过程
13.1 使能DMA时钟:
RCC_AHBPeriphClockCmd();
13.2 初始化DMA通道参数:
DMA_Init();
13.3 使能串口DMA发送,串口DMA使能函数:
USART_DMACmd();
13.4 使能DMA1通道,启动传输:
DMA_Cmd();
13.5 查询DMA传输状态:
DMA_GetFlagStatus();
13.6 获取/设置通道当前剩余数据量:
DMA_GetCurrDataCounter();
DMA_SetCurrDataCounter();
14. 相关实验代码解读
14.1 dma.h头文件代码解读
#ifndef __DMA_H
#define __DMA_H
#include "sys.h"
//申明两个函数//
void MYDMA_Config(DMA_Channel_TypeDef*DMA_CHx,u32 cpar,u32 cmar,u16 cndtr); //配置DMA1_CHx,4个入口参数//
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx); //使能DMA1_CHx//
#endif
14.2 dma.c文件代码解读
#include "dma.h"
//申明一个结构体//
DMA_InitTypeDef DMA_InitStructure;
u16 DMA1_MEM_LEN; //保存DMA每次数据传送的长度//
//编写MYDMA_Config函数,4个入口参数,包括:DMA_CHx:DMA通道CHx;cpar:外设地址;cmar:存储器地址;cndtr:数据传输量//
//该函数固定的部分包括:从存储器->外设模式;8位数据宽度;存储器增量模式(外设地址不增量)//
void MYDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
//第一步,使能DMA传输//
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(DMA_CHx); //将DMA的通道1寄存器重设为缺省值//
DMA1_MEM_LEN=cndtr;
//第二步,初始化DMA_Init函数//
DMA_InitStructure.DMA_PeripheralBaseAddr = cpar; //DMA外设基地址,其中一个入口参数//
DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //DMA内存基地址,其中一个入口参数//
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向,从内存读取发送到外设,设为固定//
DMA_InitStructure.DMA_BufferSize = cndtr; //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_InitStructure.DMA_M2M = DMA_M2M_Disable; //非存储器到存储器模式,设为固定//
DMA_Init(DMA_CHx, &DMA_InitStructure);
}
//开启一次DMA传输
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{
DMA_Cmd(DMA_CHx, DISABLE ); //关闭USART1 TX DMA1 所指示的通道//
DMA_SetCurrDataCounter(DMA_CHx,DMA1_MEM_LEN); //DMA通道的DMA缓存的大小//
DMA_Cmd(DMA_CHx, ENABLE); //使能USART1 TX DMA1 所指示的通道//
}
14.3 main.c文件代码解读
#include "led.h"
#include "key.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "dma.h"
#define SEND_BUF_SIZE 8200 //发送数据长度,最好等于sizeof(TEXT_TO_SEND)+2的整数倍.
u8 SendBuff[SEND_BUF_SIZE]; //发送数据缓冲区
const u8 TEXT_TO_SEND[]={"ALIENTEK WarShip STM32F1 DMA 串口实验"};
int main(void)
{
u16 i;
u8 t=0;
u8 j,mask=0;
// float pro=0; //进度,用不到//
delay_init(); //延时函数初始化//
uart_init(115200); //串口初始化为115200//
LED_Init(); //初始化与LED连接的硬件接口//
KEY_Init(); //按键初始化//
//DMA1通道4,外设为串口1,针对USART_DR寄存器,存储器为SendBuff,长度SEND_BUF_SIZE//
MYDMA_Config(DMA1_Channel4,(u32)&USART1->DR,(u32)SendBuff,SEND_BUF_SIZE);
//显示提示信息
j=sizeof(TEXT_TO_SEND); //计算"ALIENTEK WarShip STM32F1 DMA 串口实验"这段字符串的字节长度,远小于8200//
//填充数据到SendBuff//
for(i=0;i<SEND_BUF_SIZE;i++) //当0≤i<8200时,执行//
{
if(t>=j) //当t≥j时,执行//
{
if(mask)
{
SendBuff[i]=0x0a; //如果mask=1,则SendBuff[i]=0x0a,换行的意思//
t=0;
}else
{
SendBuff[i]=0x0d; //如果mask=0,则SendBuff[i]=0x0d,回车的意思//
mask++;
}
}else //当t<j时//
{
mask=0;
SendBuff[i]=TEXT_TO_SEND[t]; //复制TEXT_TO_SEND语句//
printf("\r\nDMA DATA:%d\r\n",SendBuff[i]);
t++;
}
}
i=0; //i至0//
while(1)
{
t=KEY_Scan(0);
if(t==KEY0_PRES) //如果KEY0按下,执行//
{
printf("\r\nDMA DATA:\r\n");
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送//
MYDMA_Enable(DMA1_Channel4); //开始一次DMA传输//
//实际应用中,传输数据期间,可以执行另外的任务//
while(1)
{
if(DMA_GetFlagStatus(DMA1_FLAG_TC4)!=RESET) //判断通道4传输是否完成//
{
LED1=1;
DMA_ClearFlag(DMA1_FLAG_TC4); //清除通道4传输完成标志//
break; //推出当前循环//
}
//pro=DMA_GetCurrDataCounter(DMA1_Channel4); //得到当前还剩余多少个数据//
//pro=1-pro/SEND_BUF_SIZE; //得到百分比//
//pro*=100; //扩大100倍//
LED1=!LED1;
}
}
i++;
delay_ms(10);
if(i==20)
{
LED0=!LED0; //提示系统正在运行
i=0;
}
}
}
实验结果
按一次KEY0键,一次导入如下字符串数据至串口调试助手上:
旧知识点
1)复习如何新建工程模板,可参考STM32学习心得二:新建工程模板;
2)复习基于库函数的初始化函数的一般格式,可参考STM32学习心得三:GPIO实验-基于库函数;
3)复习寄存器地址,可参考STM32学习心得四:GPIO实验-基于寄存器;
4)复习位操作,可参考STM32学习心得五:GPIO实验-基于位操作;
5)复习寄存器地址名称映射,可参考STM32学习心得六:相关C语言学习及寄存器地址名称映射解读;
6)复习时钟系统框图,可参考STM32学习心得七:STM32时钟系统框图解读及相关函数;
7)复习延迟函数,可参考STM32学习心得九:Systick滴答定时器和延时函数解读;
8)复习ST-LINK仿真器的参数配置,可参考STM32学习心得十:在Keil MDK软件中配置ST-LINK仿真器;
9)复习ST-LINK调试方法,可参考STM32学习心得十一:ST-LINK调试原理+软硬件仿真调试方法;
10)复习串口通信相关知识,可参考STM32学习心得十四:串口通信相关知识及配置方法。