搜索
bottom↓
回复: 0
打印 上一主题 下一主题

《ATK-DFPGL22G之FPGA开发指南_V1.0》 第三十二章 DMA实验

[复制链接]

出0入234汤圆

跳转到指定楼层
1
发表于 2023-7-14 15:55:17 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 正点原子 于 2023-7-14 15:55 编辑

1)实验平台:正点原子 DFZU2EG_4EV MPSoC开发板
2)购买链接:https://item.taobao.com/item.htm?&id=692368045899
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-340252-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子FPGA交流群:994244016






第三十二章 DMA实验

本章介绍APM32E103直接存储访问(DMA)的使用,DMA能够在无CPU干预的情况下,实现外设与存储器或存储器与存储器之间数据的高速传输,从而节省CPU资源来做其他操作。通过本章的学习,读者将学习到DMA的使用。
本章分为如下几个小节:
32.1 硬件设计
32.2 程序设计
32.3 下载验证


32.1 硬件设计
32.1.1 例程功能

1. 按下KEY0按键,USART1以DMA方式发送数据,同时在LCD上显示DMA传输的进度
2. LED0闪烁,指示程序正在运行
32.1.2 硬件资源
1. LED
        LED0 - PB5
2. 按键
        KEY0 - PE4
3. 正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
4. USART1(PA9、PA10连接至板载USB转串口芯片上)
5. DMA(DMA1通道4)
32.1.3 原理图
本章实验使用的DMA为APM32E103的片上资源,因此没有对应的连接原理图。
32.2 程序设计
32.2.1 Geehy标准库的DMA驱动
在使用DMA之前,需要先根据需求对DMA的通道进行配置,例如本章实验要使用DMA进行串口数据的发送,因此应将DMA的通道配置为存储器到外设的模式等等。使用DMA的具体步骤如下:
①:根据需求配置DMA通道
②:使能DMA通道进行数据传输
③:读DMA通道的状态标志判断传输是否完成
④:传输完成后清除DMA通道的传输完成标志
在Geehy标准库中对应的驱动函数如下:
①:配置DMA通道
该函数用于配置DMA通道的各项参数,其函数原型如下所示:
void DMA_Config(DMA_Channel_T* channel, DMA_Config_T* dmaConfig)
该函数的形参描述,如下表所示:
表32.2.1.1 函数DMA_Config()形参描述

该函数的返回值描述,如下表所示:
表32.2.1.2 函数DMA_Config()返回值描述

该函数使用DMA_Config_T类型的结构体变量传入DMA通道的配置参数,该结构体的定义如下所示:

typedef enum
{
    DMA_DIR_PERIPHERAL_SRC,
    DMA_DIR_PERIPHERAL_DST
} DMA_DIR_T;

typedef enum
{
    DMA_PERIPHERAL_INC_DISABLE,                                                /* 禁止外设增量模式 */
    DMA_PERIPHERAL_INC_ENABLE                                                /* 使能外设增量模式 */
} DMA_PERIPHERAL_INC_T;

typedef enum
{
    DMA_MEMORY_INC_DISABLE,                                                        /* 禁用存储器递增模式 */
    DMA_MEMORY_INC_ENABLE                                                        /* 使能存储器递增模式 */
} DMA_MEMORY_INC_T;

typedef enum
{
    DMA_PERIPHERAL_DATA_SIZE_BYTE,                                        /* 外设数据大小为字节 */
    DMA_PERIPHERAL_DATA_SIZE_HALFWORD,                                /* 外设数据大小为半字 */
    DMA_PERIPHERAL_DATA_SIZE_WORD                                        /* 外设数据大小为字 */
} DMA_PERIPHERAL_DATA_SIZE_T;

typedef enum
{
    DMA_MODE_NORMAL,                                                                /* 禁用循环模式 */
    DMA_MODE_CIRCULAR                                                                /* 使能循环模式 */
} DMA_LOOP_MODE_T;

typedef enum
{
    DMA_PRIORITY_LOW,                                                                /* 优先级低 */
    DMA_PRIORITY_MEDIUM,                                                        /* 优先级中 */
    DMA_PRIORITY_HIGH,                                                                /* 优先级高 */
    DMA_PRIORITY_VERYHIGH                                                        /* 优先级非常高 */
} DMA_PRIORITY_T;

typedef struct
{
    uint32_t                                        peripheralBaseAddr;        /* 外设基地址 */
    uint32_t                                        memoryBaseAddr;                /* 存储器基地址 */
    DMA_DIR_T                                        dir;                                /* 数据传输方向 */
    uint32_t                                        bufferSize;                        /* 传输的数据项数目 */
    DMA_PERIPHERAL_INC_T                peripheralInc;                /* 外设增量模式 */
    DMA_MEMORY_INC_T                        memoryInc;                        /* 存储器递增模式 */
    DMA_PERIPHERAL_DATA_SIZE_T        peripheralDataSize;        /* 外设数据大小 */
    DMA_MEMORY_DATA_SIZE_T                memoryDataSize;                /* 存储器数据大小 */
    DMA_LOOP_MODE_T                                loopMode;                        /* 循环模式 */
DMA_PRIORITY_T                                priority;                        /* 优先级 */
DMA_M2MEN_T                M2M;
} DMA_Config_T;
该函数的使用示例,如下所示:
#include "apm32e10x.h"
#include "apm32e10x_dma.h"

static uint8_t membuf[256];

void example_fun(void)
{
    DMA_Config_T dma_init_struct;
   
    /* 配置DMA1通道0通道0 */
    dma_init_struct.channel                                = DMA_CHANNEL_0;
    dma_init_struct.peripheralBaseAddr        = (uint32_t)&USART1->DATA;
    dma_init_struct.memoryBaseAddr                = (uint32_t)&membuf[0];
    dma_init_struct.dir                                        = DMA_DIR_MEMORYTOPERIPHERAL;
    dma_init_struct.bufferSize                        = sizeof(membuf) / sizeof(membuf[0]);
    dma_init_struct.peripheralInc                = DMA_PERIPHERAL_INC_DISABLE;
    dma_init_struct.memoryInc                        = DMA_MEMORY_INC_ENABLE;
    dma_init_struct.peripheralDataSize        = DMA_PERIPHERAL_DATA_SIZE_BYTE;
    dma_init_struct.memoryDataSize                = DMA_MEMORY_DATA_SIZE_BYTE;
    dma_init_struct.loopMode                        = DMA_MODE_NORMAL;
    dma_init_struct.priority                        = DMA_PRIORITY_MEDIUM;
    dma_init_struct.fifoMode                        = DMA_FIFOMODE_DISABLE;
    dma_init_struct.fifoThreshold                = DMA_FIFOTHRESHOLD_FULL;
    dma_init_struct.memoryBurst                        = DMA_MEMORYBURST_SINGLE;
    dma_init_struct.peripheralBurst        = DMA_PERIPHERALBURST_SINGLE;
    DMA_Config(DMA1_Stream0, &dma_init_struct);
}
②:使能DMA通道
该函数用于使能DMA通道进行数据传输,其函数原型如下所示:
void DMA_Enable(DMA_Channel_T *channel);
该函数的形参描述,如下表所示:
表32.2.1.3 函数DMA_Enable()形参描述

该函数的返回值描述,如下表所示:
表32.2.1.4 函数DMA_Enable()返回值描述

该函数的使用示例,如下所示:
#include "apm32e10x.h"
#include "apm32e10x_dma.h"

void example_fun(DMA_Channel_T *dma_chx)
{
    /* 使能DMA通道 */
    DMA_Enable(dma_chx);
}
③:读取DMA通道的状态标志
该函数用于读取DMA通道的指定状态标志,其函数原型如下所示:
uint8_t DMA_ReadStatusFlag(DMA_FLAG_T flag);
该函数的形参描述,如下表所示:
表32.1.2.5 函数DMA_ReadStatusFlag()形参描述

该函数的返回值描述,如下表所示:
表32.1.2.6 函数DMA_ReadStatusFlag()返回值描述

该函数的使用示例,如下所示:
#include "apm32e10x.h"
#include "apm32e10x_dma.h"

void example_fun(void)
{
    uint8_t flag;
   
    /* 读取DMA1通道4的数据传输完成标志 */
    flag = DMA_ReadStatusFlag(DMA1_FLAG_TC4);
    if (flag == SET)
    {
                    /* Do something. */
    }
    else
    {
                    /* Do something. */
    }
}
④:清除DMA通道状态标志
该函数用于清除DMA通道的状态标志,其函数原型如下所示:
void DMA_ClearStatusFlag(uint32_t flag);
该函数的形参描述,如下表所示:
表32.2.1.7 函数DMA_ClearStatusFlag()形参描述

该函数的返回值描述,如下表所示:
表32.2.1.8 函数DMA_ClearStatusFlag()返回值描述

该函数的使用示例,如下所示:
#include "apm32e10x.h"
#include "apm32e10x_dma.h"

void example_fun(void)
{
    /* 清除DMA1通道1的传输完成标志 */
    DMA_ClearStatusFlag(DMA1_FLAG_TC1);
}
32.2.2 DMA驱动
本章实验的DMA驱动主要负责向应用层提供DMA的初始化和开启传输的函数。本章实验中,DMA的驱动代码包括dma.c和dma.h两个文件。
DMA驱动中,DMA的初始化函数,如下所示:
/**
* @brief       初始化DMA
* @param       dma_chx   : DMA通道
* @param       par       : 外设地址
* @param       mar       : 存储器0地址
* @param       ndtr      : 传输的数据项数目
* @retval      无
*/
void dma_init(        DMA_Channel_T *dma_chx,
                                uint32_t par,
                                uint32_t mar,
                            uint16_t ndtr)
{
    DMA_Config_T dma_init_struct;
   
    RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_DMA1);          /* 使能DMA时钟 */
    DMA_Reset(dma_chx);                                     /* 复位DMA */
   
    /* 配置DMA */
    dma_init_struct.peripheralBaseAddr = par;               /* 外设基地址 */
    dma_init_struct.memoryBaseAddr = mar;                   /* DMA内存基地址 */
    dma_init_struct.dir = DMA_DIR_PERIPHERAL_DST;           /* 数据传输方向 */
dma_init_struct.bufferSize = ndtr;                      /* 传输的数据项数目 */
/* 外设增量模式 */
    dma_init_struct.peripheralInc = DMA_PERIPHERAL_INC_DISABLE;
dma_init_struct.memoryInc = DMA_MEMORY_INC_ENABLE;      /* 存储器递增模式 */
/* 外设数据大小 */
dma_init_struct.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_BYTE;
/* 存储器数据大小 */
    dma_init_struct.memoryDataSize = DMA_MEMORY_DATA_SIZE_BYTE;         
    dma_init_struct.loopMode = DMA_MODE_NORMAL;             /* 正常工作模式 */
dma_init_struct.priority = DMA_PRIORITY_MEDIUM;         /* 优先级 */
/* DMA通道x没有设置为内存到内存传输 */
    dma_init_struct.M2M = DMA_M2MEN_DISABLE;
    DMA_Config(dma_chx, &dma_init_struct);                  /* 配置DMA通道 */
   
    DMA_Enable(dma_chx);                                    /* 使能DMA通道 */
}
从上面的代码中可以看到,DMA的初始化函数提供了DMA通道、外设基地址、存储器基地址、传输数据项数据的配置参数,结合这些传入的参数,通过DMA通道配置函数DMA_Config()配置了指定的DMA通道。
DMA驱动中,开启DMA传输的函数,如下所示:
/**
* @brief       开启一次DMA传输
* @param       dma_chx   : DMA通道
* @param       ndtr      : 传输的数据项数目
* @retval      无
*/
void dma_enable(DMA_Channel_T *dma_chx, uint16_t ndtr)
{
    DMA_Disable(dma_chx);                    /* 关闭USART1 TX DMA1 所指示的通道 */
    DMA_ConfigDataNumber(dma_chx, ndtr);     /* 配置传输的数据项数目 */
    DMA_Enable(dma_chx);                     /* 使能USART1 TX DMA1 所指示的通道 */
}
从上面的代码中可以看到,开启DMA数据传输实际上就是调用函数DMA_Enable()使能DMA通道,但考虑到每次传输的数据量可能不同,因此在使能DMA通道前,还配置了DMA通道传输数据项目的数量。
32.2.3 实验应用代码
本实验的应用代码,如下所示:
/* 要循环发送的字符串 */
const uint8_t text_to_send[] = {"正点原子 APM32E103最小系统板 DMA实验\r\n"};
#define SEND_BUF_SIZE (sizeof(text_to_send) * 200)  /* 发送数据长度 */
uint8_t g_sendbuf[SEND_BUF_SIZE];                   /* 发送数据缓冲区 */

int main(void)
{
    uint8_t key;
    uint16_t i;
    uint16_t len;
    uint16_t pro;
   
    NVIC_ConfigPriorityGroup(NVIC_PRIORITY_GROUP_4);/* 设置中断优先级分组为组4 */
    sys_apm32_clock_init(15);                       /* 配置系统时钟 */
    delay_init(120);                                /* 初始化延时功能 */
    usart_init(115200);                             /* 初始化串口 */
    led_init();                                     /* 初始化LED */
    lcd_init();                                     /* 初始化LCD */
dma_init( DMA1_Channel4,
                  (uint32_t) & USART1->DATA,
                    (uint32_t)g_sendbuf,
              SEND_BUF_SIZE);                       /* 初始化DMA */
   
    lcd_show_string(30, 50, 200, 16, 16, "APM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "DMA TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "KEY0:Start", RED);
   
    len = sizeof(text_to_send);                                                             /* 填充发送数据缓冲区 */
   
    for (i = 0; i < SEND_BUF_SIZE; i++)
    {
        g_sendbuf = text_to_send[i % len];
    }
    i = 0;
   
    while (1)
    {
        key = key_scan(0);
        
        if (key == KEY0_PRES)
        {
            lcd_show_string(30, 130, 200, 16, 16, "Start Transimit....", BLUE);
            lcd_show_string(30, 150, 200, 16, 16, "   %", BLUE);
            
            USART_EnableDMA(USART_UX, USART_DMA_TX);                                        /* 使能USART的DMA发送 */
            dma_enable(DMA1_Channel4, SEND_BUF_SIZE);                                       /* 开启一次DMA传输 */
            
            /* 等待DMA传输完成
             * 实际应用中,在等待DMA传输完成期间,可以执行另外的任务
             */
            while (1)
            {   /* 判断通道4传输完成 */
                if (DMA_ReadStatusFlag(DMA1_FLAG_TC4) != RESET)
                {   /* 清除通道4传输完成标志 */
                    DMA_ClearStatusFlag(DMA1_FLAG_TC4);
                    break;
                }
               
                pro = DMA_ReadDataNumber(DMA1_Channel4);                                    /* 得到当前还剩余多少个数据  */
                pro = ((SEND_BUF_SIZE - pro) * 100) / SEND_BUF_SIZE;                        /* 获得百分比 */
                lcd_show_num(30, 150, pro, 3, 16, BLUE);                                    /* LCD显示DMA传输进度 */
            }
            
            pro = DMA_ReadDataNumber(DMA1_Channel4);
            pro = ((SEND_BUF_SIZE - pro) * 100) / SEND_BUF_SIZE;
            lcd_show_num(30, 150, pro, 3, 16, BLUE);
            lcd_show_string(30, 130, 200, 16, 16, "Transimit Finished!", BLUE);             /* LCD显示传送完成 */
        }
        
        i++;
        
        if ((i % 20) == 0)
        {
            i = 0;
            LED0_TOGGLE();                                                                  
        }
        delay_ms(10);
    }
}
从上面的代码中可以看到,在DMA的初始化中,初始化了DMA1通道4,这是因为USART1的发送就对应了DMA1通道4,完成初始化工作后,就不断地扫描按键,若扫描到KEY0按键,并使用DMA进行USART1发送数据,在DMA进行数据传输的此期间,LCD上不断刷新显示DMA传输的进度,直至DMA传输完毕。
32.3 下载验证
在完成编译和烧录操作后,可以看到LCD上显示了本实验的实验信息,此时若按下KEY0按键,可以看到串口调试助手不断打印“正点原子 APM32E103最小系统板 DMA实验\r\n”(200次),在此期间也能看到LCD上不断地实时刷新串口打印的进度。

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-27 18:56

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表