江协科技STM32课程笔记(五)— ADC模数转换器

一、ADC简介

stm32F103C8T6有ADC1和ADC2,10个外部输入通道。

逐次逼近型ADC:

左边IN0~IN7是输入端口,通过ADDA、ADDB和ADDC来决定通道选择,ALE为锁存信号。DAC通常us级,所以如果要对多个信号进行转换,不需要设计多个ADC,只需要通过通道地址,将通道选择器开关拨到对应通道即可。

后面是个比较器,上面为待测信号,下面为DAC输出的已知编码的电压值,通过SAR寄存器,二分法的方式逐渐查找电压对应的量化值。比如这里是8位的ADC,那编码就是从0~255,第一次比较的时候,我们就给DAC输入255的一半进行比较,那就是128,然后看谁大谁小,如果DAC电压大了,那第二次比较的时候,给128的一半,64,如果还大,第三次比较的时候就给32,如果这次DAC电压小了,那就给32到64中间的值,依次进行下去,就能最快找到未知电压的编码。128、64、32这些数据,正好是二进制每一位的位权,这个判断过程就相当于是,对二进制高位到低位依次判断是1还是0的过程。对于12位ADC就要判断12次。

ADC结束后,SAR输出到锁存缓冲器。EOC是转换结束信号,START是开始转换,给一个输入脉冲开始转换。CLOCK为时钟。VREF是参考电压,通常等于VCC。

STM32的ADC:

1、首先左边的ADC_IN为输入通道,还有两个内部输入通道温度传感器和Vrefint参考电压。

2、注入通道和规则通道,注入通道最多4通道,规则通道最多16通道。规则组可以转换16个通道,但是因为数据寄存器16位只有一个,所以后面来的通道数据会覆盖前一个的数据,通常结合DMA来使用,将数据挪到内存中。注入组可以同时存储4个通道数据,不用担心被覆盖。

3、左下角是触发转换信号。STM32触发ADC的信号有两种,软件触发(代码)和硬件触发。硬件触发主要来源于定时器触发,如TRGO主模式输出,用定时器每隔一段时间触发ADC转换,不用定时中断。也可以选择EXTI外部中断引脚触发。

4、

5、ADC时钟来自ADC预分频器。最大支持14MHz,所以分频器只能选6或者8。

6、模拟看门狗。可以存上下限阈值,如果该通道超过阈值,就会产生看门狗中断,通向NVIC的ADC中断。

7、EOC是规则组完成信号;JEOC是注入组完成信号;如果NVIC开启了相关通道,他们俩也能产生中断。

STM32F103C8的ADC:

ADC1和ADC2输入通道引脚相同,用于双ADC交叉模式,提高采样频率。STM32F103C8只有通道0到9

规则组的4种转换模式:

单次转换,非扫描模式:一次转一个通道,转换前修改通道,等待下一次触发。

连续转换,非扫描模式:一次转一个,但是一次转换后立刻开始下一次转换

单次转换、扫描模式:一次转多个通道,每次触发后等待下一次触发。转换完成后需要通过DMA及时保存寄存器中的结果。

连续转换、扫描模式:

还有一个间断扫描模式,可以每隔几个转换就暂停一次,需要等待触发再次转换。

触发控制:

规则组触发源:

注入组触发源:

数据对齐:

ADC为12位的,但是数据寄存器是16位的,存在数据对齐问题。一般采用右对齐,直接读取寄存器即可。左对齐用于可以读取寄存器只取高位,如高8位,舍弃低4位精度,12位ADC退化为8位ADC。

转换时间:

AD步骤采样、保持、量化和编码。由于AD转换需要一定时间,所以需要保持输入电压不变,通过一个采样开关控制,可以通过一个电容来实现,那这个采样并保持的过程就需要时间。如果提高ADCCLK提高了,ADC处于超频状态,但是稳定性不能保证。

校准:

ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差。建议在每次上电后执行一次校准。启动校准前, ADC必须处于关电状态超过至少两个ADC时钟周期。对于我们来说就是需要初始化后利用代码调用自校准。

二、AD单通道

cpp 复制代码
/*RCC库函数里*/
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);    // 配置ADCCLK分频器的,可以对APB2的72MHz时钟选择2、4、6、8分频,输入到ADCCLK

/*ADC库函数里*/
void ADC_DeInit(ADC_TypeDef* ADCx);        // 恢复缺省配置
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);   // 初始化
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);                // 结构体初始化
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);           // 用于给ADC上电,也就是开关控制
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);        // 用于开启DMA输出信号,若果使用DMA转运数据,就得调用这个函数
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);   // 中断输出控制,用于控制某个中断,能不能通往NVIC
/*下面4个函数是用于获取控制校准的函数、在ADC初始化完成之后,一次调用就可以了*/
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_软件开始转换控制,这个就是用于软件触发的函数了,调用一下,就能软件触发转换了
FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx); 
//ADC获取软件开始转换状态,给SWSTART位置1,以开始转换,这个函数一般不用
/*下面2个函数是用来配置ADC间断模式的*/
void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);//每隔几个通道间断一次
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);   //是不是启用间断模式
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
/*ADC规则组通道配置,很重要,作用就是给序列的每个位置填写指定的通道
ADC_Channel:要指定的通道;
Rank:序列几的位置;
ADC_SampleTime:指定通道的采样时间。
*/
void ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
//ADC外部触发转换控制,就是是否允许外部触发转换
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);  // ADC获取转换值,获取ADC转换的数据寄存器,读取转换结果就是要用这个函数
uint32_t ADC_GetDualModeConversionValue(void);//ADC获取双模式转换通道,这个是双ADC模式读取转换结果的函数,暂时不用
/*下面带Injected的函数都是对ADC注入组进行配置的*/
void ADC_AutoInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_InjectedDiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_ExternalTrigInjectedConvConfig(ADC_TypeDef* ADCx, uint32_t ADC_ExternalTrigInjecConv);
void ADC_ExternalTrigInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_SoftwareStartInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
FlagStatus ADC_GetSoftwareStartInjectedConvCmdStatus(ADC_TypeDef* ADCx);
void ADC_InjectedChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
void ADC_InjectedSequencerLengthConfig(ADC_TypeDef* ADCx, uint8_t Length);
void ADC_SetInjectedOffset(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel, uint16_t Offset);
uint16_t ADC_GetInjectedConversionValue(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel);
/*下面三个函数是对模拟看门狗进行配置的*/ 
void ADC_AnalogWatchdogCmd(ADC_TypeDef* ADCx, uint32_t ADC_AnalogWatchdog); //是否启动模拟看门狗
void ADC_AnalogWatchdogThresholdsConfig(ADC_TypeDef* ADCx, uint16_t HighThreshold, uint16_t LowThreshold);   //配置高低阈值
void ADC_AnalogWatchdogSingleChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel);  //配置看门的通道
void ADC_TempSensorVrefintCmd(FunctionalState NewState);//ADC温度传感器、内部参考电压控制,用来开启内部的两个通道的
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);   
//获取标志位状态,参数给EOC标志位,就可以判断EOC标志位是不是置1了,如果转换结束,EOC标志位置1
void ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);  //清除标志位
ITStatus ADC_GetITStatus(ADC_TypeDef* ADCx, uint16_t ADC_IT);    //获取中断状态
void ADC_ClearITPendingBit(ADC_TypeDef* ADCx, uint16_t ADC_IT);  //清除中断挂起位

三、AD多通道

江协科技是规则组一个一个转换,自己尝试改的4通道注入组的

cpp 复制代码
void AD_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);	//开启ADC1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	
	/*设置ADC时钟*/
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);						//选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA0引脚初始化为模拟输入
	
	ADC_InjectedSequencerLengthConfig(ADC1,4);
	ADC_InjectedChannelConfig(ADC1, ADC_Channel_0 , 1, ADC_SampleTime_55Cycles5);
	ADC_InjectedChannelConfig(ADC1, ADC_Channel_1 , 2, ADC_SampleTime_55Cycles5);
	ADC_InjectedChannelConfig(ADC1, ADC_Channel_2 , 3, ADC_SampleTime_55Cycles5);
	ADC_InjectedChannelConfig(ADC1, ADC_Channel_3 , 4, ADC_SampleTime_55Cycles5);
	
	/*ADC初始化*/
	ADC_InitTypeDef ADC_InitStructure;						//定义结构体变量
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;		//模式,选择独立模式,即单独使用ADC1
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//数据对齐,选择右对齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//外部触发,使用软件触发,不需要外部触发
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;		//连续转换,失能,每转换一次规则组序列后停止
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;			//扫描模式,失能,只转换规则组的序列1这一个位置
	ADC_InitStructure.ADC_NbrOfChannel = 4;					//通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
	ADC_Init(ADC1, &ADC_InitStructure);						//将结构体变量交给ADC_Init,配置ADC1
	
	/*ADC使能*/
	ADC_Cmd(ADC1, ENABLE);									//使能ADC1,ADC开始运行
	
	/*ADC校准*/
	ADC_ResetCalibration(ADC1);								//固定流程,内部有电路会自动执行校准
	while (ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while (ADC_GetCalibrationStatus(ADC1) == SET);
}

/**
  * 函    数:获取AD转换的值
  * 参    数:无
  * 返 回 值:AD转换的值,范围:0~4095
  */
void AD_GetValue(void)
{
	ADC_ExternalTrigInjectedConvConfig(ADC1,ADC_ExternalTrigInjecConv_None);
	ADC_SoftwareStartInjectedConvCmd(ADC1, ENABLE);					//软件触发AD转换一次
	while (ADC_GetFlagStatus(ADC1, ADC_FLAG_JEOC) == RESET);	//等待EOC标志位,即等待AD转换结束
	ADC_ClearFlag(ADC1,ADC_FLAG_JEOC);	
}


int main(void)
{
	uint16_t AD0,AD1,AD2,AD3;			//定义AD值变量
	float Voltage;				//定义电压变量
	OLED_Init();				//LED初始化
	AD_Init();					//PWM初始化
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "AD0:");
	OLED_ShowString(2, 1, "AD1:");
	OLED_ShowString(3, 1, "AD2:");
	OLED_ShowString(4, 1, "AD3:");
	int i;
	while(1)
	{
		for(i = 0;i<10;i++)
		{
			AD_GetValue();					//软件触发AD转换一次
			AD0 += ADC_GetInjectedConversionValue(ADC1,ADC_InjectedChannel_1);					//获取AD转换的值
			AD1 += ADC_GetInjectedConversionValue(ADC1,ADC_InjectedChannel_2);
			AD2 += ADC_GetInjectedConversionValue(ADC1,ADC_InjectedChannel_3);
			AD3 += ADC_GetInjectedConversionValue(ADC1,ADC_InjectedChannel_4);
		}
		OLED_ShowNum(1, 5, AD0/10, 4);				//显示通道0的转换结果AD0
		OLED_ShowNum(2, 5, AD1/10, 4);				//显示通道1的转换结果AD1
		OLED_ShowNum(3, 5, AD2/10, 4);				//显示通道2的转换结果AD2
		OLED_ShowNum(4, 5, AD3/10, 4);				//显示通道3的转换结果AD3
		AD0 = 0;
		AD1 = 0;
		AD2 = 0;
		AD3 = 0;
		sysDelayms(300);			//延时100ms,手动增加一些转换的间隔时间
	}

}

连续测量需要DMA,放在下一节

相关推荐
LinXunFeng7 天前
Obsidian - 使用 Share Note 分享笔记并自部署
前端·笔记·github
✎ ﹏梦醒͜ღ҉繁华落℘12 天前
单片机基础知识---stm32单片机的优先级
stm32·单片机·mongodb
闪闪发亮的小星星12 天前
高斯光以及高斯光公式解释
笔记
CNNACN电商经济12 天前
纸价波动加速中小产能出清,包装印刷板块龙头份额提升与议价能力重估
科技·生活
cqbzcsq12 天前
CellFlow虚拟细胞论文阅读
论文阅读·人工智能·笔记·学习·生物信息
牛根生同志12 天前
SPI数据收发的时候 TXE与RXNE标志位置位的时机
stm32·spi·transfer
绿算技术12 天前
Mooncake 与绿算ForinnBase GroundPool如何联手打破推理僵局?
科技·算法·架构
阿米亚波12 天前
【Windows】QEMU 启动 openEuler aarch64/arm64 架构系统 + 离线软件源
linux·windows·经验分享·笔记·架构·arm
自传.12 天前
尚硅谷 Vibe Coding|第三章(1) Claude Code深度使用与进阶技巧 学习笔记
笔记·学习·尚硅谷·vibecoding
nanoscientific12 天前
在芬顿耦合微纳米气泡系统中最大化利用界面处的Fe²⁺以实现有机污染物降解。
科技·微纳米气泡