stm32笔记(3) ————DMA数据转运&DMA+AD多通道

一、 数据转运+DMA

前言:const常量,存储在flash,则地址开头0x0800 0000,节省SRAM,若有很大的查找表或者字库,最好加const
定义一个临时变量,则地址开头0x2000 0000
外设寄存器的地址是固定的,例如ADC1->DR(4001 244C),
在数据手册中,起始地址+偏移地址
ADC1的地址0x4001 2400,DR的地址偏移为4C
在这里插入图片描述

任务目标:将SRAM里的数组DataA转运到另一个数组DataB中。转运时,两边都要自增,要转运7次,所以传输计数器给7,自动重装暂时不需要,是存储器到存储器(因为这是存储器到存储器,不需要等待硬件时机),要软件触发,这里的数据转运是一种复制转运,转运完成后DataA的数据并不会消失。

程序

DMA_BufferSize:缓冲区大小, 即传输计数器的值, 可选的值为0~65535
DMA_DIR:配置DMA运输方向, 是从站点A(Memory)到站点B(Periph)还是站点B到站点A, 位置是相对的
DMA_MemoryInc:配置存储器地址是否自增
1、MyDMA.c

#include "stm32f10x.h"                  // Device header

uint16_t MyDMA_Size;//全局变量为了使在第二个传输函数也可使用Size
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
	MyDMA_Size =Size;//存一份Size
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//DMA为AHB总线设备
	
	DMA_InitTypeDef DMA_InitStructure;
	//外设地址的起始位置、数据宽度、是否自增
	DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;//一般不写绝对地址,用变量(SRAM分到哪就是哪)
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//字节
	DMA_InitStructure.DMA_PeripheralInc =  DMA_PeripheralInc_Enable;//自增
	//存储器站点的起始地址、数据宽度、是否自增
	DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//字节
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//自增

	//传输方向、缓冲区大小(即传输计数器)、传输模式(是否重装)
	//M2M选择是否存储器到存储器(软件触发和硬件触发)
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设站点即从外设到存储器(SRC为Source,DST为目的地)
	DMA_InitStructure.DMA_BufferSize = Size;//外设数据宽度或者存储器的数据宽度,缓存区大小(传输计数器):用变量Size传参
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//正常模式(不循环,即不自动重装)
	//传输模式(是否自动重装):否(Normal),是(Circle)
	DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;//软件触发
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//中等优先级
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);//软件触发,通道可以任意选择
	//注意:自动重装DMA_Mode_Circle和软件触发DMA_M2M_Enable不能同时使用,否则软件瘫痪
	
	
	//DMA转运:(1)传输计数器>0  (2)触发源由触发信号  (3)DMA使能
	
	DMA_Cmd(DMA1_Channel1, DISABLE);//此处失能/DMA初始化后立即使能
}
void MyDMA_Transfer(void)
{
	//传输函数,调用一次就再启动一次DMA转运。
	//只有上面的Init函数转运一次(Cmd里写ENABLE时。这里把它改为了DISABLE)当计数值为0时就停止了
	
	DMA_Cmd(DMA1_Channel1,DISABLE);//先失能
	DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size);//重新给传输计数器赋值
	DMA_Cmd(DMA1_Channel1,ENABLE);
	
	while(DMA_GetFlagStatus(DMA1_FLAG_TC1)== RESET);//DMA1_FLAG_TC1为转运完成标志位,完成后置1,未完成一直在此等待
	DMA_ClearFlag(DMA1_FLAG_TC1);//清0标志位
}

2、main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"
/*const uint8_t aa = 0x66;
const常量的意思,只读不可写,此时被存在ROM里,特定情况使用可节省SRAM空间,如OLED_Font.h的数组库;
无const时变量存储在RAM。可观察地址第一个字节验证
对于变量或常量,地址不固定,由编译器确定;而外设寄存器的地址是固定的,如&ADC1 -> DR为4001244C
结构体访问寄存器:
&ADC1 -> DR中ADC1为结构体指针,指向的是ADC1外设的起始地址,访问结构体成员就相当于加个地址偏移
起始地址+偏移就是指定的寄存器,->箭头符号取结构体指针的成员。

	OLED_ShowHexNum(1,1,aa,2);//16位进制数
	OLED_ShowHexNum(1,1,(uint32_t)&aa,8);
取地址后要存在一个指针变量里,如果要直接当数字显示需要强制类型转换
显示长度为8,8个十六进制的数表示32位,32位系统地址都是32位
*/

uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};//演示四个,实际情况时可能成千上万个
uint8_t DataB[] = {0, 0, 0, 0};
 
int main(void)
{
	OLED_Init();
	
	MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);//4传输次数
	//数组名就是地址,加&是数组首元素地址。但需类型转换
	//整个过程源端数据DataA不会变化
	OLED_ShowString(1, 1, "DataA");
	OLED_ShowString(3, 1, "DataB");
	OLED_ShowHexNum(1, 8, (uint32_t)DataA, 8);
	OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8);
		
	while (1)
	{
		DataA[0] ++;
		DataA[1] ++;
		DataA[2] ++;
		DataA[3] ++;
		//为了测试时显示一个变化的过程,转运一次变化下,再转运
		OLED_ShowHexNum(2, 1, DataA[0], 2);//转运前
		OLED_ShowHexNum(2, 4, DataA[1], 2);
		OLED_ShowHexNum(2, 7, DataA[2], 2);
		OLED_ShowHexNum(2, 10, DataA[3], 2);
		OLED_ShowHexNum(4, 1, DataB[0], 2);
		OLED_ShowHexNum(4, 4, DataB[1], 2);
		OLED_ShowHexNum(4, 7, DataB[2], 2);
		OLED_ShowHexNum(4, 10, DataB[3], 2);
		
		Delay_ms(1000);
		
		MyDMA_Transfer();//数据会一个个转运,跟用for循环赋值的效果一样
		
		OLED_ShowHexNum(2, 1, DataA[0], 2);//转运后
		OLED_ShowHexNum(2, 4, DataA[1], 2);
		OLED_ShowHexNum(2, 7, DataA[2], 2);
		OLED_ShowHexNum(2, 10, DataA[3], 2);
		OLED_ShowHexNum(4, 1, DataB[0], 2);
		OLED_ShowHexNum(4, 4, DataB[1], 2);
		OLED_ShowHexNum(4, 7, DataB[2], 2);
		OLED_ShowHexNum(4, 10, DataB[3], 2);
 
		Delay_ms(1000);
	}
}

二、DMA+AD多通道数据转运

在这里插入图片描述

左边是ADC扫描模式的执行流程,7个通道,触发一次后7个通道依次进行AD转换,转换后的结果放在ADC_DR数据寄存器中,在每个单独的通道转换完成后,进行一次DMA数据转运,并且目的地址进行自增,这样数据才不会覆盖
此时DMA的配置
外设地址写着ADC_DR这个寄存器的地址 存储器的地址可以在SRAM中定一个数组ADValue 然后把ADValue的地址当做存储器的地址,之后数据宽度
因为ADValue和SRAM数组要的是unit16_t的数据,所以数据宽度都是16位的,即半字传输
外设地址不自增,存储器地址自增,传输方向为外设站点到存储器站点
传输计数器:计次7次,因为有7个通道
计数器是否自动重装,取决ADC的配置,ADC如果是单次扫描,DMA不自动重装,连续扫描,DMA就自动重装 在ADC启动下一轮转换的时候,DMA也启动下一轮的转运,ADC和DMA同步工作
触发选择:这里ADValue的值是在ADC单个通道转换完成后才会有效,所以转运DMA时机,需要和ADC单个通道转换完成同步,故DMA的触发要选择ADC的硬件触发
硬件触发:在每个单独的通道转换完成后,没有任何标志位也不会触发中断,程序不太好判断某一个通道转换完成的时间,但虽然单个通道转换完成后,不产生任何标志位和中断。但是可能会产生DMA请求,去触发DMA转运

程序

AD多通道与数据转运+DMA的结合
1.AD.c

#include "stm32f10x.h"                  // Device header

//该代码就是ADC多通道与DMA配置的结合(属于外设到存储器的数据转运)。
//我们使用的是ADC连续扫描模式+DMA循环转运。这种模式硬件外设实现了相互配合和高度自动化
//也可尝试配置成ADC单次扫描+DMA单次转运模式:可自己尝试。

uint16_t AD_Value[4];

void AD_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE );//DMA为AHB总线设备
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//6分频
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN ;//选择模拟输入模式即AIN
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//扫描2个通道,相当于点2个菜,菜单上的1~2号的空位,填上了0~1这2个通道
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//点2个菜,通道0放在序列1...
	ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);

	
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_Mode= ADC_Mode_Independent;//独立模式,ADC1和ADC2各转换各的。其他为双ADC模式较复杂
	ADC_InitStructure.ADC_DataAlign= ADC_DataAlign_Right;//数据对齐:右对齐
	ADC_InitStructure.ADC_ExternalTrigConv= ADC_ExternalTrigConv_None;//触发源:软件触发
	ADC_InitStructure.ADC_ContinuousConvMode= ENABLE;//转换模式:连续转换。单次连续都行
	ADC_InitStructure.ADC_ScanConvMode= ENABLE;//转换模式:告诉厨师点了多个菜,不要只盯着一个数看
	ADC_InitStructure.ADC_NbrOfChannel = 2;//通道数目:2  点了2个菜,看前2个位置即可
	ADC_Init(ADC1,&ADC_InitStructure);
	
	//可把ADC看作厨师,DMA为服务员,菜做好(转换完成)要赶紧端走(转运走),防止覆盖
	
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_PeripheralBaseAddr= (uint32_t)&ADC1->DR;//起始在ADC的DR寄存器
	DMA_InitStructure.DMA_PeripheralDataSize= DMA_PeripheralDataSize_HalfWord;//半字/要DR寄存器低16位数据
	DMA_InitStructure.DMA_PeripheralInc= DMA_PeripheralInc_Disable ;//地址不自增,始终转运同一个位置(DR)的数据
	DMA_InitStructure.DMA_MemoryBaseAddr= (uint32_t)AD_Value;
	DMA_InitStructure.DMA_MemoryDataSize= DMA_MemoryDataSize_HalfWord;
	DMA_InitStructure.DMA_MemoryInc= DMA_MemoryInc_Enable ;//存储器地址自增
	
	DMA_InitStructure.DMA_DIR= DMA_DIR_PeripheralSRC ;//外设站点时源
	DMA_InitStructure.DMA_BufferSize=2;
	DMA_InitStructure.DMA_Mode= DMA_Mode_Circular;//(DMA循环模式,自动组装)//若不是自动重装:Normal
	DMA_InitStructure.DMA_M2M= DMA_M2M_Disable;//硬件触发,触发源为ADC1,此时还没有触发信号
	DMA_InitStructure.DMA_Priority= DMA_Priority_Medium;
	DMA_Init(DMA1_Channel1,&DMA_InitStructure);//由DMA框架图可知,ADC1对应DMA通道1
	
	DMA_Cmd(DMA1_Channel1,ENABLE);
	ADC_DMACmd(ADC1,ENABLE);//开启ADC到DMA的输出,才能产生硬件触发信号,而且自动运行,不需要判断标志位

	ADC_Cmd(ADC1,ENABLE);
	
	
	ADC_ResetCalibration(ADC1);//校准
	while (ADC_GetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while (ADC_GetCalibrationStatus(ADC1) == SET);
	
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//(连续模式,在初始化函数中写使能ADC的函数)
}

//连续加循环模式之后,当ADC触发之后,ADC连续转换,DMA循环转运,
//两者一直在工作,始终把最新的转换结果,刷新到SRAM数组中,要数据时,去数组中取
//硬件完成ADC扫描,节省CPU资源
//可以再加一个外设,比如定时器,ADC用单次扫描,再用定时器去定时触发,这样就是定时器触发ADC,ADC触发DMA,整个过程完全自动
//这是外设的合作

//若不是自动重装
/*
void AD_GetValue(void)
{
	
	DMA_Cmd(DMA1_Channel1,DISABLE);//先失能
	DMA_SetCurrDataCounter(DMA1_Channel1,4);//重新给传输计数器赋值
	DMA_Cmd(DMA1_Channel1,ENABLE);
	
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发,ADC开始转换
	
	while(DMA_GetFlagStatus(DMA1_FLAG_TC1)== RESET);//等待DMA转运完成。因为转运总在转换后,所以省去了等待ADC转换完成得代码
	DMA_ClearFlag(DMA1_FLAG_TC1);//清0标志位
}*/


2.main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"


uint16_t AD0,AD1;//表示两个ADC输入通道的转换结果的接收变量

int main(void)
{
	OLED_Init();
	AD_Init();
	OLED_ShowString(1, 1, "AD0:");
	OLED_ShowString(2, 1, "AD1:");
	while(1)
	{
		//AD_GetValue();//调用后数据就会直接转运到AD_Value数组里
		
		OLED_ShowNum(1,5,AD_Value[0],4);
		OLED_ShowNum(2,5,AD_Value[1],4);
		Delay_ms(100);//限制OLED刷新速度
	}	
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值