跳到主要内容

08 DMAC 串口数据传输实验

在这里插入图片描述

开发板官网https://www.edevkit.com/)

本系列教程目录:


DMAC 串口数据传输实验

本章目标:

  • 了解 DMA 基本概念和 RA6M5 处理器的 DMAC 模块
  • 学会使用 RASC 配置 DMAC 在指定内存间搬移数据

硬件设计

本实验使用开发板的 SCI UART 串口配合 DMAC 模块,实现数据的 DMA 传输。

软件设计

新建工程

复制一份“07 SCI-UART_Recv_send”修改文件夹名“08 DMAC-UART”,双击打开 EBF_RA6M5.uvprojx 工程,通过 Keil 的新建文件新建 dmac.cdmac.h,保存到目录下的 \src 文件夹下。

FSP 配置

Keil 界面选择“Tools”→“RA Smart Configurator”打开 RASC 界面。

在 FSP 配置界面里面点开“Stacks”界面,“New Stack”→“Transfer”→“r_dmac”新建两个 DMAC 模块,用于 UART 的发送和接收。

插图01

插图02

添加后,配置这两个 DMAC 模块,一个配置为用于 UART 发送,一个配置为用于 UART 接收。

UART 发送的 DMAC 配置:

插图03

UART 接收的 DMAC 配置:

插图04

属性描述
Name名字。上图配置为 g_transfer_dmac0
按照自己命名习惯设置、并且与代码中的对应即可。
Channel通道:DMA 传输通道选择。这里选择通道 0,通道 0 也是优先级最高的通道。
RA6M5 有 8 个通道,0~7 可选。其他 MCU 型号可能不同。
Mode模式:DMA 传输模式选择。默认选择正常模式即可。
Transfer Size传输大小:传输数据单元的大小。默认设置为 2 字节即可。
Destination Address Mode目标地址模式:默认设置为固定。
Source Address Mode源地址模式:默认设置为固定。
Repeat Area
(Unused in Normal Mode)
重复区域(正常模式下不使用):源地址。
Destination Pointer目标指针:NULL(在这里不方便确定传输地址,后续在代码部分配置)。
Source Pointer源指针:NULL(在这里不方便确定传输地址,后续在代码部分配置)。
Number of Transfers传输次数:指定正常和重复模式的传输次数 或 重复-块传输模式的块大小。
Number of Blocks
(Valid only in Repeat,
Block or Repeat-Block Mode)
块数量:指定要在重复、块或重复-块模式下传输的块数。
Activation Source触发源:选择 DMAC 传输开始事件。
如果未选择 ELC 事件,则可以使用软件启动。
Callback回调函数:触发 DMAC 中断时调用的回调函数。
此处设置为 dmac0_callback
Context上下文:指向通过回调函数传递的上下文结构的指针。
Transfer End Interrupt Priority传输结束中断优先级:传输结束中断的优先级设置。
Interrupt Frequency中断频率 (选择什么时候触发中断):
所有传输都完成后产生中断。
传输完每一个块或重复大小的数据时都产生中断。
Offset value偏移: 偏移值在每次传输后添加到传输地址。
(仅当地址模式为“偏移”时有效)
Source Buffer Size源缓冲大小: 指定整个源缓冲区的大小。
(仅适用于带有源地址更新模式的重复-块传输模式,不适用于地址偏移模式)

配置完成之后可以按下快捷键“Ctrl + S”保存, 最后点击 “Generate Project Content” 按钮,让软件自动生成配置代码即可。

  1. DMAC配置信息

hal_data.c 中生成 DMAC 的设备对象 g_transfer_dmac_sci0_tx,它是 transfer_instance_t 类型的结构体。此结构体的成员表明了 DMAC 设备对象的控制参数、配置信息和操作 API 等信息。

const transfer_instance_t g_transfer_dmac_sci0_tx =
{
.p_ctrl = &g_transfer_dmac_sci0_tx_ctrl,
.p_cfg = &g_transfer_dmac_sci0_tx_cfg,
.p_api = &g_transfer_on_dmac
};

p_cfgtransfer_cfg_t 结构体类型常量指针成员,指向同类型的 g_transfer_dmac_sci0_tx_cfg 全局常量,g_transfer_dmac_sci0_tx_cfg 的成员取值来自 RASC 中对于该 DMAC 设备对象的配置。

const transfer_cfg_t g_transfer_dmac_sci0_tx_cfg =
{
.p_info = &g_transfer_dmac_sci0_tx_info,
.p_extend = &g_transfer_dmac_sci0_tx_extend,
};

g_transfer_dmac_sci0_tx_info:表明 DMAC 设备对象的地址变化方式、传输数据大小等信息:

transfer_info_t g_transfer_dmac_sci0_tx_info =
{
.transfer_settings_word_b.dest_addr_mode = TRANSFER_ADDR_MODE_FIXED,
.transfer_settings_word_b.repeat_area = TRANSFER_REPEAT_AREA_SOURCE,
.transfer_settings_word_b.irq = TRANSFER_IRQ_END,
.transfer_settings_word_b.chain_mode = TRANSFER_CHAIN_MODE_DISABLED,
.transfer_settings_word_b.src_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED,
.transfer_settings_word_b.size = TRANSFER_SIZE_1_BYTE,
.transfer_settings_word_b.mode = TRANSFER_MODE_NORMAL,
.p_dest = (void *) NULL,
.p_src = (void const *) NULL,
.num_blocks = 0,
.length = 1,
};

g_transfer_dmac_sci0_tx_extend 表明 DMAC 设备对象的中断、内存偏移大小、源数据内存大小等信息:

const dmac_extended_cfg_t g_transfer_dmac_sci0_tx_extend =
{
.offset = 1,
.src_buffer_size = 1,
#if defined(VECTOR_NUMBER_DMAC0_INT)
.irq = VECTOR_NUMBER_DMAC0_INT,
#else
.irq = FSP_INVALID_VECTOR,
#endif
.ipl = (6),
.channel = 0,
.p_callback = transfer_dmac_sci0_tx_callback,
.p_context = NULL,
.activation_source = ELC_EVENT_SCI0_TXI,
};
  1. 中断回调函数

在 RASC 中设置中断回调函数的名字后,会在 hal_data.h 中声明此函数。

#ifndef transfer_dmac_sci0_rx_callback
void transfer_dmac_sci0_rx_callback(transfer_callback_args_t * p_args);
#endif

#ifndef transfer_dmac_sci0_tx_callback
void transfer_dmac_sci0_tx_callback(transfer_callback_args_t * p_args);
#endif

在所有的数据都传输完毕后,中断被触发,进而调用这个回调函数。只需要设置一个变量表示 DMA 传输完毕。

程序设计

实现串口的数据收发,通过 USB 串口连接开发板和电脑,电脑端使用串口调试助手给开发板发送数据,开发板把接收的数据发送回电脑端。

  1. 打开 DMAC 设备对象
fsp_err_t (* open)(transfer_ctrl_t * const p_ctrl,
transfer_cfg_t const * const p_cfg);

p_ctrl:用来记录一些状态信息;

p_cfg:配置信息,实际上就是指向 RASC 生成的 g_dma0_cfg 结构体;

  1. 关闭 DMAC 设备对象
fsp_err_t (* close)(transfer_ctrl_t * const p_ctrl);

close 函数会将 DMAC 设备对象的 open 标志位设置为 CLOSED,并且关闭中断。

  1. 使能 DMAC 的数据传输
fsp_err_t (* enable)(transfer_ctrl_t * const p_ctrl);

可能会在其它的 API 中使能 DMAC 的数据传输功能,因而此函数并非一定要手动调用,例如 reset 函数内就会在最后使能 DMAC 的数据传输。

  1. 软件开启 DMAC 的数据传输
fsp_err_t (* softwareStart)(transfer_ctrl_t * const p_ctrl,
transfer_start_mode_t mode);

mode 参数是 transfer_start_mode_t 枚举类型,可以选择每次软件触发传输时,是只传输一个数据,还是传输多个数据直到所有数据传输完成,此枚举的原型如下:

typedef enum e_transfer_start_mode
{
TRANSFER_START_MODE_SINGLE = 0,
TRANSFER_START_MODE_REPEAT = 1
} transfer_start_mode_t;
  1. 软件停止 DMAC 的数据传输
fsp_err_t (* softwareStop)(transfer_ctrl_t * const p_ctrl);

只有在使用 softwareStart(…, TRANSFER_START_MODE_REPEAT) 模式的软件触发 DMAC 时,才能使用 softwareStop 函数中断 DMAC 的传输。

程序编写

dmac.h 头文件里定义初始化函数,设置数据长度,设置数据地址函数。

#ifndef __BSP_DMAC_H
#define __BSP_DMAC_H
#include "hal_data.h"


void dmac_Init(void);

void set_transfer_length(transfer_cfg_t const * p_config, volatile uint16_t length);

void set_transfer_address(transfer_cfg_t const * const p_config,
const volatile uint8_t * p_src, const volatile uint8_t * p_dest);

#endif

dmac.c 里实现初始化函数、中断回调函数,设置数据长度,设置数据地址函数。

#include "dmac.h"

// 初始化 DMAC 模块
void dmac_Init(void)
{
fsp_err_t err;

// 配置发送
err = g_transfer_on_dmac.open(g_transfer_dmac_sci0_tx.p_ctrl, g_transfer_dmac_sci0_tx.p_cfg);
assert(FSP_SUCCESS == err);
err = g_transfer_on_dmac.enable(g_transfer_dmac_sci0_tx.p_ctrl);
assert(FSP_SUCCESS == err);

// 配置接收
err = g_transfer_on_dmac.open(g_transfer_dmac_sci0_rx.p_ctrl, g_transfer_dmac_sci0_rx.p_cfg);
assert(FSP_SUCCESS == err);
err = g_transfer_on_dmac.enable(g_transfer_dmac_sci0_rx.p_ctrl);
assert(FSP_SUCCESS == err);
}

void set_transfer_length(transfer_cfg_t const * p_config, volatile uint16_t length)
{
p_config->p_info->length = length;
}

// 设置传输的源地址和目的地址
void set_transfer_address(transfer_cfg_t const * const p_config,
const volatile uint8_t * p_src, const volatile uint8_t * p_dest)
{
p_config->p_info->p_src = (void const * volatile) p_src;
p_config->p_info->p_dest = (void * volatile) p_dest;
}

// DMA 传输完成标志位
volatile uint8_t dmac_sci0_tx_flag = 0;
volatile uint8_t dmac_sci0_rx_flag = 0;

// DMAC 发送中断回调函数
void transfer_dmac_sci0_tx_callback(transfer_callback_args_t *p_args)
{
(void)(p_args);
dmac_sci0_tx_flag = 1;
}

// DMAC 接收回调函数
void transfer_dmac_sci0_rx_callback(transfer_callback_args_t *p_args)
{
(void)(p_args);
dmac_sci0_rx_flag = 1;
}

hal_entry.c 文件中添加头文件 dmac.h,在 hal_entry 入口函数初始化 dmac,并添加 printf("start dmac test\r\n") 一条调试语句,开启 dmac 传输数据。

#define BUFFER_SIZE 20

uint8_t sci_tx_data[BUFFER_SIZE] = {"transfer from dmac\r\n"};
uint8_t sci_rx_data[BUFFER_SIZE];

void hal_entry(void)
{
/* TODO: add your own code here */

led_Init(); // LED 初始化
uart0_Init();


printf("start dmac test\r\n");

uart_send_data(sci_tx_data, BUFFER_SIZE);

R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); //延时1秒

// 清零 ICU IELSR 发送和接收中断寄存器
R_ICU->IELSR[SCI0_RXI_IRQn] = 0U;
R_ICU->IELSR[SCI0_TXI_IRQn] = 0U;

// 配置串口接收DMA 源地址、目标地址、长度
set_transfer_address(g_transfer_dmac_sci0_rx.p_cfg, &R_SCI0->RDR, &sci_rx_data[0]);
set_transfer_length(g_transfer_dmac_sci0_rx.p_cfg, BUFFER_SIZE);

// 配置串口发送DMA 源地址、目标地址、长度
set_transfer_address(g_transfer_dmac_sci0_tx.p_cfg, &sci_tx_data[0], &R_SCI0->TDR);
set_transfer_length(g_transfer_dmac_sci0_tx.p_cfg, BUFFER_SIZE);

dmac_Init();

// 手动触发传输数据寄存器空中断
R_SCI0->SCR_b.TE = 0;
R_SCI0->SCR_b.RE = 0;
R_SCI0->SCR |= (0xF0);

while(1)
{

if( dmac_sci0_rx_flag == 1 )//接收到数据
{
dmac_sci0_rx_flag = 0;
LED1_TOGGLE();

//失能发送和接收
R_SCI0->SCR_b.TE = 0;
R_SCI0->SCR_b.RE = 0;

//重新设置发送地址,把接收的数据发送出去
set_transfer_address(g_transfer_dmac_sci0_tx.p_cfg, &sci_rx_data[0], &R_SCI0->TDR);
set_transfer_length(g_transfer_dmac_sci0_tx.p_cfg, BUFFER_SIZE);
dmac_Init();

//使能发送和接收
R_SCI0->SCR |= (0xF0);



}
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); //延时1秒
}


#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}

下载验证

首先通过 USB 串口连接电脑和开发板,串口的 RX/TX 分别连接开发板的 TX/RX,交叉连接,电脑端打开串口调试助手,波特率设置 115200。

连接下载器,将程序编译并下载到开发板之后,按下复位按键来复位开发板,程序启动初始化完会打印一句“start dmac test”,接下来发送一条数据,间隔 1s 发送第二条数据。

接下来通过串口上位机发送 20 字节字符串数据,DMAC 接收到数据后又会通过 DMAC 发送出来。

插图05