韦东山IMX6ULL Pro开发板开箱:从零配置Ubuntu 18.04到点亮第一个LED灯(保姆级避坑指南)
2026/4/7 15:12:26
ADC(Analog-to-Digital Converter,模数转换器)是嵌入式系统中连接“模拟世界”与“数字世界”的核心桥梁,其核心作用是将连续变化的模拟信号(如电压、电流、温度、压力等)转换为离散的数字信号,供处理器进行计算、分析和控制。
在嵌入式产品中,ADC的典型应用场景包括:
没有ADC,嵌入式系统无法感知物理世界的连续变化,只能处理纯数字信号,失去与现实环境交互的能力。
嵌入式场景中ADC主要分为“片上集成ADC”(MCU内置)和“外置独立ADC”两类,以下是当前主流型号及特点:
| 型号/系列 | 核心参数 | 特点 |
|---|---|---|
| STM32F103系列 | 12位分辨率、1μs转换时间、最多18通道 | 性价比高,支持连续扫描/单次采样,适配入门级工业控制;无DMA时CPU占用高 |
| STM32F407系列 | 12位分辨率、0.4μs转换时间、DMA支持 | 高性能,支持双ADC同步采样,适配中高端工业控制、汽车电子 |
| STM32L4系列 | 12位/16位分辨率、低功耗 | 内置PGA(可编程增益放大器),适配低功耗物联网设备 |
| ESP32系列 | 12位SAR-ADC、18通道 | 集成WiFi/蓝牙,适配物联网轻量级采集场景,精度略低 |
| TI MSP430系列 | 12位分辨率、超低功耗 | 适配电池供电的便携式设备(如血糖仪、手持检测仪) |
| 型号 | 核心参数 | 特点 |
|---|---|---|
| ADS1115 | 16位分辨率、I2C接口 | 低功耗、高精度,适配医疗设备、高精度传感器采集 |
| AD7606 | 16位分辨率、8通道同步 | 多通道同步采样,适配电力系统、工业振动检测 |
| MAX11614 | 12位分辨率、SPI接口 | 小型封装、宽电压输入,适配便携式设备 |
| LTC2499 | 24位分辨率、ΔΣ型ADC | 超高精度,适配实验室级数据采集、精密仪器 |
嵌入式ADC驱动的核心目标是“稳定采集、精准转换、便捷调用”,无论片上还是外置ADC,软件配置均遵循以下通用流程,以STM32片上ADC(带DMA)为例:
将ADC硬件资源(外设、GPIO、DMA、时钟)与软件通道解耦,通过结构体/类封装,避免硬件细节渗透到应用层:
ADC_CH_0~5),作为应用层调用的唯一标识;时钟使能 → GPIO初始化 → DMA配置 → ADC参数配置 → ADC校准 → 启动采样核心是“规避脏数据”,通过DMA计数器定位当前采样位置:
已传输量 = 总缓存大小 - DMA剩余未传输数;采用C++类封装可实现“驱动层与应用层解耦”,驱动层负责硬件管理,应用层仅需调用接口,无需关注底层实现。
#ifndefADC_DRIVER_H#defineADC_DRIVER_H#include"stm32f10x.h"#include<cstdint>#include<cassert>// 采样缓存大小定义constuint16_tADC_SAMPLING_SIZE=10;// 软件通道ID(与硬件通道解耦)enumclassEAdcChannel:uint8_t{CH0=0,CH1,CH2,CH3,CH4,CH5,MAX};// ADC通道硬件属性结构体structAdcChannelConfig{GPIO_TypeDef*gpioPort;// GPIO端口uint16_tgpioPin;// GPIO引脚uint8_thardwareChannel;// 硬件ADC通道号uint8_tsampleTime;// 采样时间uint8_trank;// 转换优先级};// ADC驱动类(单例模式,适配单ADC外设)classAdcDriver{public:// 获取单例实例(保证全局唯一)staticAdcDriver&getInstance(){staticAdcDriver instance;returninstance;}// 禁用拷贝构造和赋值(单例)AdcDriver(constAdcDriver&)=delete;AdcDriver&operator=(constAdcDriver&)=delete;// 初始化:GPIO+DMA+ADCvoidinit();// 获取指定通道最新采样值uint16_tgetCurrentValue(EAdcChannel ch);// 获取指定通道的历史采样缓存(buff[0]最新)uint16_tgetBuffer(EAdcChannel ch,uint16_t*buff);private:// 私有构造函数(单例)AdcDriver();// 硬件资源配置ADC_TypeDef*adcPeriph;// ADC外设(如ADC1)uint32_tadcRcc;// ADC时钟DMA_Channel_TypeDef*dmaChannel;// DMA通道uint32_tdmaRcc;// DMA时钟AdcChannelConfig channelConfig[static_cast<uint8_t>(EAdcChannel::MAX)];// 通道配置uint16_tsampleBuffer[ADC_SAMPLING_SIZE][static_cast<uint8_t>(EAdcChannel::MAX)];// 采样缓存// 辅助函数:计算DMA当前采样位置uint16_tgetDmaCurrentIndex();};#endif// ADC_DRIVER_H#include"adc_driver.h"// 构造函数:初始化硬件映射关系AdcDriver::AdcDriver(){// 配置ADC1硬件资源adcPeriph=ADC1;adcRcc=RCC_APB2Periph_ADC1;dmaChannel=DMA1_Channel1;dmaRcc=RCC_AHBPeriph_DMA1;// 软件通道 → 硬件配置映射(STM32F103 ADC1_CH10~15对应GPIOC0~5)channelConfig[static_cast<uint8_t>(EAdcChannel::CH0)]={GPIOC,GPIO_Pin_0,ADC_Channel_10,ADC_SampleTime_55Cycles5,1};channelConfig[static_cast<uint8_t>(EAdcChannel::CH1)]={GPIOC,GPIO_Pin_1,ADC_Channel_11,ADC_SampleTime_55Cycles5,2};channelConfig[static_cast<uint8_t>(EAdcChannel::CH2)]={GPIOC,GPIO_Pin_2,ADC_Channel_12,ADC_SampleTime_55Cycles5,3};channelConfig[static_cast<uint8_t>(EAdcChannel::CH3)]={GPIOC,GPIO_Pin_3,ADC_Channel_13,ADC_SampleTime_55Cycles5,4};channelConfig[static_cast<uint8_t>(EAdcChannel::CH4)]={GPIOC,GPIO_Pin_4,ADC_Channel_14,ADC_SampleTime_55Cycles5,5};channelConfig[static_cast<uint8_t>(EAdcChannel::CH5)]={GPIOC,GPIO_Pin_5,ADC_Channel_15,ADC_SampleTime_55Cycles5,6};// 采样缓存初始化for(uint8_ti=0;i<ADC_SAMPLING_SIZE;i++){for(uint8_tj=0;j<static_cast<uint8_t>(EAdcChannel::MAX);j++){sampleBuffer[i][j]=0;}}}// 初始化函数:硬件配置+启动采样voidAdcDriver::init(){// 1. 使能时钟RCC_AHBPeriphClockCmd(dmaRcc,ENABLE);// DMA时钟RCC_APB2PeriphClockCmd(adcRcc,ENABLE);// ADC时钟// 2. 初始化GPIO(模拟输入)GPIO_InitTypeDef gpioInit;gpioInit.GPIO_Mode=GPIO_Mode_AIN;for(uint8_ti=0;i<static_cast<uint8_t>(EAdcChannel::MAX);i++){gpioInit.GPIO_Pin=channelConfig[i].gpioPin;GPIO_Init(channelConfig[i].gpioPort,&gpioInit);}// 3. 初始化DMA(环形模式)DMA_InitTypeDef dmaInit;DMA_DeInit(dmaChannel);dmaInit.DMA_PeripheralBaseAddr=reinterpret_cast<uint32_t>(&(adcPeriph->DR));// ADC数据寄存器dmaInit.DMA_MemoryBaseAddr=reinterpret_cast<uint32_t>(sampleBuffer);// 采样缓存dmaInit.DMA_DIR=DMA_DIR_PeripheralSRC;// 外设→内存dmaInit.DMA_BufferSize=ADC_SAMPLING_SIZE*static_cast<uint8_t>(EAdcChannel::MAX);// 总缓存大小dmaInit.DMA_PeripheralInc=DMA_PeripheralInc_Disable;// 外设地址不递增dmaInit.DMA_MemoryInc=DMA_MemoryInc_Enable;// 内存地址递增dmaInit.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;// 16位数据dmaInit.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;// 16位数据dmaInit.DMA_Mode=DMA_Mode_Circular;// 环形模式dmaInit.DMA_Priority=DMA_Priority_High;// 高优先级dmaInit.DMA_M2M=DMA_M2M_Disable;// 禁用内存到内存DMA_Init(dmaChannel,&dmaInit);DMA_Cmd(dmaChannel,ENABLE);// 4. 初始化ADCADC_InitTypeDef adcInit;adcInit.ADC_Mode=ADC_Mode_Independent;// 独立模式adcInit.ADC_ScanConvMode=ENABLE;// 扫描模式adcInit.ADC_ContinuousConvMode=ENABLE;// 连续转换adcInit.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;// 软件触发adcInit.ADC_DataAlign=ADC_DataAlign_Right;// 右对齐adcInit.ADC_NbrOfChannel=static_cast<uint8_t>(EAdcChannel::MAX);// 通道数ADC_Init(adcPeriph,&adcInit);// 5. 配置ADC时钟(PCLK2/8=9MHz)RCC_ADCCLKConfig(RCC_PCLK2_Div8);// 6. 配置规则组通道for(uint8_ti=0;i<static_cast<uint8_t>(EAdcChannel::MAX);i++){ADC_RegularChannelConfig(adcPeriph,channelConfig[i].hardwareChannel,channelConfig[i].rank,channelConfig[i].sampleTime);}// 7. ADC校准ADC_DMACmd(adcPeriph,ENABLE);// 使能DMA请求ADC_Cmd(adcPeriph,ENABLE);// 使能ADCADC_ResetCalibration(adcPeriph);// 复位校准while(ADC_GetResetCalibrationStatus(adcPeriph));// 等待复位完成ADC_StartCalibration(adcPeriph);// 启动校准while(ADC_GetCalibrationStatus(adcPeriph));// 等待校准完成// 8. 启动采样ADC_SoftwareStartConvCmd(adcPeriph,ENABLE);// 9. 等待第一轮采样完成,初始化缓存uint16_tdmaCnt=ADC_SAMPLING_SIZE*static_cast<uint8_t>(EAdcChannel::MAX);while((dmaCnt+static_cast<uint8_t>(EAdcChannel::MAX))>(ADC_SAMPLING_SIZE*static_cast<uint8_t>(EAdcChannel::MAX))){dmaCnt=DMA_GetCurrDataCounter(dmaChannel);}// 复制第一轮数据到所有轮次for(uint8_ti=1;i<ADC_SAMPLING_SIZE;i++){for(uint8_tj=0;j<static_cast<uint8_t>(EAdcChannel::MAX);j++){sampleBuffer[i][j]=sampleBuffer[0][j];}}}// 辅助函数:获取DMA当前采样索引uint16_tAdcDriver::getDmaCurrentIndex(){constuint16_tbuffSize=ADC_SAMPLING_SIZE*static_cast<uint8_t>(EAdcChannel::MAX);uint16_tdmaCnt=DMA_GetCurrDataCounter(dmaChannel);returnbuffSize-dmaCnt;// 换算为已传输索引}// 获取最新采样值uint16_tAdcDriver::getCurrentValue(EAdcChannel ch){// 校验通道合法性assert(ch<EAdcChannel::MAX&&"Invalid ADC channel!");uint8_tchIdx=static_cast<uint8_t>(ch);uint16_tdmaIdx=getDmaCurrentIndex();constuint16_tbuffSize=ADC_SAMPLING_SIZE*static_cast<uint8_t>(EAdcChannel::MAX);// 若当前通道未完成采样,回退到上一轮if(chIdx>=(dmaIdx%static_cast<uint8_t>(EAdcChannel::MAX))){dmaIdx=(dmaIdx+buffSize-static_cast<uint8_t>(EAdcChannel::MAX))%buffSize;}returnsampleBuffer[dmaIdx/static_cast<uint8_t>(EAdcChannel::MAX)][chIdx];}// 获取历史采样缓存uint16_tAdcDriver::getBuffer(EAdcChannel ch,uint16_t*buff){// 校验参数合法性assert(ch<EAdcChannel::MAX&&buff!=nullptr&&"Invalid ADC channel or buffer!");uint8_tchIdx=static_cast<uint8_t>(ch);uint16_tdmaIdx=getDmaCurrentIndex();constuint16_tbuffSize=ADC_SAMPLING_SIZE*static_cast<uint8_t>(EAdcChannel::MAX);// 若当前通道未完成采样,回退到上一轮if(chIdx>=(dmaIdx%static_cast<uint8_t>(EAdcChannel::MAX))){dmaIdx=(dmaIdx+buffSize-static_cast<uint8_t>(EAdcChannel::MAX))%buffSize;}// 填充缓存:buff[0]最新,buff[n-1]最旧for(uint16_ti=0;i<ADC_SAMPLING_SIZE;i++){buff[i]=sampleBuffer[dmaIdx/static_cast<uint8_t>(EAdcChannel::MAX)][chIdx];dmaIdx=(dmaIdx+buffSize-static_cast<uint8_t>(EAdcChannel::MAX))%buffSize;}returnADC_SAMPLING_SIZE;}#include"adc_driver.h"intmain(){// 1. 初始化ADC驱动(全局仅需调用一次)AdcDriver&adc=AdcDriver::getInstance();adc.init();// 2. 获取单个通道最新值(如采集电池电压)uint16_tbatteryVolt=adc.getCurrentValue(EAdcChannel::CH0);// 转换为实际电压:12位ADC,参考电压3.3V → 电压值 = (采样值/4095)*3.3floatvolt=(static_cast<float>(batteryVolt)/4095.0f)*3.3f;// 3. 获取通道历史缓存(用于滤波)uint16_ttempBuff[ADC_SAMPLING_SIZE];adc.getBuffer(EAdcChannel::CH1,tempBuff);// 计算均值滤波值uint32_tsum=0;for(uint16_ti=0;i<ADC_SAMPLING_SIZE;i++){sum+=tempBuff[i];}uint16_tavgTemp=sum/ADC_SAMPLING_SIZE;while(1){// 业务逻辑处理}}EAdcChannel枚举和类接口,无需关注GPIO、DMA、ADC硬件配置;channelConfig映射,应用层无需改动;assert/条件判断确保通道ID、缓存指针合法;ADC驱动是嵌入式系统感知物理世界的核心模块,其设计的核心是“解耦”与“可靠”:通过面向对象的封装实现硬件与应用的解耦,通过DMA环形采样+数据有效性校验保证采样的可靠性。
在工程实践中,需根据场景选择合适的ADC类型(片上/外置),平衡精度、速度、功耗需求;软件层面遵循“资源封装→硬件初始化→数据安全获取”的流程,同时兼顾精度优化与性能优化,最终实现“易用、稳定、可扩展”的ADC驱动。