打印
[其他ST产品]

STM32DMA实验

[复制链接]
4513|10
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
1 DMA 简介 DMA,全称是 Direct Memory Access,中文意思为直接存储器访问。DMA 可 用于实现外设与存储器之间或者存储器与存储器之间数据传输的高效性。之所以 称为高效,是因为 DMA 传输数据移动过程无需 CPU 直接操作,这样节省的 CPU 资 源就可供其它操作使用。从硬件层面来理解,DMA 就好像是 RAM 与 I/O 设备间数 据传输的通路,外设与存储器之间或者存储器与存储器之间可以直接在这条通路 上进行数据传输。这里说的外设一般指外设的数据寄存器,比如 ADC、SPI、I2C、 DCMI 等外设的数据寄存器,存储器一般是指片内 SRAM、外部存储器、片内 Flash 等。 STM32F1 最多有 2 个 DMA 控制器( DMA2 仅存在大容量产品中),DMA1 有 7 个通道。 DMA2 有 5 个通道。每个通道专门用来管理来自于一个或多个外设 对存储器访问的请求。还有一个仲裁器来协调各个 DMA 请求的优先权。



使用特权

评论回复
沙发
欢乐家园|  楼主 | 2023-11-25 11:50 | 只看该作者

使用特权

评论回复
板凳
欢乐家园|  楼主 | 2023-11-25 11:51 | 只看该作者
目的
本章所要实现的功能是:通过 KEY_UP 按键控制 DMA 串口 1 数据的传送,在 传送过程中让 DS1 指示灯不断闪烁,直到数据传送完成。DS1 指示灯闪烁提示系 统正常运行。

程序框架如下:
(1)初始化 USART1_TX 对应的 DMA 数据流相关参数
(2)编写主函数

使用特权

评论回复
地板
欢乐家园|  楼主 | 2023-11-25 11:51 | 只看该作者
开发步骤
(1)使能 DMA 控制器(DMA1 或 DMA2)时钟 要使能 DMA 时钟,需通过 AHB1ENR 寄存器来控制,使能 DMA 时钟库函数为: void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState); 例如使能 DMA1 时钟,函数如下: RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

使用特权

评论回复
5
欢乐家园|  楼主 | 2023-11-25 11:51 | 只看该作者
(2)初始化 DMA 通道,包括配置通道、外设和内存地址、传输数据量等 要使用 DMA,必须对其相关参数进行设置,包括通道选择、外设和内存地址、 通道优先级、传输数据量的配置等。该部分设置通过 DMA 初始化函数 DMA_Init 完成的: void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx,DMA_InitTypeDef* DMA_InitStruct)typedef struct 2. { 3. uint32_t DMA_PeripheralBaseAddr; // 外设地址 4. uint32_t DMA_MemoryBaseAddr; // 存储器地址 5. uint32_t DMA_DIR; // 传输方向 6. uint32_t DMA_BufferSize; // 传输数目 7. uint32_t DMA_PeripheralInc; // 外设地址增量模式 8. uint32_t DMA_MemoryInc; // 存储器地址增量模式 9. uint32_t DMA_PeripheralDataSize; // 外设数据宽度 10. uint32_t DMA_MemoryDataSize; // 存储器数据宽度 11. uint32_t DMA_Mode; // 模式选择 12. uint32_t DMA_Priority; // 通道优先级 13. uint32_t DMA_M2M; // 存储器到存储器模式 } DMA_InitTypeDef; DMA_PeripheralBaseAddr:外设地址,通过 DMA_CPAR 寄存器设置,一般设 置为外设的数据寄存器地址,比如要进行串口 DMA 传输,那么外设基地址为串 口接受发送数据存储器 USART1->DR 的地址,表示方法为&USART1->DR。如果是 存储器到存储器模式则设置为其中一个存储区地址。 DMA_Memory0BaseAddr:存储器地址,通过 DMA_CMAR 寄存器设置,一般设置 为我们自定义存储区的首地址,即我们存放 DMA 传输数据的内存地址。比如我们 定义一个 u32 类型数组,将数组首地址(直接使用数组名即可)赋值给 DMA_Memory0BaseAddr,在 DMA 传输的时候就可以把数组内的数据发送或接收。 DMA_DIR:数据传输方向选择,可选择外设到存储器、存储器到外设以及存 储器到存储器。通过设定 DMA_CCR 寄存器的 DIR[1:0]位的值决定。比如本章实 验是从内存读取数据发送到串口,所以数据传输方向为存储器到外设,配置为 DMA_DIR_MemoryToPeripheral。 DMA_BufferSize:用来设置一次传输数据的大小,通过 DMA_CNDTR 寄存器设 置。 DMA_PeripheralInc:用来设置外设地址是递增还是不变,通过 DMA_CCR 寄 存器的 PINC 位设置,如果设置为递增,那么下一次传输的时候地址加 1。通常 外设只有一个数据寄存器,所以一般不会使能该位,即配置为 DMA_PeripheralInc_Disable。 DMA_MemoryInc:用来设置内存地址是否递增,通过 DMA_CCR 寄存器的 MINC 位设置。我们自定义的存储区一般都是存放多个数据的,所以需要使能存储器地 址自动递增功能,即配置为 DMA_MemoryInc_Enable。 DMA_PeripheralDataSize:外设数据宽度选择,可以为字节(8 位)、半字 (16 位)、字(32 位),通过 DMA_CCR 寄存器的 PSIZE[1:0]位设置。例如本章 实验数据是按照 8 位字节传输,所以配置为 DMA_PeripheralDataSize_Byte。 DMA_MemoryDataSize:存储器数据宽度选择,可以为字节(8 位)、半字(16 位)、字(32 位),通过 DMA_CCR 寄存器的 MSIZE[1:0]位设置。本章实验同样 设置为 8 位字节传输,这个要和我们定义的数组对应,所以配置为 DMA_MemoryDataSize_Byte。 DMA_Mode:DMA 传输模式选择,可选择一次传输或者循环传输,通过 DMA_CCR 寄存器的 CIRC 位来设定。比如我们要从内存(存储器)中传输 64 个字节到串口, 如果设置为循环传输,那么它会在 64 个字节传输完成之后继续从内存的第一个 地址传输,如此循环。这里我们设置为一次传输完成之后不循环。所以设置值为 DMA_Mode_Normal。 DMA_Priority:用来设置 DMA 通道的优先级,有低,中,高,超高四种级别, 可通过 DMA_CCR 寄存器的 PL[1:0]位来设定。DMA 优先级只有在多个 DMA 通道同 时使用时才有意义,本章实验我们只使用了一个 DMA 通道,所以可以任意设置 DMA 优先级,这里我们就设置为中等优先级,配置参数为 DMA_Priority_Medium。 DMA_Priority:用来设置 DMA 通道的优先级,有低,中,高,超高四种级别, 可通过 DMA_CCR 寄存器的 PL[1:0]位来设定。DMA 优先级只有在多个 DMA 通道同 时使用时才有意义,本章实验我们只使用了一个 DMA 通道,所以可以任意设置 DMA 优先级,这里我们就设置为中等优先级,配置参数为 DMA_Priority_Medium。 DMA_M2M:用来设置存储器到存储器模式,使用存储器到存储器时用到,设 定 DMA_CCR 的位 14 MEN2MEN 即可启动存储器到存储器模式。 了解结构体成员功能后,就可以进行配置,本章实验配置代码如下: 1. DMA_InitTypeDef DMA_InitStructure; 2. DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA 外设地址 3. DMA_InitStructure.DMA_MemoryBaseAddr = mar;//DMA 存储器 0 地址 4. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//存储器到外设模式 5. DMA_InitStructure.DMA_BufferSize = ndtr;//数据传输量 6. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;// 外 设 非 增 量 模式 7. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式 8. DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设 数据长度:8 位 9. DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存储器数据长 度:8 位 10. DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式 11. DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//中等优先级 12. DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA 通道 x 没有设置为内存到内存传 输 13. DMA_Init(DMAy_Channelx, &DMA_InitStructure);//初始化 DMA

使用特权

评论回复
6
欢乐家园|  楼主 | 2023-11-25 11:52 | 只看该作者
(3)使能外设 DMA 功能(DMA 请求映射图对应的外设) 配置好 DMA 后,我们就需要使能外设 DMA 功能,例如我们要使能串口的 DMA 发送功能,调用的库函数为: USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口 1 的 DMA 发送 如果是要使能串口 DMA 接受,那么第二个参数修改为 USART_DMAReq_Rx 即可。 如果是其他的外设需开启 DMA 功能,只需要在对应的标准外设库函数中查找到对 应的外设 DMA 使能函数

使用特权

评论回复
7
欢乐家园|  楼主 | 2023-11-25 11:52 | 只看该作者
(4)开启 DMA 的通道传输 初始化 DMA 后,要使用 DMA 还必须开启它,开启 DMA 通道传输的库函数为: void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState); 第一个参数为外设所对应的 DMA 通道,例如本章使用的是 USART1_TX DMA 请求,因此它对应的是 DMA1_Channel4,可通过前面 DMA 请求映射图选择。第二 个参数相信不说也知道,就是使能或失能。 本章使能 DMA1_Channel4 函数为: DMA_Cmd(DMA1_Channel4,ENABLE);

使用特权

评论回复
8
欢乐家园|  楼主 | 2023-11-25 11:52 | 只看该作者
(5)查询 DMA 传输状态 通过以上 4 步设置,我们就可以启动一次 DMA 传输了。但是在 DMA 传输过程 中,我们还需要查询 DMA 传输通道的状态,使用的库函数是: FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG); 例如我们要查询 DMA1 通道 4 传输是否完成,方法是: DMA_GetFlagStatus(DMA1_FLAG_TC4); 标准库中,还提供了获取当前剩余数据量大小的函数: uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx); 例如我们要获取 DMA1 通道 4 还有多少个数据没有传输,方法是: DMA_GetCurrDataCounter(DMA1_Channel4); 同样,标准库中还提供了设置传输数据量大小的函数: void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber); 将以上几步全部配置好后,我们就可以使用 DMA 来传输对应外设的数据了。

使用特权

评论回复
9
欢乐家园|  楼主 | 2023-11-25 11:52 | 只看该作者
dma.h+dma.c

#ifndef _dma_H
#define _dma_H
#include "system.h"

void DMAx_Init(DMA_Channel_TypeDef* DMAy_Channelx,u32 par,u32 mar,u16 ndtr);

void DMAx_Enable(DMA_Channel_TypeDef* DMAy_Channelx,u16 ndtr);

#endif

使用特权

评论回复
10
欢乐家园|  楼主 | 2023-11-25 11:52 | 只看该作者
#include "dma.h"


void DMAx_Init(DMA_Channel_TypeDef* DMAy_Channelx,u32 par,u32 mar,u16 ndtr)
//通道,外设地址,存储器地址,传输数目
{
    DMA_InitTypeDef DMA_InitStructure;
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
   
   
    DMA_InitStructure.DMA_BufferSize=ndtr;//传输数据大小
    DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralDST;//数据传输方向
    DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;//设置存储器到存储器模式
    DMA_InitStructure.DMA_MemoryBaseAddr=mar;//DMA存储器地址
    DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;//:存储器数据宽度选择,
    //与外设宽度对应,因为是从存储器传到外设
    DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//内存地址递增
    //我们自定义的存储区一般都是存放多个数据的,所以需要使能存储器地
  //址自动递增功能
    DMA_InitStructure.DMA_PeripheralInc=DMA_MemoryInc_Disable;//外设地址递增,外设数据寄存器
    //只有一个,不能递增
    DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;//设置 DMA 通道的优先级
    DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;//DMA 传输模式选择
    DMA_InitStructure.DMA_PeripheralBaseAddr=par;//DMA外设地址
    DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;//外设数据宽度选择,可以为字节(8 位)、半字
  //(16 位)、字(32 位)
    DMA_Init(DMAy_Channelx, &DMA_InitStructure);
}
void DMAx_Enable(DMA_Channel_TypeDef* DMAy_Channelx,u16 ndtr)//通道
{
    DMA_Cmd(DMAy_Channelx, DISABLE);
    DMA_SetCurrDataCounter(DMAy_Channelx,ndtr ); //传输的数据量
    DMA_Cmd(DMAy_Channelx, ENABLE);
}

使用特权

评论回复
11
欢乐家园|  楼主 | 2023-11-25 11:53 | 只看该作者
main.c
#include "stm32f10x.h"
#include "led.h"
#include "system.h"
#include "SysTick.h"
#include "beep.h"
#include "key.h"
#include "exti.h"
#include "time.h"
#include "pwm.h"
#include "usart.h"
#include "stdio.h"
#include "iwdg.h"
#include "wwdg.h"
#include "input.h"
#include "touch_key.h"
#include "wkup.h"
#include "adc.h"
#include "adc_temp.h"
#include "lsens.h"
#include "dac.h"
#include "dma.h"

#define SEND_BUF_LEN 5000
u8 send_buf[SEND_BUF_LEN];
void Send_Data(u8 *p)
{
    u16 i=0;
    for(i=0;i<SEND_BUF_LEN;i++)
    {
        *p='5';//传输的数据
        p++;
    }
}

int main()
{
    u8 i=0;
    u8 key=0;
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置优先级分组
  SysTick_Init(72);
    LED_Init();
    KEY_Init();
    USART1_Init(115200);//波特率115200
    DMAx_Init(DMA1_Channel4,(u32)&USART1->DR,(u32)send_buf[SEND_BUF_LEN],(u32)SEND_BUF_LEN);
    Send_Data(send_buf);
    while(1)
    {
        key=KEY_Scan(0);
        if(key== KEY_UP_PRESS)
        {
            USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
            DMAx_Enable(DMA1_Channel4,(u32)SEND_BUF_LEN);
            
            while(1)
            {
                if(DMA_GetFlagStatus(DMA2_FLAG_TC4)==1)
                {
                    DMA_ClearFlag(DMA2_FLAG_TC4);
                    break;
                }
                LED2=!LED2;
                delay_ms(300);
            }
        }
        
        i++;
        if(i%20==0)LED1=!LED1;
        delay_ms(10);
   
   
    }
   
}

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

106

主题

941

帖子

1

粉丝