尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

STM32F103 ADC 实战:三通道 DMA 循环采集与 Vrefint 校准全流程拆解

STM32F103 ADC 实战:三通道 DMA 循环采集与 Vrefint 校准全流程拆解 三路模拟量同时采DMA 循环不丢数Vrefint 实时校准供电波动——这是 STM32F103 外设实战第 5 篇拆解 ADC 多通道扫描 DMA 乒乓双缓冲 内部基准校准的完整链路。从时钟计算到避坑清单一篇讲透。为什么绕不开 ADC DMA做嵌入式迟早要跟模拟量打交道。电位器调参、传感器分压、电池电压监测ADC 把连续的模拟信号变成数字DMA 负责把数据搬进内存不让 CPU 干等。F103 的 ADC 是逐次逼近型SAR内部采样电容先抓住输入电压再用 12 位逐次比较逼近出数字结果。好处是转换快、无滞后代价是采样和量化都要时间——这段「时间账」是本文第一个重点后面会细算。多通道采集有个经典痛点转换完没来得及读下一次结果就把寄存器覆盖了。靠轮询读寄存器通道一多就手忙脚乱。用 DMA 循环模式 乒乓双缓冲数据由硬件搬走CPU 只处理自己那半块缓冲区。本篇案例PA0/PA1/PA2 接三路电位器分压ADC1 扫描连续模式 DMA1_Channel1 循环模式Vrefint 内部基准常驻第四通道实时校准供电波动。硬件与引脚先看清信号从哪来ADC1 的通道 0~7 对应 PA0~PA7固定映射改不了。本篇用 PA0/PA1/PA2 对应 ADC1_IN0/IN1/IN2。关键一步引脚必须配成模拟输入。// 时钟ADC1 挂 APB2和 GPIOA 一起开RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1|RCC_APB2Periph_GPIOA,ENABLE);// GPIO三路全配模拟输入GPIO_InitStructure.GPIO_PinGPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2;GPIO_InitStructure.GPIO_ModeGPIO_Mode_AIN;// 模拟输入不是浮空输入GPIO_Init(GPIOA,GPIO_InitStructure);很多新手栽在这里默认浮空输入也能读到值但数值会飘。模拟输入模式下引脚内部的施密特触发器被断开采样更干净。接线也别大意。电位器三根脚两端接 3.3V 和 GND中间抽头接 PA0。抽头输出的信号源阻抗随旋钮位置变化调到中间时阻抗最大最容易暴露采样时间不足的问题——这算是这个案例的小彩蛋。另外模拟输入引脚内部没有上下拉外面也别加加了会直接改变分压点读数系统性偏大或偏小。还有一个容易被忽略的点信号源阻抗。ADC 采样靠内部采样电容充电信号源阻抗越高电容充满越慢。传感器输出阻抗到 10kΩ 级别1.5 周期采样时间读数会偏低要拉到 7.5 或 13.5 周期。这直接影响下一步的采样时间选择。时钟与转换时间12MHz 怎么来的ADC 挂在 APB2 总线上PCLK2 是 72MHz。但 ADC 内部时钟最大只能到14MHz必须分频。RCC_ADCCLKConfig(RCC_PCLK2_Div6);// 72MHz / 6 12MHz为什么 6 分频而不是 4 分频4 分频是 18MHz超限。6 分频 12MHz 是 72MHz 下最接近上限的安全值。转换时间的公式是死的Tconv 采样时间 12.5 个 ADC 周期12.5 是逐次逼近型 ADC 的固定量化周期改不了。采样时间有 8 档可选1.5 / 7.5 / 13.5 / 28.5 / 41.5 / 55.5 / 71.5 / 239.5 周期每通道独立设置。采样时间怎么选有两条经验线信号源阻抗低、信号变化快用短档1.5/7.5阻抗高、信号慢温度、电位器、电池电压用长档。内部通道温度传感器、Vrefint最特殊要求采样时间 ≥17µs直接上最大档 239.5 周期。还有一个常识性下限采样时间至少要比信号源充放电时间常数大几倍否则采的是没充满的电压读数稳定但偏低。以 12MHz、1.5 周期采样为例Tconv (1.5 12.5) / 12MHz ≈ 1.17µs 最大持续采样率 12M / 14 ≈ 857kSPS多通道时一轮扫描总时间等于各通道 Tconv 之和。三个信号通道都配 1.5 周期每轮 3.5µs对电位器、温度这类慢速信号绰绰有余。初始化五步走完整流程初始化分五步时钟 → GPIO → ADC 工作模式 → 通道顺序 → 校准。// ADC 工作模式扫描 连续4 个通道3 信号 VrefintADC_InitStructure.ADC_ModeADC_Mode_Independent;ADC_InitStructure.ADC_ScanConvModeENABLE;// 多通道必须开ADC_InitStructure.ADC_ContinuousConvModeENABLE;// 连续转换ADC_InitStructure.ADC_ExternalTrigConvADC_ExternalTrigConv_None;// 软件触发ADC_InitStructure.ADC_DataAlignADC_DataAlign_Right;ADC_InitStructure.ADC_NbrOfChannel4;ADC_Init(ADC1,ADC_InitStructure);// 通道顺序Rank 越小越先转ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_1Cycles5);ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_1Cycles5);ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_1Cycles5);ADC_RegularChannelConfig(ADC1,ADC_Channel_17,4,ADC_SampleTime_239Cycles5);// Vrefint 内部基准ADC_Cmd(ADC1,ENABLE);// 校准必须在 ADC 上电后进行ADC_ResetCalibration(ADC1);while(ADC_GetResetCalibrationStatus(ADC1));ADC_StartCalibration(ADC1);while(ADC_GetCalibrationStatus(ADC1));两个易错点漏掉ScanConvMode结果只转第一个通道后面白配Rank顺序写错数据对不上号。另外注意校准要放在ADC_Cmd上电之后文档写的是「上电后至少等 2 个 ADCCLK 再校准」顺序反了校准无效。触发方式这里用的是软件触发ADC_ExternalTrigConv_NoneADC_SoftwareStartConvCmd。启动后连续转换自动跑起来DMA 跟着搬数据主程序完全不用管。如果你需要严格定时的采样比如音频、振动再把ADC_ExternalTrigConv配成定时器触发转换节奏由 TIM 决定——这是后话本篇先跑通软件触发这条线。Vrefint 用 239.5 周期采样不是浪费——内部基准和温度传感器都要求采样时间足够长内部通道建议 ≥17µs12MHz 下 239.5 周期约 20µs刚好满足。DMA 乒乓数据不丢的核心多通道扫描 连续转换数据产生很快。DMA 把ADC1-DR的结果搬到内存缓冲区但单缓冲有个问题CPU 处理缓冲区期间DMA 还在往里面写处理慢了新数据就覆盖旧数据。乒乓双缓冲的思路缓冲区拆成两半DMA 写前半段时 CPU 处理后半段DMA 写后半段时 CPU 处理前半段靠半传输中断HT和传输完成中断TC切换。uint16_tadc_buf[4*2];// 4 通道 × 2 轮乒乓两半DMA_InitStructure.DMA_PeripheralBaseAddr(uint32_t)ADC1-DR;DMA_InitStructure.DMA_MemoryBaseAddr(uint32_t)adc_buf;DMA_InitStructure.DMA_DIRDMA_DIR_PeripheralSRC;DMA_InitStructure.DMA_BufferSize8;DMA_InitStructure.DMA_PeripheralIncDMA_PeripheralInc_Disable;// 外设地址固定DMA_InitStructure.DMA_MemoryIncDMA_MemoryInc_Enable;DMA_InitStructure.DMA_PeripheralDataSizeDMA_PeripheralDataSize_HalfWord;DMA_InitStructure.DMA_MemoryDataSizeDMA_MemoryDataSize_HalfWord;DMA_InitStructure.DMA_ModeDMA_Mode_Circular;// 循环写满自动回绕DMA_Init(DMA1_Channel1,DMA_InitStructure);DMA_ITConfig(DMA1_Channel1,DMA_IT_HT|DMA_IT_TC,ENABLE);DMA_Cmd(DMA1_Channel1,ENABLE);ADC_DMACmd(ADC1,ENABLE);ADC_SoftwareStartConvCmd(ADC1,ENABLE);中断里判断是 HT 还是 TC各处理一半voidDMA1_Channel1_IRQHandler(void){if(DMA_GetITStatus(DMA1_IT_HT1)){process_round(adc_buf[0]);// 前半段第 1 轮 4 个结果DMA_ClearITPendingBit(DMA1_IT_HT1);}if(DMA_GetITStatus(DMA1_IT_TC1)){process_round(adc_buf[4]);// 后半段第 2 轮 4 个结果DMA_ClearITPendingBit(DMA1_IT_TC1);}}Circular 模式下 DMA 写满自动回绕HT/TC 交替触发DMA 写一半、CPU 处理另一半两边永远不撞车。ADC 挂 DMA1_Channel1这是硬件固定映射ADC2 没有 DMAADC3 走 DMA2_Channel5别记混。DMA_BufferSize 8就是一次完整循环的传输次数4 通道 × 2 轮。HT 在传完 4 次时触发TC 在传完 8 次时触发正好把两轮分界点卡死。想改乒乓粒度就调这个数——比如要更小的处理块BufferSize 保持 8、每轮独立处理 4 个也行中断函数里按需切片即可。处理函数process_round里只做换算和累加重活滤波、显示、存储放主循环中断里别拖太久。数据处理从原始值到真实电压DMA 缓冲区里是 12 位右对齐的原始值换算公式V ADC值 × VREF / 4095假设 VREF 3.3VADC 值 2048对应约 1.65V。但这里有个隐患VREF 真的等于 3.3V 吗稳压器有误差PCB 走线有压降电池供电时电压还会掉。用固定的 3.3V 去算VDD 掉到 3.0V 时读数误差高达 10%。这就是 Vrefint 要解决的问题。voidprocess_round(uint16_t*p){// p[0..2] 三路信号p[3] Vrefint 原始值floatvdd1.20f*4095.0f/p[3];// 用内部基准反推实际 VDDfloatv0p[0]*vdd/4095.0f;// 用真实 VDD 换算误差小一个数量级floatv1p[1]*vdd/4095.0f;floatv2p[2]*vdd/4095.0f;// v0/v1/v2 就是补偿后的真实电压可用作滑动平均输入}软件平均滤波也顺带做上连续几轮的值取平均白噪声能压下去不少。DMA 缓冲区轮转天然适合做滑动平均——维护一个全局累加器每来一轮结果加新值减旧值除以窗口长度就是均值staticint32_tacc[3];// 三路各自的累加器staticuint8_tcount;acc[0]v0;acc[1]v1;acc[2]v2;if(count8){// 每 8 轮输出一次均值floatm0acc[0]/8.0f;count0;acc[0]acc[1]acc[2]0;}窗口越大越平滑但滞后也越明显。信号慢、噪声大的场景用 8~16 轮响应要求高的场景宁可用轻一点的滤波也别让延迟拖了后腿。Vrefint 校准供电波动不用怕STM32F103 内部有个 1.20V 基准电压Vrefint挂在 ADC1_CH17。它的值不受 VDD 影响所以用它反推实际 VDDVDD_actual 1200mV × 4095 / ADC_VREFINT上面process_round里就是这么用的每轮扫描都带一个 Vrefint 原始值供电漂了也能实时跟住。48 脚封装的 Vref 内部直接连 VDDA没有外部参考引脚这种板子尤其依赖 Vrefint 校准。什么时候必须上校准电池供电、LDO 压差大、或者要求跨批次数据可比的项目一个都不能少。插 USB 用电脑供电时电压稳如老狗校准看不出差别一上电池就现原形。提前把校准逻辑埋进去后面少改一版代码。想更精确还有一招部分型号出厂时在闪存里存了校准值HD 大容量在 0x1FFFF7BAMD 中容量在 0x1FFFF7EA是 3.3V、30°C 下实测的 Vrefint ADC 值。读出来替换理论的 1200mV能抹掉芯片间差异。顺带一提CH16 是内部温度传感器和 Vrefint 共用同一个开关ADC_TempSensorVrefintCmd(ENABLE);// 开 Vrefint 和温度传感器25°C 时输出约 1.43V斜率约 4.3mV/°CT(°C) (1.43 − Vsense) / 0.0043 25做粗略的温度监测够用指望它当温度计精度会让你失望。避坑清单六个最常见的坑1. 忘配 GPIO_Mode_AIN。默认浮空输入能读但值飘必须显式配模拟输入。2. ADCCLK 超 14MHz。72MHz 主频用 6 分频12MHz别用 4 分频18MHz 超限。3. 多通道忘开 ScanConvMode。只转第一个通道后面的白配。Rank 顺序写错数据错位。4. DMA 数据宽度不匹配。外设 16 位、内存 halfword、缓冲区用 u16 数组。配成 32 位会错位。5. 循环模式丢数据。处理跟不上就用乒乓双缓冲HT TC 中断别只用 TC 一个中断那一半缓冲区会被覆盖。6. Vrefint 当摆设。VDD 掉到 3.0V 误差 10%用 CH17 实时校准。温度传感器采样时间必须 239.5 周期。写在最后ADC DMA Vrefint 这套组合是 STM32 模拟量采集的主流打法。DMA 乒乓解决数据不丢Vrefint 解决电压不准缺一个采集数据都不够可信。代码都在上面照着搭一遍拿万用表量一下实际电压对比你会对这套链路有更深的体感。你在实际项目中踩过 ADC 采集的什么坑评论区聊聊说不定你的经验能帮到下一个开发者。推荐阅读[STM32F103 CAN 实战双机通信与 TJA1050 全流程拆解]总线通信本系列上一篇[STM32F103 SPI 实战W25Q64 Flash 读写全流程拆解]外设寄存器操作
返回列表