Skip to content

第八章 DMA

8.1 DMA

8.1.1 DMA简介

  • DMA(Direct Memory Access)直接存储器存取
  • DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
  • 12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道)
  • 每个通道都支持软件触发和特定的硬件触发

STM32F103C8T6 DMA资源:DMA1(7个通道)

Note

外设和存储器之间的数据转运一般使用硬件触发,比如AD转换完成后触发;而存储器和存储器之间数据转运一般使用软件触发,可以快速的把数据转运到想要的位置。注意这里的外设指数据寄存器,如ADC的数据寄存器;存储器指Flash和SRAM

8.1.2 存储器映像

类型 起始地址 存储器 用途
ROM 0x0800 0000 程序存储器Flash 存储C语言编译后的程序代码
ROM 0x1FFF F000 系统存储器 存储BootLoader,用于串口下载
ROM 0x1FFF F800 选项字节 存储一些独立于程序代码的配置参数
RAM 0x2000 0000 运行内存SRAM 存储运行过程中的临时变量
RAM 0x4000 0000 外设寄存器 存储各个外设的配置参数
RAM 0xE000 0000 内核外设寄存器 存储内核各个外设的配置参数

8.1.3 DMA框图

  1. AHB从设备 ,用于配置DMA参数
  2. 仲裁器 ,DMA内部的仲裁用于给内部通道配置优先级,总线矩阵也是一个仲裁器,用于处理CPU和DMA任务冲突
  3. DMA的内部通道 ,DMA1有 7 个, DMA2有 5 个
  4. Flash ,CPU和DMA直接访问Flash只能读而不能写,要写入数据需要使用Flash接口控制器

image-20241227102219912

8.1.4 DMA基本结构

  • 触发模式 ,M2M置 1 为软件触发,置 0 为硬件触发。
  • 传输计数器 ,每触发一次,传输计数器减一,当传输计数器为 0 时,即使触发转运,也不会再发生数据转运。
  • 自动重装器 ,当传输计数器减至 0 ,会自动重装,即循环模式;与ADC的连续模式类似,配合ADC扫描模式使用。
  • 外设与存储器 ,进行数据转运需要的三个参数,即起始地址、数据宽度、地址是否自增。
Danger

软件触发和循环模式不要同时使用,因为软件触发不需要时机, 旨在快速将传输计数器清零 ,而循环模式是 清零后自动重装 ,两者逻辑叠加,DMA便会一直进行无法停止。另外,在写入传输计数器之前,需要DMA暂停工作。

image-20241227105325039

8.2 DMA请求与数据宽度

8.2.1 DMA请求映像

Note

仅关注各通道的外设请求信号,后部分信号看DMA基本结构图更好理解。

image-20241227112408133

8.2.2 数据宽度与对齐

总的来说,表格的意思就是:

  1. 小的数据转到大的里面,高位补零
  2. 大的数据转到小的里面,高位舍弃

image-20241227112945253

8.3 STM32CubeMX参数

关于数据宽度分为 字(Word)半字(Half Word)字节(Byte)。另外,“Increment Address” 指地址是否自增。

Waring

我们应了解,一个二进制位是一个 比特位(Bit) ,一个 字节(Byte) 是 8 个Bit。而 字(Word) 取决于处理器,例如:对于32位的单片机,一个 字(Word)即为 32 Bit(四个字节),半字就是 16 Bit(两个字节)。

image-20241227115032257

8.4 工程配置

8.4.1 存储器到存储器

  • 配置一个DMA通道,勾选地址自增和数据宽度为字节
  • 配置UART输出数据

image-20241227130438600

image-20241226222231023

定义测试数组为全局变量

C
/* USER CODE BEGIN PV */
uint8_t DataA[4] = {0x01, 0x02, 0x03, 0x04};                //定义测试数组DataA
uint8_t DataB[4] = {1, 1, 1, 1};                            //定义测试数组DataB
/* USER CODE END PV */
Danger

这里数据类型必须为uint8_t ,因为uint8_t正好是一个字节

建立一个DMA转运函数,将转运数组和目标数组填进去,最后一个参数是数据长度为 4

C
/* USER CODE BEGIN 0 */
void MyDMA_Transfer(void) {
    HAL_DMA_Start(&hdma_memtomem_dma1_channel2, (uint32_t)DataA, (uint32_t)DataB, 4);
    HAL_DMA_PollForTransfer(&hdma_memtomem_dma1_channel2, HAL_DMA_FULL_TRANSFER, HAL_MAX_DELAY);
}
/* USER CODE END 0 */

串口发送缓冲区

C
/* USER CODE BEGIN 2 */
char buffer[64];
/* USER CODE END 2 */

主循环代码 ,转换数组自增,调用转换函数,串口发送转换前后的数据

C
/* USER CODE BEGIN WHILE */
while (1) {
    DataA[0]++;        //变换测试数据
    DataA[1]++;
    DataA[2]++;
    DataA[3]++;
    MyDMA_Transfer();
    for (uint8_t i = 0; i < 4; i++) {
    int len1 = snprintf(buffer, sizeof(buffer), "Array[%d] = %hhu\r\n", i, DataA[i]);
    HAL_UART_Transmit(&huart1, (uint8_t *) buffer, len1, HAL_MAX_DELAY);  // 串口发送
    int len2 = snprintf(buffer, sizeof(buffer), "Array[%d] = %hhu\r\n", i, DataB[i]);
    HAL_UART_Transmit(&huart1, (uint8_t *) buffer, len2, HAL_MAX_DELAY);  // 串口发送
}

    HAL_Delay(2000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */

测试结果:

image-20241227131121937

8.4.2 ADC单次转换扫描模式DMA转运

这里继续借鉴江协科技的图,我将两个电位计接在PA3和PA4,也可以节热敏电阻等可以测模拟量的传感器。

7-1 AD单通道

STM32CubeMX配置

image-20241228230805669

DMA设置,半字传输

image-20241228231106505

配置串口输出

image-20241228231939358

配置时钟

image-20241228230840274

定义数据接收数组全局变量

C
/* USER CODE BEGIN PV */
//接收数组
uint16_t values[2];
//串口缓存
char buffer[64];
/* USER CODE END PV */

在非连续模式下,一次ADC数据全部转换完成会触发中断,所以在 中断中读取数据 :

C
/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
    if (hadc == &hadc1) {
        sprintf(buffer, "%d %d", values[0], values[1]);
        HAL_UART_Transmit(&huart1, (uint8_t *)buffer, strlen(buffer), HAL_MAX_DELAY);
    }
}
/* USER CODE END 4 */

主函数,调用ADC校准和开始,因为非连续模式, ADC开始放循环中

C
/* USER CODE BEGIN 2 */
HAL_ADCEx_Calibration_Start(&hadc1);
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) {
    HAL_ADC_Start_DMA(&hadc1, (uint32_t *) values, 2);
    HAL_Delay(500);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */

image-20241228233920303

8.4.3 ADC连续转换扫描模式DMA循环转运

开启ADC连续模式,经测试这里需要把 采样时间提高 ,不然采不到数据。

image-20241229002825401

DMA循环模式,其余与上个工程一致

image-20241228234124020

只需将ADC开始拿出循环,删除中断并把中断的程序放主循环,因为连续模式可以随时读取ADC值。

C
/* USER CODE BEGIN 2 */
HAL_ADCEx_Calibration_Start(&hadc1);
HAL_ADC_Start_DMA(&hadc1, (uint32_t *) values, 2);
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) {
    HAL_Delay(500);
    sprintf(buffer, "%d %d", values[0], values[1]);
    HAL_UART_Transmit(&huart1, (uint8_t *) buffer, strlen(buffer), HAL_MAX_DELAY);

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */