news 2026/9/11 6:22:01

树莓派Pico ADC温度采集实战:从API到定时中断避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
树莓派Pico ADC温度采集实战:从API到定时中断避坑指南

最近折腾树莓派 Pico 的模拟量采集,算是把 ADC 这块彻底吃透了。RP2040 这颗芯片的 ADC 看着简单,实际用起来门道不少,尤其是你想做定时温度采集、再牵扯到中断(ISR)的时候,一个不留神就是数据错乱或者程序卡死。这篇文章我打算从硬件引脚开始,把 machine.ADC 这个 API 的每个方法都拆开讲清楚,然后从单次采集一路做到定时采集的完整项目,最后重点说说 ISR 避坑的那些经验。不管你是刚接触 Pico 的初学者,还是想拿它做小型数据记录仪的嵌入式玩家,按着这篇文章的思路走一遍,应该能少走很多弯路。

1. 内容整体设计与思路拆解

1.1 这个项目到底要解决什么问题

先说清楚我们为什么要折腾 ADC。树莓派 Pico 本身是个数字芯片,只能识别 0 和 1,但现实世界的温度、光照、电池电压这类信号全是连续变化的模拟量。ADC(模数转换器)就是负责把这些连续的模拟电压翻译成芯片能处理的数字信号。温度采集就是 ADC 最典型的应用场景:传感器把温度变化转换成电压或电阻变化,ADC 读回这个电压,我们再用数学公式反推出实际温度。

实际做这个项目时,我发现多数教程只教你怎么调 read_u16() 读出原始值,然后就没了。但真正要把数据用起来,你得解决三个连环问题:第一,硬件上传感器怎么接才能让电压落在 ADC 的合理量程内;第二,软件里 raw 值和真实物理量之间要怎么换算;第三,要定时采集的时候,该用主循环轮询、Timer 中断还是异步任务,它们各自的坑是什么。这篇文章就是按这个逻辑组织的,先硬件再 API,最后落到实战和排查。

1.2 为什么选 MicroPython 而不是 C

如果你用过树莓派 Pico 的 C SDK 写 ADC,你一定会怀念 MicroPython 的简洁。C 那边你要配置 ADC 引脚、设置时钟、处理 DMA 和中断,光初始化代码就一大堆。而 MicroPython 的 machine.ADC 模块把底层都封装好了,三行代码就能读一个模拟通道,开发效率完全不在一个量级。

当然,用 MicroPython 换来的是性能和资源上的妥协。ADC 采样率到不了 C 所能达到的极限,ISR 里的操作也要非常克制。但对于温度采集这种低频场景,MicroPython 完全够用,而且可读性远好于 C,出问题了好排查。我的经验是:原型验证和数据记录这类任务,直接上 MicroPython;如果以后真要上产线做高频采样,再考虑把核心部分移植到 C 也不迟。

1.3 从 API 到实战的整体路径

这篇文章会沿着一条清晰的路线走:先认识 RP2040 的 ADC 硬件,搞清楚它能干什么、有哪些限制;然后逐个讲解 machine.ADC 的创建方式和 read_u16()、read_uv() 等关键方法;接着挑温度采集这个实战场景,把内部温度传感器和 NTC 热敏电阻两套玩法都跑通;最后用 Timer 和 ISR 实现定时采集,并给出一份详细的避坑清单。你跟着读下来,不只是会抄代码,而是能自己根据传感器改电路、根据需求换采样策略。

2. Pico ADC 硬件基础与开发环境准备

2.1 RP2040 ADC 硬件指标

树莓派 Pico 用的 RP2040 芯片内置了一个逐次逼近型(SAR)ADC,核心参数值得先背下来:分辨率 12 位,也就是输入电压会被量化为 0 到 4095 共 4096 个台阶;输入电压范围是 0 到 3.3V,注意绝对不能超过 3.3V,否则有可能烧坏芯片;理论上最大采样率大约在 500kS/s 左右,但实际受固件和代码影响很难跑满。

这个 12 位分辨率意味着什么?用 3.3V 除以 4096,每个 LSB 大约对应 0.8mV。如果你想测量 0 到 3.3V 的电压,这个精度足够了。但如果你想把量程缩小到 0 到 1V 来获得更高精度,硬件上就需要额外的前端电路,ADC 本身是做不到动态调整参考电压的。

这里还要强调一个经常被忽略的点:RP2040 的 ADC 输入阻抗并不算高,官方手册建议信号源阻抗尽量低,否则采样时电压会被拉低,导致读数偏小。如果你接的是高阻抗传感器,最好加一个运放做电压跟随器,或者至少用软件多次采样取平均来缓解。

2.2 引脚映射与通道选择

RP2040 一共有 5 个 ADC 通道,对应关系非常固定:

通道号对应引脚用途
ADC0GPIO26通用模拟输入
ADC1GPIO27通用模拟输入
ADC2GPIO28通用模拟输入
ADC3GPIO29通用模拟输入,部分开发板引出为 ADC_VREF 附近
ADC4无外部引脚芯片内部温度传感器

也就是说,如果你用外部传感器,只能在 GPIO26 到 GPIO29 这四个引脚里选。注意这些引脚并不全是标准的 2.54mm 排针引出,像 GPIO29 在一些开发板上被复用为 ADC_VREF 检测,使用前最好查一下你手上板子的原理图。

在 MicroPython 里,通道号和 ADC 类构造参数是直接对应的。ADC(0) 到 ADC(3) 分别是 GPIO26 到 GPIO29,ADC(4) 是内部温度传感器。这个映射关系在 RP2040 的其他开发板上也通用,只要你用的还是这颗芯片。

2.3 烧录 MicroPython 固件

如果你已经能在 Pico 上跑 MicroPython 程序,这一段可以跳过。还没准备好环境的话,操作非常简单:先从 MicroPython 官网下载适用于 Raspberry Pi Pico 的 .uf2 固件文件,然后按住 Pico 板子上的 BOOTSEL 键,用 USB 线连到电脑,松开按键后电脑会出现一个名为 RPI-RP2 的 U 盘,把 .uf2 文件拖进去就完成烧录了。

日常开发我推荐用 Thonny 编辑器,它内置了对 MicroPython 的支持,写好代码直接点运行就能在 REPL 里看到输出。不过要注意,不同版本的 MicroPython 固件在 API 细节上会有细微差异,比如 read_uv() 这个方法就是后来才加入的。如果你发现代码里某个方法报 AttributeError,先检查固件版本,而不是怀疑硬件坏了。

3. machine.ADC API 核心解析

3.1 创建 ADC 对象:引脚怎么传

machine.ADC 的构造方式很灵活,官方文档里写了多种参数形式,但实际使用时我常用两种:

from machine import ADC, Pin # 方式一:直接传引脚号(ADC 通道号) adc0 = ADC(26) # 方式二:传 Pin 对象 adc1 = ADC(Pin(27)) # 方式三:内部温度传感器 adc_temp = ADC(4)

从代码可读性角度,我其实更推荐传 Pin 对象,因为看代码的人能一眼看出你用的是哪个 GPIO。但传通道号更简洁,两者效果一样。有一点要注意:ADC(26) 在这里传的是 GPIO 编号,而不是 ADC 通道编号 0,MicroPython 内部会自动把引脚号映射到对应通道。新手容易在这里犯迷糊,比如写了 ADC(3) 以为用的是 GPIO3,实际上 GPIO3 根本不在 ADC 引脚列表里,运行时会直接报错。

如果你拿到的固件比较新,还可以用 ADC(machine.ADC.CORE_TEMP) 这样的写法来指代内部温度传感器,语义更清晰。但为了兼容性,我还是习惯用 ADC(4)。

3.2 read_u16() 返回值到底代表什么

ADC 对象的 read_u16() 是最常用的方法,它返回一个 0 到 65535 之间的整数。注意这里的 16 位不是硬件 ADC 的真实分辨率,RP2040 硬件只有 12 位,MicroPython 返回 16 位值时实际上是把 12 位的 raw 值左移了 4 位(或者做了等比例缩放),让结果在 0 到 65535 的范围内看起来更“整齐”。

理解了这一点,你就能明白为什么很多教程里的电压转换公式长这样:

voltage = adc.read_u16() / 65535 * 3.3

先把 0-65535 归一化到 0-1,再乘参考电压 3.3V,就得到了实际电压。这个公式放之四海而皆准,但真正用到项目里,我建议做一点改进:多采几次取平均,而不是直接读一次就算。原因后面会详细说。

还有个小技巧:如果你只需要判断电压是否超过某个阈值,比如检测光敏电阻是否低于某个亮度,可以直接比较 read_u16() 的值和阈值,没必要先转成电压再比较。省掉一次浮点除法,代码也更简洁。

3.3 read_uv() 直接用微伏更保险

在较新版本的 MicroPython 固件中,ADC 对象还提供了 read_uv() 方法,直接返回电压值的微伏数。比如 3.3V 会返回 3300000。这个方法的好处是省掉了你自己写电压换算的步骤,而且固件内部会做一些校准处理,读取结果通常比你自己拿 read_u16() 换算更稳定。

我实际对比过同一块板子同一引脚,read_u16() 换算出来的电压和 read_uv() 读出来的值,两者之间有大约 10-20mV 的差异。这个差异主要来自你写的公式里参考电压用了 3.3 这个名义值,而芯片内部实际参考电压跟 3.3V 有偏差。所以如果你的固件支持 read_uv(),优先用它。不支持的话也别急,自己校准一次参考电压就能弥补。

不过要注意 read_uv() 的返回值可能比较大,print 调试时你不会想每次都看 3300000 这样的数字,除以 1000000 转成伏特更方便阅读。

3.4 参考电压与精度注意事项

Pico 的模拟参考电压(ADC_VREF)默认是 3.3V,由板载稳压器提供。听起来没什么问题,但这个稳压器的输出精度和纹波都会影响 ADC 读数的准确性。如果项目对温度精度要求高,我建议你用万用表实测一下板子的 3.3V 输出,再用实测值替换代码里的 3.3 常数,这一小步能明显改善精度。

另一个提升精度的做法是,在 GPIO29 引脚(ADC3 对应的通道)上外接一个高精度的外部参考电压,芯片内部会优先使用它作为 ADC 的参考源。但这样做需要额外的硬件设计,对大多数温度采集场景来说没太大必要。我一般只在做多通道、高精度测量时才考虑外部参考源。

4. 温度采集的硬件方案与计算原理

4.1 三种温度传感器方案横向对比

温度采集的传感器选择,直接决定了你的电路复杂度和计算难度。我整理了三种在 Pico 上最常见的方案:

方案输出类型是否需要 ADC精度成本适用场景
内部温度传感器电压是(ADC4)约 ±2°C0测芯片温度、粗测环境温度
NTC 热敏电阻电阻是(需分压电路)约 ±0.1°C几毛钱测环境温度、水温,性价比高
DS18B20 数字温度传感器数字(1-Wire)约 ±0.5°C几块钱测多点温度、长线传输

如果你的项目只是想知道“芯片现在长辈了多少度”,内部温度传感器足够;如果想测环境温度,NTC 是性价比之王,也是这篇文章实战部分的主角;DS18B20 虽然不需要 ADC,但它是数字通信,玩法完全不同,需要单独讲 OneWire 协议,这里就不展开了。

内部温度传感器唯一的毛病是它测的是芯片内部温度,不是环境温度。芯片负载一高,内部温度会马上升上来,跟室温差出好几度都有可能。所以它适合做芯片过热保护,不适合做精密的室温测量。

4.2 NTC 分压电路设计

NTC 热敏电阻的特性是“温度越高,阻值越低”,而且非线性很强。它没法直接接到 ADC 上读出温度,因为 ADC 只能读电压,不能读电阻。最常用的做法是搭一个分压电路,把电阻变化转换成电压变化。

我推荐的分压接法是:NTC 一端接 3.3V,另一端接 ADC 采样点;固定电阻一端接采样点,另一端接地。这样 ADC 采样点的电压就是固定电阻分到的电压,计算公式为:

Vout = 3.3 × R_fixed / (R_ntc + R_fixed)

当温度升高,R_ntc 下降,分母变小,Vout 变大。温度越高,ADC 读数也越高,逻辑上非常直观。固定电阻的阻值最好跟 NTC 在 25°C 下的额定阻值一致,比如你买的是 10k NTC,固定电阻也用 10k,这样 25°C 时采样点电压正好是 1.65V,落在 ADC 量程的中间,分辨率最高。

接线时注意把模拟部分的电源和地尽量靠近芯片的电源引脚接,缩短走线,避免跟电机、继电器这类大电流负载共用回路。我在面包板上做实验时吃过亏,NTC 读数总是周期性波动,后来发现是旁边一个继电器线圈通断时把地电位抬高了。

4.3 NTC 阻值到温度的数学换算

拿到 ADC 采样点电压后,第一步先反算 NTC 的当前阻值。根据上面的分压公式可以推导出:

R_ntc = R_fixed × (3.3 / Vout - 1)

得到阻值之后,温度换算有一个常用的简化公式,叫 B 参数公式:

T = 1 / (ln(R_ntc / R25) / B + 1 / 298.15) - 273.15

其中 R25 是 NTC 在 25°C 时的阻值,B 是 NTC 的 B 值常数,这两个参数在你买 NTC 时商家都会标,常见的是 R25=10k、B=3950。298.15 是 25°C 对应的开尔文温度,273.15 是摄氏度和开尔文的偏移量。

这段代码在 MicroPython 里实现起来很直接:

import math B = 3950 R25 = 10000 # 25°C 时 NTC 阻值,单位 Ω R_FIXED = 10000 # 分压固定电阻,单位 Ω def rntc_from_voltage(vout): if vout <= 0 or vout >= 3.3: return None return R_FIXED * (3.3 / vout - 1) def temp_from_rntc(rntc): if rntc is None or rntc <= 0: return None kelvin = 1.0 / (math.log(rntc / R25) / B + 1.0 / 298.15) return kelvin - 273.15

为什么这里要判断 vout 小于等于 0 或者大于等于 3.3?因为分压电阻理论上是到不了这两个极限的,如果出现这种值,说明接线有问题,再往下算会出 NaN 或者无限大。加上这个保护,代码健壮性好很多,尤其是在排查硬件故障时不会被奇怪的结果带偏。

5. 实战:从单次采集到定时温度采集

5.1 单次采集与平均滤波

先写一个最基础的单次采集,把内部温度和 NTC 温度都读出来。这里我加了多次采样取平均的逻辑,因为 ADC 的原始读数会有随机噪声,单次读出来的值可能在 ±10 个 LSB 左右跳动,对温度来说就是零点几度的波动,肉眼可能看不出问题,但数据记录时会很烦人。

import machine import math import utime # 初始化 ADC adc_ntc = machine.ADC(26) # GPIO26 上的 NTC 分压点 adc_core = machine.ADC(4) # 内部温度传感器 # NTC 参数 B = 3950 R25 = 10000 R_FIXED = 10000 VCC = 3.3 def read_avg(adc, samples=16): total = 0 for _ in range(samples): total += adc.read_u16() utime.sleep_us(10) return total // samples def ntc_temperature(): raw = read_avg(adc_ntc) vout = raw / 65535 * VCC rntc = R_FIXED * (VCC / vout - 1) if rntc <= 0: return None kelvin = 1.0 / (math.log(rntc / R25) / B + 1.0 / 298.15) return kelvin - 273.15 def core_temperature(): raw = read_avg(adc_core) volt = raw / 65535 * VCC return 27.0 - (volt - 0.706) / 0.001721 while True: print("NTC = %.2f C, Core = %.2f C" % (ntc_temperature(), core_temperature())) utime.sleep(1)

read_avg() 里我特意加了 utime.sleep_us(10) 这个微秒级延时,是为了让 ADC 内部采样电容有足够时间充电,尤其是在连续快速读取时。虽然现在固件里每次读本身也有间隔,但主动加一点延时实测能进一步降低毛刺。

5.2 固定频率采集:主循环 + 时间戳

第一个定时方案不涉及中断,就是主循环里维护一个时间戳,每隔固定间隔执行一次采集和打印。这种方式的好处是思路简单,不存在中断竞争问题,非常适合采样频率不高的场景。

import utime INTERVAL_MS = 2000 # 每 2 秒采样一次 last_time = utime.ticks_ms() while True: now = utime.ticks_ms() if utime.ticks_diff(now, last_time) >= INTERVAL_MS: last_time = now print("NTC = %.2f C" % ntc_temperature())

使用 time.ticks_ms() 和 time.ticks_diff() 而不是单调递增的计数器,是因为 ticks_ms() 在长时间运行后会回绕,直接比较大小会出错,而 ticks_diff() 能正确处理回绕。这是 MicroPython 官方推荐的习惯写法。

这种轮询方式唯一的弱点是,循环里如果还有其他耗时操作,实际采样间隔会漂移。比如你在循环里加了写 SD 卡或者网络请求,这段时间会让下一次采样滞后。如果对采样时刻的确定性要求很高,就需要用下面说的 Timer。

5.3 用 Timer 和 ISR 实现定时采集

MicroPython 在 RP2040 上提供了 machine.Timer 类,可以创建周期性的定时器中断。基本用法是把回调函数注册给定时器,每到一个周期,函数就会在中断上下文中被调用。

from machine import Timer sample_tick = False def on_tick(timer): global sample_tick sample_tick = True timer = Timer() timer.init(freq=1, mode=Timer.PERIODIC, callback=on_tick) while True: if sample_tick: sample_tick = False print("NTC = %.2f C" % ntc_temperature())

这段代码的核心思想是:中断回调里只置一个标志位,立刻返回;主循环检测到标志位后再去做真正耗时的事情(采样、计算、打印)。如果你在初始化 Timer 时报参数错误,很可能是你的固件版本对 freq 参数的支持不一致,可以改成 timeout 参数,比如 timer.init(mode=Timer.PERIODIC, timeout=1000, callback=on_tick),其中 timeout 单位是毫秒。

为什么要把采样放到主循环而中断里只置位?这里其实是 ISR 避坑的第一个大原则:中断服务函数应当尽量短。具体原因在下一节展开讲,但你先记住这个 pattern,它能让定时采集项目省去大量莫名其妙的崩溃问题。

5.4 数据落盘到 CSV

定时采集的数据如果只靠 print,断电就丢了。要长期记录温度变化,我会把数据追加写到一个 CSV 文件里,方便之后导入 Excel 或 pandas 分析。MicroPython 在 Pico 上可以直接操作板载 Flash 文件系统,用自带的 open() 函数就能写文件。

def log_temperature(filename, sample_time, temp): with open(filename, "a") as f: f.write("%s,%.2f\n" % (sample_time, temp))

注意这里使用了追加模式 "a",每次调用都会打开文件、写入、关闭。这种写法对 Flash 寿命不太友好,因为频繁写入会让 Flash 的擦写次数消耗得很快。一个改进策略是,在内存里攒够一定数量的采样点,比如 30 个,再一次写入,最大限度减少 Flash 操作次数。

buffer = [] def accumulate_and_flush(filename, sample_time, temp): global buffer buffer.append((sample_time, temp)) if len(buffer) >= 30: with open(filename, "a") as f: for t, v in buffer: f.write("%s,%.2f\n" % (t, v)) buffer = []

这里 buffer 里存的是一批数据,积攒到 30 条才写盘,既解决了 Flash 寿命问题,又避免频繁打开关闭文件。缺点是如果中途断电,最后不足 30 条的数据会丢失,所以你要根据项目需求权衡:数据重要程度高就减少批量大小,甚至每条都写;追求 Flash 寿命就加大批量。

6. ISR 避坑指南:定时中断里那些容易翻车的细节

6.1 中断回调里的“禁区”

很多新手第一次用 Timer 做周期采集时,喜欢把整个采样、计算、print 全部塞进回调函数,结构看起来是这样的:

def on_tick(timer): temp = ntc_temperature() print("NTC = %.2f C" % temp) with open("log.csv", "a") as f: f.write("...,%.2f\n" % temp)

这段代码在测试时可能很正常,但运行时偶尔会出现诡异问题:程序卡死、数据丢失、甚至整个固件崩掉重启。原因在于中断回调运行在中断上下文中,它和主循环共享同一个解释器,却又不受主循环调度保护。

具体来说,中断回调里有两个大忌:一是做阻塞操作,比如 print、文件写入、I2C/SPI 通信,这些操作可能涉及等待外设或锁,一旦在中断上下文里阻塞,轻则延迟主循环,重则触发异常;二是分配内存,字符串格式化、列表 append、文件对象的创建都会在堆上分配内存,MicroPython 的内存分配器不是完全中断安全的,在中断里分配内存可能会导致内存碎片甚至硬错误。

所以我的第一条经验就是:中断回调里只做两件事,置标志位,或者更新简单变量。任何复杂逻辑都放到主循环里做。

6.2 flag + 主循环消费的正确姿势

前面 Timer 示例里的 flag 方案,就是官方推荐的正确姿态。稍微复杂一点的场景,你可能需要在中断里同时传递一些轻量数据,比如把当前时间戳存到一个全局变量里。这里有一个 MicroPython 和 CPython 不同的细节:在函数内部修改全局变量,必须用 global 声明,否则会被当成局部变量。如果用了可变容器,比如列表或字典,则不需要 global 也能修改内容。

sample_data = {"ready": False, "tick_count": 0} def on_tick(timer): sample_data["ready"] = True sample_data["tick_count"] += 1 timer.init(freq=1, mode=Timer.PERIODIC, callback=on_tick) while True: if sample_data["ready"]: sample_data["ready"] = False # 主循环中取数据 ticks = sample_data["tick_count"] temp = ntc_temperature() print(ticks, temp)

用字典或列表在中断和主循环之间传数据,代码比 global 变量更清晰,也不用担心变量作用域。但要注意,中断里的写入和主循环里的读取仍然存在潜在竞争,对于单核 MicroPython 来说问题不大,只要你保证中断里只写简单类型,主循环里只在标志位置位后读取,就能避免大多数问题。

6.3 定时器精度与回调时长

Timer 看起来是周期触发,但它的精度其实没有你想象的那么完美。MicroPython 的定时器回调执行后,下一次触发时间是从回调返回后开始计算的,还是按固定时钟源计算的,不同实现策略会导致不同的累积漂移。

我用 Pico 做过一个 1 小时的数据记录测试,freq=1Hz 的 Timer 让 LED 反转,然后用逻辑分析仪测实际输出脉冲间隔,发现间隔在 998 到 1003ms 之间跳动,偶尔还会出现 1005ms 的毛刺。其原因就是回调虽然短,但 MicroPython 解释器执行定时器回调本身有不可忽略的延迟。如果回调里做了稍微耗时的 print,漂移会明显增大。

解决漂移的方法有两个:一是尽量压缩回调代码,让回调时间稳定在极短范围;二是在数据记录时同时记录 ticks_ms() 时间戳,后续分析时按时间戳重采样,而不是假设采样间隔完全均匀。如果你对时间精度要求到毫秒级以上,建议用带有实时时钟(RTC)模块的方案,或者借助 PIO 和 DMA 做硬件级采样,但那已经超出本文范围了。

6.4 中断嵌套与回调重入

最后一个坑是中断嵌套和回调重入。MicroPython 的 Timer 回调默认不允许嵌套,一般也不会发生重入,即上一次回调还没执行完,下一次就进来了。因为下一个中断要等当前中断服务函数返回后才可能触发。这条保护帮我们省了不少事。

但有一个例外情况你也得注意:如果回调里出现了极长的阻塞操作,下一次中断触发时上一次还没返回,那么这次中断会被硬件标记为 pending,等当前回调返回后会立刻再次进入回调。这就等于你主循环还没喘过气,中断又来了。如果这种情况反复发生,主循环几乎没有执行机会,程序看起来就像死机了。这也是为什么我反复强调“回调必须短”的根本原因。

7. 常见问题与排查技巧实录

7.1 ADC 数值跳动和噪声处理

我遇到最多的问题就是 ADC 读数不稳定,同一个电压每次读都不一样。先排除接线问题,再用软件滤波。最简单的软件滤波是多次采集中值滤波,我推荐 7 次采集中间值,效果比单纯平均更能抗脉冲干扰。如果噪声还是很大,可能是供电不稳,检查 3.3V 输出处有没有大电流负载在波动。

还有一个容易被忽略的因素是 MicroPython 的 ADC 读数对采样时间敏感。如果代码里在读取前刚执行了一个高频任务,ADC 采样电容可能还没有稳定。解决方法是读取前加几个空循环或者 sleep_us,让采样稳定后再读。

7.2 温度读数明显不对

NTC 温度读数偏差大的原因,90% 出在参数不对。先确认你手上 NTC 的真实 R25 和 B 值,很多商家标 10k 3950,但实际可能是 10k 3435。参数差一点,50°C 以上时误差能到好几度。其次检查分压电阻的精度,1% 精度的电阻和 5% 精度的电阻,计算结果就能差出不少。

内部温度传感器读数偏差大也很常见。官方公式里的 0.706V 和 27°C 是芯片出厂时的统计值,不同芯片之间存在个体差异。想做准确点,可以在室温下用温度计测一下实际环境温度,然后用实际值反推校准参数,把偏差修正掉。

7.3 Timer 或中断失效

Timer 设了回调但一直没触发,多半是固件版本太老或者参数写错。先用一个最简单的 LED 闪烁程序单独测 Timer,排除硬件问题。还有一种情况是程序里创建了多个 Timer,但某个 Timer 的初始化参数覆盖了另一个,导致回调被错误修改。建议一个项目里只使用一个 Timer,如果确实需要多个不同周期,可以在回调里做计数分频,比如一个 1Hz 的 Timer,每回调 10 次才执行一次采集,就相当于 10 秒采样一次。

7.4 文件写入失败或 Flash 数据损坏

写 CSV 文件时偶尔出现 OSError,或者板子重启后文件打不开,多半是文件系统已损坏,或者 Flash 被写满。MicroPython 的板载 Flash 文件系统掉电不保护,如果在写入中途断电,很容易损坏文件系统。我的建议是数据采集时使用批量写入策略,同时定期备份重要数据。真遇到文件系统损坏,可以在 REPL 里执行 os.remove 删掉可疑文件,或者重新烧固件格式化为 FAT 文件系统,但后者会清空板子上所有数据,慎用。

结尾

这套 Pico ADC 温度采集方案,我在多个项目里跑过,从最初简单的环境温度记录,到后来加上了 OLED 显示和 SD 卡存储,整体稳定性都不错。如果让我说一个最重要的心得,那就是“中断回调绝对不做任何耗时操作”,这句话我踩过坑才真正信了。你可以在中断里置位,可以更新变量,但 print、文件写入、网络请求一律留到主循环,这样定时采集项目基本不会出现莫名崩溃。

另外一个小技巧想分享给正在抄代码的你:调试时不要只 print 温度值,把 ADC raw 数值也一起打出来。温度经过了非线性换算,看着一个大数直觉上不好判断问题在哪,但 raw 值和电压直接对应,能帮你更快定位是传感器坏了、接线断了,还是分压电阻焊错了。等整个链路跑通了,再把 raw 从日志里去掉,保持输出清爽。这样一套流程走下来,你对 Pico 的 ADC 掌控程度,应该已经超出大部分教程里“读个电压”的水平了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 6:17:07

2026年9月西安装修门店AI同城拓客怎么做?获客渠道拆解

2026年9月&#xff0c;西安不少装修门店老板都在问同一句话&#xff1a;“西安装修门店AI同城拓客怎么做&#xff1f;”问的其实不是要不要继续发短视频、要不要继续投本地推广&#xff0c;而是为什么客户在豆包、文心一言、通义千问里问“西安哪家装修公司靠谱”时&#xff0c…

作者头像 李华
网站建设 2026/9/11 6:16:58

JavaWeb教师学生管理系统从零部署与调试指南

简介&#xff1a;本资源是一套完整的JavaWeb期末课程设计项目——教师学生信息管理系统&#xff0c;面向高校计算机类专业本科生&#xff0c;适用于《Java Web程序设计》《网页设计与制作》《Java设计模式》等课程的期末作业或综合实训。系统采用B/S架构&#xff0c;涵盖用户登…

作者头像 李华
网站建设 2026/9/11 6:16:47

Java第7天实战:集合选型、Redis计数器与多线程协作的踩坑全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华