资讯动态

STM32 ADC从入门到精通:原理、配置与多通道DMA采集实战

发布时间:2026/8/28 6:39:58 来源:尧图企业网站定制
1. 项目概述从模拟世界到数字世界的桥梁如果你正在用STM32做一个温湿度监测、电池电压检测或者音频信号采集的项目那么ADC模数转换器这个外设就是你绕不开的核心。很多新手朋友一听到“ADC”、“采样率”、“分辨率”这些词就有点发怵觉得这是模拟电路工程师才需要深究的东西。其实不然在嵌入式开发中ADC是我们与真实物理世界交互最直接的窗口。你想想传感器输出的电压信号、麦克风采集的音频波形、电位器调节的电压值这些都是连续的模拟量。而我们的STM32微控制器是个数字世界的居民它只认识0和1。ADC的作用就是在这两个世界之间担任翻译官把连续的电压信号转换成微控制器能理解和处理的数字值。我刚开始接触STM32的ADC时也走过不少弯路。比如采回来的数据跳得厉害根本没法用或者配置了半天DMA就是传不过来数据又或者采样速度总是不对达不到项目要求。这些问题大多不是因为芯片不好而是对ADC这个外设的工作机制理解不够透彻配置时忽略了某些关键细节。标准外设库Standard Peripheral Library虽然封装了底层寄存器操作让我们能用更直观的函数来配置但它也像一把双刃剑。用好了事半功倍用不好你甚至不知道问题出在库函数调用上还是硬件连接上。所以这一章我们不打算一上来就贴大段代码。那样做你只是照猫画虎知其然不知其所以然。我们要做的是先把STM32 ADC的“脾气秉性”摸清楚。它有哪些工作模式精度和速度如何权衡触发转换的“扳机”有哪些只有把这些基础原理吃透了你再去看库函数里的那些参数比如ADC_InitTypeDef结构体里的ADC_ContinuousConvMode、ADC_ExternalTrigConv才会恍然大悟“哦原来这个参数是干这个用的” 接下来我们就从最核心的概念开始一步步拆解STM32的ADC。2. ADC核心概念与STM32的实现解析2.1 分辨率、量程与参考电压ADC的“尺子”你可以把ADC想象成一把用来测量电压的“数字尺子”。这把尺子有几个关键属性决定了它的测量能力。首先是分辨率也就是这把尺子的刻度有多密。STM32F1系列最常见的ADC是12位的。什么是12位这意味着这把尺子有 2^12 4096 个刻度。它能输出的数字值范围是0到4095或者0到4096取决于你怎么看通常说0-4095。如果你的测量电压范围是0-3.3V那么理论上每个数字刻度1 LSB代表的电压值是 3.3V / 4096 ≈ 0.000806V也就是0.8mV。分辨率越高这把尺子就越精密能区分更微小的电压变化。但要注意高分辨率不等于高精度精度还受参考电压稳定性、内部噪声等多种因素影响。这就引出了第二个关键量程与参考电压。ADC测量的是输入引脚ADCx_INy上的电压相对于它的参考电压的比例。STM32的ADC通常有多个参考电压引脚VREF和VREF-在部分型号上独立引出以及VDDA和VSSA。对于大多数通用型号我们通常将VREF连接到VDDA模拟电源通常3.3VVREF-连接到VSSA模拟地0V。那么ADC的量程就是0V到VDDA例如3.3V。当输入电压为0V时ADC输出0当输入电压等于VDDA时ADC输出满量程值4095。重要提示VDDA的供电质量直接影响ADC的测量精度。务必确保VDDA电源干净、稳定。在设计PCB时VDDA引脚附近必须放置一个10uF的钽电容和一个0.1uF的陶瓷电容进行去耦并且最好通过磁珠与数字电源VDD隔离。这是很多初学者采样值跳动大的首要原因。2.2 采样、保持与转换ADC的“工作流水线”ADC不是瞬间完成转换的它遵循一个经典的流程采样-保持-量化-编码。STM32内部已经为我们集成了这个流水线。采样内部的一个采样开关闭合让外部信号给内部的一个小电容采样保持电容充电。这个时间必须足够长让电容上的电压能跟上外部信号的变化。这个时间就是采样时间在STM32中可以通过寄存器ADC_SMPR1和ADC_SMPR2来配置每个通道可以独立设置。采样时间设置得太短电容还没充到实际的电压值开关就断开了会导致测量误差设置得太长又会影响总的转换速度。对于信号源内阻较大的情况比如用一个大电阻分压必须延长采样时间。保持采样开关断开。此时外部信号的变化不再影响那个小电容电容上的电压被“保持”住成为一个稳定的值供后续电路进行转换。这就好比摄影师按下快门瞬间定格了一幅画面。量化与编码这是ADC核心的“算数”部分将保持住的模拟电压值与内部的一系列基准电压进行比较通过逐次逼近寄存器SAR逻辑最终产生对应的12位数字代码。这个过程需要一定数量的ADC时钟周期对于12位分辨率STM32通常需要12.5或13.5个ADC时钟周期。总转换时间的计算公式至关重要Tconv 采样时间 12.5个周期。例如如果ADC时钟ADCCLK配置为12MHz采样时间设置为239.5个周期这是STM32F1支持的最长时间那么总转换时间 Tconv (239.5 12.5) / 12MHz ≈ 21us。这意味着这个ADC通道每秒最多能进行约47,600次转换。理解这个计算你才能根据项目需求信号频率、精度要求合理配置时钟和采样时间。2.3 触发源与转换模式谁来按下“开始”按钮ADC不会自己无缘无故开始转换需要有一个“触发信号”来告诉它“开始干活”。STM32提供了丰富的触发源软件触发最简单直接在程序中调用库函数ADC_SoftwareStartConvCmd(ADCx, ENABLE)来启动一次转换。适合手动、单次采集的场景。外部引脚触发可以通过特定的GPIO引脚如EXTI线上的上升沿或下降沿来触发ADC。常用于需要与外部事件严格同步的场合。定时器触发这是最强大、最常用的自动触发方式。你可以配置一个定时器如TIM2、TIM3、TIM4的某个事件更新事件、比较匹配事件、捕获事件来产生周期性的触发信号。这是实现固定采样率、进行信号处理如音频采集的黄金标准。例如用TIM2的更新事件每1ms触发一次ADC你就得到了一个1kHz的采样率。确定了“扳机”后还要选择开火模式单次转换模式触发一次只对一个选定的通道进行一次转换然后ADC就停止等待下一次触发。连续转换模式一旦启动ADC就会马不停蹄地、一个接一个地对选定的通道进行转换转换完立即开始下一次形成连续流。通常需要配合DMA来搬运数据否则数据会被覆盖。扫描模式当你有多个通道需要测量时比如同时测温度、电压、光强可以启用扫描模式。ADC会按照你预先设置好的通道顺序ADC_RegularChannelConfig自动依次对每个通道进行转换。扫描模式可以和单次/连续模式组合使用。2.4 STM32F1 ADC的特性概览以最经典的STM32F103系列为例其ADC模块的主要特性包括12位分辨率支持左对齐或右对齐的数据寄存器。多达18个复用通道包括16个外部GPIO引脚ADCx_IN0~IN15和2个内部信号源温度传感器、内部参考电压VREFINT。这2个内部通道非常有用温度传感器可以用于监测芯片结温VREFINT可以用于计算实际的VDDA电压实现“测电压”的自我校准。单次或连续转换模式可配置的扫描模式。多种触发源软件触发、定时器触发、外部引脚触发。模拟看门狗可以设置一个电压阈值窗口当转换结果高于上限或低于下限时会产生中断。非常适合用于电池电压监控只在电压异常时才通知CPU。DMA支持这是实现高效数据流的关键。在连续转换或多通道扫描时必须启用DMA让ADC转换完成的数据自动搬运到内存数组中从而解放CPU。3. 标准外设库驱动框架与关键结构体3.1 初始化结构体ADC的“身份证”标准外设库通过ADC_InitTypeDef这个结构体来统一配置ADC的工作模式。理解每个成员的含义是正确配置的前提。typedef struct { uint32_t ADC_Mode; // ADC工作模式独立/双ADC模式 FunctionalState ADC_ScanConvMode; // 扫描模式使能ENABLE/DISABLE FunctionalState ADC_ContinuousConvMode; // 连续转换模式使能 uint32_t ADC_ExternalTrigConv; // 外部触发选择 uint32_t ADC_DataAlign; // 数据对齐方式左对齐/右对齐 uint8_t ADC_NbrOfChannel; // 规则通道组中要转换的通道数目 } ADC_InitTypeDef;ADC_Mode对于单个ADC就选ADC_Mode_Independent独立模式。F1还有双ADC模式同步、交替等用于提高采样率或实现相位差采集初期可以先不管。ADC_ScanConvMode如果你需要按顺序转换多个通道比如IN0, IN1, IN5就必须设为ENABLE。如果只用一个通道可以设为DISABLE。ADC_ContinuousConvMode如果需要ADC不停地自动转换就设为ENABLE通常配合DMA。如果是单次触发转换一次就设为DISABLE。ADC_ExternalTrigConv这是配置触发源的关键。如果你用软件触发就选ADC_ExternalTrigConv_None。如果用定时器2的通道2事件触发就选ADC_ExternalTrigConv_T2_CC2。这里一定要和你硬件上连接的定时器事件对应上。ADC_DataAlign通常选ADC_DataAlign_Right右对齐。这样读取到的12位数据就在一个16位变量的低12位处理起来直观。左对齐有时用于快速判断电压范围。ADC_NbrOfChannel这个参数不是手动填写的它在你后续调用ADC_RegularChannelConfig函数配置通道顺序时库函数内部会自动管理。你只需要在初始化时给它赋一个值比如1但最终以实际配置的通道数为准。有些教程这里会让人困惑。3.2 通道配置与采样时间初始化完ADC主体后还需要告诉ADC具体转换哪个或哪些通道以及每个通道用多长的采样时间。void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime)ADC_Channel指定具体的通道例如ADC_Channel_0对应GPIO PA0。Rank规则通道组的转换顺序1~16。Rank为1的通道最先转换。在扫描模式下ADC会从Rank1转换到RankN。ADC_SampleTime为该通道设置采样时间周期数。可选1.5, 7.5, 13.5, 28.5, 41.5, 55.5, 71.5, 239.5个周期。时间越长采样越充分抗噪声能力越强但转换速度越慢。对于高阻抗信号源必须选择较长的采样时间如239.5。3.3 校准消除系统误差ADC内部电路存在微小的偏移和增益误差。因此在上电初始化后、开始第一次转换前必须执行校准流程。这是一个固定的操作序列ADC_ResetCalibration(ADC1); // 重置校准寄存器 while(ADC_GetResetCalibrationStatus(ADC1)); // 等待重置完成 ADC_StartCalibration(ADC1); // 开始校准 while(ADC_GetCalibrationStatus(ADC1)); // 等待校准完成切记每次ADC上电或从低功耗模式唤醒后都需要重新校准。不校准可能导致转换结果存在固定的偏差。4. 单通道单次转换实战以电位器电压读取为例理论说得再多不如动手调一遍。我们来实现一个最基础的场景用ADC1的通道0PA0引脚读取一个电位器的电压并通过串口打印出来。采用软件触发、单次转换模式。4.1 硬件连接与GPIO配置首先准备一个10kΩ的电位器两端分别接3.3V和GND中间滑动端接STM32的PA0引脚。PA0是ADC1_IN0的复用功能。在代码中首先要初始化对应的GPIO为模拟输入模式。这一点非常重要如果配置成浮空输入或其他模式会导致采样不准确。// 1. 开启GPIOA和ADC1的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); // 2. 配置PA0为模拟输入 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; // 模拟输入模式 GPIO_Init(GPIOA, GPIO_InitStructure);4.2 ADC初始化与校准接下来按照我们前面分析的步骤配置ADC1。// 3. 初始化ADC1 ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_Mode ADC_Mode_Independent; // 独立模式 ADC_InitStructure.ADC_ScanConvMode DISABLE; // 单通道禁用扫描 ADC_InitStructure.ADC_ContinuousConvMode DISABLE; // 单次转换模式 ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; // 软件触发 ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; // 数据右对齐 ADC_InitStructure.ADC_NbrOfChannel 1; // 转换通道数为1 ADC_Init(ADC1, ADC_InitStructure); // 4. 配置ADC1通道0的转换顺序和采样时间 // 参数ADC1, 通道0, 转换顺序Rank为1, 采样时间239.5周期保证充分采样 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); // 5. 使能ADC1 ADC_Cmd(ADC1, ENABLE); // 6. 执行ADC校准必须步骤 ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1));4.3 软件触发与数据读取初始化完成后我们就可以在需要的时候启动转换并读取结果了。通常我们会封装一个Get_Adc函数。uint16_t Get_Adc(uint8_t ch) { // 设置指定通道的转换顺序单通道时Rank总是1 ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5); // 软件启动转换 ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 等待转换结束标志位EOC置位 while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); // 清除EOC标志读取DR寄存器会自动清除但显式清除是好习惯 ADC_ClearFlag(ADC1, ADC_FLAG_EOC); // 返回转换结果 return ADC_GetConversionValue(ADC1); }在主循环中我们可以周期性地调用这个函数并将得到的12位原始值转换为电压值。int main(void) { // ... 初始化系统时钟、GPIO、ADC、串口等 ... USART_Config(); // 假设已配置好串口用于打印 while(1) { uint16_t adc_value; float voltage; adc_value Get_Adc(ADC_Channel_0); // 读取原始值 voltage (float)adc_value / 4096 * 3.3; // 计算电压假设VDDA3.3V printf(ADC Value: %d, Voltage: %.3f V\r\n, adc_value, voltage); Delay_ms(500); // 延时500ms避免打印太快 } }烧录程序旋转电位器你应该能在串口助手上看到变化的ADC值和计算出的电压值。恭喜你完成了与模拟世界的一次成功对话5. 多通道扫描与DMA传输实战高效采集传感器数据单次转换模式适合不频繁的采样。但在实际项目中我们经常需要同时监测多个传感器比如一个智能花盆需要同时测土壤湿度、环境温度、光照强度并且需要以固定的频率持续采样。这时多通道扫描模式 连续转换模式 DMA就是最佳组合拳。5.1 场景与配置思路假设我们需要以100Hz的频率每10ms一次同时采集三个传感器通道0PA0土壤湿度传感器模拟电压输出通道1PA1NTC热敏电阻温度传感器通过分压电路通道4PA4光照传感器模拟电压输出我们希望ADC每10ms自动完成这三个通道的转换并将数据自动存放到一个数组中完全不需要CPU干预。等存够一组数据比如100组CPU再一次性处理。这需要ADC配置为扫描模式、连续转换模式。使用定时器触发例如TIM3精确产生10ms的触发信号。启用DMA让ADC转换完成的数据自动搬运到内存。5.2 DMA配置详解DMA直接存储器访问是STM32的“数据搬运工”可以在外设和内存之间直接传输数据不占用CPU。对于ADC多通道扫描我们配置DMA从ADC的DR数据寄存器搬运到我们定义的一个数组。// 定义存储ADC值的数组。数组大小要能容纳所有通道的一次扫描。 #define ADC_CHANNEL_NUM 3 uint16_t ADC_ConvertedValue[ADC_CHANNEL_NUM]; // 存放通道0,1,4的值 // 1. 开启DMA1时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 2. 配置DMA1通道1ADC1固定使用DMA1通道1 DMA_InitTypeDef DMA_InitStructure; DMA_DeInit(DMA1_Channel1); // 复位通道1配置 DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)(ADC1-DR); // 外设地址ADC1数据寄存器 DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)ADC_ConvertedValue; // 内存地址我们的数组 DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; // 传输方向从外设到内存 DMA_InitStructure.DMA_BufferSize ADC_CHANNEL_NUM; // 缓冲区大小即一次传输的数据量3个 DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; // 外设地址固定 DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; // 内存地址递增存到数组的不同位置 DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; // 外设数据宽度16位半字 DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; // 内存数据宽度16位 DMA_InitStructure.DMA_Mode DMA_Mode_Circular; // 循环模式传输完自动从头开始实现连续搬运 DMA_InitStructure.DMA_Priority DMA_Priority_High; // 优先级高 DMA_InitStructure.DMA_M2M DMA_M2M_Disable; // 禁止内存到内存模式 DMA_Init(DMA1_Channel1, DMA_InitStructure); // 3. 使能DMA通道 DMA_Cmd(DMA1_Channel1, ENABLE);关键点解析DMA_Mode_Circular循环模式这是实现连续自动采集的核心。当DMA传输完BufferSize3个数据后会自动将MemoryBaseAddr和BufferSize重置为初始值然后等待下一次ADC转换完成继续搬运。这样就形成了一个“环形缓冲区”ADC的数据流可以源源不断地注入数组。DMA_MemoryInc_Enable必须使能。这样第一次转换的数据存到ADC_ConvertedValue[0]第二次存到[1]第三次存到[2]符合我们的预期。5.3 ADC与定时器联动配置接下来配置ADC工作在扫描、连续、定时器触发模式。// 4. 配置定时器3产生10ms更新事件作为ADC触发源 // 假设系统时钟72MHz定时器预分频7200-1则计数器时钟为10kHz // 自动重装载值设为100-1则产生100Hz10ms的更新事件 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); TIM_TimeBaseStructure.TIM_Period 100 - 1; TIM_TimeBaseStructure.TIM_Prescaler 7200 - 1; TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, TIM_TimeBaseStructure); TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update); // 设置更新事件为触发输出 TIM_Cmd(TIM3, ENABLE); // 启动定时器 // 5. 配置ADC1 ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode ENABLE; // 使能扫描模式 ADC_InitStructure.ADC_ContinuousConvMode ENABLE; // 使能连续转换 ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_T3_TRGO; // 定时器3触发 ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel ADC_CHANNEL_NUM; // 这里填3 ADC_Init(ADC1, ADC_InitStructure); // 6. 配置ADC1的规则通道组顺序0 - 1 - 4 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 3, ADC_SampleTime_55Cycles5); // 7. 使能ADC1的DMA请求 ADC_DMACmd(ADC1, ENABLE); // 8. 使能ADC1 ADC_Cmd(ADC1, ENABLE); // 9. 校准ADC1 ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); // 10. 由定时器触发开始转换对于外部触发需要先软件启动一次 ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 注意在使能了外部触发且为连续模式后第一次可以用软件启动之后将由定时器自动触发。配置完成后整个系统就开始自动运行了TIM3每10ms产生一个触发信号ADC1收到信号后依次快速转换通道0、1、4每转换完一个数据DMA就立刻将其搬运到ADC_ConvertedValue数组的对应位置。由于是循环模式这个搬运过程会一直持续。此时你的主循环while(1)里几乎不需要做任何事情只需要定期比如每秒去读取ADC_ConvertedValue数组里的最新值进行处理即可。CPU占用率极低。6. 常见问题、调试技巧与进阶优化6.1 采样值不稳定跳动大这是ADC调试中最常见的问题。别急着怀疑代码按以下顺序排查硬件和软件电源与地这是首要怀疑对象。用示波器测量VDDA和VSSA引脚看纹波是否过大应小于50mV。确保模拟部分和数字部分用电感或磁珠隔离并在VDDA引脚最近处放置10uF钽电容0.1uF陶瓷电容。参考电压如果VREF是独立引脚确保其连接了稳定、干净的参考电压源。如果是与VDDA相连则VDDA的质量就是参考电压的质量。信号源阻抗ADC输入引脚内部可以等效为一个采样开关和一个小电容。在采样期间需要信号源在采样时间内对该电容充电到稳定值。如果信号源阻抗太高比如用了几百kΩ的电阻分压充电就会很慢导致采样不充分。解决方法a) 在ADC输入引脚对地加一个100pF~10nF的小电容注意这会形成一个低通滤波器可能影响高频信号。b) 显著增加该通道的采样时间设置为239.5个周期。c) 在分压电路后加一个电压跟随器运放进行缓冲提供低阻抗输出。采样时间不足如第3点所述对于高阻抗源必须增加ADC_SampleTime。用公式算一下充电时间是否足够。PCB布局与走线模拟信号线应远离数字信号线尤其是时钟、PWM线最好用地线隔离。避免在ADC输入引脚附近走高频数字线。软件滤波硬件无法完全消除噪声时软件滤波是最后的手段。最简单的是一阶滞后滤波低通滤波new_value old_value * 0.9 sample * 0.1。更复杂的有中值滤波、滑动平均滤波等。6.2 DMA传输数据错位或溢出现象数组里的数据顺序不对或者数据全是0或固定值。检查DMA缓冲区大小DMA_BufferSize必须等于你希望DMA一次传输的数据量。对于多通道扫描这等于通道数N。DMA会在传输完N个数据后才产生传输完成中断如果使能了的话或重置指针循环模式。检查内存地址递增DMA_MemoryInc必须使能并且DMA_MemoryDataSize要与数组元素类型匹配uint16_t对应HalfWord。检查ADC的DMA是否使能务必调用ADC_DMACmd(ADCx, ENABLE)。检查ADC是否真的在转换可以用ADC_GetFlagStatus(ADCx, ADC_FLAG_EOC)在循环里检查EOC标志或者用逻辑分析仪/示波器查看ADC的转换完成信号引脚如果有引出。DMA通道冲突确保没有其他外设使用了同一个DMA通道。STM32的DMA通道是多个外设复用的。6.3 转换速度达不到预期计算的理论采样率很高但实际测量发现间隔时间很长。计算总转换时间务必用公式Tconv (采样周期 12.5) / ADCCLK计算单个通道的转换时间。多通道扫描时一次扫描的总时间是Tscan N * Tconv。你的触发间隔定时器周期必须大于等于Tscan否则ADC还在忙新的触发会丢失。检查ADCCLKSTM32F1的ADC时钟最大为14MHz且来自APB2时钟最大72MHz的分频。确保RCC_ADCCLKConfig(RCC_PCLK2_Div6)这样的分频配置正确使得ADCCLK不超过14MHz。连续模式下的数据覆盖在连续转换扫描模式下如果不用DMA你必须以快于Tscan的速度读取ADC_DR寄存器否则数据会被新转换的数据覆盖。这就是为什么这种模式必须搭配DMA。6.4 使用内部参考电压校准VDDA这是一个高级技巧能显著提高测量精度。STM32内部有一个出厂时校准过的参考电压VREFINT约1.2V。在芯片温度变化时VDDA可能会波动但VREFINT相对于VDDA的比例是稳定的。我们可以通过测量VREFINT通道的ADC值反向推算出实际的VDDA电压。在初始化时除了测量外部通道也把内部参考电压通道ADC_Channel_Vrefint加入扫描序列或者单独测量一次。读取VREFINT的ADC原始值记为AdcVrefint。从芯片数据手册或参考手册中找到VREFINT的标准电压值Vrefint_cal通常在1.20V~1.25V之间这个值在芯片生产时被校准并存储在系统存储区的一个特定地址如0x1FFFF7BA。计算实际VDDAVdda_actual Vrefint_cal * 4096 / AdcVrefint。用计算出的Vdda_actual去计算其他外部通道的真实电压Vchannel_actual AdcChannel * Vdda_actual / 4096。这种方法可以有效补偿电源电压波动带来的测量误差特别适合电池供电等电压不稳定的场景。6.5 注入通道的使用我们前面用的都是“规则通道”。STM32还有一组“注入通道”你可以把它理解为一个可以“插队”的高优先级通道组。当注入通道被触发时可以是外部事件如过压信号它会立即中断当前正在进行的规则通道转换优先执行注入通道的转换完成后再回到规则通道。这在处理紧急信号时非常有用。标准外设库也提供了ADC_InjectedChannelConfig等函数来配置注入通道其使用方式与规则通道类似但有独立的数据寄存器(JDRx)和触发源。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价