ARTICLE DETAIL

资讯详情

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

树莓派Pico温度记录实战:MicroPython文件读写入门教程

树莓派Pico温度记录实战:MicroPython文件读写入门教程 树莓派 Pico 这个小板子是我玩单片机这几年里觉得最“亲民”的一颗 MCU。今天要聊的项目名字挺朴素——MicroPython 文件读写入门教程树莓派 Pico 温度数据记录实战——但它把嵌入式开发里最常用、也最容易出暗坑的两件事串在了一起从传感器读数据再把数据可靠地写进文件系统。这个项目不需要额外接一堆传感器Pico 板载的温度传感器就能采集温度我们用 MicroPython 把它读出来再存到板载 flash 上的文件里实现一个真正的“数据记录器”。整个过程不依赖电脑串口持续在线Pico 自己跑、自己存后面把文件导出来就能分析。对刚入门 MicroPython 的朋友来说这是一个非常合适的练手项目硬件成本极低、代码量不大但涉及的知识点很完整——模拟量采集、文件系统、文本格式化、异常处理全都覆盖了。我接下来会把设计思路、文件系统原理、完整代码、实测过程、踩坑经验都摊开讲尽量让第一次接触 Pico 的人也能照着做出来。1. 项目怎么来的为什么选 Pico 做温度记录器1.1 板载温度传感器省掉了外接硬件的麻烦做温度记录器通常想到的方案是拿一个 DS18B20 或者 DHT11 接在单片机上去读。但 Pico 有个别的开发板少有的特点芯片内部自带一个温度传感器连接到 ADC 通道 4不需要外加任何元器件。这个设计本来是帮开发者监控芯片工作温度的但拿来做环境温度参考也够用。精度不算高误差一般在 ±2℃ 左右但用来做趋势记录、温度变化监测完全足够。如果你想测的是室温变化、设备散热情况或者只是学习数据记录的工作流程这个内置传感器就是零成本起点。我这里也提一句Pico 内置温度传感器测的是RP2040 芯片内部的温度不是直接测量空气温度。芯片运行时自身会发热读数会比环境温度偏高几度。如果要做精确环境测量建议外接 DS18B20 这类数字传感器但项目逻辑是完全一样的——换一下读取函数就行。这篇教程先用内置传感器把流程跑通。1.2 为什么用文件读写而不是一直往串口打印很多新手做完采集之后习惯性就是print()往串口输出电脑上开着串口助手看着数据滚动。这种方式的问题在于数据没有落地。一旦拔掉 USB 线或者 Pico 意外重启前面采集的数据全丢。而且串口打印速度再快也没法在离线场景下工作。文件读写的意义就是“持久化”。把温度值格式化成一行的文本追加写到文件里数据就永久保存在 Pico 板载的 flash 闪存中。设备可以脱离电脑独立运行之后用一根 USB 线把 Pico 重新连上直接通过 Thonny 打开文件就能看到历史数据。选择 MicroPython 来做还有一个非常实际的原因它对文件操作足够友好。open()、write()、read()这些函数用法和电脑上的 Python 几乎一样完全不需要像 C 语言那样管理文件描述符、处理缓冲区。在嵌入式开发里MicroPython 把“读写文件”这个门槛降得非常低这正是它适合入门的原因。1.3 硬件和软件清单做这个项目需要的东西非常少树莓派 Pico 或 Pico W 开发板 1 块Micro-USB 数据线 1 根注意要选能传数据的线有的线只能充电电脑一台且能上网Thonny IDE 或 mpremote 命令行工具预算基本就是一块 Pico 的钱二三十块搞定。如果你手头有 Pico W也没问题同样的代码可以跑只是 WiFi 模块不参与这个项目。软件方面重点准备两样MicroPython 固件和Thonny。Pico 出厂默认不带 MicroPython 固件需要先烧录。这个流程不复杂但有几个细节容易踩坑我在后面“实操记录”部分会专门说。2. 先搞懂文件系统再动手写代码2.1 MicroPython 的虚拟文件系统到底是什么MicroPython 里操作文件很多教程直接让你抄代码但我觉得至少要搞明白文件系统这层概念否则报错都不知道去哪查。MicroPython 底层有一套**虚拟文件系统VFS**机制。它把 flash 存储、SD 卡、甚至外部 SPI Flash 都抽象成统一的文件系统。在 Pico 上默认用的是LittleFS。LittleFS 是一个专门为嵌入式设备设计的小型文件系统特点是省空间、防断电损坏、支持磨损均衡。这和电脑上常见的 FAT32、NTFS 不完全一样但在 MicroPython 里你用open()读写文件时不需要关心底层是小文件系统还是别的接口是一样的。可以把它理解成一个微型 U 盘Pico 的 flash 就是 U 盘的存储颗粒LittleFS 就是 U 盘的文件分配表而你用 MicroPython 写的文件名就是 U 盘里的文件。在 REPL 环境里可以用这些命令直接查看文件系统import os # 列出当前目录所有文件 print(os.listdir(/)) # 查看某个文件的详细信息 print(os.stat(/data.txt))如果你用 Thonny 把 Pico 连接好左侧文件浏览器里可以直接看到板子上的文件列表手动删除、重命名都可以。非常直观。2.2 文件打开模式r、w、a 的区别MicroPython 文件操作的核心就是open()函数。第一个参数是路径第二个参数是打开模式。很多入门教程只是让你照抄w或r但没有讲清楚三个模式的区别导致数据被覆盖的问题非常常见。我用一张表说明模式含义文件不存在时文件存在时文件指针位置r只读报错 OSError正常打开文件开头w只写覆盖自动创建清空原有内容文件开头a只写追加自动创建保留原有内容文件末尾r读写报错正常打开文件开头w读写覆盖自动创建清空原有内容文件开头a读写追加自动创建保留原有内容文件末尾做数据记录时最常用的就是a模式。每次打开文件写入新数据内容会追加到文件末尾之前的数据不会丢。如果你误用了w第二次运行程序时文件会被清空前面的温度记录全部消失——这是最容易踩的坑而且是静默发生的没有任何提示。我的建议是凡是做日志、记录、追加采集数据一律用a模式。还顺便说一个问题MicroPython 并不要求你用with语句但强烈建议用。下面这两种写法的区别在于文件是否正确关闭# 推荐写法 with open(data.txt, a) as f: f.write(26.5\n)# 不推荐容易忘记 close f open(data.txt, a) f.write(26.5\n) f.close()在 MicroPython 里如果程序异常中途退出忘记关闭的文件可能导致写入内容没有真正刷到 flash 上。虽然 MicroPython 的write()通常会直接写入但用with语句可以把资源管理交给解释器少操一份心。2.3 写入之后为什么需要 flush 和 close这一点值得单独拎出来说。很多人在 REPL 里测试时发现明明执行了write()但用文件编辑器打开看到的内容是空的或者不完整。原因在于写入缓存。MicroPython 在底层会做数据缓冲write()执行后数据可能还在内存缓冲区里并没有立刻物理写入 flash。只有执行flush()或close()时数据才会真正落盘。所以重要的数据写入推荐这种写法f.write(26.5\n) f.flush()如果只是每隔几分钟写一条数据可以不频繁 flush但如果要确保任何时刻断电都不丢数据就要每写一条就 flush 一次。不过 flash 写入次数有限频繁写入会加速磨损这里要按项目需求做个平衡。我这个温度记录器和大多数日志类项目一样几秒或几十秒写一条用 flush 没任何问题。3. 动手写温度采集与文件存储3.1 内置温度传感器的读取原理Pico 内置温度传感器的读取流程比外接传感器简单很多。它连接到 ADC 模拟数字转换器的第 4 通道。ADCAnalog-to-Digital Converter就是把模拟电压信号转换成数字数值的模块。读取代码一般是这样的import machine sensor_temp machine.ADC(4) # 通道4接温度传感器 raw sensor_temp.read_u16() # 读取16位原始值read_u16()返回的是 0~65535 的值代表传感器输出的电压经过 ADC 转换后的数字值。这个数字本身不是温度需要换算。RP2040 数据手册提供了一个换算公式在 MicroPython 社区里也形成了统一写法voltage raw * 3.3 / 65535 temperature 27 - (voltage - 0.706) / 0.001721这里的参数含义是传感器在 27℃ 时输出电压 0.706V温度每上升 1℃电压下降约 1.721mV。写成完整函数就是这样import machine import time def read_temperature(): sensor_temp machine.ADC(4) raw sensor_temp.read_u16() voltage raw * 3.3 / 65535 temp 27 - (voltage - 0.706) / 0.001721 return temp注意一点machine.ADC(4)这个对象不需要重复创建可以在模块初始化时就创建好提升效率。上面的写法是为了展示完整流程实际项目里应该在文件开头初始化一次。3.2 代码怎么组织先跑通一步再写循环我不建议一上来就写一个“完整”的采集程序因为一旦出错你很难判断是采集模块的问题还是文件写入的问题。我自己写项目的时候习惯分三步推进。第一步先确认能读到温度值import machine import time sensor_temp machine.ADC(4) def read_temp(): raw sensor_temp.read_u16() voltage raw * 3.3 / 65535 return 27 - (voltage - 0.706) / 0.001721 while True: print(温度:, read_temp()) time.sleep(2)这一步跑通之后说明 ADC 采集没问题。第二步测试文件写入with open(/test.txt, a) as f: f.write(hello pico\n)在 Thonny 里运行一次然后在文件浏览器里打开 test.txt看看有没有内容。这一步验证文件系统工作正常。第三步把两者结合起来做完整的记录程序。3.3 完整代码采集温度并写入文件下面是我推荐的一个标准示例代码真正做记录时可以直接参考import machine import time import os # 初始化 ADC 通道4读取内置温度传感器 sensor_temp machine.ADC(4) # 数据文件路径 DATA_FILE /temperature.csv # 采集间隔单位秒 INTERVAL 10 def read_temperature(): 读取 Pico 内置温度传感器温度值返回摄氏度 raw sensor_temp.read_u16() voltage raw * 3.3 / 65535 temp 27 - (voltage - 0.706) / 0.001721 return temp def init_file(): 初次运行时创建文件并写入表头 try: # 尝试以只读方式打开文件存在则跳过 with open(DATA_FILE, r): pass except OSError: # 文件不存在创建并写入表头 with open(DATA_FILE, w) as f: f.write(timestamp,temperature_c\n) def append_temperature(): 读取温度并追加写入文件 temp read_temperature() # 当前时间戳用系统运行时间模拟也可以接 RTC 模块 timestamp time.ticks_ms() line {},{:.2f}\n.format(timestamp, temp) with open(DATA_FILE, a) as f: f.write(line) # 打印到控制台方便现场观察 print(line.strip()) # 初始化文件 init_file() # 主循环 while True: append_temperature() time.sleep(INTERVAL)这个代码有几个设计点说明一下。数据格式用的是 CSV。温度和时间用逗号分隔后缀名用.csv。这样导到电脑上之后Excel 或 pandas 可以直接读取分析不用额外转换。init_file()函数先检查文件是否存在。如果文件不存在就创建并写入表头timestamp,temperature_c。如果文件已存在就跳过这一步。这样程序重启后不会覆盖旧数据也不会重复写入表头。时间戳先用了time.ticks_ms()。这是 Pico 上电后的毫秒数不是真实时间。如果只记录相对变化趋势这个够用。如果需要真实时间需要接一个 RTC 模块或者从电脑同步后面我会在扩展部分专门讲。3.4 为什么用 try-except 判断文件不存在这里有个细节判断文件是否存在MicroPython 里没有 Python 的os.path.exists()标准做法是尝试打开文件捕获OSError异常。try: with open(DATA_FILE, r): pass except OSError: # 文件不存在 ...有的朋友可能见过用os.listdir()判断这方法也行但要遍历目录代码反而啰嗦if DATA_FILE not in os.listdir(/): ...两种写法效果差不多我更推荐 try-except因为代码量少还能顺便处理文件损坏、路径错误等异常情况。4. 完整运行实录与调试记录4.1 从烧录固件到第一行数据这里我把实际操作过程完整记录下来给第一次用 Pico 的人参考。第一步烧录 MicroPython 固件从树莓派官网或者 MicroPython 官网下载.uf2固件文件。然后按住 Pico 板子上的 BOOTSEL 按键同时用 USB 线连接电脑。此时电脑上会出现一个名为RPI-RP2的 U 盘。把下载好的.uf2文件拖进这个 U 盘拷贝完成后 Pico 会自动重启固件就烧录好了。这个步骤里最常见的坑是连接后电脑没有出现 U 盘图标。原因通常是 USB 线质量差或者不支持数据传输——只能充电的线是不会出现 U 盘的。此时换一根数据线试试大多数问题都能解决。第二步打开 Thonny选择正确的解释器Thonny 安装好之后打开菜单“运行”选择“选择解释器”在列表里找到MicroPython (Raspberry Pi Pico)。如果列表里没有需要先安装 MicroPython 插件或者更新 Thonny 到最新版本。选对解释器之后Thonny 右下角会显示MicroPython (Raspberry Pi Pico) USB 串行设备。此时就可以在 Shell 窗口直接输入 Python 命令测试了。第三步先手动测试一下文件写入在 Shell 窗口输入with open(test.txt, w) as f: f.write(temperature test\n)然后到 Thonny 左侧的文件浏览器里双击test.txt确认内容已经写入。这步能确认文件系统正常工作同时也能确认你对设备的读写权限没问题。第四步运行完整项目把上面的完整代码复制到 Thonny 编辑器里点击“运行当前脚本”。Shell 窗口每隔 10 秒就会打印一条数据。同时左侧文件浏览器里会生成temperature.csv文件。可以趁运行的时候在板上找到 ADC 附近的位置摸一下芯片——温度读数会明显上升然后过一会儿回落这是个很有趣的实时反馈。4.2 循环采集的实测效果我这里跑了一个小时数据文件大概是这样的timestamp,temperature_c 4698,28.31 14698,28.17 24698,28.44 ...因为我设置的采集间隔是 10 秒一个小时大概产生 360 条数据文件大小在 15KB 左右。相对于 Pico 板载 flash 存储空间Pico 有 2MBMicroPython 文件系统可用大概 1.5MB这个容量可以连续记录很久。如果你希望记录得更密可以把INTERVAL设为 1也就是每秒一条一小时就是 3600 条文件也不大。但要注意越密集的写入越考验 flash 的寿命。后面“避坑经验”部分我专门说这个。4.3 数据导出到电脑采集完成后把数据从 Pico 导出的方法很简单。在 Thonny 左侧文件浏览器里找到temperature.csv右键选择“下载到…”保存到电脑本地即可。如果不用 Thonny也可以自己写个 mpremote 命令mpremote connect /dev/ttyACM0 cp :temperature.csv ./temperature.csvWindows 下端口名一般是COM3、COM4之类的根据实际情况调整。文件导出到电脑后用 Excel 打开或者用 pandas 读取import pandas as pd df pd.read_csv(temperature.csv) print(df.head()) print(df[temperature_c].mean())一次数据记录闭环就完成了采集、存储、导出、分析。5. 常见问题速查与避坑经验5.1 最常见的问题排查表我在教学和实际项目中遇到过很多次类似问题整理成一张表方便对照排查现象可能原因解决方法OSError: [Errno 28] No space left on deviceflash 空间被写满删除无用文件或定期将数据转存到电脑文件内容为空文件没有 close或没执行 flush使用with语句或写入后调用f.flush()数据每次都覆盖旧的用了w模式改成a追加模式ValueError: invalid ADC pinADC 通道号写错内置温度传感器必须是 ADC(4)温度读数异常偏高五六十度可能 ADC 浮动电压问题检查是否有外接负载影响供电或改用外接传感器对比程序跑一会后卡死写入频繁、flash 写入耗时加大INTERVAL或减少写入频率Thonny 连接不上 Pico驱动缺失或线材问题换数据线、重新插拔、重装驱动5.2 flash 寿命与磨损均衡Pico 板载 flash 用的是W25Q16 等 SPI NOR Flash 芯片理论上擦写次数在 10 万次左右。可能有人一看到 10 万次就慌了——每秒写一次三天就完蛋了这里要解释一下MicroPython 使用的是 LittleFS 文件系统它自带磨损均衡wear leveling机制会自动把写入分散到不同的物理块上。也就是说逻辑上你一直往同一个文件追加数据物理上数据其实分布在不同区块不会总擦写同一个位置。所以实际寿命比“10 万次除以每秒一次”要长得多。但我依然建议不要过于频繁地写入。如果是高频率数据采集最佳实践是先在内存里积累一批数据每攒到一定量或一定时间再批量写入一次。举个例子buffer [] def collect_data(): global buffer temp read_temperature() buffer.append({}\n.format(temp)) if len(buffer) 10: with open(DATA_FILE, a) as f: f.writelines(buffer) buffer.clear()这样把十次采集的数据攒到内存一次性写入既减少了写入次数也降低了 flash 磨损。5.3 掉电丢失数据的风险与对策这个可能是很多人在实际项目中遇到过的问题运行时一切正常数据也能写入但只要一拔电或者重启发现之前写入的一部分数据没了。原因还是 LittleFS 的缓存机制。某些情况下最后写入的数据还停留在内存中没有真正刷到 flash 上突然断电就会导致这部分数据丢失。所以如果项目要求任何时刻断电都不丢数据就要在每次写入后主动调用flush()with open(DATA_FILE, a) as f: f.write(line) f.flush()flush()执行后数据会尽力物理写入 flash。当然即使是flush()也不能保证 100% 抗断电极端情况还有 flash 写入过程中掉电导致数据损坏的风险但对于大多数记录任务来说每条数据都flush()已经足够可靠。从另一个角度想如果你的采集间隔很长比如一分钟一条那每一条数据都很宝贵丢一条都可惜所以这个场景下我强烈建议每条都flush()。如果采集间隔很短比如 10 毫秒一条那么丢失最后几毫秒的数据可能无所谓但也要保证批量丢数据不会影响整体。5.4 时间戳ticks_ms 不能满足所有需求代码里用的time.ticks_ms()只是相对时间也就是说你不知道某条数据对应的“真实时间”是几点几分。如果只是分析温度变化趋势这个够用但如果你要知道某一天某个时刻的温度就不行了。解决办法有几种接一个外部 RTC 模块比如 DS3231 或 DS1302通过 I2C 接口读取实时时间。从电脑同步时间如果你用 Thonny 连接着电脑MicroPython 有ntptime模块但 Pico 自身不带 WiFi除非你用 Pico W 并联网。Pico W 可以通过 WiFi 连接 NTP 服务器获取时间这个问题就迎刃而解。手动设置一个起始时间上电后从电脑串口输入当前时间后续按 ticks 递增计算。Pico W 用户可以直接这样拿到网络时间import ntptime import network wlan network.WLAN(network.STA_IF) wlan.active(True) wlan.connect(你的SSID, 你的密码) ntptime.settime()然后通过time.localtime()获取真实时间。我早期做数据记录器时也走过弯路用 ticks_ms 记录之后才发现没法对应具体时间后面才补上 RTC 方案。建议你从一开始就决定好只要是要长期记录必须上真实时间。6. 进阶玩法从“能跑”到“好用”6.1 数据格式升级从 CSV 到带日期时间前面提到用纯 CSV 存数据最简单。你如果加了 RTC 模块写入的每行可以带上完整时间格式比如2025-01-15 14:30:00,26.32。这样导出后直接可视化非常方便。时间戳格式化可以这样写import time # 假设 RTC 已经设置好 t time.localtime() timestamp {:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}.format( t[0], t[1], t[2], t[3], t[4], t[5] )6.2 增量记录温度变化快时加密采样固定频率采集的好处是简单坏处是在温度平稳时浪费存储空间。更聪明的方式是“变化驱动”记录温度变化超过阈值才记录平稳期自动降低采集频率。这个逻辑写起来也不复杂last_temp read_temperature() last_record_temp last_temp while True: temp read_temperature() if abs(temp - last_record_temp) 0.5: # 变化超过0.5℃才记录 append_temperature(temp) last_record_temp temp time.sleep(5)这样可以大幅减少数据量同时对温度变化的捕捉比固定间隔更灵敏。6.3 把数据自动拷贝到电脑如果你和我一样懒得每次手动在 Thonny 里点下载还可以写一个脚本自动拉取数据。关键工具就是 mpremotempremote connect auto cp :temperature.csv ./data/$(date %Y%m%d_%H%M%S).csv放到定时任务里每天自动从 Pico 把数据拉取到电脑归档Pico 端还可以继续记录新数据。这个用法适合长时间挂机运行的记录任务。6.4 把 Pico 变成无线传感器如果你用 Pico W可以再进一步把温度数据不仅写入文件还定期通过 WiFi 发送到电脑或云端的 MQTT broker。文件系统仍然作为本地备份留存网络作为实时通道。这种“本地存储 网络上报”的双通道方案是很多 IoT 数据采集设备的标准做法。核心代码大概是这样import network from umqtt.simple import MQTTClient wlan network.WLAN(network.STA_IF) wlan.active(True) wlan.connect(SSID, PASSWORD) client MQTTClient(pico_temp, 192.168.1.100, port1883) client.connect() # 每次采集后发送主题 client.publish(sensor/temperature, {:.2f}.format(temp))本地文件继续存网络消息也发送两边都不耽误。6.5 文件按天自动轮转如果记录时间很长所有数据堆在一个文件里文件会越来越大打开也越来越慢。可以做一个简单的“按天轮转”每天 0 点新建一个以日期命名的文件比如temp_20250115.csv。实现思路是每次写入前判断当前日期如果和当前文件名的日期不一致就新建文件。使用真实 RTC 时间才能做这个逻辑。current_date {:04d}-{:02d}-{:02d}.format(t[0], t[1], t[2]) if current_date ! CURRENT_DATE: DATA_FILE /temp_{}.csv.format(current_date) init_file() CURRENT_DATE current_date这样每天的记录独立成文件后期分析可按天归档也不担心单个文件过大。写在最后这个项目看着简单但麻雀虽小五脏俱全。做完一遍你会熟悉 Pico 的 ADC 读取、MicroPython 的文件系统、文件读写在不同模式下的区别还会踩一遍断电丢数据、文件被覆盖这类坑——这些经验在以后做更复杂的项目时都是通用的。我个人实际操作下来的体会是MicroPython 入门的捷径不是看一堆文档而是尽快做出一个能独立运行、持续产出数据的小东西。温度记录器就是这样一个恰到好处的起点——“传感器采集、数据落地、导出分析”这三步环环相扣每一步都有成就感。最后分享一个小技巧写完代码后把main.py保存到 Pico 里再按一下板上的 RESET 键。这样 Pico 会在上电时自动运行main.py完全脱离电脑独立运行。插上充电宝放在房间里它就是一台小小的温度记录仪了。这个“脱机运行”的感觉和连在电脑上跑程序是完全不一样的建议你一定试一下。
返回列表