您的位置:首页 > 健康 > 美食 > STM32学习笔记3---ADC,DMA

STM32学习笔记3---ADC,DMA

2024/10/11 7:11:11 来源:https://blog.csdn.net/2301_80397661/article/details/141171784  浏览:    关键词:STM32学习笔记3---ADC,DMA

目录

ADC模拟数字转换器

规则组的四种转换模式

AD单通道 

AD多通道

 常用代码函数相关

DMA直接存储器 存取(访问)

两个应用

DMA存储器到存储器的转运

ADC+DMA 


ADC模拟数字转换器

stm32数字电路,只有高低电平,无几V电压的概念

ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量

12位(分辨率,0-2的12次方减1)逐次逼近型(工作模式)ADC,1us(转换频率1MHz)转换时间

输入电压范围:0~3.3V,转换结果范围:0~4095

18个输入通道,可测量16(16GPIO)个外部(最多)和2个内部(内部温度传感器,内部参考电压)信号源

规则组和注入组两个转换单元

STM32F103C8T6 ADC资源:ADC1、ADC2,10个外部输入通道(该型号不够16)

(ADC模拟到数字)(DAC,PWM数字到模拟)

根据引脚定义表,PAO到PB1这10个引脚是ADC的10个通道,但是其他的这些引脚。不是ADC的通道,就不能接模拟电压了

规则组的四种转换模式

连续转换or单次转换

扫描模式or非扫描模式

非扫描模式,菜单列表只用第一个,序列1-16,只有第一个有效

单次转换:触发一次仅进行一次转换,若需下一次需要再次触发

连续转换:一次转换后不会停止,立刻开始下一轮,一直持续下去


AD单通道 

1.初始化

RCC(GPIO , ADC1(APB2),ADC CLK(clock)的分频器

GPIO(模拟输入)

规则组通道配置,多路开关(左边通道,接入右边的规则组)(点菜)(指定通道通道0,序列,指定通道采样时间)

配置ADC转换器

开关控制,开启ADC

ADC校准


main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"uint16_t ADValue;			//定义AD值变量
float Voltage;				//定义电压变量int main(void)
{OLED_Init();			AD_Init();				OLED_ShowString(1, 1, "ADValue:");OLED_ShowString(2, 1, "Voltage:0.00V");while (1){ADValue = AD_GetValue();					//AD转换的值(0-4095)Voltage = (float)ADValue / 4095 * 3.3;		//AD值线性变换到0~3.3的范围,表电压OLED_ShowNum(1, 9, ADValue, 4);				//显示AD值OLED_ShowNum(2, 9, Voltage, 1);				//显示电压值整数部分OLED_ShowNum(2, 11, (uint16_t)(Voltage * 100) % 100, 2);	//显示电压值的小数部分Delay_ms(100);			//延时100ms,手动增加一些转换的间隔时间!!!!!}
}
//AD.c
#include "stm32f10x.h"                  // Device headervoid AD_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//ADCCLK,6分频(ADCCLK=72/6=12MHz)						//选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHzGPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模拟输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);					//规则组通道配置ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);//ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 2, ADC_SampleTime_55Cycles5);//若想填充菜单列表//ADC初始化ADC_InitTypeDef ADC_InitStructure;						ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;		//独立模式(而非双ADC模式)ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//数据对齐右对齐ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//触发源,内部软件触发ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;		//连续转换,单次转换ADC_InitStructure.ADC_ScanConvMode = DISABLE;			//扫描模式,非扫描ADC_InitStructure.ADC_NbrOfChannel = 1;					//通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1ADC_Init(ADC1, &ADC_InitStructure);						//ADC使能ADC_Cmd(ADC1, ENABLE);									//ADC校准ADC_ResetCalibration(ADC1);//复位校准								while (ADC_GetResetCalibrationStatus(ADC1) == SET);//返回复位校准的状态(等待校准完成)ADC_StartCalibration(ADC1);//启动校准while (ADC_GetCalibrationStatus(ADC1) == SET);//等待校准完成
}uint16_t AD_GetValue(void)//获取返回电压值
{ADC_SoftwareStartConvCmd(ADC1, ENABLE);					//软件触发AD转换while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);	//等待EOC标志位置1,等待AD转换结束return ADC_GetConversionValue(ADC1);					//读数据寄存器
}

AD多通道

后面两种扫描模式,这个最好配合DMA(数据覆盖)

利用单次转换非扫描模式实现多通道

每次触发转换之前,手动更改列表第一个位置的通道

依次启动四次转换,并在转换前,指定转换通道,每次转换完成,把结果存在四个里面

uint16_t AD_GetValue(uint8_t ADC_Channel)
{ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_55Cycles5);	//在每次转换前,根据函数形参灵活更改规则组的通道1ADC_SoftwareStartConvCmd(ADC1, ENABLE);					//软件触发AD转换一次while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);	//等待EOC标志位,即等待AD转换结束return ADC_GetConversionValue(ADC1);					//读数据寄存器,得到AD转换的结果
}
/
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"uint16_t AD0, AD1, AD2, AD3;	//定义AD值变量int main(void)
{/*模块初始化*/OLED_Init();				//OLED初始化AD_Init();					//AD初始化/*显示静态字符串*/OLED_ShowString(1, 1, "AD0:");OLED_ShowString(2, 1, "AD1:");OLED_ShowString(3, 1, "AD2:");OLED_ShowString(4, 1, "AD3:");while (1){AD0 = AD_GetValue(ADC_Channel_0);		//单次启动ADC,转换通道0AD1 = AD_GetValue(ADC_Channel_1);		//单次启动ADC,转换通道1AD2 = AD_GetValue(ADC_Channel_2);		//单次启动ADC,转换通道2AD3 = AD_GetValue(ADC_Channel_3);		//单次启动ADC,转换通道3OLED_ShowNum(1, 5, AD0, 4);				//显示通道0的转换结果AD0OLED_ShowNum(2, 5, AD1, 4);				//显示通道1的转换结果AD1OLED_ShowNum(3, 5, AD2, 4);				//显示通道2的转换结果AD2OLED_ShowNum(4, 5, AD3, 4);				//显示通道3的转换结果AD3Delay_ms(100);			//延时100ms,手动增加一些转换的间隔时间}
}

 常用代码函数相关

//配置ADCCLK分频器
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);
//给ADC上电
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
//ADC 中断输出控制
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);
//复位校准
void ADC_ResetCalibration(ADC_TypeDef* ADCx);
//获取复位校准状态
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
//开始校准
void ADC_StartCalibration(ADC_TypeDef* ADCx);
//获取开始校准状态
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);
//软件触发转换(触发控制)
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);//ADC规则组通道配置(给序列的每个位置填写指定的通道)
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
//读取转换结果
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);

DMA直接存储器 存取(访问)

数据转运

提供外设(外设寄存器:ADC、串口的数据寄存器)(硬件触发)存储器(运行内存SRAM和程序存储器Flash)(软件触发)或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源

数据转运的路径:12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道)

每个通道都支持软件触发和特定的硬件触发

STM32F103C8T6 DMA资源:DMA1(7个通道)

stm32中存储器:

DMA

所以DMA,即是总线矩阵的主动单元,可以读写各种存储器,也是AHB总线上的被动单元

CPU通过这一条线路,就可以对DMA进行配置了

用于访问各个存储器的DMA总线

内部的多个通道,可以进行独立的数据转运

仲裁器,用于调度各个通道,防止产生冲突
AHB从设备。用于配置DMA参数

DMA请求,用于硬件触发DMA的数据转运

总之就是CPU或者DMA直接访问Flash(只读存储器的一种)的话。是只可以读而不可以写的

然后SRAM是运行内存,可以任意读写。没有问题

注意一下。写传输计数器时。必须要先关闭DMA,再进行

两个应用

外设地址显然应该填DataA数组的首地址

所以数据宽度都是按8位的字节传输

均自增

打印变量的地址,确定其存在的位置

uint8_t aa=0x66

(uint8_t)&aa

(uint32_t)&ADC1->DR(stm32结构体访问寄存器)

在STM32中,使用const定义的变量,是存储在Flash里面的

。自动重装和软件触发不能同时使用

DMA存储器到存储器的转运

1.初始化

开启时钟

DMA是AHB总线的设备,所以要用AHB开启时钟的函数

DMA1初始化

使能

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};	//DMA转运的源端数组(数组名就是地址)
uint8_t DataB[] = {0, 0, 0, 0};///DMA转运的目的数组int main(void)
{OLED_Init();					MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);//传输四次/*显示静态字符串*/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);		//显示数组DataAOLED_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);		//显示数组DataBOLED_ShowHexNum(4, 4, DataB[1], 2);OLED_ShowHexNum(4, 7, DataB[2], 2);OLED_ShowHexNum(4, 10, DataB[3], 2);Delay_ms(1000);		//延时1s,观察转运前的现象MyDMA_Transfer();	//使用DMA转运数组,从DataA转运到DataBOLED_ShowHexNum(2, 1, DataA[0], 2);		//显示数组DataAOLED_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);		//显示数组DataBOLED_ShowHexNum(4, 4, DataB[1], 2);OLED_ShowHexNum(4, 7, DataB[2], 2);OLED_ShowHexNum(4, 10, DataB[3], 2);Delay_ms(1000);		//延时1s,观察转运后的现象}
}

///MyDMA.c
#include "stm32f10x.h"                  // Device headeruint16_t MyDMA_Size;					//定义全局变量,用于记住Init函数的Size,供Transfer函数使用void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{MyDMA_Size = Size;					//将Size写入到全局变量,记住参数SizeRCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);						DMA_InitTypeDef DMA_InitStructure;										DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;//外设站点起始地址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;//存储器地址自增DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//数据传输方向,选择由外设到存储器(外设站点作为源头)DMA_InitStructure.DMA_BufferSize = Size;//缓存区大小(传输计数器)?!!DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//传输模式(自动重装?)(因为是存储器到存储器)DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;//存储器到存储器(软件触发)DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//优先级,选择中等DMA_Init(DMA1_Channel1, &DMA_InitStructure);//将结构体变量交给DMA_Init,配置DMA1的通道1//DMA使能DMA_Cmd(DMA1_Channel1, DISABLE);
}//调用一次这个函数,就再次启动一次DMA转运
void MyDMA_Transfer(void)//需要重新给传输计数器赋值(DATAA变化了也能继续转运)
{DMA_Cmd(DMA1_Channel1, DISABLE);//DMA失能DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);	//写入传输计数器,指定将要转运的次数DMA_Cmd(DMA1_Channel1, ENABLE);//DMA使能while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);	//等待DMA工作完成DMA_ClearFlag(DMA1_FLAG_TC1);//清除工作完成标志位
}

ADC+DMA 

AD多通道+DMA数据转运功能

AD扫描模式(PA0-PA3)

菜单点4个(菜做好后放在了ADC_DR寄存器中)

DMA服务员

源地址((uint32_t)&ADC1->DR)

数据宽度,我们想要DR奇存器低16位的数据,所以数据宽度,就是HalfWord,以半字,16位来转运

就是开启ADC到DMA的输出

这里有3个硬件触发源,具体使用哪个,取决于把哪个的DMA输出给开启了

ADC_DMACmd(ADC1,ENABLE)

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com