学习目标

  1. 加强理解DMA数据传输过程
  2. 加强掌握DMA的初始化流程
  3. 掌握DMA数据表查询
  4. 理解源和目标的配置
  5. 理解数据传输特点
  6. 能够动态配置源数据

    学习内容

    需求

    1. uint8_t data = 0x01;
    2. 串口发送(data);

    实现串口的发送数据, 要求采用dma的方式

    数据交互流程

    image.png

  7. CPU配置好DMA

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

    开发流程

    依赖引入

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

    DMA初始化

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

//////// dma 配置 dma_single_data_parameter_struct dsdps; dma_single_data_para_struct_init(&dsdps); // 方向 dsdps.direction = DMA_MEMORY_TO_PERIPH; // 内存: src // dsdps.memory0_addr = (uint32_t)src; dsdps.memory_inc = DMA_MEMORY_INCREASE_ENABLE; // 外设: dst dsdps.periph_addr = (uint32_t)(&USART_DATA(USART0)); dsdps.periph_inc = DMA_PERIPH_INCREASE_DISABLE; // 数据长度 // dsdps.number = ARR_LEN; // 数据宽度 dsdps.periph_memory_width = DMA_MEMORY_WIDTH_8BIT; // 传输优先级 dsdps.priority = DMA_PRIORITY_ULTRA_HIGH; dma_single_data_mode_init(DMA1, DMA_CH7, &dsdps);

//////// 配置 dma 子连接 dma_channel_subperipheral_select(DMA1, DMA_CH7, DMA_SUBPERI4);

  1. 1. 配置时钟
  2. 2. 初始化dma通道
  3. 3. 配置dma与外设间的子链接
  4. <a name="ZK7mP"></a>
  5. #### DMA传输请求
  6. ```c
  7. // 数据来源 和 长度
  8. dma_memory_address_config(DMA1, DMA_CH7, DMA_MEMORY_0, (uint32_t)(&data));
  9. dma_transfer_number_config(DMA1, DMA_CH7, len);
  10. // 触发传输
  11. dma_channel_enable(DMA1, DMA_CH7);
  12. // 等待DMA传输完成
  13. while(RESET == dma_flag_get(DMA1, DMA_CH7, DMA_FLAG_FTF));
  14. // 清理标记
  15. dma_flag_clear(DMA1, DMA_CH7, DMA_FLAG_FTF);
  • dma_channel_enable: 请求dma数据传输
  • DMA_FLAG_FTF:为传输完成标记

    串口外设DMA开启

    1. // DMA发送功能配置
    2. usart_dma_transmit_config(usartx, USART_TRANSMIT_DMA_ENABLE);
  • 需要开启dma发送配置,才可以打开dam的功能

    发送功能

    1. static void dma_send_byte(uint8_t data) {
    2. // 数据来源 和 长度
    3. dma_memory_address_config(DMA1, DMA_CH7, DMA_MEMORY_0, (uint32_t)(&data));
    4. dma_transfer_number_config(DMA1, DMA_CH7, 1);
    5. // 触发传输
    6. dma_channel_enable(DMA1, DMA_CH7);
    7. // 等待DMA传输完成
    8. while(RESET == dma_flag_get(DMA1, DMA_CH7, DMA_FLAG_FTF));
    9. // 清理标记
    10. dma_flag_clear(DMA1, DMA_CH7, DMA_FLAG_FTF);
    11. }
    1. static void dma_send(uint8_t* data, uint32_t len) {
    2. // 数据来源 和 长度
    3. dma_memory_address_config(DMA1, DMA_CH7, DMA_MEMORY_0, (uint32_t)(&data));
    4. dma_transfer_number_config(DMA1, DMA_CH7, len);
    5. // 触发传输
    6. dma_channel_enable(DMA1, DMA_CH7);
    7. // 等待DMA传输完成
    8. while(RESET == dma_flag_get(DMA1, DMA_CH7, DMA_FLAG_FTF));
    9. // 清理标记
    10. dma_flag_clear(DMA1, DMA_CH7, DMA_FLAG_FTF);
    11. }
    1. static void dma_send_string(const char* str) {
    2. dma_send((uint8_t*)str, strlen(str));
    3. }

    完整代码

    ```c

    include “gd32f4xx.h”

    include “systick.h”

    include

    include

    include “main.h”

static void DMA_config() { /* DMA m2p */ // 时钟 rcu_periph_clock_enable(RCU_DMA1); // 重置dma dma_deinit(DMA1, DMA_CH7);

  1. //////// dma 配置
  2. dma_single_data_parameter_struct dsdps;
  3. dma_single_data_para_struct_init(&dsdps);
  4. // 方向
  5. dsdps.direction = DMA_MEMORY_TO_PERIPH;
  6. // 内存: src
  7. // dsdps.memory0_addr = (uint32_t)src;
  8. dsdps.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
  9. // 外设: dst
  10. dsdps.periph_addr = (uint32_t)(&USART_DATA(USART0));
  11. dsdps.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
  12. // // 数据长度
  13. // dsdps.number = ARR_LEN;
  14. // dst数据宽度
  15. dsdps.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
  16. // 传输优先级
  17. dsdps.priority = DMA_PRIORITY_ULTRA_HIGH;
  18. dma_single_data_mode_init(DMA1, DMA_CH7, &dsdps);
  19. //////// 配置 dma 子连接
  20. dma_channel_subperipheral_select(DMA1, DMA_CH7, DMA_SUBPERI4);

}

static void dma_send_byte(uint8_t data) { // 数据来源 和 长度 dma_memory_address_config(DMA1, DMA_CH7, DMA_MEMORY_0, (uint32_t)(&data)); dma_transfer_number_config(DMA1, DMA_CH7, 1);

  1. // 触发传输
  2. dma_channel_enable(DMA1, DMA_CH7);
  3. // 等待DMA传输完成
  4. while(RESET == dma_flag_get(DMA1, DMA_CH7, DMA_FLAG_FTF));
  5. // 清理标记
  6. dma_flag_clear(DMA1, DMA_CH7, DMA_FLAG_FTF);

}

static void dma_send(uint8_t* data, uint32_t len) { // 数据来源 和 长度 dma_memory_address_config(DMA1, DMA_CH7, DMA_MEMORY_0, (uint32_t)(&data)); dma_transfer_number_config(DMA1, DMA_CH7, len);

  1. // 触发传输
  2. dma_channel_enable(DMA1, DMA_CH7);
  3. // 等待DMA传输完成
  4. while(RESET == dma_flag_get(DMA1, DMA_CH7, DMA_FLAG_FTF));
  5. // 清理标记
  6. dma_flag_clear(DMA1, DMA_CH7, DMA_FLAG_FTF);

}

static void dma_send_string(const char str) { dma_send((uint8_t)str, strlen(str)); }

static void Usart_config() { // 哪个串口 uint32_t usartx = USART0; uint32_t usartx_rcu = RCU_USART0; uint32_t usartx_irq = USART0_IRQn;

  1. // 波特率
  2. uint32_t usartx_p_baudrate = 115200;
  3. // tx和rx,用了哪个引脚
  4. uint32_t usartx_tx_port_rcu = RCU_GPIOA;
  5. uint32_t usartx_tx_port = GPIOA;
  6. uint32_t usartx_tx_pin = GPIO_PIN_9;
  7. // 复用功能编号
  8. uint32_t usartx_tx_af = GPIO_AF_7;
  9. // tx和rx,用了哪个引脚
  10. uint32_t usartx_rx_port_rcu = RCU_GPIOA;
  11. uint32_t usartx_rx_port = GPIOA;
  12. uint32_t usartx_rx_pin = GPIO_PIN_10;
  13. // 复用功能编号
  14. uint32_t usartx_rx_af = GPIO_AF_7;
  15. /*************** gpio *****************/
  16. // TX
  17. // 配置时钟
  18. rcu_periph_clock_enable(usartx_tx_port_rcu);
  19. // 配置模式: 复用功能
  20. gpio_mode_set(usartx_tx_port, GPIO_MODE_AF, GPIO_PUPD_NONE, usartx_tx_pin);
  21. // 配置复用功能
  22. gpio_af_set(usartx_tx_port, usartx_tx_af, usartx_tx_pin);
  23. gpio_output_options_set(usartx_tx_port, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, usartx_tx_pin);
  24. // RX
  25. // 配置时钟
  26. rcu_periph_clock_enable(usartx_rx_port_rcu);
  27. gpio_mode_set(usartx_rx_port, GPIO_MODE_AF, GPIO_PUPD_NONE, usartx_rx_pin);
  28. gpio_af_set(usartx_rx_port, usartx_rx_af, usartx_rx_pin);
  29. // 配置输出参数
  30. gpio_output_options_set(usartx_rx_port, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, usartx_rx_pin);
  31. /*************** usart ****************/
  32. // 串口时钟
  33. rcu_periph_clock_enable(usartx_rcu);
  34. // USART复位
  35. usart_deinit(usartx);
  36. // 波特率
  37. usart_baudrate_set(usartx, usartx_p_baudrate);
  38. // 校验位
  39. usart_parity_config(usartx, USART_PM_NONE);
  40. // 数据位数
  41. usart_word_length_set(usartx, USART_WL_8BIT);
  42. // 停止位
  43. usart_stop_bit_set(usartx, USART_STB_1BIT);
  44. // 先发送高位还是低位
  45. usart_data_first_config(usartx, USART_MSBF_LSB);
  46. // 发送功能配置
  47. usart_transmit_config(usartx, USART_TRANSMIT_ENABLE);
  48. // 接收功能配置
  49. usart_receive_config(usartx, USART_RECEIVE_ENABLE);
  50. // 接收中断配置
  51. nvic_irq_enable(usartx_irq, 2, 2);
  52. usart_interrupt_enable(usartx, USART_INT_RBNE);
  53. usart_interrupt_enable(usartx, USART_INT_IDLE);
  54. // DMA发送功能配置
  55. usart_dma_transmit_config(usartx, USART_TRANSMIT_DMA_ENABLE);
  56. // 使能串口
  57. usart_enable(usartx);

}

static void send_byte(uint8_t data) { //通过USART发送 usart_data_transmit(USART0, data);

  1. //判断缓冲区是否已经空了
  2. //FlagStatus state = usart_flag_get(USART_NUM,USART_FLAG_TBE);
  3. while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));

}

static void send_string(char data) { while(data && data){ send_byte((uint8_t)(*data)); data++; } }

int fputc(int ch, FILE *f){ send_byte((uint8_t)ch); return ch; }

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

  1. DMA_config();
  2. uint8_t cnt = 0;
  3. while(1) {
  4. // dma_send_byte(cnt++);
  5. printf("hello %d\r\n", cnt++);
  6. delay_1ms(1000);
  7. }

}

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

    串口发送理解

    028.png

    源地址和目标地址

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

    自增长

    每传输一个字节数据,地址是否需要增长。这里包含了源地址是否需要增长,也包含了目标地址是否需要增长。
    串口中,寄存器地址不变,大小不变,我们只是向这个里面放数据,所以不需要增长。

    数据宽度

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

    数据长度

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

    练习

  1. 实现串口数据的发送