11:(寄存器开发)DMA的使用
1、存储器ROM到存储器SRAM
案例:使用DMA1将ROM中的数据搬运到SRAM中,ROM中保存的数据一般是常量,所以在定义的时候前面加上关键词const,不可在改变。实现存储器到存储器其实就是数据的拷贝,相当于复制粘贴。
①DMA.c文件的代码如下:
#include "DMA.h"
/**
* DMA1的初始化
*/
void DMA1_Init(void)
{
/* 1、开启DMA1的时钟 */
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
/* 2、DMA的配置 */
/* 2.1、搬运的方向:存储器(ROM)->存储器(SRAM),通道随意
DMA_CCRx_MEM2MEM = 1(存储器到存储器),DMA_CCRx_DIR = 0(从外设读:相当于将ROM映射到外设)*/
DMA1_Channel1->CCR |= DMA_CCR1_MEM2MEM;//将DMA1的通道1配置为从ROM搬运到SRAM
DMA1_Channel1->CCR &= ~DMA_CCR1_DIR;//从外设读取数据到存储(ROM映射到外设,相当于外设)
/* 2.2、通道的优先级:DMA_CCRx_PL[1:0]*/
DMA1_Channel1->CCR &= ~DMA_CCR1_PL;//00最低
/* 2.2、搬运的数据位宽:8位(一个字节),16位(半字),32位(字)
被搬运的外设位宽DMA_CCRx_PSIZE[1:0],存储器位宽DMA_CCRx_MSIZE[1:0]*/
DMA1_Channel1->CCR &= ~DMA_CCR1_PSIZE;//00,外设位宽为8位(1个字节)
DMA1_Channel1->CCR &= ~DMA_CCR1_MSIZE;//00,存储器位宽为8位(1个字节)
/* 2.3、搬运的地址和目的地址是否自增
外设地址自增DMA_CCRx_PINC = 1,存储器地址自增DMA_CCRx_MINC = 1*/
DMA1_Channel1->CCR |= DMA_CCR1_PINC;
DMA1_Channel1->CCR |= DMA_CCR1_MINC;
/* 2.4、开启搬运完成的中断:DMA_CCRx_TCIE = 1*/
DMA1_Channel1->CCR |= DMA_CCR1_TCIE;
/* 2.5、NVIC的配置 */
NVIC_SetPriorityGrouping(4);
NVIC_SetPriority(DMA1_Channel1_IRQn,0);
NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}
/**
* 搬运启动配置
* StartAddress:起始地址
* TargetAddress:目的地址
* Len:搬运数据的数目
*
*/
void DMA1_TransportStart(uint32_t StartAddress ,uint32_t TargetAddress,uint16_t Len)
{
/* 1、不执行循环搬运:DMA_CCRx_CIRC = 0:就相当于不执行重装载 */
DMA1_Channel1->CCR &= ~DMA_CCR1_CIRC;
/* 2、搬运的数据的数量:DMA_CNDTRx_NDT[15:0]:相当于计数器的值 */
DMA1_Channel1->CNDTR = Len;
/* 3、外设地址和存储器地址:[注]写地址在必须开启通道之前
外设地址:DMA_CPARx_PA[31:0],存储器地址:DMA_CMARx_MA[31:0]*/
DMA1_Channel1->CPAR = StartAddress;
DMA1_Channel1->CMAR = TargetAddress;
/* 3、通道开启:*/
DMA1_Channel1->CCR |= DMA_CCR1_EN;
}
/**
* 中断服务函数:当搬运完成,关闭DMA通道
*/
uint8_t Flag = 0;
void DMA1_Channel1_IRQHandler(void)
{
/* 判断中断标志位:DMA_ISR_TCIFx = 1 */
if(DMA1->ISR & DMA_ISR_TCIF1)
{
/* 清除中断标志位:DMA_IFCR_CTCIFx= 1*/
DMA1->IFCR |= DMA_IFCR_CTCIF1;
//关闭DMA1通道1
DMA1_Channel1->CCR &= ~DMA_CCR1_EN;
Flag = 1;
}
}
②主函数文件的代码如下:
#include "DMA.h"
#include "Delay.h"
#include "OLED.h"
const uint8_t Data[] = {1,2,3,4,5};//在ROM中定义一个常量的数组
//在DAM1的初始化中设置的搬运的位宽为8位,所以定义最好用uint8_t
uint8_t Buff[5];//在SRAM定义5个数组变量
int main(void)
{
OLED_Init();
OLED_Clear();
DMA1_Init();
DMA1_TransportStart((uint32_t)Data,(uint32_t)Buff,5);//开始搬运吧,将ROM中的数据进行拷贝SRAM中
OLED_ShowString(1,1,"StaAdd:");
OLED_ShowHexNum(1,8,(uint32_t)Data,8);//打印出源地址,即ROM中存储常量数组的地址
OLED_ShowString(2,1,"Datas:");
for(uint8_t i = 0; i<5; i++)
{
OLED_ShowNum(2,7+i,Data[i],1);//打印出常量数据
}
OLED_ShowString(3,1,"TarAdd:");
OLED_ShowHexNum(3,8,(uint32_t)Buff,8);//打印目的地址,即SRAM中存储变量数据的地址
OLED_ShowString(4,1,"Buffs:");
while(1)
{
if(Flag == 1)
{
for(uint8_t i = 0; i<5; i++)
{
OLED_ShowNum(4,7+i,Buff[i],1);//打印出SRAM中被搬运来的数据
}
Flag = 0;
}
}
}
实物效果如下图所示:
如图:地址为0800 0E64为ROM的地址,地址为2000 0001为SRAM的地址
【注】
①当DMA将设置的搬运数量全部搬运完成后才会执行中断服务函数。
②将数据从ROM搬运到SRAM中,是由软件请求搬运信号,不想其他的外设会给DMA发送一个请求信号才开始搬运。即在DMA一使能,DMA就会开始搬运
2、SRAM到串口
实验:通过DMA1将SRAM中的数据搬运到串口USART1的发射数据寄存器,然后通过串口发射到上位机。
①DMA.c文件的代码如下:
#include "DMA.h"
/**
* DMA1的初始化
*/
void DMA1_Init(void)
{
/* 1、开启DMA1的时钟 */
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
/* 2、DMA的配置 */
/* 2.1、搬运的方向:存储器(SRAM)->外设(USART1_Tx),则通道不能随意了,通道为:通道4
DMA_CCRx_MEM2MEM = 0(非存储器到存储器),DMA_CCRx_DIR = 1(从存储器读:相当于将存储器搬运到外设)*/
DMA1_Channel4->CCR &= ~DMA_CCR4_MEM2MEM;
DMA1_Channel4->CCR |= DMA_CCR4_DIR;//从存储器读取数据到外设
/* 2.2、通道的优先级:DMA_CCRx_PL[1:0]*/
DMA1_Channel4->CCR &= ~DMA_CCR4_PL;//00最低
/* 2.2、搬运的数据位宽:8位(一个字节),16位(半字),32位(字)
被搬运的外设位宽DMA_CCRx_PSIZE[1:0],存储器位宽DMA_CCRx_MSIZE[1:0]*/
DMA1_Channel4->CCR &= ~DMA_CCR4_PSIZE;//00,外设位宽为8位(1个字节)
DMA1_Channel4->CCR &= ~DMA_CCR4_MSIZE;//00,存储器位宽为8位(1个字节)
/* 2.3、搬运的地址和目的地址是否自增
外设地址不能自增,因为只有一个DR寄存器,存储器地址自增DMA_CCRx_MINC = 1*/
DMA1_Channel4->CCR &= ~DMA_CCR4_PINC;//外设地址不自增
DMA1_Channel4->CCR |= DMA_CCR4_MINC;
/* 2.4、开启搬运完成的中断:DMA_CCRx_TCIE = 1*/
DMA1_Channel4->CCR |= DMA_CCR4_TCIE;
/* 2.5、使能串口的DMA发送的请求:USART_CR3_DMAT = 1 */
USART1->CR3 |= USART_CR3_DMAT;//当串口的数据寄存器里面没有数据时,就会发送一个DMA请求,
//让DMA将数据搬运到串口的数据寄存器里面
/* 2.6、NVIC的配置 */
NVIC_SetPriorityGrouping(4);
NVIC_SetPriority(DMA1_Channel4_IRQn,0);
NVIC_EnableIRQ(DMA1_Channel4_IRQn);
}
/**
* 搬运启动配置
* StartAddress:起始地址(SRAM的地址)
* TargetAddress:目的地址(USART的数据寄存器的地址)
* Len:搬运数据的数目
*
*/
void DMA1_TransportStart(uint32_t StartAddress ,uint32_t TargetAddress,uint16_t Len)
{
/* 1、不执行循环搬运:DMA_CCRx_CIRC = 0 */
DMA1_Channel4->CCR &= ~DMA_CCR4_CIRC;
/* 2、搬运的数据的数量:DMA_CNDTRx_NDT[15:0]*/
DMA1_Channel4->CNDTR = Len;
/* 3、外设地址和存储器地址:[注]写地址在必须开启通道(EN)之前
外设地址:DMA_CPARx_PA[31:0],存储器地址:DMA_CMARx_MA[31:0]*/
DMA1_Channel4->CPAR = TargetAddress;
DMA1_Channel4->CMAR = StartAddress;
/* 3、通道开启:*/
DMA1_Channel4->CCR |= DMA_CCR4_EN;
}
/**
* 中断服务函数:当搬运完成,关闭DMA通道
*/
void DMA1_Channel4_IRQHandler(void)
{
/* 判断中断标志位:DMA_ISR_TCIFx = 1 */
if(DMA1->ISR & DMA_ISR_TCIF4)
{
/* 清除中断标志位:DMA_IFCR_CTCIFx= 1*/
DMA1->IFCR |= DMA_IFCR_CTCIF4;
//关闭DMA1通道1
DMA1_Channel4->CCR &= ~DMA_CCR4_EN;
}
}
②主函数文件的代码如下:
#include "DMA.h"
#include "USART.h"
uint8_t Data[] = {1,2,3,4,5};//在SRAM中定义一个变量的数组
//在DAM1的初始化中设置的搬运的位宽为8位,所以定义最好用uint8_t
int main(void)
{
USART1_Init();
DMA1_Init();
DMA1_TransportStart((uint32_t)Data,(uint32_t)(&(USART1->DR)),5);//搬运的准备工作(地址的对接),将SRAM中的数据进行搬运到串口的DR数据寄存器里面
while(1)
{
}
}
实物效果如下图所示:
3、ADC的连续扫描+DMA
案例:将ADC的规则组配置为多通道的连续扫描模式,通道DMA将被转换的数据搬运到SRAM内存中。
①ADC.c文件代码如下:
#include "ADC.h"
/**
* ADC1的初始化
*/
void ADC1_Init(void)
{
/* 1、开启时钟:ADC1和GPIOA*/
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
/* 2、PA0(通道AIN1),PA1(通道AIN2),PA2(通道AIN3)的工作模式:模拟输入模式MODE = 00,CNF = 00*/
GPIOA->CRL &= ~GPIO_CRL_MODE0;
GPIOA->CRL &= ~GPIO_CRL_CNF0;
GPIOA->CRL &= ~GPIO_CRL_MODE1;
GPIOA->CRL &= ~GPIO_CRL_CNF1;
GPIOA->CRL &= ~GPIO_CRL_MODE2;
GPIOA->CRL &= ~GPIO_CRL_CNF2;
/* 3、ADC的配置*/
/* 3.1、设置采样时间:ADC_SMPR2_SMP0 = 001(通道1采样时间为7.5周期)*/
ADC1->SMPR2 |= ADC_SMPR2_SMP0_0;
ADC1->SMPR2 &= ~(ADC_SMPR2_SMP0_1 | ADC_SMPR2_SMP0_2);
/* 3.2、通道组的配置:使用规则通道组 */
/* 3.2.1:配置规则组要转换的序列个数:ADC_SQR1_L[3:0]:为0010,则3个序列需要转换 */
ADC1->SQR1 &= ~(ADC_SQR1_L_0 | ADC_SQR1_L_2 |ADC_SQR1_L_3);
ADC1->SQR1 |= ADC_SQR1_L_1;
/* 3.2.2:将通道号(AI0,AI1,AI2)写入转换的序列中:
ADC_SQR3_SQ1[0:4] = 00000(AIN0)
ADC_SQR3_SQ2[0:4] = 00001(AIN1)
ADC_SQR3_SQ3[0:4] = 00010(AIN2),*/
ADC1->SQR3 &= ~ADC_SQR3_SQ1;//将AI0放入规则组序列1
ADC1->SQR3 &= ~(ADC_SQR3_SQ2_1|ADC_SQR3_SQ2_2|ADC_SQR3_SQ2_3|ADC_SQR3_SQ2_4);
ADC1->SQR3 |= ADC_SQR3_SQ2_0;//将AIN1放入规则组序列2
ADC1->SQR3 &= ~(ADC_SQR3_SQ3_0|ADC_SQR3_SQ3_2|ADC_SQR3_SQ3_3|ADC_SQR3_SQ3_4);
ADC1->SQR3 |= ADC_SQR3_SQ3_1;//将AIN2放入规则组序列3
/* 3.3、使用扫描模式:ADC_CR1_SCAN = 1*/
ADC1->CR1 |= ADC_CR1_SCAN;
/* 3.4、使用连续转换模式:ADC_CR2_CONT = 1*/
ADC1->CR2 |= ADC_CR2_CONT;
/* 3.5、数据的对齐方式:右对齐:ADC_CR2_ALIGN = 0*/
ADC1->CR2 &= ~ADC_CR2_ALIGN;
/* 3.6、使用软件触发 */
ADC1->CR2 &= ~ADC_CR2_EXTTRIG;//禁用外部事件启动转换
ADC1->CR2 |= ADC_CR2_EXTSEL;//111,为软件触发
}
/**
* 启动ADC1
*/
void ADC1_Start(void)
{
/* 1、上电:把ADC从休眠模式唤醒*/
ADC1->CR2 |= ADC_CR2_ADON;
/* 2、执行校准 */
ADC1->CR2 |= ADC_CR2_CAL;
/* 3、等待校准完成 */
while(ADC1->CR2 & ADC_CR2_CAL);//校准完成为0
/* 4、再次上电,开始转换*/
ADC1->CR2 |= ADC_CR2_ADON;
/* 5、开始规则组转换:给一个触发信号*/
ADC1->CR2 |= ADC_CR2_SWSTART;
}
②DMA.c文件的代码如下:
#include "DMA.h"
/**
* DMA1的初始化
*/
void DMA1_Init(void)
{
/* 1、开启DMA1的时钟 */
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
/* 2、DMA的配置 */
/* 2.1、搬运的方向:外设(ADC)->存储器(SRAM),则通道不能随意了,通道为:通道1
DMA_CCRx_MEM2MEM = 0(非存储器到存储器),DMA_CCRx_DIR = 0(从外设读:相当于将数据从外设搬运到存储器)*/
DMA1_Channel1->CCR &= ~DMA_CCR1_MEM2MEM;
DMA1_Channel1->CCR &= ~DMA_CCR1_DIR;//从外设读取数据到存储器
/* 2.2、通道的优先级:DMA_CCRx_PL[1:0]*/
DMA1_Channel1->CCR &= ~DMA_CCR1_PL;//00最低
/* 2.2、搬运的数据位宽:8位(一个字节),16位(半字),32位(字)
被搬运的外设位宽DMA_CCRx_PSIZE[1:0],存储器位宽DMA_CCRx_MSIZE[1:0]*/
DMA1_Channel1->CCR |= DMA_CCR1_PSIZE_0;//01,外设位宽为16位(半个字),因为ADC的数据是12位的
DMA1_Channel1->CCR &= ~DMA_CCR1_PSIZE_1;
DMA1_Channel1->CCR |= DMA_CCR1_MSIZE_0;//01,存储器位宽为16位(半个字)
DMA1_Channel1->CCR &= ~DMA_CCR1_MSIZE_1;//01,存储器位宽为16位(半个字)
/* 2.3、搬运的地址和目的地址是否自增
外设地址不能自增,因为只有一个DR寄存器,存储器地址自增DMA_CCRx_MINC = 1*/
DMA1_Channel1->CCR &= ~DMA_CCR1_PINC;//外设地址不自增,因为始终是ADC的数据寄存器
DMA1_Channel1->CCR |= DMA_CCR1_MINC;
/* 2.5、使能ADC的DMA的请求[注:只有ADC1和ADC3才有DMA请求]:ADC_CR2_DMA = 1 */
ADC1->CR2 |= ADC_CR2_DMA;//当转换好的数据保存到ADC1的数据寄存器时,
//就会发生一个DMA请求,让DMA将数据搬运走
}
/**
* 搬运启动配置
* StartAddress:起始地址(ADC的数据寄存器的地址)
* TargetAddress:目的地址(SRAM的地址)
* Len:搬运数据的数目
*
*/
void DMA1_TransportStart(uint32_t StartAddress ,uint32_t TargetAddress,uint16_t Len)
{
/* 1、执行循环搬运:DMA_CCRx_CIRC = 1 */
DMA1_Channel1->CCR |= DMA_CCR1_CIRC;
/* 2、搬运的数据的数量:DMA_CNDTRx_NDT[15:0]*/
DMA1_Channel1->CNDTR = Len;
/* 3、外设地址和存储器地址:[注]写地址在必须开启通道(EN)之前
外设地址:DMA_CPARx_PA[31:0],存储器地址:DMA_CMARx_MA[31:0]*/
DMA1_Channel1->CPAR = StartAddress;
DMA1_Channel1->CMAR = TargetAddress;
/* 3、通道开启:*/
DMA1_Channel1->CCR |= DMA_CCR1_EN;
}
③主函数文件的代码如下:
#include "DMA.h"
#include "ADC.h"
#include "OLED.h"
#include "Delay.h"
uint16_t Data[3];//在SRAM中定义一个变量的数组
//在DAM1的初始化中设置的搬运的位宽为16位,所以定义最好用uint16_t
float Buff;
int main(void)
{
OLED_Init();
ADC1_Init();
DMA1_Init();
OLED_ShowString(1,1,"Value01:0.00v");
OLED_ShowString(2,1,"Value02:0.00v");
OLED_ShowString(3,1,"Value03:0.00v");
/* 当ADC中的规则组的一个序列的通道转换完成后,将数据保存到数据寄存器里面,就会产生一个DMA搬运请求
此时ADC1:连续+扫描模式(3个通道),所以DMA1:搬运数据3+循环模式(计数器重装载,每搬完3个数据后,又开始搬运)
*/
DMA1_TransportStart((uint32_t)&(ADC1->DR),(uint32_t)(Data),3);//DMA执行搬运准备工作(将地址对接,当寄存器里面有数据来了,硬件就会自动搬运)
ADC1_Start();//开始转换,等待寄存器里面来数据
while(1)
{
Buff = (Data[0] * 3.3) / 4095;//将通道AIO测量的数据给Buff
OLED_ShowNum(1,9,Buff,1);//显示通道1转换的整数
OLED_ShowNum(1,11,(uint16_t)(Buff * 100)%100,2);//显示通道1转换的整数
Buff = (Data[1] * 3.3) / 4095;//将通道AI1测量的数据给Buff
OLED_ShowNum(2,9,Buff,1);//显示通道1转换的整数
OLED_ShowNum(2,11,(uint16_t)(Buff * 100)%100,2);//显示通道1转换的整数
Buff = (Data[2] * 3.3) / 4095;//将通道AI2测量的数据给Buff
OLED_ShowNum(3,9,Buff,1);//显示通道1转换的整数
OLED_ShowNum(3,11,(uint16_t)(Buff * 100)%100,2);//显示通道1转换的整数
Delay_ms(30);
}
}
实物效果如下图所示:
DMA+ADC
【注】
①主函数中搬运的准备工作(地址的对接)应该在ADC开始转换数据的前面,因为地址对接好后,开启ADC转换,转换的数据保存到数据寄存器里面,然后数据寄存器申请DMA请求,DMA就开始搬运数据,这样通道AIN0测量的数据就正好搬运到Data[0]中。
原文地址:https://blog.csdn.net/qq_51284092/article/details/142910205
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!