学习目标

  1. 理解DMA数据传输过程
  2. 掌握DMA的初始化流程
  3. 理解源和目标的配置
  4. 理解数据传输特点
  5. 能够动态配置源数据
  6. 能够实现DMA中断

    学习内容

    需求

    1. #define ARR_LEN 1024
    2. char src[ARR_LEN] = "hello\0";
    3. char dst[ARR_LEN] = {0};

    将src这个数组的值,赋值到dst这个数组中,不可以采取直接赋值的方式,需要通过DMA将数据进行传递。

    数据交互流程

    image.png

  7. CPU配置好DMA

  8. CPU通知DMA干活
  9. DMA请求源数据
  10. DMA获取源数据
  11. DMA将获取的源数据交给目标

    开发流程

    依赖引入

    添加标准库中的gd32f4xx_dma.c文件

    DMA初始

    ```c /* DMA m2m */ // 时钟 rcu_periph_clock_enable(RCU_DMA1); // 重置dma dma_deinit(DMA1, DMA_CH0);

//////// dma 配置 dma_single_data_parameter_struct dsdps; dma_single_data_para_struct_init(&dsdps); // 方向 dsdps.direction = DMA_MEMORY_TO_MEMORY;
// 内存 dsdps.memory0_addr = (uint32_t)dst; dsdps.memory_inc = DMA_MEMORY_INCREASE_ENABLE; // 外设 dsdps.periph_addr = (uint32_t)src; dsdps.periph_inc = DMA_PERIPH_INCREASE_ENABLE; // 数据长度 dsdps.number = ARR_LEN; // 数据宽度 dsdps.periph_memory_width = DMA_PERIPH_WIDTH_8BIT; dma_single_data_mode_init(DMA1, DMA_CH0, &dsdps);

  1. 1. 配置时钟
  2. 2. 初始化dma通道
  3. <a name="ZK7mP"></a>
  4. #### DMA传输请求
  5. ```c
  6. dma_channel_enable(DMA1, DMA_CH0);
  7. while(RESET == dma_flag_get(DMA1, DMA_CH0, DMA_FLAG_FTF));
  8. dma_flag_clear(DMA1, DMA_CH0, DMA_FLAG_FTF);
  • dma_channel_enable: 请求dma数据传输
  • DMA_FLAG_FTF:为传输完成标记

    完整代码

    ```c

    include “gd32f4xx.h”

    include “systick.h”

    include

    include

    include “main.h”

    include “Usart0.h”

define ARR_LEN 1024

char src[ARR_LEN] = “hello\0”; char dst[ARR_LEN] = {0};

void Usart0_recv(uint8_t* data, uint32_t len) { printf(“recv: %s\r\n”, data);

  1. dma_channel_enable(DMA1, DMA_CH0);
  2. while(RESET == dma_flag_get(DMA1, DMA_CH0, DMA_FLAG_FTF));
  3. dma_flag_clear(DMA1, DMA_CH0, DMA_FLAG_FTF);
  4. printf("dst: %s\n", dst);

}

static void DMA_config() {

  1. /***************** DMA m2m *******************/
  2. // 时钟
  3. rcu_periph_clock_enable(RCU_DMA1);
  4. // 重置dma
  5. dma_deinit(DMA1, DMA_CH0);
  6. //////// dma 配置
  7. dma_single_data_parameter_struct dsdps;
  8. dma_single_data_para_struct_init(&dsdps);
  9. // 方向
  10. dsdps.direction = DMA_MEMORY_TO_MEMORY;
  11. // 内存
  12. dsdps.memory0_addr = (uint32_t)dst;
  13. dsdps.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
  14. // 外设
  15. dsdps.periph_addr = (uint32_t)src;
  16. dsdps.periph_inc = DMA_PERIPH_INCREASE_ENABLE;
  17. // 数据长度
  18. dsdps.number = ARR_LEN;
  19. // 数据宽度
  20. dsdps.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
  21. dma_single_data_mode_init(DMA1, DMA_CH0, &dsdps);
  22. // // 中断配置
  23. // nvic_irq_enable(DMA1_Channel0_IRQn, 2, 2);
  24. // // 中断使能
  25. // dma_interrupt_enable(DMA1, DMA_CH0, DMA_CHXCTL_FTFIE);
  26. // // 开启DMA通道
  27. // dma_channel_enable(DMA1, DMA_CH0);

}

//void DMA1_Channel0_IRQHandler() { // // 判断中断标记 // if(SET == dma_interrupt_flag_get(DMA1, DMA_CH0, DMA_INT_FLAG_FTF)) { // printf(“dst: %s\n”, dst); // } // // 清理标记位 // dma_interrupt_flag_clear(DMA1, DMA_CH0, DMA_INT_FLAG_FTF); //}

int main(void) { nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); systick_config(); Usart0_init();

  1. DMA_config();
  2. while(1) {
  3. }

}

  1. <a name="EFWCI"></a>
  2. ### 关心的内容
  3. ```c
  4. uint32_t dmax = DMA1;
  5. uint32_t dmax_rcu = RCU_DMA1;
  6. uint32_t dmax_ch = DMA_CH0;
  7. uint32_t dmax_dirction = DMA_MEMORY_TO_MEMORY;
  8. uint32_t dmax_src = (uint32_t)src;
  9. uint32_t dmax_src_inc = DMA_PERIPH_INCREASE_ENABLE;
  10. uint32_t dmax_src_width = DMA_PERIPH_WIDTH_8BIT;
  11. uint32_t dmax_src_len = ARR_LEN;
  12. uint32_t dmax_dst = (uint32_t)dst;
  13. uint32_t dmax_dst_inc = DMA_MEMORY_INCREASE_ENABLE;
  14. /***************** DMA m2m *******************/
  15. // 时钟
  16. rcu_periph_clock_enable(dmax_rcu);
  17. // 重置dma
  18. dma_deinit(dmax, dmax_ch);
  19. //////// dma 配置
  20. dma_single_data_parameter_struct dsdps;
  21. dma_single_data_para_struct_init(&dsdps);
  22. // 方向
  23. dsdps.direction = dmax_dirction;
  24. // 内存
  25. dsdps.memory0_addr = dmax_dst;
  26. dsdps.memory_inc = dmax_dst_inc;
  27. // 外设
  28. dsdps.periph_addr = dmax_src;
  29. dsdps.periph_inc = dmax_src_inc;
  30. // 数据长度
  31. dsdps.number = dmax_src_len;
  32. // 数据宽度
  33. dsdps.periph_memory_width = dmax_src_width;
  34. dma_single_data_mode_init(dmax, dmax_ch, &dsdps);
  • 哪个dma:DMA0或者DMA1
  • dma的哪个通道:ch0到ch7
  • 传输方向是什么:内存到内存,内存到外设,外设到内存
  • 源地址是多少?源的数据长度是多少(几个byte)?源的数据宽度是多少(几个bit)?
  • 目标地址是什么?
  • 从源地址向目标地址传输数据时,源地址的指针是否需要增长。
  • 从源地址向目标地址传输数据时,目标地址的指针是否需要增长。

    数据传输理解

    025.png

    源地址和目标地址

    源地址和目标地址都是提前配置好的,当传输时,就会从源地址将数据传递给目标地址。

    自增长

    每传输一个字节数据,地址是否需要增长。这里包含了源地址是否需要增长,也包含了目标地址是否需要增长。

    数据宽度

    数据宽度表示一次传递多上个bit

    数据长度

    传输了多少个数据宽度的数据。

    源的数据未定

    这里主要指的是在配置DMA的源和目标的时候,我们无法给出一个具体的源地址和源的数据长度。
    026.png
    通常我们要解决的也是初始化和数据传输请求。

    DMA初始化

    ```c uint32_t dmax = DMA1; uint32_t dmax_rcu = RCU_DMA1; uint32_t dmax_ch = DMA_CH0;

uint32_t dmax_dirction = DMA_MEMORY_TO_MEMORY;

//uint32_t dmax_src = (uint32_t)src; uint32_t dmax_src_inc = DMA_PERIPH_INCREASE_ENABLE; uint32_t dmax_src_width = DMA_PERIPH_WIDTH_8BIT; //uint32_t dmax_src_len = ARR_LEN;

uint32_t dmax_dst = (uint32_t)dst; uint32_t dmax_dst_inc = DMA_MEMORY_INCREASE_ENABLE; /* DMA m2m */ // 时钟 rcu_periph_clock_enable(dmax_rcu); // 重置dma dma_deinit(dmax, dmax_ch);

//////// dma 配置 dma_single_data_parameter_struct dsdps; dma_single_data_para_struct_init(&dsdps); // 方向 dsdps.direction = dmax_dirction;
// 内存 dsdps.memory0_addr = dmax_dst; dsdps.memory_inc = dmax_dst_inc; // 外设 //dsdps.periph_addr = dmax_src; dsdps.periph_inc = dmax_src_inc; // 数据长度 //dsdps.number = dmax_src_len; // 数据宽度 dsdps.periph_memory_width = dmax_src_width; dma_single_data_mode_init(dmax, dmax_ch, &dsdps);

  1. 初始化时,不再初始化源地址和要传输的长度。
  2. <a name="A9RDs"></a>
  3. #### DMA数据传输请求
  4. ```c
  5. dma_periph_address_config(DMA1, DMA_CH0,(uint32_t)data);
  6. dma_transfer_number_config(DMA1, DMA_CH0, len);
  7. dma_channel_enable(DMA1, DMA_CH0);
  8. while(RESET == dma_flag_get(DMA1, DMA_CH0, DMA_FLAG_FTF));
  9. dma_flag_clear(DMA1, DMA_CH0, DMA_FLAG_FTF);

请求数据传输前,进行动态配置:

  • 在此需要注意的是,要分清楚当前是否是调用源的地址配置

    完整代码

    ```c

    include “gd32f4xx.h”

    include “systick.h”

    include

    include

    include “main.h”

    include “Usart0.h”

define ARR_LEN 1024

char dst[ARR_LEN] = {0};

void Usart0_recv(uint8_t* data, uint32_t len) { printf(“recv: %s\r\n”, data);

  1. dma_periph_address_config(DMA1, DMA_CH0,(uint32_t)data);
  2. dma_transfer_number_config(DMA1, DMA_CH0, len);
  3. dma_channel_enable(DMA1, DMA_CH0);
  4. while(RESET == dma_flag_get(DMA1, DMA_CH0, DMA_FLAG_FTF));
  5. dma_flag_clear(DMA1, DMA_CH0, DMA_FLAG_FTF);
  6. dst[len] = '\0';
  7. printf("dst: %s\n", dst);

}

static void DMA_config() {

  1. uint32_t dmax = DMA1;
  2. uint32_t dmax_rcu = RCU_DMA1;
  3. uint32_t dmax_ch = DMA_CH0;
  4. uint32_t dmax_dirction = DMA_MEMORY_TO_MEMORY;
  5. //uint32_t dmax_src = (uint32_t)src;
  6. uint32_t dmax_src_inc = DMA_PERIPH_INCREASE_ENABLE;
  7. uint32_t dmax_src_width = DMA_PERIPH_WIDTH_8BIT;
  8. //uint32_t dmax_src_len = ARR_LEN;
  9. uint32_t dmax_dst = (uint32_t)dst;
  10. uint32_t dmax_dst_inc = DMA_MEMORY_INCREASE_ENABLE;
  11. /***************** DMA m2m *******************/
  12. // 时钟
  13. rcu_periph_clock_enable(dmax_rcu);
  14. // 重置dma
  15. dma_deinit(dmax, dmax_ch);
  16. //////// dma 配置
  17. dma_single_data_parameter_struct dsdps;
  18. dma_single_data_para_struct_init(&dsdps);
  19. // 方向
  20. dsdps.direction = dmax_dirction;
  21. // 内存
  22. dsdps.memory0_addr = dmax_dst;
  23. dsdps.memory_inc = dmax_dst_inc;
  24. // 外设
  25. //dsdps.periph_addr = dmax_src;
  26. dsdps.periph_inc = dmax_src_inc;
  27. // 数据长度
  28. //dsdps.number = dmax_src_len;
  29. // 数据宽度
  30. dsdps.periph_memory_width = dmax_src_width;
  31. dma_single_data_mode_init(dmax, dmax_ch, &dsdps);

}

int main(void) { nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); systick_config(); Usart0_init();

  1. DMA_config();
  2. while(1) {
  3. }

}

  1. <a name="ZBJS5"></a>
  2. ### DMA中断
  3. 通常,dma数据传输完成,我们也是可以知道的。可以通过中断的方式获取。
  4. <a name="KFYdv"></a>
  5. #### 开启中断
  6. ```c
  7. // 中断配置
  8. nvic_irq_enable(DMA1_Channel0_IRQn, 2, 2);
  9. // 中断使能
  10. dma_interrupt_enable(DMA1, DMA_CH0, DMA_CHXCTL_FTFIE);
  11. // 开启DMA通道
  12. dma_channel_enable(DMA1, DMA_CH0);
  • DMA_CHXCTL_FTFIE: 表示中断数据传输完成标记

    中断函数实现

    ```c void DMA1_Channel0_IRQHandler() { // 判断中断标记 if(SET == dma_interrupt_flag_get(DMA1, DMA_CH0, DMA_INT_FLAG_FTF)) {

    1. printf("INT dst: %s\n", dst);

    } // 清理标记位 dma_interrupt_flag_clear(DMA1, DMA_CH0, DMA_INT_FLAG_FTF); }

  1. - DMA_INT_FLAG_FTF 表示中断传输完成标记,需要在标记触发时做业务逻辑
  2. - 同时,需要配合寄存器,清除标记
  3. <a name="bo4Fk"></a>
  4. #### 完整代码
  5. ```c
  6. #include "gd32f4xx.h"
  7. #include "systick.h"
  8. #include <stdio.h>
  9. #include <string.h>
  10. #include "main.h"
  11. #include "Usart0.h"
  12. #define ARR_LEN 1024
  13. char src[ARR_LEN] = "hello\0";
  14. char dst[ARR_LEN] = {0};
  15. void Usart0_recv(uint8_t* data, uint32_t len) {
  16. printf("recv: %s\r\n", data);
  17. memcpy(src, data, len);
  18. src[len] = '\0';
  19. dma_channel_enable(DMA1, DMA_CH0);
  20. printf("dst: %s\n", dst);
  21. }
  22. static void DMA_config() {
  23. uint32_t dmax = DMA1;
  24. uint32_t dmax_rcu = RCU_DMA1;
  25. uint32_t dmax_ch = DMA_CH0;
  26. uint32_t dmax_dirction = DMA_MEMORY_TO_MEMORY;
  27. uint32_t dmax_src = (uint32_t)src;
  28. uint32_t dmax_src_inc = DMA_PERIPH_INCREASE_ENABLE;
  29. uint32_t dmax_src_width = DMA_PERIPH_WIDTH_8BIT;
  30. uint32_t dmax_src_len = ARR_LEN;
  31. uint32_t dmax_dst = (uint32_t)dst;
  32. uint32_t dmax_dst_inc = DMA_MEMORY_INCREASE_ENABLE;
  33. /***************** DMA m2m *******************/
  34. // 时钟
  35. rcu_periph_clock_enable(dmax_rcu);
  36. // 重置dma
  37. dma_deinit(dmax, dmax_ch);
  38. //////// dma 配置
  39. dma_single_data_parameter_struct dsdps;
  40. dma_single_data_para_struct_init(&dsdps);
  41. // 方向
  42. dsdps.direction = dmax_dirction;
  43. // 内存
  44. dsdps.memory0_addr = dmax_dst;
  45. dsdps.memory_inc = dmax_dst_inc;
  46. // 外设
  47. dsdps.periph_addr = dmax_src;
  48. dsdps.periph_inc = dmax_src_inc;
  49. // 数据长度
  50. dsdps.number = dmax_src_len;
  51. // 数据宽度
  52. dsdps.periph_memory_width = dmax_src_width;
  53. dma_single_data_mode_init(dmax, dmax_ch, &dsdps);
  54. // 配置中断
  55. nvic_irq_enable(DMA1_Channel0_IRQn, 2, 2);
  56. dma_interrupt_enable(dmax, dmax_ch, DMA_CHXCTL_FTFIE);
  57. }
  58. void DMA1_Channel0_IRQHandler() {
  59. // 判断中断标记
  60. if(SET == dma_interrupt_flag_get(DMA1, DMA_CH0, DMA_INT_FLAG_FTF)) {
  61. printf("INT dst: %s\n", dst);
  62. }
  63. // 清理标记位
  64. dma_interrupt_flag_clear(DMA1, DMA_CH0, DMA_INT_FLAG_FTF);
  65. }
  66. int main(void)
  67. {
  68. nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
  69. systick_config();
  70. Usart0_init();
  71. DMA_config();
  72. while(1) {
  73. }
  74. }

练习

  1. 实现内存到内存数据传递。