学习目标

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

    学习内容

    需求

    ```c uint8_t data; 串口接收(&data);

data有数据了

  1. 实现串口的数据接收,要求采用dma的方式。
  2. <a name="CZdgS"></a>
  3. ### 数据交互流程
  4. ![image.png](https://cdn.nlark.com/yuque/0/2023/png/27903758/1700722229113-bcfb93d1-e2a9-494a-9b3c-db00ec1be748.png#averageHue=%23fbe2c3&clientId=ucf437be5-16f9-4&from=paste&height=403&id=u33f91ca2&originHeight=504&originWidth=915&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=32837&status=done&style=none&taskId=u8b430d54-0e2f-4961-928f-d7edf0e7094&title=&width=732)
  5. 1. CPU配置好DMA
  6. 2. 外部数据发送给串口外设
  7. 3. 串口外设触发中断
  8. 4. CPU处理中断逻辑,通知DMA干活
  9. 5. DMA请求源数据
  10. 6. DMA获取源数据
  11. 7. DMA将获取的源数据交给目标
  12. <a name="ayYkU"></a>
  13. ### 开发流程
  14. <a name="Ee1EY"></a>
  15. #### 依赖引入
  16. 添加标准库中的`gd32f4xx_dma.c`文件
  17. <a name="KGhto"></a>
  18. #### DMA初始化
  19. ```c
  20. /**************** DMA p2m *******************/
  21. // 时钟
  22. rcu_periph_clock_enable(RCU_DMA1);
  23. // 重置dma
  24. dma_deinit(DMA1, DMA_CH5);
  25. //////// dma 配置
  26. dma_single_data_parameter_struct dsdps;
  27. dma_single_data_para_struct_init(&dsdps);
  28. // 方向
  29. dsdps.direction = DMA_PERIPH_TO_MEMORY;
  30. // 内存: dst
  31. dsdps.memory0_addr = (uint32_t)(g_recv_buff);
  32. dsdps.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
  33. // 外设: src
  34. dsdps.periph_addr = (uint32_t)(&USART_DATA(USART0));
  35. dsdps.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
  36. // 数据长度
  37. dsdps.number = USART_RECEIVE_LENGTH;
  38. // dst数据宽度
  39. dsdps.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
  40. // 传输优先级
  41. dsdps.priority = DMA_PRIORITY_ULTRA_HIGH;
  42. dma_single_data_mode_init(DMA1, DMA_CH5, &dsdps);
  43. //////// 配置 dma 子连接
  44. dma_channel_subperipheral_select(DMA1, DMA_CH5, DMA_SUBPERI4);
  45. // 默认开启接收
  46. dma_flag_clear(DMA1, DMA_CH5, DMA_FLAG_FTF);
  47. dma_channel_enable(DMA1, DMA_CH5);
  1. 配置时钟
  2. 初始化dma通道
  3. 配置dma与外设间的子链接
  4. 开启dma接收功能

    接收中断

    1. if (usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE) == SET) {
    2. //读取缓冲区,清空缓冲区
    3. usart_data_receive(USART0);
    4. // 停止DMA传输,防止数据污染
    5. dma_channel_disable(DMA1, DMA_CH5);
    6. // 计算接收的数据长度
    7. g_recv_length = USART_RECEIVE_LENGTH - dma_transfer_number_get(DMA1, DMA_CH5);
    8. if(g_recv_length) {
    9. g_recv_buff[g_recv_length] = '\0';
    10. // TODO: g_recv_buff为接收的数据,g_recv_length为接收的长度
    11. printf("rcv: %s\r\n", g_recv_buff);
    12. }
    13. g_recv_length = 0;
    14. dma_flag_clear(DMA1, DMA_CH5, DMA_FLAG_FTF);
    15. dma_channel_enable(DMA1, DMA_CH5);
    16. }
  • dma_channel_enable: 请求dma数据传输
  • DMA_FLAG_FTF:为传输完成标记

    串口外设DMA开启

    1. // DMA接收功能配置
    2. usart_dma_receive_config(usartx, USART_RECEIVE_DMA_ENABLE);
  • 需要开启dma接收配置,才可以打开dam的功能

    完整代码

    ```c

    include “gd32f4xx.h”

    include “systick.h”

    include

    include

    include “main.h”

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

static void DMA_tx_config() { uint32_t dmax = DMA1; uint32_t dmax_rcu = RCU_DMA1; uint32_t dmax_ch = DMA_CH7; uint32_t damx_sub = DMA_SUBPERI4;

  1. uint32_t dmax_dirction = DMA_MEMORY_TO_PERIPH;
  2. // uint32_t dmax_src = (uint32_t)src;
  3. uint32_t dmax_src_inc = DMA_MEMORY_INCREASE_ENABLE;
  4. uint32_t dmax_src_width = DMA_MEMORY_WIDTH_8BIT;
  5. // uint32_t dmax_src_len = ARR_LEN;
  6. uint32_t dmax_dst = (uint32_t)(&USART_DATA(USART0));
  7. uint32_t dmax_dst_inc = DMA_PERIPH_INCREASE_DISABLE;
  8. /***************** DMA m2p *******************/
  9. // 时钟
  10. rcu_periph_clock_enable(dmax_rcu);
  11. // 重置dma
  12. dma_deinit(dmax, dmax_ch);
  13. //////// dma 配置
  14. dma_single_data_parameter_struct dsdps;
  15. dma_single_data_para_struct_init(&dsdps);
  16. // 方向
  17. dsdps.direction = dmax_dirction;
  18. // 内存: src
  19. // dsdps.memory0_addr = (uint32_t)src;
  20. dsdps.memory_inc = dmax_src_inc;
  21. // 外设: dst
  22. dsdps.periph_addr = dmax_dst;
  23. dsdps.periph_inc = dmax_dst_inc;
  24. // // 数据长度
  25. // dsdps.number = ARR_LEN;
  26. // 数据宽度
  27. dsdps.periph_memory_width = dmax_src_width;
  28. // 传输优先级
  29. dsdps.priority = DMA_PRIORITY_ULTRA_HIGH;
  30. dma_single_data_mode_init(dmax, dmax_ch, &dsdps);
  31. //////// 配置 dma 子连接
  32. dma_channel_subperipheral_select(dmax, dmax_ch, damx_sub);

}

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. // DMA接收功能配置
  57. usart_dma_receive_config(usartx, USART_RECEIVE_DMA_ENABLE);
  58. // 使能串口
  59. 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; }

define USART_RECEIVE_LENGTH 1024

//串口接收缓冲区大小 uint8_t g_recv_buff[USART_RECEIVE_LENGTH]; // 接收缓冲区 //接收到字符存放的位置 uint32_t g_recv_length = 0;

void USART0_IRQHandler(void) { if (usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE) == SET) { //读取缓冲区,清空缓冲区 usart_data_receive(USART0);

  1. // 停止DMA传输,防止数据污染
  2. dma_channel_disable(DMA1, DMA_CH5);
  3. // 计算接收的数据长度
  4. g_recv_length = USART_RECEIVE_LENGTH - dma_transfer_number_get(DMA1, DMA_CH5);
  5. if(g_recv_length) {
  6. g_recv_buff[g_recv_length] = '\0';
  7. // TODO: g_recv_buff为接收的数据,g_recv_length为接收的长度
  8. printf("rcv: %s\r\n", g_recv_buff);
  9. }
  10. g_recv_length = 0;
  11. dma_flag_clear(DMA1, DMA_CH5, DMA_FLAG_FTF);
  12. dma_channel_enable(DMA1, DMA_CH5);
  13. }

}

static void DMA_rx_config() { /** DMA p2m */ // 时钟 rcu_periph_clock_enable(RCU_DMA1); // 重置dma dma_deinit(DMA1, DMA_CH5);

  1. //////// dma 配置
  2. dma_single_data_parameter_struct dsdps;
  3. dma_single_data_para_struct_init(&dsdps);
  4. // 方向
  5. dsdps.direction = DMA_PERIPH_TO_MEMORY;
  6. // 内存: dst
  7. dsdps.memory0_addr = (uint32_t)(g_recv_buff);
  8. dsdps.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
  9. // 外设: src
  10. dsdps.periph_addr = (uint32_t)(&USART_DATA(USART0));
  11. dsdps.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
  12. // 数据长度
  13. dsdps.number = USART_RECEIVE_LENGTH;
  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_CH5, &dsdps);
  19. //////// 配置 dma 子连接
  20. dma_channel_subperipheral_select(DMA1, DMA_CH5, DMA_SUBPERI4);
  21. // 默认开启接收
  22. dma_flag_clear(DMA1, DMA_CH5, DMA_FLAG_FTF);
  23. dma_channel_enable(DMA1, DMA_CH5);

}

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

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

}

  1. <a name="BZwJW"></a>
  2. ### 关心的内容
  3. ```c
  4. uint32_t dmax = DMA1;
  5. uint32_t dmax_rcu = RCU_DMA1;
  6. uint32_t dmax_ch = DMA_CH5;
  7. uint32_t damx_sub = DMA_SUBPERI4;
  8. uint32_t dmax_dirction = DMA_PERIPH_TO_MEMORY;
  9. uint32_t dmax_src = (uint32_t)(&USART_DATA(USART0));
  10. uint32_t dmax_src_inc = DMA_PERIPH_INCREASE_DISABLE;
  11. uint32_t dmax_src_width = DMA_PERIPH_WIDTH_8BIT;
  12. uint32_t dmax_src_len = USART_RECEIVE_LENGTH;
  13. uint32_t dmax_dst = (uint32_t)(g_recv_buff);
  14. uint32_t dmax_dst_inc = DMA_MEMORY_INCREASE_ENABLE;
  15. /**************** DMA p2m *******************/
  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 = DMA_PERIPH_TO_MEMORY;
  25. // 内存: dst
  26. dsdps.memory0_addr = dmax_dst;
  27. dsdps.memory_inc = dmax_dst_inc;
  28. // 外设: src
  29. dsdps.periph_addr = dmax_src;
  30. dsdps.periph_inc = dmax_src_inc;
  31. // 数据长度
  32. dsdps.number = dmax_src_len;
  33. // dst数据宽度
  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);
  40. // 默认开启接收
  41. dma_flag_clear(dmax, dmax_ch, DMA_FLAG_FTF);
  42. dma_channel_enable(dmax, dmax_ch);
  • 哪个dma:DMA0或者DMA1
  • dma的哪个通道:ch0到ch7
  • dma的哪个子级:0到7
  • 传输方向是什么:内存到内存,内存到外设,外设到内存
  • 源地址是多少?源的数据长度是多少(几个byte)?源的数据宽度是多少(几个bit)?
  • 目标地址是什么?
  • 从源地址向目标地址传输数据时,源地址的指针是否需要增长。
  • 从源地址向目标地址传输数据时,目标地址的指针是否需要增长。

    串口接收理解

    030.png

    地址和目标地址

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

    自增长

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

    数据宽度

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

    数据长度

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

练习题

  1. 实现dma的串口接收