拓冰建站拓冰建站
首页 / 资讯中心 / 正文

STM32+LM2904+VDO NTC水温传感器采集电路设计详解

简介这份资源面向嵌入式开发者与电子工程学习者提供一套基于STM32F407的完整温度监测方案。方案结合LM2904运算放大器、分压电阻网络与VDO温度传感器实现模拟信号采集、放大、ADC转换及串口输出最终通过串口助手实时显示温度值。工程基于HAL库编写代码结构清晰适合需要掌握ADC单通道采集、信号调理电路设计及USART通信的初中级开发者参考。资源包共1135个文件大小20.46MB包含592个c源码文件、284个h头文件、78个s汇编文件、46个icf链接配置文件以及uvprojx工程文件、ioc引脚配置、hex烧录文件、axf调试文件和map映射文件等涵盖从源码编译到硬件调试的完整链路。已有255人学习使用工程可直接导入STM32CubeMX生成的项目环境中查看引脚配置与初始化逻辑便于二次开发和移植。 最近在调试一套车载水温采集板朋友特意指定要用VDO温度传感器这就把一套很经典的组合凑齐了STM32F103 HAL库 LM2904双运放 ADC采集 VDO温度传感器。单独看每个器件都很常见但真要把它们正确搭在一起电路参数怎么算、ADC怎么配、温度怎么算出来每一步都有不少坑。这篇就把整个电路设计和代码实现完整拆开讲一遍。这套方案解决的是什么问题VDO的NTC温度传感器输出的是电阻值变化不能直接被单片机读取需要先做分压转换和信号调理再由STM32内置12位ADC采样最后在软件里通过B值公式或者查表换算成真实温度。适合正在做车载仪表、水温/油温监控、工业设备温控的朋友参考即使你不用VDO传感器改用其他NTC一样能套用。1. 方案选型与分析1.1 VDO温度传感器输出的是什么信号VDO是汽车电子领域非常常见的传感器品牌尤其是发动机水温传感器、机油温度传感器、变速箱油温传感器大量用的都是NTC热敏电阻方案。NTC的意思就是负温度系数温度升高时电阻值下降这和PT100正好相反。以常见的一款VDO水温传感器为例25摄氏度时电阻可能是1kΩ到了100摄氏度可能只剩180Ω左右具体数值要看型号的数据手册不同车型规格差异很大。很多新手第一次接触这种传感器第一反应就是“既然是个电阻直接和固定电阻分压然后接ADC不就行了”表面看确实是这样但实际测温环境往往很恶劣。汽车发动机舱里线束很长点火线圈、继电器、电机产生的电磁干扰非常强直接分压出来的信号阻抗又高ADC引脚本身的采样电容充放电行为会和源阻抗互相作用导致采样结果飘忽不定。这时候就需要运放介入做阻抗变换和信号幅度调理。1.2 为什么用LM2904而不是LM358LM2904和LM358在电路上几乎可以互换很多初学者会觉得这两者没区别。其实LM2904的定位就是LM358的工业/汽车级版本工作温度范围通常做到-40摄氏度到125摄氏度而普通商用级LM358一般是0到70摄氏度。上车的项目就要选LM2904这也是标题里特别标注LM2904的原因。但用LM2904有一个必须注意的点它不是轨到轨运放。在3.3V单电源供电下它的输出电压最高大约只能到电源电压减1V左右也就是2.3V上下负载电流越大这个掉得越多输入共模电压范围也有限制正相输入最高大约是VCC减1.5V也就是1.8V左右。这就决定了后续电路设计时必须把信号工作区压在比较低的位置不能在满量程3.3V附近做文章。2. 硬件电路设计与计算2.1 传感器分压网络设计要让NTC的电阻变化变成电压变化最基本的做法就是电阻分压。我采用的方式是上拉电阻接3.3VNTC传感器一端接分压节点另一端接地分压节点作为信号输出然后送入运放。这样温度升高时NTC电阻减小分压节点电压降低不过我这个方案里实际取的是反过来的思路。更常见、更直观的接法是NTC接在低侧上拉电阻从3.3V接到分压节点P分压点P再接到NTC到地。这样温度升高NTC电阻下降分压点电压也会降低。如果你想温度升高时ADC数值变大就把NTC放在低侧让分压节点电压从NTC两端取V_in 3.3V * R_NTC / (R_pullup R_NTC)。在这个接法里NTC两端电压就是分压点对地电压温度升高时R_NTC减小这个电压也减小所以ADC读数会随温度升高而下降。想让读数随温度升高而上升就把上拉电阻和NTC位置互换NTC接3.3V固定电阻在下拉到地再从NTC和固定电阻之间取信号。我这次用的是固定电阻下拉、NTC接电源侧的方案电压随温度升高而上升逻辑上更直观。举例假设25摄氏度时NTC为1kΩ100摄氏度时NTC为180Ω下拉电阻R_pullup取2.2kΩVCC3.3V。25摄氏度时V_in3.31000/(22001000)1.03V100摄氏度时V_in3.32200/(2200180)不对这里要仔细算。如果用NTC在上、固定电阻在下节点电压是固定电阻两端电压那么V_inVCCR_pullup/(R_NTCR_pullup)。因此25摄氏度时V_in3.32200/(10002200)2.27V100摄氏度时V_in3.32200/(1802200)3.06V。这个范围对LM2904来说就太高了输入共模范围超了输出也不可行。所以这里其实应该改成NTC在下、固定电阻在上确保信号电压在低区间。即VCC-R_pullup-节点P-NTC-GND节点P作为输出。25摄氏度时V_in3.31000/(10002200)1.03V100摄氏度时V_in3.3*180/(1802200)0.25V。这个0.25到1.03V的范围既在LM2904共模范围内又比较低方便后面放大。所以实际方案要按这个顺序来VCC接固定上拉电阻节点P接NTC再接GND信号从P点取。上拉电阻选2.2kΩ是比较折中的既保证分压曲线在测温区间内有足够斜率又不至于让功耗过大。如果你手里的VDO传感器规格不同比如25摄氏度下是500Ω那上拉电阻就要相应调整原则是让目标测温区间的电压落在0.2V到1.2V之间。分压节点后面我加了一级RC滤波一个1kΩ电阻串联再加100nF电容到地截止频率算下来大约是1.6kHz。温度信号本身就是缓慢变化的这个滤波既能滤掉高频干扰又不会影响响应速度。RC时间常数约100µs对于温度传感器来说完全够用。2.2 LM2904放大电路设计因为直接分压出来的信号只有0.25V到1.03V12位ADC在3.3V参考下的分辨率是0.000805V/LSB这段电压只对应大约969个码值直接把测温分辨率压到0.1摄氏度以下其实也够但还是浪费了量程。所以用LM2904做个同相放大器把信号拉伸到更合适的位置。同相放大器的增益公式是G1Rf/Rg。我选用Rg10kΩRf8.2kΩ算下来G1.82倍输出范围就变成0.45V到1.87V左右。这个值是有讲究的LM2904在3.3V供电下输出最多大约2.3V我留了0.4V左右的余量避免放大器先饱和了后面ADC读满量程还在涨那就完全失真了。运放供电方面LM2904的VCC直接接3.3V和STM32同一路电源。芯片电源脚旁要放一个100nF陶瓷电容建议再加一个10µF电解电容尤其当板子上还有继电器或者电机驱动时电源纹波会直接影响运放输出精度。运放输出到STM32的ADC引脚之间串一个100Ω电阻这个电阻不参与分压纯粹是保护作用万一ADC引脚配置错或者带电插拔能起到限流作用。LM2904里面有两个运放第二个运放如果暂时不用一定不要让它悬空。我通常把同相输入端接地反相输入端接输出做成一个电压跟随器这样既不会产生不必要的振荡又能在将来需要扩展第二路信号时直接用。2.3 与STM32 ADC的接口细节STM32F103的ADC输入可以理解为一个采样电容加一个模拟开关采样瞬间它需要从外部源抽取电荷。如果外部源阻抗太高采样电容充不满测出来的电压就会偏低。运放输出阻抗非常低只有几十毫欧量级所以加了跟随/放大之后相当于把传感器的“高阻”问题彻底解决ADC采样精度由这个缓冲结构兜住了。另一个细节是ADC参考电压。STM32F103C8T6的VREF内部接到VDDA也就是3.3V。如果3.3V电源精度不高ADC的绝对精度也会受影响。实际项目中如果要求测温误差小于1摄氏度最好给模拟部分单独用高精度LDO供电。3. STM32 HAL库工程与ADC代码实现3.1 CubeMX配置要点打开STM32CubeMX芯片选STM32F103C8T6时钟配置为外部8MHz晶振倍频到72MHz。在Analog菜单里开启ADC1把通道0也就是PA0配置为模拟输入。ADC参数建议这样设置Scan Conversion Mode关闭Continuous Conversion Mode开启数据对齐方式选右对齐采样时间选最大的239.5周期。采样时间这个很关键F103的ADC工作在12MHz下239.5周期算下来大约20µs一次采样对付这种慢变温度信号绰绰有余而且能最大限度降低源阻抗带来的误差。DMA设置里选Circular模式数据宽度Half Word这样DMA会自动把ADC结果刷新到内存变量里不需要CPU介入。USART也要开一个用来打印温度数据方便调试。我习惯用USART1115200/8N1。生成工程之后main函数里第一件事不是启动ADC而是做ADC校准。STM32F1系列的ADC有一个内置校准功能HAL库里对应的是HAL_ADCEx_Calibration_Start。不校准的话ADC的零点误差可能有好几个LSB温度上会误差好几度。3.2 ADC采样与软件滤波用DMA方式采样有一个很舒服的点启动一次就可以一直跑数据自动更新。代码如下uint16_t adc_raw 0; uint32_t adc_sum 0; uint16_t adc_buffer[16]; uint8_t adc_index 0; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); MX_ADC1_Init(); MX_USART1_UART_Init(); HAL_ADCEx_Calibration_Start(hadc1); HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_raw, 1); while (1) { // 用最新ADC值做滑动平均 adc_sum - adc_buffer[adc_index]; adc_buffer[adc_index] adc_raw; adc_sum adc_buffer[adc_index]; adc_index (adc_index 1) % 16; uint16_t adc_filtered adc_sum / 16; float voltage (float)adc_filtered * 3.3f / 4095.0f; float temp_c convert_voltage_to_temperature(voltage); char msg[64]; sprintf(msg, ADC:%4d V:%.3f T:%.1f\r\n, adc_filtered, voltage, temp_c); HAL_UART_Transmit(huart1, (uint8_t *)msg, strlen(msg), 100); HAL_Delay(100); } }滑动平均是16点平均每100ms读一次等效时间常数1.6秒温度读数会非常平滑。如果觉得响应太慢可以改成8点或5点平均看具体需求。有一个坑必须提醒在使用DMA连续模式时不要在每次循环里重复调用HAL_ADC_Start_DMA。很多人第一次接触HAL库会犯这个错在while里反复启动ADC结果DMA传输错乱读出来的数据一直是同一个值。配置成Continuous模式后启动一次就行DMA会自动持续搬运。3.3 电压到温度的换算现在ADC采集到的是电压值要回到温度值还得再走两步电压反推NTC电阻电阻再查温度。电压反推电阻很简单NTC在低侧的情况下float convert_voltage_to_resistance(float voltage) { if (voltage 3.29f) return 999999.0f; // 防止除零 float rt 2200.0f * voltage / (3.3f - voltage); return rt; }这里2200是上拉电阻2.2kΩ如果换成其他阻值要同步修改。拿到电阻之后用B值公式算温度float convert_resistance_to_temperature(float resistance) { float T0 298.15f; // 25摄氏度对应的开尔文温度 float RT25 1000.0f; // 25摄氏度下的NTC电阻以手中传感器实测为准 float B 3900.0f; // 传感器的B值查数据手册 float t (float)(B * T0) / (B T0 * logf(resistance / RT25)) - 273.15f; return t; }B值公式在窄温区范围内精度足够比如0到120摄氏度之间误差一般可以控制在1摄氏度以内。如果你的传感器没有给B值或者要求全温区精度更高那就用查表法。先把温度-电阻表做成结构体数组然后线性插值。查表法代码稍长但逻辑直白不容易出错。我这里B值设的3900是常见NTC参数VDO传感器的具体B值需要看手册或者用恒温箱实测标定。批量做项目时建议每批抽样校准一次不然不同的传感器批次B值差异会直接体现成温度偏差。4. 调试排查与实战经验4.1 ADC读数恒定满量程或者恒为零这是我最常被问到的问题。如果ADC读到4095附近先别急着怀疑代码用万用表测运放输出引脚电压。如果运放输出已经顶到2.3V以上问题大概率在模拟前端上拉电阻有没有虚焊分压节点是否被拉高如果是NTC开路比如传感器线束断了那么固定上拉电阻会把节点电压拉到接近3.3V运放输出自然顶到轨。我遇到过一次是因为传感器插头氧化接触电阻变大读出来直接满量程当时排查了很久才想到是机械连接问题。ADC恒为零的情况也类似先量分压节点电压。如果节点有电压但运放输出为零检查运放电源有没有接反或者第二个运放有没有干扰到第一个。再往后就是STM32的ADC引脚是不是真的配置成了模拟输入被复用成了GPIO输出低电平也会把电压拉死。4.2 温度读数跳动剧烈跳变的根因通常是高频干扰或者采样时间不够。车载环境里点火线圈和雨刮电机工作时会产生很强的宽带噪声虽然加了RC滤波但如果布线时信号线走得太靠近电源线还是会有串扰。这种情况下硬件上要把信号线换成双绞线或者加屏蔽层软件上增大采样时间到最大值同时把滑动平均窗口加长。还有一种情况是ADC的采样周期确实不够长。F103的ADC采样时间选项有1.5、7.5、13.5、28.5、41.5、71.5、239.5周期如果你之前为了追求采样速率用7.5周期源阻抗稍微大一点读数就是会周期性偏小看起来像跳动。直接选239.5周期我实测同样硬件情况下码值波动从三四十个LSB降到五六个LSB。4.3 DMA回调不触发或者程序卡死HAL库的DMA模式依赖中断完成标志位的翻转。如果回调函数不触发先检查两处NVIC里DMA中断是否被勾选了DMA中断优先级是否太低被其他中断饿死。另一个容易被忽略的问题是你的DMA缓冲区地址。STM32 DMA要求地址按半字对齐如果定义的是uint8_t数组或者结构体里偏移了奇数地址DMA传输时会进入HardFault。如果你用回调函数HAL_ADC_ConvCpltCallback做数据搬运不要在回调里做浮点运算或者打印回调环境适合做轻量级工作比如置标志位真正的计算放到主循环。我当时调试时在回调里直接调printf结果一圈下来时间全耗在串口上数据直接卡死后面改成主循环读标志位才算干净。4.4 LM2904输出异常的非线性表现LM2904最典型的症状是输入电压明明在按曲线变化但输出电压到了某个点之后不再跟着变就像被人按住了。两种常见情况一是输入共模电压超过了VCC-1.5V内部输入级工作点不对输出就失效了二是输出电压逼近轨到轨上限输出级进入饱和区域。解决办法就一句话把信号工作区往下压。比如把上拉电阻从2.2kΩ加大到4.7kΩ让25摄氏度时节点电压从1.03V降到0.6V左右输出范围变成0.2V到1.2VLM2904就很舒服了。如果这样动态范围又被压缩就调整运放增益把增益从1.82加大到2.5倍仍然能保证不饱和。5. 方案扩展与适用范围5.1 扩展成多路温度采集LM2904是双运放也就是说天然支持两路信号的调理。第二个运放可以接成相同的同相放大器接入另一只VDO温度传感器然后STM32的ADC1开启扫描模式通道0和通道1轮流转换用DMA存储两路结果。这样一套硬件就能同时测水温、油温在需要多点取样的设备上非常实用。要注意双通道扫描模式下DMA的缓冲区长度要设为2并且每次读取数据时按缓冲区下标区分通道。5.2 把温度数据接入总线温度采集的最终目的大多不是自己在板子上看而是上报给仪表盘或者上位机。最省事的方式是串口发送就像示例代码那样。如果要上车载总线把UART那部分逻辑替换成CAN发送即可STM32F103的bxCAN外设性能应对这种低频温度数据绰绰有余。温度值可以放大10倍后作为整数发送比如25.3摄氏度发253避免浮点协议解析的麻烦。对于工业场景也可以换成RS485加Modbus协议改动量都不大。5.3 这套方案到底适合用在哪从实际应用角度讲这类“NTC运放MCU ADC”的组合是成本最低、可靠度又够高的测温通道标准做法。它不仅能测水温和机油温度农机设备的液压油温、发电机组冷却水温、工业减速机轴承温度凡是温度变化慢、环境干扰大的场景都能用。LM2904和STM32F103这两颗料的供货量非常大成本控制很理想后期的维护替换也容易。如果你在车内空间有12V供电只要把测量系统的3.3V电源做好隔离和DC-DC降压这套方案基本就能直接上车。最后分享一个我自己的小习惯。整套系统搭好后先用一个精密电阻箱模拟传感器在几个标定温度点分别给到电阻值然后看软件输出的温度误差而不是直接拿着烙铁去试传感器。这样可以用很低的成本完成全温区标定也方便复现问题。等电阻箱测试全部通过后再换上真传感器做整机测试这样排错效率高很多。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门