STM32DMA
如果想要实现高性能的内存存取,DMA是你最好的猫娘助手
DMA
DMA(Directly Memory Access),直接存储器存取
可以提供外设1和存储器2或存储器与存储器之间的传输,无须CPU干预,节省CPU资源。类似于汇编里的movsb
和movsw
支持硬件触发和软件触发,有DMA1的7个+DMA2的5个=12个独立配置的通道。
本质上是存储器之间的数据转运,“外设”不过是特别制定了可以转运外设的存储器而已。
存储器映像
类型 | 起始地址 | 存储器 | 用途 |
---|---|---|---|
ROM | 0x0800 0000 | 程序存储器Flash | 程序代码 |
ROM | 0x1FFF F000 | 系统存储器 | BootLoader,用于串口下载 |
ROM | 0x1FFF F800 | 选项字节 | 独立于代码的配置参数 |
RAM | 0x2000 0000 | 运行内存SRAM | 运行时临时变量 |
RAM | 0x4800 0000 | 外设寄存器 | 各个外设的配置参数 |
RAM | 0xE800 0000 | 内核外设寄存器 | 内核各个外设的配置参数 |
江科大自化协:外设就是寄存器,寄存器就是存储器
DMA框图
系统总线左侧的是主动单元,右侧的是被动单元
DCode总线专门访问Flash,系统总线访问其他东西
各个通道可以分别设置源地址和目的地址。DMA总线只有一条,只能分时复用,如果产生了冲突,会由仲裁器,根据通道的优先级决定谁先用谁后用。
在总线矩阵处,如果DMA与CPU产生了冲突,则DMA会阻止CPU的访问,但又为CPU留有一半的带宽,使得CPU可以正常运行。
AHB从设备用于配置DMA参数。
DMA也有外设寄存器,也就是说,它也是AHB上的被动单元。
DMA请求的触发源是各个外设,即硬件触发。通过DMA请求
向DMA发送信号。
Flash对CPU和DMA是只读的,不能写入。需要配置Flash接口控制器才能写入。
SRAM可以任意读写
有的寄存器只读,有的寄存器只写。
以太网DMA是私有的,不用管。
基本结构
咕咕咕
请求映象
原理看上面的江科大的图就好,这里主要是标记了请求映象。
使用前需要开启对应的通道,例如ADC_DMA
、TIM1_DMA
,函数名称类似于TIM1_DMACmd()
存储器到存储器的数据转运用软件触发就可以,尽快完成,不需要等待硬件触发。
仲裁器里通道号越小优先级越高,可以配置……但没必要。
数据宽度与对齐
如果数据宽度一致或是不一致,如参考手册的图
ADC扫描模式+DMA
硬件触发,ADC与DMA单个通道转换完成相同步。
根据江科大的实验,单个ADC通道肯定有DMA请求
示例
DMA数据转运
这里调用的OLED相关函数都是我那个oled.h里的
验证存储器映像
1 | // includes... |
寄存器地址
1
2
3
4
5
6 // includes...
int main(void){
OLED_Init();
OLED_Shell_ShowHex(1, 1, (uint32_t)&ADC1->DR, 2);
}main.c
我们要将第一段区域内的数据转移到第二段内存区域中去。
电路图只是连一块OELD,不放了。
1 | // includes... |
thedma.c
thedma可以放在Sys
文件夹里
1 | void thedma_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size){ |
DMA + AD多通道
main.c
1 |
|
thedma.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
uint16_t AD_Value[4];
void thedma_Init(){
// 开启ADC时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// ADC初始化
ADC_RegularChannelConfig(ADC1, ADC_Channel1_0, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel1_1, 2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel1_2, 3, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel1_3, 4, ADC_SampleTime_55Cycles5);
DMA_InitTypeDef DMA_Initstructure;
DMA_Initstructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_Initstructure.DMA_PeripheralDatasize = DMA_PeripheralDataSize_HalfWord; // 以字节方式自增
DMA_Initstructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
DMA_Initstructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;
DMA_Initstructure.DMA_MemoryDatasize = DMA_PeripheralDataSize_HalfWord;
DMA_Initstructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_Initstructure.DMA_Buffersize = 4;
DMA_Initstructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 存储器->外设
DMA_Initstructure.DMA_M2M = DMA_M2M_Disable;
DMA_Initstructure.DMA_Mode = DMA_Mode_Normal;
DMA_Initstructure.DMA_Priority = DMA_Priority_Medium;
DMA_Init(DMA1_Channel1, &DMA_Initstructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
}
可以把ADC连续模式和DMA循环模式打开,把ADC处直接放在初始化之后一行,这样就能始终吧最新的数值刷新到数组里,这样就不再需要ADC_GetValue()了。当然,用定时器也可以。
杂记
ChatGPT的大作《猫娘之诗》
其一
其二
角标
1. 指外设寄存器,如ADC的数据寄存器、串口数据寄存器等等。 ↩
2. SRAM、FLASH ↩