4. STM32微控制器与ADC外设:选型、框图、时钟与采样时间
各位同学,咱们今天聊聊STM32和它的ADC。说实话,做嵌入式血压计,ADC就是咱们的“耳朵”和“眼睛”。传感器传来的微弱脉搏信号,全靠它来听、来看。选错了芯片,或者配置没弄对,后面滤波算法写得再漂亮也是白搭。
我这些年经手的血压计方案,从早期的8位单片机一路做到现在的Cortex-M4。嗯,踩过的坑确实不少。今天就把这些经验掰开揉碎了讲给你听。
4.1 STM32系列怎么选?
先说说选型。很多新手一上来就问:“哪个型号最好?”其实没有最好的,只有最合适的。血压计对MCU的要求,我总结下来就三条:
- ADC精度要够:至少12位,最好有硬件过采样
- 功耗要低:便携设备,电池供电,你懂的
- 价格要亲民:量产的东西,成本是硬道理
我个人习惯,入门级血压计用STM32F103系列。这颗芯片太经典了,资料多、价格便宜、开发板遍地都是。它的ADC是12位的,采样率最高1MHz,做血压计绰绰有余。
如果要做高端一点的,比如带蓝牙、带波形显示,我会选STM32L4系列。为什么?因为它有硬件过采样,能把12位ADC“模拟”成16位用。我在一个项目中试过,信噪比直接提升了10个dB。这可不是小数目。
重要提醒:别盲目追求高主频。血压计是慢速信号处理,主频高了反而增加功耗和噪声。F103的72MHz,足够了。
4.2 ADC外设框图——你得看懂这张图
好,芯片选好了,接下来看ADC内部长什么样。STM32的ADC框图,说白了就是一条“信号流水线”。
从左边开始:模拟输入通道。F103有18个通道,其中16个是外部引脚。你想想看,血压计一般用1-2个通道就够了,一个测压力传感器,一个测温度补偿。
然后进入采样保持电路。这里有个电容,叫采样电容。它的作用是在极短的时间内把输入电压“存”下来。我刚开始做的时候,总觉得这个电容无所谓,直到有一次发现采样值总是偏小……查了半天,原来是采样时间不够,电容没充满。
接着是逐次逼近比较器。这是ADC的核心,它把模拟电压和内部DAC产生的参考电压做比较,逐位逼近,最终输出数字量。这个过程很快,但需要时钟驱动。
最后是数据寄存器。转换结果就存在这里,你可以用CPU去读,也可以用DMA自动搬走。我个人强烈建议用DMA,原因后面会讲。
我的经验:看框图时,重点关注“采样时间”和“转换时间”这两个参数。它们直接决定了你能达到的最高采样率。我在F103上实测过,12位分辨率下,最快能跑到1MHz,但实际用的时候建议留余量,跑800kHz左右比较稳。
4.3 时钟配置——别让ADC饿着
ADC需要时钟才能工作。STM32的ADC时钟来自APB2总线,但有个限制:ADC时钟不能超过14MHz(F103是这样,其他系列略有不同)。
为什么会这样?因为ADC内部是模拟电路,对时钟频率有严格要求。跑太快了,比较器来不及稳定,结果就不准了。
配置方法很简单。假设APB2时钟是72MHz,你需要分频。STM32提供了2、4、6、8分频可选。我一般这样算:
// 目标:ADC时钟 = 12MHz
// APB2 = 72MHz
// 分频系数 = 72 / 12 = 6
// 所以选择6分频
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
这里有个小技巧:时钟越低,噪声越小。如果你对精度要求高,可以降到8分频,也就是9MHz。代价是采样率会降低,但血压计完全够用。
避坑指南:我曾经在一个项目中,为了追求采样率,把ADC时钟设到了14MHz。结果发现数据抖动很大,尤其是小信号的时候。后来降到10MHz,问题就解决了。所以,别把时钟推到极限,留点余量。
4.4 采样时间——决定精度的关键
采样时间,就是ADC内部采样电容充电的时间。这个时间太短,电容没充满,测量值偏小;时间太长,又浪费了采样机会。
STM32的采样时间可以编程设置,有1.5、7.5、13.5、28.5、41.5、55.5、71.5、239.5个周期可选。注意,这里的“周期”指的是ADC时钟周期。
怎么选?我有个经验公式:
采样时间(us) >= (采样电容(pF) + 外部电容(pF)) × 源阻抗(kΩ) × 7
举个例子。血压计的压力传感器,输出阻抗一般在10kΩ左右。STM32的采样电容是8pF,外部再加一个10nF的滤波电容。那么:
采样时间 >= (8pF + 10nF) × 10kΩ × 7
≈ 0.7ms
嗯,这个时间有点长。但别担心,实际中我们不会加那么大的外部电容。一般加个1nF就够了,这样采样时间只需要几十微秒。
我个人习惯,在F103上设置28.5个周期。ADC时钟12MHz,一个周期约83ns,28.5个周期就是2.4μs。这个值对大多数传感器都够用。
核心要点:采样时间不是越长越好。太长会降低采样率,而且可能引入额外的噪声。找到一个平衡点,才是高手所为。
4.5 实战配置示例
说了这么多,咱们来点实际的。下面是我在血压计项目中常用的ADC初始化代码:
void ADC_Init(void)
{
ADC_InitTypeDef ADC_InitStructure;
// 使能ADC时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
// 配置ADC时钟为6分频,即12MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// ADC结构体配置
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
// 配置采样时间:28.5个周期
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_28Cycles5);
// 使能ADC
ADC_Cmd(ADC1, ENABLE);
// 校准
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
// 开始转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
这段代码有几个关键点:
- 连续转换模式:启动后自动不停转换,省去每次手动触发
- 右对齐:数据存在低12位,方便读取
- 校准:每次上电都要做,消除内部误差
我的建议:实际项目中,别用CPU轮询读ADC。用DMA+定时器触发的方式,效率高得多。这个我们后面章节会详细讲。
好了,关于STM32和ADC的基础知识,今天就聊到这里。下一章咱们会深入讲DMA传输和双缓冲技术,那才是真正提升系统性能的关键。记得动手试试今天的代码,纸上得来终觉浅嘛。