ARM32开发——DMA内存到外设

秋野酱:《个人主页》
个人专栏:《Java专栏》《Python专栏》

⛺️心若有所向往,何惧道阻且长

文章目录

    • 学习目标
    • 学习内容
      • 需求
      • 数据交互流程
    • 开发流程
      • 依赖引入
      • DMA初始化
      • DMA传输请求
      • 串口外设DMA开启
      • 发送功能
    • 完整代码
    • 关心的内容
    • 串口发送理解
    • 源地址和目标地址
    • 自增长
    • 数据宽度
    • 数据长度

学习目标

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

学习内容

需求

串口发送数据

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

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

数据交互流程

ARM32开发——DMA内存到外设_第1张图片

  1. CPU配置好DMA
  2. CPU通知DMA干活
  3. DMA请求源数据
  4. DMA获取源数据
  5. DMA将获取的源数据交给目标

开发流程

依赖引入

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

DMA初始化

/***************** 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. 配置时钟
  2. 初始化dma通道
  3. 配置dma与外设间的子链接

DMA传输请求

// 数据来源 和 长度
dma_memory_address_config(DMA1, DMA_CH7, DMA_MEMORY_0, (uint32_t)(&data));
dma_transfer_number_config(DMA1, DMA_CH7, len);

// 触发传输
dma_channel_enable(DMA1, DMA_CH7);

// 等待DMA传输完成
while(RESET == dma_flag_get(DMA1, DMA_CH7, DMA_FLAG_FTF));
// 清理标记
dma_flag_clear(DMA1, DMA_CH7, DMA_FLAG_FTF);

● dma_channel_enable: 请求dma数据传输
● DMA_FLAG_FTF:为传输完成标记

串口外设DMA开启

// DMA发送功能配置
usart_dma_transmit_config(usartx, USART_TRANSMIT_DMA_ENABLE);

需要开启dma发送配置,才可以打开dam的功能

发送功能

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);

    // 触发传输
    dma_channel_enable(DMA1, DMA_CH7);

    // 等待DMA传输完成
    while(RESET == dma_flag_get(DMA1, DMA_CH7, DMA_FLAG_FTF));
    // 清理标记
    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);

    // 触发传输
    dma_channel_enable(DMA1, DMA_CH7);

    // 等待DMA传输完成
    while(RESET == dma_flag_get(DMA1, DMA_CH7, DMA_FLAG_FTF));
    // 清理标记
    dma_flag_clear(DMA1, DMA_CH7, DMA_FLAG_FTF);
}
static void dma_send_string(const char* str) {
    dma_send((uint8_t*)str, strlen(str));
}

完整代码

#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include <string.h>
#include "main.h"

static void DMA_config() {
    /***************** 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;
    // dst数据宽度
    dsdps.periph_memory_width = DMA_PERIPH_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);
}

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);

    // 触发传输
    dma_channel_enable(DMA1, DMA_CH7);

    // 等待DMA传输完成
    while(RESET == dma_flag_get(DMA1, DMA_CH7, DMA_FLAG_FTF));
    // 清理标记
    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);

    // 触发传输
    dma_channel_enable(DMA1, DMA_CH7);

    // 等待DMA传输完成
    while(RESET == dma_flag_get(DMA1, DMA_CH7, DMA_FLAG_FTF));
    // 清理标记
    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;

    // 波特率
    uint32_t usartx_p_baudrate = 115200;

    // tx和rx,用了哪个引脚
    uint32_t usartx_tx_port_rcu = RCU_GPIOA;
    uint32_t usartx_tx_port = GPIOA;
    uint32_t usartx_tx_pin = GPIO_PIN_9;
    // 复用功能编号
    uint32_t usartx_tx_af = GPIO_AF_7;

    // tx和rx,用了哪个引脚
    uint32_t usartx_rx_port_rcu = RCU_GPIOA;
    uint32_t usartx_rx_port = GPIOA;
    uint32_t usartx_rx_pin = GPIO_PIN_10;
    // 复用功能编号
    uint32_t usartx_rx_af = GPIO_AF_7;


    /*************** gpio *****************/
    // TX
    // 配置时钟
    rcu_periph_clock_enable(usartx_tx_port_rcu);
    // 配置模式: 复用功能
    gpio_mode_set(usartx_tx_port, GPIO_MODE_AF, GPIO_PUPD_NONE, usartx_tx_pin);
    // 配置复用功能
    gpio_af_set(usartx_tx_port, usartx_tx_af, usartx_tx_pin);
    gpio_output_options_set(usartx_tx_port, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, usartx_tx_pin);

    // RX
    // 配置时钟
    rcu_periph_clock_enable(usartx_rx_port_rcu);
    gpio_mode_set(usartx_rx_port, GPIO_MODE_AF, GPIO_PUPD_NONE, usartx_rx_pin);
    gpio_af_set(usartx_rx_port, usartx_rx_af, usartx_rx_pin);
    // 配置输出参数
    gpio_output_options_set(usartx_rx_port, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, usartx_rx_pin);

    /*************** usart ****************/
    // 串口时钟
    rcu_periph_clock_enable(usartx_rcu);
    // USART复位
    usart_deinit(usartx);

    // 波特率
    usart_baudrate_set(usartx, usartx_p_baudrate);
    // 校验位
    usart_parity_config(usartx, USART_PM_NONE);
    // 数据位数
    usart_word_length_set(usartx, USART_WL_8BIT);
    // 停止位
    usart_stop_bit_set(usartx, USART_STB_1BIT);
    // 先发送高位还是低位
    usart_data_first_config(usartx, USART_MSBF_LSB);

    // 发送功能配置
    usart_transmit_config(usartx, USART_TRANSMIT_ENABLE);

    // 接收功能配置
    usart_receive_config(usartx, USART_RECEIVE_ENABLE);
    // 接收中断配置
    nvic_irq_enable(usartx_irq, 2, 2);
    usart_interrupt_enable(usartx, USART_INT_RBNE);
    usart_interrupt_enable(usartx, USART_INT_IDLE);

    // DMA发送功能配置
    usart_dma_transmit_config(usartx, USART_TRANSMIT_DMA_ENABLE);

    // 使能串口
    usart_enable(usartx);
}

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

    //判断缓冲区是否已经空了
    //FlagStatus state = usart_flag_get(USART_NUM,USART_FLAG_TBE);
    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();

    DMA_config();

    uint8_t cnt = 0;
    while(1) {
        // dma_send_byte(cnt++);
        printf("hello %d\r\n", cnt++);
        delay_1ms(1000);
    }
}

关心的内容

uint32_t dmax = DMA1;
uint32_t dmax_rcu = RCU_DMA1;
uint32_t dmax_ch = DMA_CH7;
uint32_t damx_sub = DMA_SUBPERI4;

uint32_t dmax_dirction = DMA_MEMORY_TO_PERIPH;

//    uint32_t dmax_src = (uint32_t)src;
uint32_t dmax_src_inc = DMA_MEMORY_INCREASE_ENABLE;
uint32_t dmax_src_width = DMA_MEMORY_WIDTH_8BIT;
//    uint32_t dmax_src_len = ARR_LEN;

uint32_t dmax_dst = (uint32_t)(&USART_DATA(USART0));
uint32_t dmax_dst_inc = DMA_PERIPH_INCREASE_DISABLE;

/***************** DMA m2p *******************/
// 时钟
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;
// 内存: src
//    dsdps.memory0_addr = (uint32_t)src;
dsdps.memory_inc = dmax_src_inc;
// 外设: dst
dsdps.periph_addr = dmax_dst;
dsdps.periph_inc = dmax_dst_inc;
//    // 数据长度
//    dsdps.number = ARR_LEN;
// 数据宽度
dsdps.periph_memory_width = dmax_src_width;
// 传输优先级
dsdps.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_single_data_mode_init(dmax, dmax_ch, &dsdps);

 配置 dma 子连接
dma_channel_subperipheral_select(dmax, dmax_ch, damx_sub);

● 哪个dma:DMA0或者DMA1
● dma的哪个通道:ch0到ch7
● dma的哪个子级:0到7
● 传输方向是什么:内存到内存,内存到外设,外设到内存
● 源地址是多少?源的数据长度是多少(几个byte)?源的数据宽度是多少(几个bit)?
● 目标地址是什么?
● 从源地址向目标地址传输数据时,源地址的指针是否需要增长。
● 从源地址向目标地址传输数据时,目标地址的指针是否需要增长。

串口发送理解

ARM32开发——DMA内存到外设_第2张图片

源地址和目标地址

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

自增长

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

数据宽度

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

数据长度

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

你可能感兴趣的:(嵌入式,c,单片机,stm32)