十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MicroPython文件读写实战:用树莓派Pico制作温度记录器

MicroPython文件读写实战:用树莓派Pico制作温度记录器 我曾做过一个温度监测小项目一开始只是习惯性用print把温度打到 REPLMicroPython 交互式解释器里。串口一关、电脑一断所有采集数据瞬间归零。那个瞬间我才意识到“显示数据”和“记录数据”是两码事。后来我把树莓派 Pico 的 MicroPython 文件读写摸了一遍做成了真正能用的温度数据记录器板子独立运行每隔几秒把温度追加到 CSV 文件断电重启后数据还在。这篇内容就是那次完整实践的复盘核心是 MicroPython 文件读写 API、Pico 文件系统和 CPython 的差异、能直接抄走的温度记录代码以及几个我踩过的文件系统相关的坑。适合刚拿到 Pico、想从点灯往“做小工具”走的入门者也适合在电脑上写过 Python 文件操作、但第一次在单片机上处理文件的人。1. 为什么说“温度记录”是入门文件读写最好的练习1.1 文件读写的本质是数据留存任何程序在运行时变量都存在内存里。Pico 的内存掉电即失代码里算出的温度值如果只放在变量中一断电就没了。文件读写解决的就是“数据如何越过断电边界、进入持久化存储”的问题。MicroPython 把文件操作做得很接近桌面 Python所以在 Pico 上可以只用一个open()函数完成这一整套流程。温度记录是这个场景里最典型的例子。温度变化缓慢、采样频率低、数据量小、格式统一简直是为文本文件读写量身定做的实验样本。你不用考虑高性能写入、并发、数据库索引这些复杂问题只需要关心三个核心动作打开文件、写入数据、关闭文件。这三个动作做顺了文件读写就算是入门了。1.2 一台合格温度记录器的三个要求做数据记录器不能只写数据还得能事后取出来用。把它拆解一下大概是三个要求定时采样每隔固定间隔读一次温度这对应 MicroPython 里的time.sleep()或定时器。可靠落盘数据能真正写到 flash 里断电不丢这对应文件打开方式和flush()、close()的时机。可解析导出把记录文件从 Pico 拷贝到电脑后能分析这对应文件格式设计我推荐 CSV。如果你只想让 Pico 把温度打到串口那不叫数据记录。串口一旦断开历史数据就没了。只有文件里的数据才是真正属于你的。2. 动手前的准备固件、开发环境与温度获取方案2.1 烧录 MicroPython 固件的基本动作树莓派 Pico 出厂默认不带 MicroPython需要手动烧录。整个流程比大多数单片机刷固件都简单按住 Pico 板子上的 BOOTSEL 按钮用 USB 线连接电脑松开按钮后电脑会出现一个名为RPI-RP2的可移动磁盘。去树莓派官方 MicroPython 页面下载最新版.uf2固件文件。把.uf2文件拖进RPI-RP2磁盘Pico 会自动重启随后就从“U 盘模式”切回了 MicroPython 模式。我下载完成后习惯先用 Thonny 验证一下环境。Thonny 右下角解释器选择 MicroPython (Raspberry Pi Pico)连接成功后可以在 Shell 窗口输入print(ok)能看到ok输出就说明固件没问题。这里有个特别容易忽略的点USB 线一定要用支持数据传输的不能是那种只充电的数据线。我至少见过三个人因为线的问题卡在“连不上开发板”这一步。2.2 温度获取方案板载 ADC4 还是 DS18B20做温度记录先得有温度来源。树莓派 Pico 的 RP2040 芯片内部集成了一个温度传感器连接到 ADC 通道 4。好处是零接线烧录固件后直接读就能出温度用于验证文件读写流程非常合适。缺点是它测量的是芯片内部温度不是环境温度而且每块芯片的校准可能有偏差精度一般。读取板载温度传感器的代码很短import machine sensor_temp machine.ADC(4) reading sensor_temp.read_u16() voltage reading * 3.3 / 65535 temperature 27 - (voltage - 0.706) / 0.001721公式来自 RP2040 数据手册。read_u16()返回 0 到 65535 之间的整数先换算成电压再按设定好的线性的关系算出温度。要注意的是3.3是 ADC 参考电压Pico 的 3V3 引脚实测可能略有偏差所以这个温度和精密温度计对不上是很正常的。如果要做“真正测环境温度”的项目我更推荐 DS18B20。它是一颗数字温度传感器接线稍微多一点VCC 接 3V3GND 接 GND数据线 DQ 接 GPIO 引脚比如 GPIO0同时 DQ 和 VCC 之间要加一个 4.7kΩ 上拉电阻。读取代码需要使用onewire和ds18x20这两个 MicroPython 内置模块import machine import onewire import ds18x20 import time ow onewire.OneWire(machine.Pin(0)) ds ds18x20.DS18X20(ow) roms ds.scan() ds.convert_temp() time.sleep_ms(750) temp ds.read_temp(roms[0])scan()会在数据线上找传感器找到后convert_temp()发起一次转换等待 750 毫秒左右再读取。找不到传感器的话九成是接线问题尤其是上拉电阻忘了接。对于本教程来说先用板载 ADC4 跑通文件读写再换 DS18B20 是更顺的学习路径。3. MicroPython 文件读写与 CPython 有差异的细节3.1 open() 的完整参数和常用模式熟悉 Python 的人不会对open()陌生。MicroPython 在 Pico 上保留了同样的用法f open(/data.txt, w) f.write(hello) f.close()文件模式有以下几种r只读文件不存在会报OSError。w写入会清空覆盖原文件。a追加在文件末尾续写文件不存在就创建。r可读也可写不会清空文件。x独占创建文件已存在会报错。MicroPython 的文件对象同样支持迭代读取也就是说for line in f:这种写法是能用的。所以我强烈建议用with语句管理文件句柄写完了自动关闭不用手动记close()with open(/temperature.csv, a) as f: f.write(1,26.5\n)不过有一点必须明确Pico 上的文件系统和电脑上的文件系统是隔离的。你在 Pico 上运行的程序open(data.txt)操作的是板子 flash 里的文件不是 Thonny 电脑上的文件。这也是很多入门者最容易搞混的地方。3.2 和 CPython 不同的几个重点MicroPython 文件读写整体上很接近桌面 Python但这些差异会直接影响你是否踩坑第一个是encoding参数不可用。在电脑上你可能会写open(a.txt, w, encodingutf-8)但 MicroPython 的open()不接受encoding参数传了会直接TypeError。默认编码就是 UTF-8所以不用管。第二个是路径问题。MicroPython 里根目录是/建议所有文件名都写绝对路径例如/temperature.csv。如果你只写temperature.csv能不能找到文件取决于当前工作目录而这个目录在不同运行方式下可能不一样。最省心的方式就是始终带/。第三个是os模块。文件操作不只是open还要配合这些函数使用import os os.listdir(/) os.stat(/temperature.csv) os.remove(/temperature.csv) os.rename(/old.txt, /new.txt) os.mkdir(/logs)os.stat()返回一个元组其中索引 6 是文件大小单位字节后面做文件轮转时要用到。第四个是flush()的作用。MicroPython 的文件写入有缓冲并不是每次write()都立刻写进 flash。close()时会自动提交但如果你程序跑了很久才关闭文件中途断电的话缓冲区里的数据就丢了。所以需要在关键位置手动调f.flush()强制把数据刷到存储介质。3.3 为什么传感器数据我推荐 CSV 格式温度数据本质上是一张二维表时间戳一列温度一列。CSV 是一种纯文本的表格格式每行一条记录字段用逗号分隔。它的好处是肉眼直接能看懂还能用 Excel、pandas 等工具处理调试成本最低。JSON 当然也能用MicroPython 同样提供json模块。但传感器采样数据通常非常规整用 JSON 的嵌套结构反而多余而且解析时要多一层json.loads()。如果只是“一行一条记录”CSV 就够了。实际的 CSV 文件大概长这样timestamp,temperature 1710000000,25.83 1710000005,25.91第一行是表头后面是采样结果。这个格式带来的附加好处是即使某一行因为断电写坏了也不影响其他行解析时可以跳过坏行容错性比二进制格式好很多。4. 核心实战把温度写进 CSV 文件4.1 先跑通最小闭环写入然后读回我建议不要一上来就写完整记录器先做一次最简单的写入读取验证确认文件系统没问题。with open(/temperature.csv, w) as f: f.write(timestamp,temperature\n) f.write(1,26.5\n)然后在同一个 REPL 里读回来with open(/temperature.csv, r) as f: content f.read() print(content)如果能看到两行文本那么文件写入、关闭、读取的最小闭环已经通了。接下来任何复杂逻辑都建立在这个基础上。4.2 定时采样并追加写入的正式代码现在写正式的记录器。我用板载 ADC4 读取温度每 5 秒记录一次文件不存在时先写表头然后用追加模式不断写入。from machine import ADC import time import os sensor_temp ADC(4) def read_temperature(): reading sensor_temp.read_u16() * 3.3 / 65535 return 27 - (reading - 0.706) / 0.001721 filename /temperature.csv try: os.stat(filename) except OSError: with open(filename, w) as f: f.write(timestamp,temperature\n) for _ in range(20): temp read_temperature() now time.time() with open(filename, a) as f: f.write(%d,%.2f\n % (now, temp)) f.flush() time.sleep(5)几个重点说明一下os.stat(filename)用于判断文件是否存在。MicroPython 的os模块没有我常用的os.path.exists()用try/except OSError是最稳妥的写法。用a模式而不是w模式。w每次打开都会清空文件等你第二次采样时前面的记录就没了。a模式会在文件末尾追加保证历史数据不被覆盖。f.flush()很重要。因为代码使用了with每次写入后文件句柄关闭按理说数据会自动提交但为了确保异常断电时尽量少丢数据我在写入后立刻flush()一次。这个习惯可以在程序复杂后避免很多玄学问题。叠加固定次数而不是死循环。for _ in range(20)可以让你在测试时不会无限写入跑完 20 次看看结果确认没问题了再改成while True。4.3 给长期运行的记录器加异常保护真实记录器不能一遇到问题就崩溃。最常见的两个故障是磁盘空间满和传感器读取失败。可以用try/except把任务包起来while True: try: temp read_temperature() now time.time() with open(filename, a) as f: f.write(%d,%.2f\n % (now, temp)) f.flush() except OSError as e: print(记录失败:, e) time.sleep(10) else: time.sleep(5)如果 flash 满了代码不会直接traceback停掉而是打印错误并等待十秒再重试。传感器如果换成 DS18B20 之后产生读取异常也应该把读取温度的逻辑放进try避免程序崩溃导致后面的数据全部丢失。实际记录大量数据后不要无限追加到同一个文件否则文件会越来越大也会加速 flash 磨损。这个问题在下一个章节展开。5. 数据导出来读取、校验和简单分析5.1 在 Pico 上直接读取和统计数据记录完成后第一件事是确认文件里有没有数据。在 Pico 上可以直接读回来并做简单统计比如计算平均温度total 0.0 count 0 with open(/temperature.csv, r) as f: f.readline() # 跳过表头 for line in f: line line.strip() if not line: continue parts line.split(,) if len(parts) 2: continue try: total float(parts[1]) count 1 except ValueError: pass print(有效记录数:, count) print(平均温度:, total / count)这个脚本用了几个防御性写法。line.strip()去掉换行空行跳过parts line.split(,)按逗号拆分len(parts) 2可能是断电导致的半行数据try/except ValueError是防止某一行温度字段不是数字。这样即使记录文件里混入了残片统计分析也不会崩。5.2 把文件从 Pico 拷贝到电脑数据在 Pico 上只是临时存放最终还是要到电脑上做分析。最省事的方式是 Thonny左侧文件浏览器展开树莓派 Pico找到temperature.csv右键选择“下载到”本地目录。如果你更习惯命令行可以用mpremote工具pip install mpremote mpremote cp :/temperature.csv ./temperature.csv注意命令里的冒号它表示设备端。上传本地文件到 Pico 则是mpremote cp ./local.txt :/remote.txt。还有一个细节Thonny 连接串口时会占用设备此时命令行里跑mpremote可能会提示串口被占用建议先断开 Thonny 连接再执行。5.3 到电脑上用 pandas 做进一步分析把 CSV 拉回电脑后分析就简单了。pandas 一行代码读取import pandas as pd df pd.read_csv(temperature.csv, header0) print(df.describe()) df.to_excel(temperature.xlsx, indexFalse)describe()会输出记录数、平均值、标准差、最小值和最大值对观察温度波动非常直观。to_excel()可以把结果转成 Excel 表格方便给别人看。需要特别提醒的是pandas 以及to_excel必须在电脑上运行不能塞进 Pico 内存里。Pico 只有几百 KB 可用内存处理几万行 CSV 都不现实它的任务是采集和保存分析交给电脑完成就好。6. 我在这个项目里踩过的坑文件系统与写入时机6.1 路径坑在 Thonny 里运行却找不到文件我最早写记录脚本时用了open(temperature.csv, w)在 Thonny 里第一次运行一切正常但脚本第二次运行却报了FileNotFoundError。排查了很久才发现问题出在“当前工作目录”上。Thonny 运行脚本时MicroPython 的工作目录不一定是根目录尤其是在你用 Thonny 打开电脑上的脚本文件时Pico 上实际的当前目录可能早就变了。从那之后我的习惯是所有文件路径一律从/写起。要确认程序当前从哪个目录运行可以在脚本开头打印import os print(os.getcwd()) print(os.listdir(/))这样能快速定位文件到底被写到了哪里。如果文件不在预期目录去根目录列表里找即可。6.2 缓冲区生效的时机另一个印象深刻的问题是“写入后没有 close数据看起来消失了”。当时我图省事直接写了f open(/a.txt, w)然后f.write(hello)没有立刻关闭文件。结果 Pico 复位之后文件里什么都没读出来。原因在于文件写入有缓冲数据还留在内存缓冲区并没有真正落到 flash。解决方式就是写完后close()或者用with块自动关闭。如果是长时间保持句柄反复写入要定期flush()让数据及时提交。不过这里也有一个平衡问题。flash 的擦写次数是有限的如果每秒钟都flush()对 flash 寿命不友好。我的做法是采样数据先攒几十条批量写入一次然后flush()。这样既保证数据基本不丢又减少 flash 写入频率。当然如果项目要求极端可靠那就必须每一条都flush()两者要按实际需求取舍。6.3 flash 空间到底能存多少行树莓派 Pico 标准版板载 flash 是 2MBMicroPython 固件占掉一部分后用户可用空间通常只剩 1MB 多一点。一行1710000000,26.42加换行大约是 19 个字符也就是 19 字节。粗略估算 1MB 能存 5 万行左右。如果每 5 秒写一条一天是 17280 条大约能连续记录 3 天。如果每 30 秒写一条撑 18 天没问题。但连续跑几个月的记录器肯定要处理空间问题。空间满的时候写入会报OSError: [Errno 28] No space left on device。所以我在长期记录脚本里都会捕获这个异常然后删除最旧的备份文件或者干脆停止记录并闪灯提示。6.4 Excel 打开 CSV 乱码和文件末尾残片把 CSV 从 Pico 下载到 Windows 电脑用 Excel 直接打开有时候会看到乱码。原因不是数据坏了而是 MicroPython 写的是 UTF-8 无 BOMWindows 的 Excel 默认按本地编码去猜猜错了就乱码。最省事的方案是 CSV 表头和数据都只用 ASCII 字符不写中文。比如timestamp,temperature就不会出问题。另一个现象是末尾不完整。运行中直接拔电最后一行可能只写了一半比如1710000010,25.就没有换行。遇到这种情况不用紧张这是断电瞬间写入中断的正常结果。解析时用line.split(,)后判断字段数以及float()转换时捕获ValueError就能跳过这些残片。7. 还能怎么玩记录频率、文件轮转与外部存储7.1 采样频率需要匹配物理量变化速度温度变化很慢5 秒甚至 30 秒记录一次都完全够用。但如果你换成了加速度计、光线传感器或者正在测量快速的瞬时变化文件系统写入速度就是瓶颈。MicroPython 在 Pico 上写文件一个循环大概几毫秒到几十毫秒但真正的问题不是单次速度而是 flash 寿命。高频写入对 flash 的磨损非常明显。我个人的建议是传感器数据先在内存储量里累积达到一定数量后一次性写入文件并flush()把写入频率从“每次采样都写”降到“每 N 次采样写一回”。这种批量写入模式能显著减少 flash 的擦写次数。代价是一旦断电最近一批未落盘的数据会丢失这个可以用电池、电容或者定期上报机制去弥补。7.2 按日期生成文件或者按大小轮转长期记录一定不能只有一个文件。最简单的策略是按日期命名t time.localtime() filename /temp_%04d%02d%02d.csv % (t[0], t[1], t[2])这样每天自动生成一个新文件方便归档也避免单个文件过大。如果采集周期很短当天文件也可能很大可以改成按大小轮转filename /temperature.csv if os.stat(filename)[6] 50 * 1024: os.rename(filename, /temperature_%d.csv % time.time()) with open(filename, w) as f: f.write(timestamp,temperature\n)os.stat(filename)[6]取的是文件大小单位字节。文件超过 50KB 就改名为带时间戳的历史文件然后新建当前文件。这样每个文件都保持在一个可控大小flash 空间也能被合理分配。7.3 接入外部传感器扩展记录维度文件读写这套逻辑完全不依赖你用什么传感器。前面提到 DS18B20 测温度DHT11/DHT22 测温湿度也都是同样写法。DHT22 的读取代码甚至比 DS18B20 还简单import dht import machine d dht.DHT22(machine.Pin(2)) d.measure() print(d.temperature(), d.humidity())把d.temperature()和d.humidity()写进 CSV就变成了一个温湿度记录器。如果接多个 DS18B20可以循环扫描roms列表把每个传感器地址和温度都记录到一行里。文件格式变一下即可核心思路不变。7.4 把本地记录变成联网数据链路如果你用的是 Pico W 或 Pico 2 W还可以把记录和上报结合起来。采集到的温度先写入本地文件作为备份同时通过 MQTT 或 HTTP 上报到服务器。网络断掉时程序只写本地文件网络恢复后再读取文件补充上传。这样一来文件系统就不仅仅是“入门练习”而是一个可靠数据管道的兜底环节。我自己做数据采集项目第一件事永远是先把原始数据稳定落盘再想上报、展示或者分析。这个温度记录器虽然简单但它把这条最可靠的路趟通了。接下来换任何传感器、存任何格式都是同一套文件读写的思路在延续。
返回列表