1.1 DMA结构框图
DMA 控制器独立于内核,属于一个单独的外设,结构比较简单,从编程的角度来看,我们只需掌握结构框图中的三部分内容即可。
如图:(大家也可以查看《STM32F10x中文参考手册》-10 DMA控制器(DMA)章节
内容)
(1)标号1:DMA请求
如果外设要想通过 DMA 来传输数据,必须先给 DMA 控制器发送 DMA请求, DMA收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且 DMA 控制器收到应答信号之后,就会启动 DMA 的传输,直到传输完毕。
根据前面介绍我们知道,DMA含有DMA1和DMA2两个控制器,其中DMA1含有7个通道,DMA2含有5个通道,不同的 DMA 控制器的通道对应着不同的外设请求,如图
(2)标号2:DAM通道
DMA 具有 12 个独立可编程的通道,其中 DMA1 有 7 个通道, DMA2有 5 个通道,每个通道对应不同的外设的 DMA 请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。
(3)标号3:仲裁器
当发生多个 DMA 通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器也管理。仲裁器管理 DMA 通道请求分为两个阶段。第一阶段属于软件阶段,可以在DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高,比如通道 0 高于通道 1。在大容量产品和互联型产品中,DMA1 控制器拥有高于 DMA2 控制器的优先级。
1.2 DMA数据配置
(1)外设到存储器
当我们使用从外设到存储器传输时,以 ADC 采集为例。 DMA 外设寄存器的地址对应的就是 ADC 数据寄存器的地址, DMA 存储器的地址就是我们自定义的变量(用来接收存储 AD 采集的数据)的地址。方向我们设置外设为源地址。
(2)存储器到外设
当我们使用从存储器到外设传输时,以串口向电脑端发送数据为例。DMA 外设寄存器的地址对应的就是串口数据寄存器的地址, DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目标地址。
STM32F1 DMA配置步骤
接下来我们介绍下如何使用库函数对DMA进行配置。这个也是在编写程序中必须要了解的。具体步骤如下:(DMA相关库函数在stm32f10x_dma.c和stm32f10x_dma.h文件中)
1)使能DMA控制器(DMA1或DMA2)时钟
void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph,
FunctionalState NewState);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
(2)初始化DMA通道,包括配置通道、外设和内存地址、传输数据量等
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx,DMA_InitTypeDef* DMA_InitStruct);
typedef struct
{
uint32_t DMA_PeripheralBaseAddr; // 外设地址
uint32_t DMA_MemoryBaseAddr; // 存储器地址
uint32_t DMA_DIR; // 传输方向
uint32_t DMA_BufferSize; // 传输数目
uint32_t DMA_PeripheralInc; // 外设地址增量模式
uint32_t DMA_MemoryInc; // 存储器地址增量模式
uint32_t DMA_PeripheralDataSize; // 外设数据宽度
uint32_t DMA_MemoryDataSize; // 存储器数据宽度
uint32_t DMA_Mode; // 模式选择
uint32_t DMA_Priority; // 通道优先级
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_M2M:用来设置存储器到存储器模式,使用存储器到存储器时用到,设定DMA_CCR 的位 14 MEN2MEN 即可启动存储器到存储器模式。
了解结构体成员功能后,就可以进行配置,本章实验配置代码如下:
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA外设地址
DMA_InitStructure.DMA_MemoryBaseAddr = mar;//DMA 存储器0地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//存储器到外设模式
DMA_InitStructure.DMA_BufferSize = ndtr;//数据传输量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存储器数据长度:8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//中等优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
DMA_Init(DMAy_Channelx, &DMA_InitStructure);//初始化DMA
(3)使能外设DMA功能(DMA请求映射图对应的外设)
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA
发送
(4)开启DMA的通道传输
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
DMA_Cmd(DMA1_Channel4,ENABLE);
(5)查询DMA传输状态
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
例如我们要查询DMA1通道4传输是否完成,方法是:
DMA_GetFlagStatus(DMA1_FLAG_TC4);
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);
3.硬件电路
本实验使用到硬件资源如下:
(1)D1和D2指示灯
(2)K_UP按键
(3)串口1
(4)DMA
D1和D2指示灯、K_UP按键、串口1电路在前面章节都介绍过,这里就不多说,至于DMA它属于STM32F1芯片内部的资源,只要通过软件配置好DMA即可使用。D1指示灯用来提示系统运行状态,K_UP按键用来控制DMA发送,每按一次K_UP键,DMA就将内存(自定义的一个数组)内数据发送USART1,并通过串口1将发送的内容打印出来,在DMA数据传输的过程中让D2指示灯不断闪烁,直到数据传输完成。D2指示灯闪烁表示CPU在执行
其他的任务,说明DMA传输是不需要占用CPU的。
4.编写DMA控制程序
本章所要实现的功能是:通过K_UP按键控制DMA串口1数据的传送,在传送过程中让D2指示灯不断闪烁,直到数据传送完成。D1指示灯闪烁提示系统正常运行。程序框架如下:
(1)初始化USART1_TX对应的DMA通道相关参数
(2)编写主函数
dma.h
#ifndef _dma_H
#define _dma_H
#include "system.h"
void DMAx_Init(DMA_Channel_TypeDef* DMAy_Channelx,u32 par,u32 mar,u16 ndtr);//配置DMAx_CHx
void DMAx_Enable(DMA_Channel_TypeDef *DMAy_Channelx,u16 ndtr);//使能一次DMA传输
#endif
dma.c
#include "dma.h"
/*******************************************************************************
* 函 数 名 : DMAx_Init
* 函数功能 : DMA初始化函数
* 输 入 :
DMAy_Channelx:DMA通道选择,@ref DMA_channel DMA_Channel_0~DMA_Channel_7
par:外设地址
mar:存储器地址
ndtr:数据传输量
* 输 出 : 无
*******************************************************************************/
void DMAx_Init(DMA_Channel_TypeDef* DMAy_Channelx,u32 par,u32 mar,u16 ndtr)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//DMA1时钟使能
//DMA_DeInit(DMAy_Channelx);
/* 配置 DMA */
DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA外设地址
DMA_InitStructure.DMA_MemoryBaseAddr = mar;//DMA 存储器0地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//存储器到外设模式
DMA_InitStructure.DMA_BufferSize = ndtr;//数据传输量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存储器数据长度:8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//中等优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
DMA_Init(DMAy_Channelx, &DMA_InitStructure);//初始化DMA
}
/*******************************************************************************
* 函 数 名 : DMAx_Enable
* 函数功能 : 开启一次DMA传输
* 输 入 : DMAy_Channelx:DMA通道选择,@ref DMA_channel DMA_Channel_0~DMA_Channel_7
ndtr:数据传输量
* 输 出 : 无
*******************************************************************************/
void DMAx_Enable(DMA_Channel_TypeDef *DMAy_Channelx,u16 ndtr)
{
DMA_Cmd(DMAy_Channelx, DISABLE); //关闭DMA传输
DMA_SetCurrDataCounter(DMAy_Channelx,ndtr); //数据传输量
DMA_Cmd(DMAy_Channelx, ENABLE); //开启DMA传输
}
main.c
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "key.h"
#include "dma.h"
#define send_buf_len 5000
u8 send_buf[send_buf_len];
/*******************************************************************************
* 函 数 名 : Send_Data
* 函数功能 : 要发送的数据
* 输 入 : p:指针变量
* 输 出 : 无
*******************************************************************************/
void Send_Data(u8 *p)
{
u16 i;
for(i=0;i *p='5'; p++; } } /******************************************************************************* * 函 数 名 : main * 函数功能 : 主函数 * 输 入 : 无 * 输 出 : 无 *******************************************************************************/ int main() { u8 i=0; u8 key; SysTick_Init(72); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2组 LED_Init(); USART1_Init(9600); KEY_Init(); DMAx_Init(DMA1_Channel4,(u32)&USART1->DR,(u32)send_buf,send_buf_len); Send_Data(send_buf); while(1) { key=KEY_Scan(0); if(key==KEY_UP) { USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送 DMAx_Enable(DMA1_Channel4,send_buf_len); //开始一次DMA传输! //等待DMA传输完成,此时我们来做另外一些事 //实际应用中,传输数据期间,可以执行另外的任务 while(1) { if(DMA_GetFlagStatus(DMA1_FLAG_TC4)!=0)//判断通道4传输完成 { DMA_ClearFlag(DMA1_FLAG_TC4); break; } led2=!led2; delay_ms(300); } } i++; if(i%20==0) { led1=!led1; } delay_ms(10); } }
上一篇:STM32学习—外部中断EXTI
下一篇:STM32——pwm控制LED
推荐阅读最新更新时间:2024-11-07 22:08
设计资源 培训 开发板 精华推荐
- LT3029EDE 演示板、双通道 500mA/500mA 低压差、低噪声、微功率线性稳压器
- LTC2655BCUF-H16 具有 I2C 基准的四通道、16 位轨至轨 DAC 的典型应用
- LT1683 推挽式转换器
- ESP32 USB-V
- DC919A-H,用于 LTC2206-14 CMOS 输出、DC 输入、80Msps 14 位 ADC、DC 的演示板Ain< 70MHz
- ELRS Gemini双子座高频头
- 具有复位功能的 NCV4275C 450 mA 低压降稳压器的典型应用
- 适用于空调应用的,使用VIPer35LD的16 W三路输出准谐振反激转换器
- DI-176 - 20W空调电源
- Si4455 EZRADIO 双向链路演示套件