花10几元买ESP32-C3,体验一下MicroPython (和CircuitPython)
本文章可以配合我发的一个视频食用: https://www.bilibili.com/video/BV1RV4y1e79H/
ESP32是近年很火的国产低成本MCU系列。模组自带WiFi、蓝牙、天线。本篇以ESP32-C3为主来讲。
ESP32系列的MCU一般只需要UART就可以烧录。ESP32-C3有USB D+/D-引脚,若用USB,可以不用UART也可以烧录调试。
这里不讲ESP-IDF的C语言/FreeRTOS开发,我通过MicroPython来体验这款系列的MCU。(CircuitPython是MPY的衍生品,目前不完善,只稍带提一下。)
小知识:Micropython在32位MCU上默认的整型数是30位的,同时也有文档说也能使用大整数
以下描述都是在Linux下进行。Windows用户请将串口
/dev/ttyUSB0
自行替换为Windows的COM
ESP32-C3
这个芯片型号是RISC-V架构。若使用MicroPython,那么MicroPython的native code或viper(这两个东西能让python写的东西运行更快)都尚未支持RISC-V。ESP32-Cx属于ESP32系列中的便宜精简低功耗系列。要求高的建议买ESP32系列其他型号(请查MicroPython文档中native对各个架构的支持情况)。
烧录MicroPython/CircuitPython到ESP32C3
-
使用
esptool.py
清除整个Flash(必须)。其中的esptool.py
来自ESP官方IDFesptool.py --chip esp32c3 --port /dev/ttyUSB0 erase_flash
-
烧录:
# 如果是旧些的micropython版本 esptool.py --chip esp32c3 --port /dev/ttyUSB0 -b 460800 write_flash -z 0x0 firmware.bin # 如果是新些的micropython版本(本例为4M Flash) esptool.py --chip esp32c3 --port /dev/ttyUSB0 -b 460800 write_flash -z 0x0 bootloader.bin 0x8000 partition-table.bin 0x10000 micropython.bin
如果型号是ESP32,则应把bootloader烧到
0x1000
用串口线(USB转UART)操作MCU,和在电脑和MCU的flash之间传文件
题外话:其实这里"python shell"这个叫法更准确的应该叫"REPL"
获取MCU的python shell
用mpremote获取MCU的python shell
mpremote是个针对串口线连接MicroPython设备情况下的操作工具。
在电脑上用pip安装mpremote。
使用mpremote连接MCU并获取python shell, 只需要简洁的mpremote
命令,不加参数时它默认使用串口0, 默认波特率是115200:
mpremote
用picocom (Linux) 获取MCU的python shell
可以使用picocom这个串口工具(Linux端的),它就是个通用串口工具
picocom -b 115200 /dev/ttyUSB0
有些板子因接线原因,用picocom还需要加
--lower-dtr --lower-rts
在MCU的flash和电脑之间互传文件:
mpremote cp 电脑文件.py :/
还有其他工具,如ampy、rshell等,不介绍,感觉mpremote最好
我未使用类似Thonny这样的IDE,下文全都在终端及编辑器中可以完成,,
GPIO点个灯
import machine
pin5 = machine.Pin(5, machine.Pin.OUT)
pin5.value(0)
pin5.value(1)
PWM输出电压方波信号
from machine import Pin
from machine import PWM
p1=Pin(1)
p1.init(mode=Pin.OUT)
pwm1 = PWM(p1, freq=5000)
pwm1.deinit() # 停止
ADC采个样
from machine import Pin
from machine import ADC
pin0 = Pin(0, Pin.IN)
ad0 = ADC(pin0, atten=ADC.ATTN_11DB)
ad0.read_u16()
ESP32-C3的ADC最大量程是0V至2.5V,需要把衰减设置为11DB才能达到这个量程,否则量程很小。
必须注意,使用wifi对ADC有极大影响,会产生许多突然的尖峰
让MCU连上wifi
ESP32系列支持Wifi。
连上wifi后就有办法使用无线python shell,还可以无线传文件以进行无线.py
OTA。
我们想要在Flash上创建wifi.py
、main.py
(在boot.py
之后固件会自动调用main.py
),实现启动后自动连接wifi
# wifi.py
import network
nic = network.WLAN(network.STA_IF)
nic.active(True)
nic.ifconfig( [ "192.168.5.20", "255.255.255.0", "192.168.5.1", "192.168.5.1" ])
nic.connect("ssid", "密码")
# main.py
import wifi
以上是MicroPython的。CircuitPython的wifi函数都不一样,略
一般让ESP连路由器的wifi。若是想在Linux电脑上设个专门的wifi让ESP来连,可以使用linux-router这个Bash脚本:
sudo lnxrouter --ap wlan0 ssid -p 密码 -g 192.168.5.1
通过webREPL无线操作MCU (python shell),以及无线传文件
若成功连上了wifi,就可以试试这个。webREPL是MicroPython带的东西,可以在电脑和MCU的Flash之间传文件上传、下载文件,也可以提供无线python shell。有了这个就能够快速更新.py
代码,相当于可以OTA。(它是通过websocket协议通信的。)
用python shell时还是串口线好用,无线webREPL的输入和回显有延时
CircuitPython那边,似乎还没有这样完整的一套无线shell和无线传文件的东西。有相关讨论、有一些文档,略看了一下,还不完整不易用。
让MCU端的webREPL服务自动启动
在MicroPython的python shell中:
>>> import webrepl_setup
WebREPL daemon auto-start status: enabled
Would you like to (E)nable or (D)isable it running on boot?
输入E
。然后会让你设置密码。完成之后它会改写boot.py
,并创建webrepl_cfg.py
用于记录密码
电脑端的webREPL客户端工具
把webREPL仓库里的文件下载到电脑上运行。里面的HTML可以用浏览器打开(Firefox不支持),即可以获得一个web GUI界面。虽然有web GUI,但频繁的操作及上传.py
文件当然是用CLI更快。
获取无线python shell
./webrepl_cli.py -p 密码 192.168.5.20
用webREPL在电脑和MCU之间传文件(无线)
用来当OTA升级程序真的快。
上传使用命令:
./webrepl_cli.py -p 密码 /电脑上的路径/test.py 192.168.5.20:test.py
上传了test.py
后,平时就可以在无线python shell里使用
exec(open('test.py').read())
来直接执行该文件
(用
import test
也会执行,但与exec
不一样。都懂)
偶尔用UDP代替串口接收输出
串口的默认115200的baud很慢。在有大量文字输出时,不如电脑用wifi UDP接收。经测试,电脑显示最快达到过1MB/s接收速度。
在电脑上:
nc -k -u -l 0.0.0.0 9995
在MicroPython那边:
import socket
u = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def log(s) :
u.sendto(str(s)+'\n' , ('192.168.5.1', 9995) )
log("xxxxxx")
但要注意:
- 测试
while
连续地使用UDP的sendto()
函数,有时会出现ENOMEM
错误(有时换个wifi ap可以避免)。要try
处理一下gc.collect()
、time.sleep_ms()
、重发。 - 而且,UDP是不保证收到的
串口高波特率
ESP32C3手册说支持5M baud。某开发板上的CH340C串口转USB芯片说支持2M baud。实测调成2M(即200kB/s)在连续发送时有字节丢失。调成1M baud(即100kB/s)则无问题。
如果想使用超过115200的波特率与电脑通信,电脑上的系统必须支持才行。我是仅在Linux上测试过可以。
简单的性能测试
MicroPython内存占用情况
经测试,gc.mem_free()
显示的可用内存,MicroPython最大时有约150kB(在gc.collect()
过之后)。据说自己编译MicroPython,改些参数,就可以让用户可分配的内存更多。而CircuitPython有87kB。这一芯片的实际内存是300kB+。
MicroPython速度性能情况
Python解析执行起来肯定比C慢(据说慢100倍)。
MicroPython的time
模块里有一些可以用于记录时间的函数:
import time
time.ticks_cpu() # 据说最精确
time.ticks_us() # 微秒
time.ticks_ms() # 毫秒
经试验,每通过python语句执行一个与硬件有关的操作,至少需要30us。
而CircuitPython则是用
import time
time.monotonic_ns()
可以看时间。
这速度,在有实时性高的需求时,不启用DMA或native code或viper肯定是不行的。但risc-v架构还不能使用MicroPython的native或viper。(所以目前ESP32的「C3」这个型号在这种情况下不建议选用)
另外,测试了MicroPython的长时间运行一个有实时性要求的任务。有些MPY版本运行了一整天都没问题,有些版本刚开始10分钟内没什么问题的,但几分钟后,开始有多次时不时的定时器中断不能及时响应的现象。所以,它未必是个100%可靠的实时系统,投入使用前最好认真测试。
乐鑫ESP32 MCU的初步体验总结
官方芯片手册文档写得不够完整,不如ESP-IDF完整。手册中会出现某部分篇章未完成(已是极少量了)之类的提示。
试过自己配置寄存器,结果出现过这样的状况:1. 发现过手册中的错误。 2. 按照手册里的软件流程做,失败。利用ESP-IDF里的examples才成功。
尽管不完美,还是值得尝试的。想入手的请到商城看一看
本文章可以配合我发的一个视频食用: https://www.bilibili.com/video/BV1RV4y1e79H/
黑科技:直接内存读写(类似指针)、操作寄存器、DMA
MicroPython支持用mem32
,mem16
,mem8
(是数组)来直接读写任何地址,也就可以配置MCU寄存器,理论上有办法使用那些MicroPython未整理的硬件功能。
MicroPython只对极少部分的型号添加了DMA支持,我们这个ESP32还没有支持DMA。理论上通过操作寄存器可让DMA工作(很麻烦,未试)。
CircuitPython目前未实现直接读写任何地址。
进阶:py与C结合-创造自己的python模块
MicroPython支持自己做usermod(用户模块)。
由于乐鑫官方ESP-IDF提供了大量example,比MicroPython已支持的多。
MicroPython的固件本身就是C写的,用ESP-IDF编译出来的。为了不浪费ESP-IDF的能力和C的速度,应该学习一下如何搞自己的python模块编译进MicroPython里。
创建usermod
文件夹并准备好文件:
micropython/ports/esp32/usermod/我的模块.c
micropython/ports/esp32/usermod/我的模块.cmake
编译的时候用命令:
cd micropython/ports/esp32
make USER_C_MODULES=$PWD/usermod/我的模块.cmake # (代替`idf.py build`这一条)
题外话:手动修改过sdkconfig文件后,要
rm -r build-xxxxxx/
再构建(重新构建很慢),修改才生效
usermod/我的模块.cmake
文件内容:
# Create an INTERFACE library for our C module.
add_library(usermod_我的模块 INTERFACE)
# Add our source files to the lib
target_sources(usermod_我的模块 INTERFACE
${CMAKE_CURRENT_LIST_DIR}/我的模块.c
)
# Add the current directory as an include directory.
target_include_directories(usermod_我的模块 INTERFACE
${CMAKE_CURRENT_LIST_DIR}
)
# Link our INTERFACE library to the usermod target.
target_link_libraries(usermod INTERFACE usermod_我的模块)
我们给“我的模块”设置两个可调用的函数:init1()
和see_init1()
。
c文件内容:
// Include MicroPython API.
#include "py/runtime.h"
// Used to get the time in the Timer class example.
#include "py/mphal.h"
uint32_t read_len;
uint32_t buf_size;
uint32_t sample_freq;
uint32_t channel_num;
// 4个参数的py函数 init1()
// 使用时先import我的模块,再 '我的模块.init(arg1, arg2, arg3, arg4)' 来调用
static mp_obj_t 我的模块_init1(
mp_uint_t n_args, const mp_obj_t *args
// 因为参数个数超过3,所以下面这几个不直接使用
// mp_obj_t read_len_obj,
// mp_obj_t buf_size_obj,
// mp_obj_t sample_freq_obj,
// mp_obj_t channel_num_obj
)
{
// (void)n_args; // unused, we know it's 4
read_len = mp_obj_get_int(args[0]);
buf_size = mp_obj_get_int(args[1]);
sample_freq = mp_obj_get_int(args[2]);
channel_num = mp_obj_get_int(args[3]);
mp_printf(&mp_plat_print, "read_len=%ld buf_size=%ld sample_freq=%ld channel_num=%ld\n", read_len, buf_size, sample_freq, channel_num);
return mp_const_none;
};
// 定义这个有4个参数的函数
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(我的模块_init1_obj, 4, 4, 我的模块_init1);
static mp_obj_t 我的模块_see_init1()
{
mp_printf(&mp_plat_print, "read_len=%ld buf_size=%ld sample_freq=%ld channel_num=%ld\n", read_len, buf_size, sample_freq, channel_num);
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_0(我的模块_see_init1_obj, 我的模块_see_init1);
// 用 MP_DEFINE_CONST_FUN_OBJ_X (X=0~3)来设置参数个数,最大3。4或以上要用另一个
// 所有函数要加进这里面
static const mp_rom_map_elem_t 我的模块_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_我的模块) },
{ MP_ROM_QSTR(MP_QSTR_init1), MP_ROM_PTR(&我的模块_init1_obj) },
{ MP_ROM_QSTR(MP_QSTR_see_init1), MP_ROM_PTR(&我的模块_see_init1_obj) },
};
static MP_DEFINE_CONST_DICT(我的模块_module_globals, 我的模块_module_globals_table);
const mp_obj_module_t 我的模块_user_cmodule = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&我的模块_module_globals,
};
MP_REGISTER_MODULE(MP_QSTR_我的模块, 我的模块_user_cmodule);
关于自定义py模块的官方文档:
- Implementing a Module — MicroPython latest documentation
- MicroPython external C modules — MicroPython latest documentation
- Argument parsing (自定义py模块函数参数解析)
其他
运行代码过程中查芯片的MAC地址
import ubinascii
import network
print ( ubinascii.hexlify ( network.WLAN(network.STA_IF).config('mac') , ':' ).decode().upper() )
import ubinascii
import bluetooth
ble=bluetooth.BLE()
ble.active(True)
print ( ubinascii.hexlify (ble.config('mac')[1] , ':' ).decode().upper() )
mpremote的一点详细用法
mpremote的resume
保证执行这个命令的前后不进行MCU的软复位
-
列出MCU上的文件列表:
mpremote connect /dev/ttyUSB0 resume ls
-
打印MCU上的文件到电脑终端:
mpremote connect /dev/ttyUSB0 resume cat file_on_mcu.txt
-
从电脑传文件到MCU:
mpremote connect /dev/ttyUSB0 resume cp 电脑上的文件 :MCU上的文件名
冒号表示是MCU上的路径(没有这个冒号的话,就在电脑本地上复制了。然而
cat
和ls
却没有冒号这个要求) -
传输单条python语句给MCU的Micropython去执行,并获取执行结果:
mpremote connect /dev/ttyUSB0 resume eval "py语句"
-
传文件到MCU的RAM内并让其Micropython马上执行(文件不会被写入Flash)
mpremote connect /dev/ttyUSB0 resume run /path_on_pc/xxx.py
mpremote还有其他功能,其文档在这里
关于芯片、模组版本
截止2025年初,ESP32-C3的芯片版本有
- ...
- Rev v0.3 (ECO3) (CONFIG_ESP32C3_REV_MIN_3)
- Rev v0.4 (ECO4) (CONFIG_ESP32C3_REV_MIN_4)
- Rev v1.1 (CONFIG_ESP32C3_REV_MIN_101)
而编译MicroPython(v1.24.1)要求芯片版本>=0.3,IDF 5.x。
ESP32-C3 datasheet v2.0 (2024.11) 写的芯片命名、芯片版本、GPIO数量的关系:
- ESP32-C3 v0.4 22
- ESP32-C3FN4(停产) v0.4 22
- ESP32-C3FH4 v0.4 22
- ESP32-C3FH4AZ (NRND) v0.4 16
- ESP32-C3FH4X(推荐) v1.1 16
(16GPIO的那些不再引出6个SPI FLASH的脚,不兼容原来的合宙ESP32-C3开发板的LED)
芯片丝印第二行是日期,最后一行的第二个字母代表芯片版本:
v0.0 XAXXXXXXXX
v0.1 XBXXXXXXXX
v0.2 XCXXXXXXXX
v0.3 XDXXXXXXXX
v0.4 XEXXXXXXXX
v1.1 XHXXXXXXXX
ESP32-C3 勘误表 v1.1 (2024.1) 写,ESP32-C3 模组量产了的之中只会含有以下版本芯片(及其对应识别码):
v0.3 XXXXXX
v0.4 M4XXXX
ESP32-C3的ADC若与DMA一起用有bug,bug影响直到rev v1.1还有。
某牌子ESP32-C3模组问题解决
我买了芯片ESP32-C3
的模组安信可 ESP-C3-32S
的开发板安信可 NodeMCU ESP-C3-32S-Kit
。开发板很小,没有任何多余的东西,还不如叫它「最小系统+最小连接板」。
烧录只需要以上加一条microUSB线就可以,不用买任何的232 TTL、烧录器之类的,开发板上有USB转串口的芯片。
另外,看文档说,改变接线后,可以启用USB JTAG(无需任何额外芯片),然后可以单步调试、看寄存器之类的(有对应的开源跨平台软件openocd)。这一开发板也引出了USB数据的两个pin
便宜是便宜,但买得不够好。这个模组配的Flash只有2M。MicroPython官方提供的bin文件(1.4M左右)虽然足够烧进去,但功能有问题。建议至少选4M Flash的。
不过还好
- 这里有个老外编译了2M Flash版本的ESP32C3的MicroPython的bin。版本号
1.16.0 210824 v1.16-236-gb51e7e9d0
,python 3.4.0。其中也包括他修改自己的安信可开发板,焊上缺失的两个开关管的说明。- (建议)我自己编译了MicroPython 1.19 for ESP32-C3 2M Flash
若选CircuitPython: 这是MicroPython的衍生版。它提供针对这一开发板的2M Flash固件adafruit-circuitpython-ai_thinker_esp32-c3s-2m-en_US-7.3.3.bin
。Python 3.4.0
关注一下
📺 Bilibili 收集有趣科技 和发布原创视频 | 🖥️ Github 有用的和没用的开源项目代码
玩而后赏
子曰,玩而予赏,善莫大焉?
又曰,玩而不赏,良心安焉?
写作不易,感谢支持!

虽然,小小玩意,不足挂齿;
亦是,卅年老刀,献丑于此。
其实,多赏非求,少许亦可。
进者,参观主页,玩物更多。
未联系作者获得同意前,不可转载
转载必须附上源地址,并连我博客上的宣传内容一并转载