STM32+DMA自学笔记
DMA
一、简介
- 使用场景:DMA可以提供外设(指外设的寄存器,一般是数据寄存器DR)和存储器(运行内存SRAM和程序存储器Flash)或者存储器与存储器之间的高速数据传输
- 优点:无需CPU干预,节省CPU资源
- DMA通道:STM32一共有12个DMA通道,DMA1(7个),DMA2(5个),STM32F103C8T6的DMA资源只有DMA1
- DMA触发条件:每个通道都支持软件触发和特定的硬件触发
- 软件触发:存储器与存储器
- 硬件触发:外设和存储器,每次数据的转运都需要触发,每个DMA通道的硬件触发源是不同的
二、STM32存储器映像
计算机系统由运算器、控制器、存储器、输入设备和输出设备五部分组成,运算器和控制器合一起称为CPU,所以计算机的核心组成就是CPU和存储器。存储器最重要的就是存储器的内容和地址,存储器总共分为ROM和RAM
- 程序存储器Flash:就是主闪存,起始地址是0x0800 0000,存储C语言编译后的程序代码
- 系统存储器:起始地址是0x1FFF F000,存储BootLoader,用于串口下载,BootLoader程序由芯片出厂自动写入,一般不允许用户自己写入
- 选项字节:起始地址是0x1FFF F800,存储C语言编译后的程序代码,主要存的是Flash的读保护、写保护和看门狗等配置
-
运行内存SRAM:对于电脑来说运行内存就是内存条,起始地址是0x2000 0000,存储运行过程中的临时变量也就是我们在程序中定义变量、数组、结构体的地方
-
外设寄存器:起始地址是0x4000 0000,存储各个外设的配置参数
-
内核外设寄存器:起始地址是0xE000 0000,存储内核各个外设的配置参数
三、DMA框图
总线矩阵的左端是主动单元,拥有存储器的访问权;右端是被动单元,它们的存储器只能被左边的主动单元读写;由于DMA需要转运数据,那就必须能够访问被动单元的存储器,所以DMA也必须有访问的主动权,那主动单元除了内核CPU,剩下的就是DMA总线了
DMA总线:从图中能看出总线矩阵右端总共有3条DMA总线,分别是DMA1、DMA2和以太网的DMA,从DMA1和DMA2的框图中能看见它们分别有7个通道和5个通道,其中的每一条通道都可以单独设置转运数据的原地址和目的地址。
DMA框图中有一个仲裁器和AHB从设备,仲裁器:虽然DMA有多条通道,但是每个DMA最终都只有一条总线,也就是一个DMA同一时间只能转运一个数据,那么仲裁器的作用就是选择通道,如果同一条DMA总线的通道产生了冲突, 那么就由仲裁器根据通道的优先级来决定数据转运的顺序;AHB从设备:DMA本身作为一个外设,他也会有相应的配置寄存器,在图中它连接在了AHB总线上,所以DMA既是总线的矩阵的主动单元可以读写各种存储器,也是被动单元,CPU可以通过系统总线经过总线矩阵和AHB总线对DMA进行配置。
DMA数据转运程序
一、程序功能
主程序定义两个数组DataA和DataB,使用DMA将DataA的数据转运到DataB中
uint8_t DataA[] = {0x01, 0x02, 0x03,0x04};
uint8_t DataB[] = {0, 0, 0, 0};
二、程序实现
1.复制OLED的工程文件,重命名为08_1DMA数据转运
2.在工程的System目录下新建DMA的.c文件和.h文件
因为DMA不涉及外围硬件电路,所以建在System目录下
3.在MyDMA.c文件中编写DMA初始化函数
由DMA的基本结构图得到初始化步骤
- RCC开启DMA时钟
- 配置DMA,初始化结构体
- 开启DMA
#include "stm32f10x.h" // Device header
//一、RCC开启DMA时钟
//二、配置DMA,初始化结构体
//三、开启DMA,如果想要在初始化之后就立刻使用DMA,就可以在初始化DMA函数中开启DMA
uint16_t MyDMA_Size = 0;
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
MyDMA_Size = Size;
//一、RCC开启DMA时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
//二、配置DMA,初始化结构体
DMA_InitTypeDef DMA_InitStruct;
/*
我们这个程序是要把DataA的数据转运到DataB中,这两个数组会被存放在SRAM中,并且地址是不固定的,由
编译器分配,在配置DMA_PeripheralBaseAddr参数时,不能填入一个绝对地址,而是通过数组名来获取,我
们把转运数据的源地址放到初始化函数的参数中,这样想转运那个数组就可以将地址传到DMA初始化函数
的参数中
*/
DMA_InitStruct.DMA_PeripheralBaseAddr = AddrA;//源数据的起始地址
/*
DMA_PeripheralDataSize_Byte:Byte,字节,就是uint8_t
DMA_PeripheralDataSize_HalfWord:HalfWord,半字,就是uint16_t
DMA_PeripheralDataSize_Word:Word,字,就是uint32_t
由于传递的参数是uint8_t的数组,所以选择第一个
*/
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//源数据的数据宽度
/*
DMA_PeripheralInc_Enable:数据地址自增
DMA_PeripheralInc_Disable:不自增
由于转运的是数组,它里面存放的数据地址都是自增的,选第一个
*/
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable;//源地址是否自增
/* 以下三个参数参考上面 */
DMA_InitStruct.DMA_MemoryBaseAddr = AddrB;//目的地址
DMA_InitStruct.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;//数据宽度
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;//目的地址是否自增
/*
用来指定外设站点是源端还是目的地
DMA_DIR_PeripheralDST:DST,destination,目的地,就是外设站点作为目的地,方向为存储器到外设
DMA_DIR_PeripheralSRC:SRC,source,源头,就是外设站点作为源地址,方向为外设到存储器
我们这里由于是打算把数组A放在外设站点,数组B放在储存器站点,所以是选第二个
*/
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;//数据传输方向
/*
读DMA_Init函数的源码,可以知道DMA_BufferSize参数对应的范围是1~65536,所以这个参数应该是
uint16_t类型,把这个参数提取到MyDMA_Init函数的形参去
*/
DMA_InitStruct.DMA_BufferSize = Size;//缓存区大小,就是传输计数器
/*
这个参数的自动重装和软件触发不能同时使用
*/
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;//是否自动重装
DMA_InitStruct.DMA_M2M = DMA_M2M_Enable;//硬件和软件触发选择
DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;//优先级设置
DMA_Init(DMA1_Channel1, &DMA_InitStruct);
//三、关闭DMA
DMA_Cmd(DMA1_Channel1, DISABLE);
}
//DMA传输函数,调用一次,DMA转运一次数据
/*
由于DMA初始化函数里自动重装选择的是不重装,在一次DMA转运完成后,传输计数器清零,所以在DMA传输函数中
要对传输计数器重新赋值,在对传输计数器重新赋值前必须要先失能DMA
*/
void MyDMA_Transfer(void)
{
DMA_Cmd(DMA1_Channel1, DISABLE);//失能DMA
/*
这个函数的参数就是转运数据的次数,这个参数在DMA初始化函数中定义了,由于这个参数要在两个
不同的函数中调用,所以需要一个变量来储存,在顶部创建一个MyDMA_Size变量
*/
DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);//传输计时器重新赋值
DMA_Cmd(DMA1_Channel1, ENABLE);//使能DMA
//等待转运完成
while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
//手动清除标志位
DMA_ClearFlag(DMA1_FLAG_TC1);
}
4.MyDMA.h
#ifndef __MYDMA_
#define __MYDMA_
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size);
void MyDMA_Transfer(void);
#endif
5.main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"
uint8_t DataA[] = {0x01, 0x02, 0x03,0x04};
uint8_t DataB[] = {0, 0, 0, 0};
int main(void)
{
OLED_Init();
MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);
OLED_ShowString(1, 1, "DataA");
OLED_ShowString(3, 1, "DataB");
OLED_ShowHexNum(1, 8, (uint32_t)DataA, 8);
OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8);
while (1)
{
DataA[0] ++;
DataA[1] ++;
DataA[2] ++;
DataA[3] ++;
OLED_ShowHexNum(2, 1, DataA[0], 2);
OLED_ShowHexNum(2, 4, DataA[1], 2);
OLED_ShowHexNum(2, 7, DataA[2], 2);
OLED_ShowHexNum(2, 10, DataA[3], 2);
OLED_ShowHexNum(4, 1, DataB[0], 2);
OLED_ShowHexNum(4, 4, DataB[1], 2);
OLED_ShowHexNum(4, 7, DataB[2], 2);
OLED_ShowHexNum(4, 10, DataB[3], 2);
Delay_ms(1000);
MyDMA_Transfer();
OLED_ShowHexNum(2, 1, DataA[0], 2);
OLED_ShowHexNum(2, 4, DataA[1], 2);
OLED_ShowHexNum(2, 7, DataA[2], 2);
OLED_ShowHexNum(2, 10, DataA[3], 2);
OLED_ShowHexNum(4, 1, DataB[0], 2);
OLED_ShowHexNum(4, 4, DataB[1], 2);
OLED_ShowHexNum(4, 7, DataB[2], 2);
OLED_ShowHexNum(4, 10, DataB[3], 2);
Delay_ms(1000);
}
}
作者:delay_134