什么是 ADC?为什么 MCU 需要 ADC?
答:ADC就是模拟数字转换器,是STM32芯片上的一个外设;因为外界信号并不只有高低电平两种状态,而是相对连续的介于二者之间的值,但是普通引脚输入只有高低电平,所以MCU需要ADC感知这些介于二者之间的值,将从引脚输入的信号转换成连续的数字。
概念:模拟电压信号,离散数字量,采样,量化,数字编码,数字值
纠正:ADC(Analog-to-Digital Converter)是模拟数字转换器,是 MCU 中用于采集模拟信号的外设。现实中的很多传感器输出的是连续变化的模拟电压,例如温度、光照、压力等,而 GPIO 普通数字输入只能识别高低电平,无法获取具体电压值。因此 MCU 需要通过 ADC 对模拟输入电压进行采样和量化,将其转换为离散的数字量,供 CPU 进行处理。
ADC 的分辨率是什么意思?12 位 ADC 为什么能够表示 0~4095 的数字值?
答:ADC的分辨率就是ADC对模拟电压信号进行采样加量化后的离散数字量的变化范围,ADC的分辨率越高,对模拟电压信号的感知越精确。2的12次方刚好等于4096,所以可以表示4096个数字,所以可以表示0到4095的数字值。
概念:量化等级范围,ADC的分辨率,ADC 位数
纠正:ADC 分辨率表示 ADC 对输入模拟电压的最小分辨能力,通常由 ADC 位数决定。位数越高,量化等级越多,对输入电压的划分越细,能够检测到更小的电压变化。12 位 ADC 表示输出数字量有 12 位,因此共有 $2^{12}=4096$ 个量化等级,对应数字范围为 0~4095。
ADC 的参考电压 Vref、分辨率和 ADC 转换结果之间有什么关系?如何根据 ADC 数值计算实际输入电压?
答:ADC对模拟电压信号的转化结果就是离散数字量,分辨率=3.3V/(量化等级范围(由ADC位数决定)),ADC 的参考电压 Vref=分辨率*ADC 转换结果,ADC 的参考电压 Vref就是实际输入电压的估计值。
概念:参考电压Verf,量化等级范围,ADC 位数,ADC转换结果
纠正:ADC 的参考电压 Vref 决定 ADC 的输入测量范围,例如 Vref=3.3V 时,ADC 可以测量 0~3.3V 的输入电压。ADC 位数决定量化等级,例如 12 位 ADC 有 4096个等级。ADC 转换结果表示输入电压在这些等级中的位置,分辨率=参考电压Verf/(量化等级范围-1),因此实际输入电压可以通过 ADC转换结果*ADC分辨率 计算得到。
ADC 采样时间是什么意思?为什么采样时间不能设置得太短?
答:ADC采样时间,是指ADC对模拟电压信号采样使用了多长时间,如果ADC采样时间过短,那么因噪声的干扰,导致这段时间内电压偏高,那么ADC转换结果就会偏高,不符合实际。
概念:采样保持电路,输入电压, 采样电容,保存电压,ADC量化转换,输出阻抗
纠正:ADC采样时间表示 ADC 对输入模拟信号进行采集的时间。在采样阶段,ADC内部的采样电容需要通过输入信号进行充电,使电容电压接近输入电压。如果采样时间过短,采样电容无法充分充电,导致采样电压与实际输入电压存在偏差,从而造成 ADC 转换结果误差。因此,对于输入阻抗较大的信号,需要设置更长的采样时间。
ADC 转换完成后,CPU 如何知道转换结束?ADC 查询、中断和 DMA 三种方式有什么区别?
答:ADC转化完成后,可以通过给模拟看门狗针对AD数据寄存器设置一个检测范围,这样当ADC转换结果数据超出一定范围后就会发出中断请求到NVIC,再到CPU(好吧,这个我真不知道)。ADC查询和DMA这两种方式不需要CPU频繁参与数据读取,相比于中断更加节省CPU资源;DMA方式一次性可以获取多个值。
概念:EOC标志位置1,EOC
纠正:ADC转换完成后,通常通过转换完成标志EOC通知CPU。ADC查询方式中,CPU不断读取EOC标志位,等待转换完成后读取数据;ADC中断方式中,ADC转换完成后产生中断请求,通过NVIC通知CPU执行中断服务函数读取数据;DMA方式中,ADC转换后的数据由DMA自动搬运到内存,CPU无需参与每次数据读取,只需要在数据达到一定数量后进行处理。三种方式中,查询方式CPU占用最高,中断方式适合低频采样,DMA适合大量连续数据采集。
讲讲ADC独立模式
答:独立模式本质上是两个片上的ADC具有独立关系;在独立模式下ADC可以选择不同的触发源,也能使用不同的DMA通道读取数据。
概念:输入通道,触发方式,转换流程
纠正:ADC 独立模式是指 MCU 中多个 ADC 外设之间相互独立工作,每个 ADC 可以单独配置自己的输入通道、触发方式和转换流程,不需要与其他 ADC 同步。例如 ADC1 和 ADC2 可以分别采集不同传感器的数据,各自完成采样、转换和数据读取。相比之下,双 ADC 模式会让多个 ADC 按照一定方式协同工作,用于提高采样速度或者实现同步采样。
左对齐和右对齐区别
答:左对齐就是从左往右依次往寄存器中填入数字,右对齐就是从右往左依次往寄存器中填入数字,前者的值会比后者大。
纠正:左对齐和右对齐是ADC 转换结果在数据寄存器中的存储位置。右对齐时,ADC结果存放在低位,高位补0,读取后可以直接作为ADC数值使用;左对齐时,ADC结果存放在高位,低位补0,读取时通常需要进行移位处理。两种方式不会改变ADC转换结果,只影响数据读取方式。
通道号、Rank、采样时间三者关系
答:通道号就是输入通道的编号,Rank意思是排号?,采样时间影响估计模拟电压信号的值的准确度;三者的关系是不同通道具有不同的Rank值,Rank值越高采样时间越长,对模拟电压信号的值的估计越准确。
概念:ADC 多通道转换序列中的位置编号(Rank)
纠正:ADC通道号表示 ADC 输入的模拟通道,用于指定采集哪个 GPIO 上的模拟信号;Rank 表示多个通道转换时的先后顺序,ADC 会按照 Rank 从小到大的顺序依次转换;采样时间表示 ADC 对某个通道进行采样的持续时间,影响采样电容充电是否充分,从而影响转换精度。三者关系是:ADC 根据 Rank 确定转换顺序,每次转换时选择对应 Channel,并按照该 Channel 配置的采样时间完成采样。
采样时间为什么影响转换结果
答:因为当模拟电压信号输入到ADC时,首先会给采样电容充电,然后才会将模拟电压信号转换为离散数字量,如果采样时间过短,那么采样电容充电不充分,则转换结果将偏低。
ADC为什么需要校准
答:ADC的输入通道是GPIO引脚的功能复用,由于GPIO引脚所处的物理环境,导致引脚带有电荷,不是理想的电势,所以需要校准。
纠正:ADC需要校准是因为ADC内部模拟电路存在非理想误差,例如零点偏移和增益误差,导致转换结果与实际输入电压存在偏差。校准过程可以让ADC测量并补偿自身误差,提高转换结果的准确性。在STM32中,通常在ADC初始化后、开始转换前执行校准操作。
如何使用 STM32 的 ADC 读取一个模拟传感器电压?请描述完整的软件实现流程。
答:梳理事件链:模拟电压信号输入到GPIOA引脚;ADC输入通道对模型电压信号采样,量化;ADC将设置EOC标志;CPU读取并判断EOC标志;CPU读取ADC数据寄存器中的值。
代码实现流程,这里先使用查询方式读取数据:
启动GPIOA与ADC外设的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
初始化GPIOA,配置GPIOA为模拟输入模式
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; // 比如PA0 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入!重点 // 模拟输入模式下:GPIO_Speed 不生效,可以随便写 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct);// 3. 调用初始化函数
初始化ADC,配置ADC工作模式{独立模式,双ADC模式},数据对齐方式{左对齐,右对齐},通道配置{ADC通道号,Rank顺序,采样时间},ADC校准
ADC_InitTypeDef ADC_InitStruct; ADC_InitStruct.ADC_ScanConvMode = DISABLE; ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStruct.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStruct);
通道配置{ADC通道号,Rank顺序,采样时间}
// 函数原型 void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime); 参数: ADCx:ADC1 / ADC2 ADC_Channel`:通道号,如 ADC_Channel_0(PA0) Rank`:1~16,规则序列顺序 ADC_SampleTim:采样时间,例如 `ADC_SampleTime_55Cycles5
设置ADC时钟频率
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
使能ADC外设
ADC_Cmd(ADC1, ENABLE);
ADC校准
ADC_ResetCalibration(ADC1); // 复位校准 while(ADC_GetResetCalibrationStatus(ADC1)); // 等待复位校准完成 ADC_StartCalibration(ADC1); // 启动校准 while(ADC_GetCalibrationStatus(ADC1)); // 等待校准完成
启动ADC转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
CPU读取并判断EOC标志,在合适条件下CPU读取ADC数据寄存器中的值。
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint16_t ADC_FLAG); // 等待EOC,直到转换完成 while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); // 读取ADC数据 uint16_t adc_val = ADC_GetConversionValue(ADC1); // 清除EOC标志(F1:读DR之后硬件自动清除,也可以手动清) ADC_ClearFlag(ADC1, ADC_FLAG_EOC);
E:代码一直卡在了while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); 为什么没有设置EOC标志位置为1?
流程:模拟电压信号输入到GPIOA引脚;ADC输入通道对模型电压信号采样,量化;ADC将设置EOC标志;CPU读取并判断EOC标志;CPU读取ADC数据寄存器中的值。(这个流程相对来说有点问题)
1.引脚GPIOA_pin0接在了ADC的通道0上,这没问题;
所以问题出现在ADC输入通道对模型电压信号采样,量化;ADC将设置EOC标志这个步骤,这个步骤对应于初始化ADC外设。
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
代表用软件触发,依靠ADC_SoftwareStartConvCmd()函数启动转换,不用定时器、外部引脚来触发 ADC。
2.初始化ADC也排除了错误,并在检测ADC_FLAG_EOC,开启了软件触发
其实这时候我应该问ADC初始化有几个步骤或者我是否忽略了与初始化ADC同级的某些步骤(初始化ADC,设置 ADC 模块工作的时钟频率,使能ADC外设),然后依次排查这些步骤