我第一次拿到 ESP32 开发板时差点被装开发环境这件事劝退。Windows 上装 Arduino IDE、下载 ESP32 扩展包光等那几百兆文件就够喝一壶的好不容易装完点个灯还要盯着编译进度条。后来把主力开发方式换成 MicroPython才发现 ESP32 的开发居然可以那么轻——不用编译一个串口终端就能和板子直接对话改一行代码立刻生效。这篇是微雪 ESP32 MicroPython 入门教程的第二篇专门讲 MicroPython 的基础知识固件怎么烧、REPL 怎么用、语法怎么写、GPIO 怎么控制以及几个绕不开的坑。给两类人看一是完全没碰过单片机的小白二是从 Arduino 转过来、想快速验证想法又不想被工程化流程拖住的开发者。1. 同样是点灯为什么我建议你先试 MicroPython1.1 从 Arduino 转过来时最直观的感受Arduino 的点灯程序长这样void setup() { pinMode(2, OUTPUT); } void loop() { digitalWrite(2, HIGH); delay(500); digitalWrite(2, LOW); delay(500); }同样的逻辑MicroPython 是这样from machine import Pin import time p Pin(2, Pin.OUT) while True: p.value(1) time.sleep_ms(500) p.value(0) time.sleep_ms(500)代码量其实没差多少真正拉开差距的是开发体验。Arduino 每次改完代码都要经历编译→烧录→等待复位的循环哪怕只是把延时从 500 改成 700也要重新走一遍流程。而在 MicroPython 里你可以直接在 REPL 里敲一句p.on()灯当场就亮不满意就敲p.off()整个过程不需要第二块电路板不需要重新烧录。这背后的原因在于 MicroPython 并不是把 Python 代码编译成机器码写进 flash而是把 Python 解释器本身放进了单片机。你的 .py 文件只是一份普通文本由运行在 ESP32 上的解释器逐行读进去执行。换个角度理解ESP32 上等于跑着一个微型 Python 环境你的代码和它之间的关系就像在电脑上运行一个 Python 脚本一样。1.2 省下编译时间到底省了什么很多从 Windows 编译 ESP32 项目慢到怀疑人生的人转用 MicroPython 之后最大的感叹就是没有编译这一步了。传统开发流程里写完代码、编译、烧录、看结果最少也要一两分钟如果编译链配置有问题光是解决环境问题就能浪费半天。MicroPython 把修改→验证的循环压缩到了秒级这对学习硬件尤其重要因为在调试阶段你需要大量地试错改引脚、改延时、改逻辑试错成本越低学到的东西就越多。我个人觉得这种交互式开发特别适合做原型验证。比如想验证某个 I2C 传感器能不能读数据在 REPL 里输几行代码马上就能看到返回值不行就换一个地址再试。这种敲键盘看反应的体验在传统嵌入式开发流程里很难体会到。1.3 谁适合用 MicroPython谁最好别勉强MicroPython 不是万能的它最大的代价是性能和资源。Python 解释器本身要占用一部分内存运行速度也比编译型语言慢一个量级。以下几种情况我建议你还是回到 C 或者 ESP-IDF需要做亚毫秒级精确时序控制的比如某些高速传感器、严格协议时序需要做大量浮点运算或者图像处理的需要极端低功耗、精细管理睡眠和唤醒时机的需要用到某些冷门外设库而 MicroPython 里还没有对应封装的。打个比方MicroPython 像自动挡汽车你不需要关心离合器怎么配合专注在路线和观察路况上而 Arduino 和 ESP-IDF 更像手动挡要花精力处理编译链接这些琐碎环节但性能上限更高。作为入门教程我建议你先用自动挡把路跑熟再决定要不要换手动挡。2. 固件烧录和 REPL先和板子说上话2.1 固件选型官方版还是板厂定制版要让 ESP32 跑 MicroPython第一步是往芯片里烧一份 MicroPython 固件。固件选择上有三条路固件来源优点缺点MicroPython 官方固件稳定、通用、更新及时文档和社区资料最多不含板厂特定硬件的驱动配置开发板厂商定制固件出厂就适配板载屏幕、传感器等外设开箱即用版本往往落后于官方可能带厂商自己的修改自己编译固件可以裁剪模块、启用自定义配置需要搭 ESP-IDF 工具链入门阶段不推荐微雪官方给的教程里通常会直接提供对应板卡的预编译固件下载下来就能烧。如果你的板子没有定制固件用官方固件也没问题无非是板载 LED 点亮后类型和引脚号和丝印对不上而已这个问题后面会讲到。入门阶段我建议选板厂提供的固件省心优先。2.2 烧录之前的驱动检查烧录需要开发板和电脑之间建立串口通信。现在的 ESP32 开发板基本都集成了 USB 转串口芯片常见的有 CP2102、CH340、CH9102 等。插上 USB 线后Windows 上打开设备管理器如果看到端口COM 和 LPT下多了一个 COM 口比如 COM3、COM7说明驱动正常。如果显示黄色感叹号那就先装对应芯片的驱动。这里有个细节很多人忽视不同芯片的驱动不要装混。CH340 和 CP2102 的驱动是独立的如果你电脑上之前装过其他设备用的串口驱动最好确认当前用的 COM 口对应的是哪颗芯片。实测中我遇到过 COM 口被占用导致烧录失败关掉串口监视工具就好。2.3 烧录姿势新手最省事的烧录工具是 Thonny。打开 Thonny在菜单栏选工具→选项→解释器左侧选MicroPython (ESP32)下方选择对应的串口然后点安装或更新 MicroPython按钮选择下载好的 .bin 固件文件确认后开始烧录。烧录过程中不要拔线等进度条走完会自动复位进入 MicroPython 环境。如果你想用命令行烧录推荐 esptoolpip install esptool # 先擦除 flash避免残留数据导致启动异常 esptool.py --port COM3 erase_flash # 烧录固件注意地址是 0x1000 esptool.py --port COM3 --baud 460800 write_flash -z 0x1000 esp32-xxxxxxxx.bin很多新板子带有自动下载电路也就是 USB 串口芯片会在烧录请求时自动把芯片拉进下载模式不需要手动按键。如果烧录时提示无法进入下载模式可以按住板上的 BOOT 按键插 USB先让芯片停留在下载模式再执行烧录命令。烧录完成之后原本 flash 里的内容会被清空包括之前可能烧过的 Arduino 程序。所以烧录前先想清楚有没有需要备份的配置。2.4 认识 REPLREPL 读作rep-el全称是 Read-Eval-Print Loop翻译过来就是读取-执行-打印-循环。简单说它是一个交互式命令行你敲一行 Python解释器执行一行并返回结果。Thonny 下方的 Shell 窗口就是这个东西。进入 REPL 后你会看到这样的提示符。试着敲几行 print(hello dash) hello dash 1 2 3 import sys sys.platform esp32第一个例子验证基础输出第二个验证运算第三个验证你的固件确实跑在 ESP32 上。REPL 是学习 MicroPython 最容易上手的地方因为所有尝试都是即时的不会弄坏板子也不会影响 flash 里的文件。3. MicroPython 语法速览够用就好3.1 变量与类型快速扫一眼MicroPython 采用了 Python 3 的语法核心写法和电脑上的 Python 几乎一致。变量不需要声明类型赋值就是定义count 10 # 整数 voltage 3.3 # 浮点数 name esp32 # 字符串 enabled True # 布尔值 data b\x01\x02 # 字节串串口通信常用有一点需要特别留意ESP32 上的浮点数是软件模拟的不是硬件 FPU 直接计算。所以浮点运算速度慢、精度还容易出问题。判断电压或者作阈值比较时尽量用整数运算非要算小数也要做好结果可能和电脑上 Python 不一致的心理准备。3.2 函数、类与模块用def定义函数用class定义类用import导入模块这些和普通 Python 没有区别import machine import time def blink(times, interval_ms): pin machine.Pin(2, machine.Pin.OUT) for _ in range(times): pin.on() time.sleep_ms(interval_ms) pin.off() time.sleep_ms(interval_ms) blink(5, 200)需要注意的是MicroPython 的标准库是精简过的。电脑上 Python 带的os、sys在单片机上只有一小部分功能math、random、json这些看规模裁剪很多第三方库在嵌入式环境根本没有对应版本。所以写 MicroPython 之前不要去背 CPython 的标准库文档而是先看这块板子上用help(modules)命令列出的模块列表。3.3 boot.py 和 main.py 是怎么跑的每次上电复位MicroPython 会按固定顺序执行两个文件先执行boot.py再执行main.py。boot.py习惯上放硬件初始化代码比如设置某个引脚默认电平、配置网络参数main.py放主循环逻辑。两个文件都不是必须存在的没有的话就直接进入 REPL。理解这个顺序对调试很有帮助。如果你发现某个引脚每次上电都被拉到高电平那很可能是boot.py里写了初始化逻辑。我在实际项目里就把 WiFi 配置放在 boot.py 里main.py 只负责应用逻辑重启后网络秒连。3.4 内存上的一课ESP32 的 RAM 大概 320KB听起来不少但对 Python 这种动态类型语言来说这点内存非常紧。一个由几千个小对象组成的列表就能吃掉一大片。在 REPL 里随时可以看剩余内存 import gc gc.mem_free() 114000 gc.mem_alloc() 192000如果代码运行中突然报MemoryError最常见的两个原因一是循环里不断创建新对象没有释放二是占用了超大缓冲区。解决办法是尽量复用变量、避免一次性读大文件、在关键时刻手动调用gc.collect()回收垃圾。别迷信自动垃圾回收嵌入式环境里它不一定来得及。4. 点亮一盏灯顺便搞懂 GPIO4.1 输出模式三行代码控制高低电平GPIO通用输入输出是单片机最基础的外设通过控制引脚输出高电平或低电平就能驱动 LED、继电器、蜂鸣器等设备。MicroPython 里用machine.Pin对象操作from machine import Pin p Pin(2, Pin.OUT) p.on() # 输出高电平LED 亮 p.off() # 输出低电平LED 灭 p.value(1) # 等价于 on() p.value(0) # 等价于 off()Pin(2, Pin.OUT)的意思是让 GPIO2 作为输出引脚。on()和off()是高电平/低电平的友好写法value(1)和value(0)是更底层的写法实际开发中两种都会用到。如果只是简单开关用on/off可读性更强如果要和 ADC 采集值做比较然后输出结果通常直接用value(阈值判断结果)。外接 LED 时必须串联一个限流电阻阻值 220Ω 到 1kΩ 都行。直接用 GPIO 引脚接 LED 不加电阻短时间能亮但电流过大轻则引脚发热重则烧掉 GPIO 控制器。别问我怎么知道的烧了一片引脚才长记性。4.2 输入模式按键和电平读取读取外部信号时把引脚设为输入模式from machine import Pin key Pin(0, Pin.IN, Pin.PULL_UP) print(key.value())Pin.PULL_UP启用内部上拉电阻使得按键未按下时读到高电平1按下后引脚接地读到低电平0。相反用Pin.PULL_DOWN则空闲时读到低电平。ESP32 内部上拉电阻的值通常很高适合做数字信号读取但不适合驱动外部负载。读取按键有个经典问题叫抖动。机械按键在按下瞬间电平会在高/低之间快速跳变好几次如果不做处理一个按键动作可能被当成多次输入。最简单的去抖方法是延时后再次读取from machine import Pin import time key Pin(0, Pin.IN, Pin.PULL_UP) if key.value() 0: time.sleep_ms(20) if key.value() 0: print(key pressed)先检测到低电平等 20ms 再检测一次如果仍然是低电平说明确实是按键按下而不是抖动。这个方法虽然土但够用且可靠。4.3 引脚编号的真相丝印不等于 GPIO 号这是新手翻车率最高的地方。开发板上丝印写的数字和代码里 Pin() 括号里的数字不一定是一回事。很多 ESP32 开发板丝印写的是D4、D5它对应的可能是 GPIO4、GPIO5也可能是别的有些板子丝印直接写 GPIO 号但板载 LED 的 GPIO 却可能在原理图的边角处。例如常见的 ESP32 DevKitC板载蓝色 LED 一般接在 GPIO2但有的版本接 GPIO1而某些 ESP32-S3、C3 小开发板的板载 LED 又可能是 GPIO48、GPIO8、GPIO0。所以拿到开发板第一件事就是去翻原理图确认每个丝印对应的真实 GPIO 号以及板载 LED 接在哪个引脚。这不是可选操作是必须操作。验证方法很简单在 REPL 里逐个尝试import machine, time for gpio in [1, 2, 4, 5, 8, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 27, 32, 33]: p machine.Pin(gpio, machine.Pin.OUT) p.on() time.sleep_ms(100) p.off()看哪次执行时 LED 亮了一下就知道板载 LED 对应的 GPIO 号了。如果板上有两个 LED就分两次跑或者每个引脚多亮几秒。5. PWM 和 ADC控制亮度与读取电压5.1 PWM呼吸灯原理与代码PWM脉冲宽度调制的本质是在一个周期内不断切换高电平和低电平通过改变高电平所占的时间比例也就是占空比来模拟不同的输出强度。LED 虽然一直在快速亮灭但人眼有视觉残留看到的就是从暗到亮的渐变效果。MicroPython 里用machine.PWMfrom machine import Pin, PWM import time pwm PWM(Pin(2), freq1000) # 频率 1kHz while True: for duty in range(0, 65535, 500): pwm.duty_u16(duty) time.sleep_ms(10) for duty in range(65535, 0, -500): pwm.duty_u16(duty) time.sleep_ms(10)duty_u16()的取值是 0 到 655350 对应完全低电平65535 对应完全高电平。这个 API 是较新固件推荐的写法老教程里常见的duty()返回范围是 0 到 1023因为 ESP32 的 LEDC 控制器是 10 位的但新固件做了归一化建议优先用duty_u16()。PWM 不只用来调光舵机也是靠 PWM 控制的。舵机一般需要一个 50Hz 的 PWM 信号0.5ms 到 2.5ms 的高电平脉宽对应 0 到 180 度角度换算成占空比大约是 2.5% 到 12.5%。用duty_u16计算时50Hz 对应周期 20ms所以高电平 1ms 就是 5% 占空比duty_u16(3277)左右。5.2 ADC从模拟世界读数ADC模数转换器把模拟电压变成数字值。ESP32 内置的 ADC 是 12 位的也就是把 0 到 3.3V 的电压范围映射成 0 到 4095 的整数。MicroPython 里这样使用from machine import ADC, Pin adc ADC(Pin(36)) # 创建 ADC 对象 adc.atten(ADC.ATTN_11DB) # 设置衰减到 3.3V 量程 value adc.read_u16() # 读取返回 0-65535atten()方法至关重要。不设置衰减的话ADC 只能测很小的电压范围实际读出来的数值可能只覆盖满量程的四分之一甚至更少。ADC.ATTN_11DB把输入范围扩展到约 3.3V适合大多数传感器场景。如果想更精确可以调成ADC.ATTN_6DB量程约 2V但分辨率会更高。read_u16()返回 0 到 65535方便和 PWM 的duty_u16()直接联动比如用 ADC 读电位器来控制 LED 亮度读到的值直接丢给duty_u16()就能实现拧电位器调亮度的效果中间不需要任何换算。这个联动设计是 MicroPython 比较贴心的地方。5.3 ADC2 与 WiFi 的冲突ESP32 内部有两组 ADCADC1 和 ADC2。ADC2 的通道在工作有个隐藏条件WiFi 开启时ADC2 会被系统占用你的程序读取相关引脚会得到无效数据或直接报错。更气人的是这个冲突不是立刻体现的可能在连接 WiFi 后突然采集数据异常。所以凡是需要用 ADC 通道做模拟量采集的设计优先接 ADC1 的引脚。ADC1 的常见引脚是 GPIO32、GPIO33、GPIO34、GPIO35、GPIO36、GPIO39。如果需要同时用 WiFi 和多个 ADC 通道预先分配好引脚资源别在上电后再查为什么某一路读不到值。6. 让程序上电自启文件系统与恢复手段6.1 断电重启后自动运行很多人的第一个 MicroPython 程序是在 REPL 里敲进去的断电之后什么都没了。要让程序每次上电自动运行需要把代码保存为开发板内的main.py。MicroPython 上电后的启动顺序是芯片硬件初始化MicroPython 解释器启动尝试导入并执行boot.py可选尝试导入并执行main.py可选进入 REPL也就是说只要你的程序保存在开发板 flash 里的main.py断电重启后就会自动执行不需要手动加载。这就是热词里常说的esp32 micropython 断电运行——本质是把程序固化到开发板文件系统而不是依赖电脑端 IDE 的运行按钮。6.2 文件传输把脚本放到开发板Thonny 是最简单的文件传输工具。写好脚本后菜单栏选文件→保存副本弹出窗口选择MicroPython 设备输入文件名main.py确认保存即可。保存成功后你在电脑编辑器里修改代码Thonny 也会提供一键运行到开发板的操作。如果不想用图形界面可以装ampy工具pip install adafruit-ampy ampy --port COM3 put main.py main.py ampy --port COM3 ls ampy --port COM3 rm boot.pyampy在自动化脚本场景特别好用比如批量给多块板子刷程序。它支持上传、下载、删除、运行文件和查看开发板文件系统基本覆盖日常需求。要注意一个 Windows 编辑器经常埋的坑文件保存时如果自动加了.txt后缀比如main.py.txtMicroPython 是不会执行它的。保存完在 Thonny 的文件浏览器里确认文件名不要凭空多出.txt。6.3 安全模式与救砖写了几次main.py之后你迟早会遇到这个场景程序里有死循环或者开机就报错导致板子反复重启连 REPL 都进不去了。这时候别慌MicroPython 提供了安全模式。具体操作是按住开发板上的 BOOT 按键然后按下复位或者重新上电保持 BOOT 按键按住 3 秒左右。进入安全模式后MicroPython 会跳过boot.py和main.py直接进入 REPL。然后在 REPL 里删除或改名出问题的文件import os os.remove(main.py) # 或者 os.rename(main.py, main_bak.py)如果连安全模式都救不回来那就重新烧录固件。烧录固件会清空整个 flash等于重装系统但至少板子还在。这种变砖又救活的过程每个 MicroPython 玩家都会经历几次经历之后你对启动流程、文件系统这些概念的理解会深很多。7. 实操中踩过的几个坑7.1 丝印、GPIO 号、板载 LED 傻傻分不清这个坑我到现在还在帮人排查。有一次帮朋友调一块小板子他在代码里写Pin(48, Pin.OUT)灯一直不亮。后来查原理图才发现那块板子的板载 LED 接的不是 GPIO48而是 GPIO0。丝印上明明印着IO48其实是另外一个引脚的丝印标号并非板载 LED 的编号。我的习惯是拿到新板子先做三件事打开原理图把所有丝印编号对应的 GPIO 号列一张表找到板载 LED、按键、USB 串口芯片对应的引脚在 REPL 里实测点灯和按键读取验证表格无误。做完这三步你后面写代码才不会天天试错。丝印信息只能作为物理位置参考真正的硬件连接关系永远以原理图为准。7.2 板载 LED 可能是 WS2812很多新款 ESP32 开发板尤其是 ESP32-S3、C3 系列的板子板载LED根本不是普通 LED而是 WS2812 RGB 灯珠。WS2812 是单总线可控彩灯需要发送特定时序的数据信号才能控制颜色你用普通 GPIO 输出高电平去驱动它它只会亮个默认颜色或者根本不亮甚至看着像坏了。判断方法很简单看板子上 LED 是否是一颗小巧的三色灯珠或者看原理图里 LED 旁边是不是标着 WS2812、SK6812 之类的型号。如果是控制方式完全不同得用neopixel模块import machine, neopixel np neopixel.NeoPixel(machine.Pin(48), 1) np[0] (255, 0, 0) # 红色 np.write()这种 RGB 灯珠是很有趣的显示设备但它和普通 LED 的驱动方式确实是两套逻辑第一次用千万别用点普通 LED 的方法去试。7.3 程序一跑终端就卡住不是故障不少新手遇到这种情况main.py 里写了个while True死循环保存并重启后Thonny 的 REPL 窗口敲命令没反应了以为板子死机了。实际上板子运行得好好的只是 REPL 被程序里的死循环占用了。在 Thonny 里按 CtrlC 会中断当前运行的 Python 程序回到 REPL 提示符。真死机和被占用是两回事被占用的时候CtrlC 能快速回到真死机了按 CtrlC 也没用只能按复位键。判断清楚这一点能省不少无意义的拔线操作。7.4 脚本写错导致无限重启有一种比死循环更难受的情况main.py 里执行到某一行就抛异常MicroPython 报错重启重启后又走到同样的一行再报错再重启形成无限循环。此时 REPL 窗口会被刷屏而且因为程序一直在重启你可能根本来不及按 CtrlC。这种时候安全模式是首选方案。按住 BOOT 上电进入安全模式然后立刻把 main.py 改名或者删掉。如果你手头还有另一台机器方便重新烧录也可以直接重刷固件但那样会把 flash 里所有文件清空比较粗暴。7.5 浮点和 print 的隐形开销浮点精度问题在嵌入式环境比电脑上更突出。0.1 0.2 ! 0.3这种经典现象在 ESP32 上一样存在所以做阈值判断时不要直接比较浮点相等而是比较差值小于某个容差if abs(value - 3.3) 0.01: print(voltage ok)另一个隐形开销是print()。在 REPL 里频繁打印调试信息看起来没什么但 print 走串口输出是有延时的。循环里每秒打印几百行串口输出就会成为瓶颈程序的实时性被打印拖慢。调试结束就删掉多余的 print或者只在关键节点打印一次。就我个人经验来说学习 MicroPython 最好的方式不是从头到尾看手册而是拿一块板子、一个 LED、一个按键在 REPL 里不断试错。故意写坏几次 main.py再亲手救回来你对整个启动流程和文件系统的理解会比看十遍教程都深刻。后面教程还会继续深入外部中断、I2C 传感器、WiFi 联网这些实战内容但今天这 7 个部分才是所有 ESP32 MicroPython 项目的共同地基。