STM32学习笔记05-ADC模数转换器

目录

ADC简介

逐次逼近型ADC

ADC框图

ADC基本结构

输入通道

转换模式

单次转换非扫描模式

连续转换非扫描模式

单次转换扫描模式

连续转换扫描模式

触发控制

数据对齐

转换时间

校准

硬件电路

AD应用

AD单通道

AD多通道 


ADC简介

  • ADC(Analog-Digital Converter)模拟-数字转换器
  • ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁
  • 12位逐次逼近型ADC,1us转换时间
  • 输入电压范围:0~3.3V,转换结果范围:0~4095
  • 18个输入通道,可测量16个外部和2个内部信号源
  • 规则组和注入组两个转换单元
  • 模拟看门狗自动监测输入电压范围
  • STM32F103C8T6 ADC资源:ADC1、ADC2,10个外部输入通道

逐次逼近型ADC

1398981369eb48b8948142473ab66258.png

ADC框图

eabba9b6bb3843d39a0a73390a84e13c.png

ADC基本结构

938672a6bde74bfd988417ddf9f18112.png

输入通道

f04fa81641a443b8a73dbd7db2ee5902.png

只有IN0-IN9总共10个通道,使用这个芯片只能有10个外部输入通道。ADC12_IN0的意思是ADC1和ADC2的IN0到在PA0上。

e2e0fd4633aa460392b80b90840b46d4.png

转换模式

在我们ADC初始化的结构体里,会有两个参数,一个是选择单次转换还是连续转换的,另一个是选择扫描模式还是非扫描模式的 ,这两个参数组合起来有下面四种转换方式。

单次转换非扫描模式

 522623f576144ef997488f069f896467.png

连续转换非扫描模式

 db5abccaaf4949c6bff71c7d530c2129.png

单次转换扫描模式

 6a95eb9ed0814323a243c9208418b747.png

连续转换扫描模式

 12906af9a54249eaac8189ed5dfef723.png

在扫描模式的情况下,还可以有一种模式,叫间断模式,它的作用是在扫描过程中每隔几个转换就暂停一次,需要再次触发才能继续,了解即可。

触发控制

0b470c75dbfe41e9b070e432a78a929f.png

数据对齐

我们这个ADC是12位的他的转换结果就是一个12位的数据,但是这个数据寄存器是16位的,所以就存在一个数据对齐的问题。我们一般使用右对齐。这样读取这个16位寄存器直接就是转换结果。如果选择左对齐直接读取的话得到的数据会比实际的大,因为数据左对齐就是把数据左移了4位,相当于把结果乘16了。那要这个左对齐有什么用呢?这个用途就是如果你不想要这么高的分辨率,你觉得0-4095太大了,我们就做一个简单的判断,不需要这么高的分辨率,那就可以选择左对齐,然后再把这个数据的高8位取出来,这样就舍弃了后面四位的精度,这个12位的ADC就退化成8位的ADC了,不过我们一般不用的话选择又对齐就好了。

ebb0c8abd7f448d7917b4a5fd70ed557.png

转换时间

转换时间这个参数一般不太敏感,因为一般AD转换都很快,如果不需要非常高速的转换频率,那转换时间就可以忽略了。前文提到AD转换是需要一小段时间的,就像厨子做菜一样也是需要等一会才能上菜的,那AD转换的时候都有哪些步骤需要花时间呢?

  • AD转换的步骤:采样,保持,量化,编码

其中采样保存可以放在一起,量化编码可以放在一起。量化编码就是我们之前讲的ADC逐次逼近的过程,这个是需要花时间的,一般位数越多花的时间越长。采样保持是因为我们的AD转换,就是后面的量化编码需要一小段时间,如果在这一小段时间里输入电压还在不断变化那就无法定位输入电压到底在哪了,所以在量化编码之前我们需要设置一个采样开关,先打开采样开关收集一下外部电压,比如可以用一个小容量电容存储一下这个电压存储好之后断开采样开关,再进行后面的AD转换,这样在量化编码期间电压始终保持不变这样才能精确定位未知电压的位置。那采样保持的过程需要闭合采样开关,过一段时间再断开,这里就会产生一个采样时间。所以有以下:

  • STM32 ADC的总转换时间为:TCONV = 采样时间 + 12.5个ADC周期

采样时间是采样保持花费的时间,这个可以在程序中进行配置,采样时间越大越能避免一些毛刺信号的干扰,不过转换时间也会相应延长。12.5个ADC周期是量化编码花费的时间,因为是12位的ADC,所以需要12个周期,这里多了半个周期可能是做其他东西花费的时间。ADC周期就是从RCC分频过来的ADCCLK,这个ADCCLK最大是14MHz。

  1. 例如:当ADCCLK=14MHz,采样时间为1.5个ADC周期
  2.   TCONV = 1.5 + 12.5 = 14个ADC周期 = 1μs(在14MHz ADCCLK的情况下)

校准

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

校准过程是固定的只需要在ADC初始化的最后加几条代码就行了。至于怎么计算怎么校准的我们不需要管。所以这个了解一下就行。

硬件电路

看第二个电路,是传感器输出电压的电路光敏电阻、热敏电阻、红外接收管、麦克风都可以等效为一个可变电阻,电阻阻值无法直接测量,所以这里可以通过和一个固定阻值的电阻串联分压来得到一个反应电阻值电压的电路。传感器阻值变小时,下拉作用变强,输出端电压就下降,传感器阻值变大时,下拉作用变弱,输出端受上拉电阻的作用电压就会升高。

第三个电路是简单的电压转换电路。比如想测一个0-5V的VIN电压,但是ADC只能接收0-3.3V的电压,那就可以搭建一个这样的简易转换电路,在这里还是使用电阻分压,上面阻值17K,下面阻值33K,加一起是50K,所以根据分压公式中间的电压就是VIN/50K*33K,最后得到的电压范围就是0-3.3V了,就可以进行ADC转换了。

f229d41ddf814d478042598319ce6d19.png

AD应用

AD单通道

d4c5924042b44dd3ae205ffcc8064334.png

建立AD模块,在AD.c的AD_Init()函数进行AD初始化。初始化步骤根据ADC基本结构图:

48468c521f524722890a9298fd944592.png

开启ADC之后,我们需要进行校准,这样可以减少误差。那在ADC工作的时候,如果想要软件触发转换,那会有函数可以触发,如果想读取转换结果,那也会有函数可以读取结果。

接下来介绍ADC相关库函数:

/*
* 配置ADC时钟(ADCCLK)。可以对APB2的72MHz时钟选择2、4、6、8分频输出到ADCCLK
* 参数 RCC_PCLK2:定义ADC时钟分频器。
* 返回值:无
*/
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);
void DAC_DeInit(void);//恢复缺省配置
void DAC_Init(uint32_t DAC_Channel, DAC_InitTypeDef* DAC_InitStruct);//初始化
void DAC_StructInit(DAC_InitTypeDef* DAC_InitStruct);//结构体初始化
/*
* 启用或禁用指定的ADC外设。
* 参数1 ADCx:其中x可为1、2或3,选择ADC外设。
* 参数2 NewState: ADCx外设的新状态。取值为:ENABLE或DISABLE。
* 返回值:无
*/
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
/*
* 启用或禁用指定的ADC DMA请求。
* 参数1 ADCx:其中x可为1或3,选择ADC外设。注意:ADC2没有DMA功能。
* 参数2 NewState:所选ADC DMA传输的新状态。取值为:ENABLE或DISABLE。
* 返回值:无
*/
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);
/*
* 启用或禁用指定的ADC中断。控制某个中断能不能通往NVIC
* 参数1 ADCx:其中x可为1、2或3,选择ADC外设。
* 参数2 ADC_IT:开启或关闭ADC中断源。
* 参数3 NewState:指定ADC中断的新状态。取值为:ENABLE或DISABLE。
* 返回值:无
*/
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);
/* 用于控制校准的函数,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);//获取开始校准状态
/*
* 启用或禁用所选ADC软件触发转换。
* 参数1 ADCx:其中x可为1、2或3,选择ADC外设。
* 参数2 NewState:所选ADC软件启动转换的新状态。取值包括:ENABLE或DISABLE。
* 返回值:无
*/
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
/*
* 检查是否设置了指定的ADC标志。判断是否转换结束
* 参数 ADCx:其中x可为1、2或3,选择ADC外设。
* ADC_FLAG:要检查的标志位。
* 返回值:ADC_FLAG的新状态(SET或RESET)。
*/
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
/*
* ADC规则组通道配置。
* 参数1 ADCx:其中x可为1、2或3,选择ADC外设。
* 参数2 ADC_Channel:要配置的ADC通道。
* 参数3 Rank:序列几的位置。取值范围为1 ~ 16。
* 参数4 ADC_SampleTime:为所选通道设置的采样时间值。
* 返回值:无
*/
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
/*
* 使能或失能通过外部触发器ADCx转换。
* 参数1 ADCx:其中x可为1、2或3,选择ADC外设。
* 参数2 NewState:所选ADC外部触发转换开始的新状态。取值包括:ENABLE或DISABLE。
* 返回值:无
*/
void ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
/*
* 返回常规通道的最后一次ADCx转换结果数据。
* 参数 ADCx:其中x可为1、2或3,选择ADC外设。
*返回值:转换的数据值。
*/
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);

第一步,RCC开启时钟。ADC都是APB2上的设备所以使用APB2外设时钟控制函数。然后我们还使用了PA0口,所以再开启GPIOA时钟。接着不要忘了ADCCLK的配置。我们选择6分频,分频之后ADCCLK=72MHz/6 = 12MHz。

/* 第一步开启RCC开启时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//配置ADCCLK

第二步,配置GPIO。选择模拟输入模式。我们使用PA0,所以初始化GPIO_Pin_0。

/* 第二步配置GPIO */
GPIO_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,第二个参数指定通道我们选择通道0,第三个参数规则组序列器里的次序,必须在1-16,对应规则组的16个序列。我们目前只有PA0一个通道,使用的是非扫描模式,使用指定的通道在序列1.第四个参数指定通道采样时间,根据需求来,需要快的转换就选小的参数,需要更稳定的转换就选大的参数,如果对速度和稳定性都没什么要求那随便选就可以了,我们就随便选个555吧。这时的采样时间就是55.5个ADCCLK的周期。

/* 第三步选择规则组输入通道 */
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);

目前我们的配置是在规则组菜单列表的第一个位置写入通道0这个通道。

第四步,用结构体初始化ADC。利用ADC_Init()函数。第一个参数给ADC1,第二个参数是结构体,它有6个成员:

  • ADC_ModeADC,工作模式(独立模式还是双ADC模式)
  • ADC_DataAlign,数据对齐(左对齐、右对齐)
  • ADC_ExternalTrigConv,外部触发转换选择,就是触发控制的触发源
  • ADC_ContinuousConvMode,连续转换模式(选择连续转换还是单次转换)
  • ADC_ScanConvMode,扫描转换模式(选择扫描模式还是非扫描模式)
  • ADC_NbrOfChannel,通道数目,指定在扫描模式下总共会用到几个通道

模式我们选择独立模式;数据对齐方式根据前文我们选右对齐;本节代码我们使用软件触发,外部触发源我们选择None,不使用外部触发也就是使用内部软件触发;我们使用单次转换非扫描模式;通道数为1。

/* 第四步配置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;//通道数目,指定在扫描模式下总共会用到几个通道
ADC_Init(ADC1,&ADC_InitStructure);

到这里ADC整体结构就配置完成了。之后,中断和模拟看门狗你如果需要的话可以再继续配置。我们就暂时不使用了。

第五步,开启ADC电源,并校准。

/* 第五步开启ADC并进行校准 */
ADC_Cmd(ADC1,ENABLE);
	
ADC_ResetCalibration(ADC1);//复位校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET);//等待返回复位校准的状态
ADC_StartCalibration(ADC1);//开启校准
while (ADC_GetCalibrationStatus(ADC1) == SET);//等待校准完成

这样ADC就处于准备就绪的状态了,我们想启动转换获取结果,就再写一个函数,流程是:首先软件触发转换,然后等待转换完成,也就是等待EOC标志位置1,最后读取ADC寄存器的值。在ADC_GetFlagStatus()里选择规则组转换完成标志位ADC_FLAG_EOC。当标志位为0表示转换未完成,为1表示转换完成,使用EOC标志位==RESET时表示为完成转换,执行空循环等待,转换完成后由硬件自动置1跳出循环。

uint16_t AD_GetValue(void)
{
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//触发转换
	while (ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//等待转换完成
	return ADC_GetConversionValue(ADC1);//获取AD转换的结果,自动清除标志位我们不需要手动清除
}

完整代码:

AD.c:

#include "stm32f10x.h"                  // Device header

void AD_Init(void)
{
	/* 第一步开启RCC开启时钟 */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//配置ADCCLK
	
	/* 第二步配置GPIO */
	GPIO_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转换器 */
	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;//通道数目,指定在扫描模式下总共会用到几个通道
	ADC_Init(ADC1,&ADC_InitStructure);
	
	/* 第五步开启ADC并进行校准 */
	ADC_Cmd(ADC1,ENABLE);
	
	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);//触发转换
	while (ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//等待转换完成
	return ADC_GetConversionValue(ADC1);//获取AD转换的结果,自动清除标志位我们不需要手动清除
}

main.c:

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

uint16_t ADValue;
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();
		Voltage = (float)ADValue / 4095 * 3.3;
		OLED_ShowNum(1,9,ADValue,4);
		OLED_ShowNum(2,9,Voltage,1);
		OLED_ShowNum(2,11,(uint16_t)(Voltage*100)%100,2);
		
		Delay_ms(100);
	}
}

AD多通道 

我们使用四个AD通道,分别是电位器、光敏传感器、热敏传感器、反射式红外传感器。使用了PA0、PA1、PA2、PA3。

82802e35c07e471f8a8d24900e6b8b4f.png

在上一个代码基础上,多通道我们可以用扫描模式利用列表把四个通道填进去,然后触发转换就能多通道了,这样确实不错的方法但是还是那个数据覆盖的问题,如果想要用扫描模式实现多通道最好配合DMA实现,我们下节再试扫描模式。我们可以使用单通道非扫描实现多通道,只需要在每次触发转换之前手动改一下列表第一个位置的通道就行了。比如第一次转换先写入通道0,之后触发,等待,读值,第二次转换再先把通道改成通道1,之后触发、等待、读值,第三次再先把通道改成通道2等等。

我们先把填充通道的代码改到触发转换之前,我们想指定的通道可以提取成AD_Channel参数,在调用AD_GetValue时只需要指定一个转换通道返回值就是我们指定通道的结果了。

uint16_t AD_GetValue( uint8_t ADC_Channel)
{
	/* 第三步选择规则组输入通道 */
	ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);
	
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//触发转换
	while (ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//等待转换完成
	return ADC_GetConversionValue(ADC1);//获取AD转换的结果
}

我们要指定通道0、1、2、3所以GPIO初始化也要加,完整代码:

AD.c:

#include "stm32f10x.h"                  // Device header

void AD_Init(void)
{
	/* 第一步开启RCC开启时钟 */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//配置ADCCLK
	
	/* 第二步配置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);
	

	
	/* 第四步配置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;//通道数目,指定在扫描模式下总共会用到几个通道
	ADC_Init(ADC1,&ADC_InitStructure);
	
	/* 第五步开启ADC并进行校准 */
	ADC_Cmd(ADC1,ENABLE);
	
	ADC_ResetCalibration(ADC1);//复位校准
	while (ADC_GetResetCalibrationStatus(ADC1) == SET);//等待返回复位校准的状态
	ADC_StartCalibration(ADC1);//开启校准
	while (ADC_GetCalibrationStatus(ADC1) == SET);//等待校准完成
	
}



uint16_t AD_GetValue( uint8_t ADC_Channel)
{
	/* 第三步选择规则组输入通道 */
	ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);
	
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//触发转换
	while (ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//等待转换完成
	return ADC_GetConversionValue(ADC1);//获取AD转换的结果
}

main.c:

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

uint16_t ADValue;
uint16_t AD0,AD1,AD2,AD3;

int main(void)
{
	OLED_Init();
	AD_Init();
	
	OLED_ShowString(1,1,"AD0:");
	OLED_ShowString(2,1,"AD1:");
	OLED_ShowString(3,1,"AD2:");
	OLED_ShowString(4,1,"AD3:");

	while(1)
	{
        //依次启动4次转换,并且在转换之前指定了转换的通道
		//每次转换完成之后把结果分别存在4个数据里,最后显示
		AD0 = AD_GetValue(ADC_Channel_0);
		AD1 = AD_GetValue(ADC_Channel_1);
		AD2 = AD_GetValue(ADC_Channel_2);
		AD3 = AD_GetValue(ADC_Channel_3);
		
		OLED_ShowNum(1,5,AD0,4);
		OLED_ShowNum(2,5,AD1,4);
		OLED_ShowNum(3,5,AD2,4);
		OLED_ShowNum(4,5,AD3,4);
		
		Delay_ms(100);
	}
}

AD转换的扫描模式和更高级的玩法请看下一篇吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值