见过circuitpython介绍的,一般都垂涎circuitpython新增的模块。例如可以py直接驱动摄像头、支持MP3软解、numpy库、矩阵LED驱动等。但是理想很丰满,现实很骨感,骨感到你认为有的东西,它其实没有(或者换了另外一种方式)。
circuitpython虽然说是基于micropython,但是实际上与micropython有很大的区别,别天真的认为micropython能跑的代码拿到circuitpython就可以跑起来。在此记录一下蛋疼的点以及对应应对方案:
1)基础模块不一样,没有machine,改用了microcontroller,其他的模块也是,造成micropython的代码在circuitpython里几乎无法运行。这叫什么基于micropython,完全已经改头换面了。。。
2)PIN接口不一样,无法根据参数动态指定PIN,我写了一段代码,根据名称将int值和PIN做了对应:
import sys
import gc
CIRCUIT: bool = (sys.implementation.name == 'circuitpython') # 是否circuitpython
if CIRCUIT:
# circuitpython的IO映射
import microcontroller
iomapper: dict[int, microcontroller.Pin] = {}
for pin_str in dir(microcontroller.pin):
pin = getattr(microcontroller.pin, pin_str)
if isinstance(pin, microcontroller.Pin):
iomapper[int(pin_str[4:])] = pin
然后iomapper[10]即获取到IO10
3)IO读写、SPI、I2C使用不一样:
使用另外的两个模块busio和digitalio
busio提供了SPI,而digitalio提供了IO电平读取和设置。
IO写类似如下:注意mpy是用的0和1表示高低电平,而cpy是用的True和False
def digital_write(self, pin: Pin | digitalio.DigitalInOut, value):
if CIRCUIT:
pin.value = (value > 0)
else:
pin.value(value)
4)键盘模块不一样,虽然做了简单的包装,但是完全把中断的接口屏蔽并封装了,重新包装一下,勉强还是可以用。之前的键盘模块整合circuitpython后的代码:
from lib.epui import *
import lib.time as time
if CIRCUIT:
from microcontroller import Pin
else:
from machine import Pin
"""
micropython switch check module
By @Jim 2023-2025
"""
class BaseSwitchButton:
"""
按钮接口
支持防抖及持续按的按键检测模块
"""
def check_continuous(self):
"""
检测是否在持续按下
:return:
"""
pass
def pull_state(self) -> tuple(bool, bool):
"""
拉取状态
:return: (期间是否被按下,是否被持续按下)
"""
return (None, None)
if CIRCUIT:
import lib.adafruit_ticks as adafruit_ticks
import keypad
class SwitchButton(BaseSwitchButton):
"""Switch Button Class
支持防抖及持续按的按键检测模块Circuitpython
"""
continuous_period = 1000 # 连续按多少秒进入持续触发
pins: list[Pin] = []
buttons = []
def __init__(self, pin_num: int):
self.__pressed = False
self.__continuous = False
self.__press_tick = 0
self.__stat_on = False # 是否按下状态
self.pin = iomapper[pin_num]
if self.pin is None:
raise ValueError(f'pin number {pin_num} not available')
self.key_number = len(SwitchButton.pins)
SwitchButton.pins.append(self.pin)
SwitchButton.buttons.append(self)
@classmethod
def init_keypad(cls):
SwitchButton.keys = keypad.Keys(tuple(SwitchButton.pins), value_when_pressed=False, pull=True)
@classmethod
def sync_button(cls):
while True: # 拉取所有的状态
event = SwitchButton.keys.events.get()
if event is None:
break
button = SwitchButton.buttons[event.key_number]
if event.pressed:
button.__pressed = True # 记录有按下
button.__press_tick = event.timestamp
button.__stat_on = True
if event.released:
button.__stat_on = False
def check_continuous(self):
if self.__stat_on: # 按下
if adafruit_ticks.ticks_diff(adafruit_ticks.ticks_ms(), self.__press_tick) > SwitchButton.continuous_period:
self.__continuous = True
self.__pressed = True
def pull_state(self) -> tuple(bool, bool):
result = (self.__pressed, self.__continuous)
self.__pressed = False
self.__continuous = False
return result
else: # micropython
class SwitchButton(BaseSwitchButton):
"""Switch Button Class
支持防抖及持续按的按键检测模块Micropython
"""
continuous_period = 1000 # 连续按多少秒进入持续触发
throttle_ms = 400 # 坑爹会触发多次,做一下节流
def __init__(self, pin_num: int):
"""
:param pin_num:
"""
self.__pin = Pin(pin_num, Pin.IN, Pin.PULL_UP)
self.__pin.irq(handler=self.__switch_change, trigger=Pin.IRQ_FALLING) # 下降沿触发
self.__pressed = False
self.__continuous = False
self.__press_tick = 0
def __switch_change(self, _):
if time.ticks_diff(time.ticks_ms(), self.__press_tick) < SwitchButton.throttle_ms: # throttle
return
self.__pressed = True
self.__press_tick = time.ticks_ms()
self.__continuous = False
def check_continuous(self):
if self.__pin.value() == 0: # 按下
if time.ticks_diff(time.ticks_ms(), self.__press_tick) > SwitchButton.continuous_period:
self.__continuous = True
self.__pressed = True
def pull_state(self) -> tuple(bool, bool):
result = (self.__pressed, self.__continuous)
self.__pressed = False
self.__continuous = False
return result
def sleep(self): # 休眠irq
self.__pin.irq(trigger=0)
def awake(self):
self.__pin.irq(handler=self.__switch_change, trigger=Pin.IRQ_FALLING) # 下降沿触发
if __name__ == '__main__':
my_switch = SwitchButton(12 if CIRCUIT else 2)
if CIRCUIT:
SwitchButton.init_keypad()
else:
from machine import disable_irq, enable_irq
print('start')
while True:
if CIRCUIT:
SwitchButton.sync_button()
my_switch.check_continuous()
pressed = continuous = False
if not CIRCUIT:
irq_state = disable_irq()
[pressed, continuous] = my_switch.pull_state()
if CIRCUIT:
time.sleep(0.1)
else:
enable_irq(irq_state)
time.sleep_ms(100)
if pressed:
print(f'OK 被{"持续按下" if continuous else "按下"}')
4)没有协程模块。这个没有打包在固件里,但是单独有py代码提供,看了下,似乎勉强能用(话说Micropython的协程也是打包的一堆py):
https://github.com/adafruit/Adafruit_CircuitPython_asyncio/tree/main
5)如果画图一般是从framebuffer开始,但是没有framebuffer,只有一个高度封装的framebufferio。这个没有打包在固件里,也有单独的py代码
但是……上面这个模块是用python重新实现的,居然没实现blit,这个最能提升性能的块拷贝居然没有。。。OMG,解决的办法就是移植,本人已将framebuffer1.20的版本移植到circuitpython(已提供固件),framebuffer1.20比1.19多了很多好东西,例如画椭圆(包括圆形,圆角矩形一下变得简单了),多边形填充,这样就解决了绘图的问题
好吧,没有也没关系,大不了咱从micropython去搬,请看相关文章《编译带smartconfig、framebuffer的circuitpython》
6)zlib只有decompress,没有DecompIO
circuitpython尽落下些好东西,decompress将解压缩的数据放到内存里。而DecompIO可以实现流式解压。比方说要处理一个10M的文本进行流式检索,那么DecompIO可以流失处理,而decompress则是无能力为了,又比方说可以把一个二叉树索引链表放到压缩文件里。。。等等
最后还是只能移植,本人成功完成了从micropython移植对应的功能到circuitpython的移植
7) 根分区默认挂载的是只读的,也就意味着所有的写操作都会失败。解决办法:在code.py添加:
import storage
storage.remount("/", False) #坑爹,默认挂载的是readonly
然后拔电重启
8)时间模块不一样,tuple里多了一个没鸟用的-1,自己写了一个time模块抹平了两个系统的差别:
"""
同时兼容micropython和circuitpython的时间模块
这样抹平了平台差异
@Jim 2023-07
"""
from lib.epui import *
if CIRCUIT:
from lib.adafruit_ticks import *
from time import *
def sleep_ms(ms: int):
sleep(ms/1000)
def sleep_us(us: int):
sleep(us/1000000)
wrap_mktime = mktime
wrap_localtime = localtime
def mktime(t: struct_time | tuple) -> int:
if isinstance(t, tuple) and len(t) == 8:
l = list(t)
l.append(-1)
return wrap_mktime(tuple(l))
else:
return wrap_mktime(t)
def localtime(secs: int = None) -> tuple:
s_t = wrap_localtime(secs)
s_t = list(s_t)
del s_t[-1]
return tuple(s_t)
else:
from utime import *
下面是本人编译的模块移植版本:
circuitpython模块一览
🐍Wi-Fi: off | REPL | 8.2.0-dirty\
Adafruit CircuitPython 8.2.0-dirty on 2023-07-22; S2Mini with ESP32S2-S2FN4R2
>>> help('modules')
__future__ countio msgpack sys
__main__ digitalio neopixel terminalio
_asyncio displayio neopixel_write time
_pixelmap dualbank nvm touchio
adafruit_bus_device errno onewireio traceback
adafruit_bus_device.i2c_device espidf os ulab
adafruit_bus_device.spi_device espnow paralleldisplay ulab.numpy
adafruit_pixelbuf espulp ps2io ulab.numpy.fft
aesio fontio pulseio ulab.numpy.linalg
alarm framebuf pwmio ulab.scipy
analogbufio framebufferio rainbowio ulab.scipy.linalg
analogio frequencyio random ulab.scipy.optimize
array gc re ulab.scipy.signal
atexit getpass rgbmatrix ulab.scipy.special
audiobusio hashlib rotaryio ulab.utils
audiocore i2cperipheral rtc usb_cdc
audiomixer i2ctarget sdcardio usb_hid
audiomp3 io select usb_midi
binascii ipaddress sharpdisplay uselect
bitbangio json smartconfig vectorio
bitmaptools keypad socketpool watchdog
board math ssl wifi
builtins mdns storage zlib
busio memorymap struct
canio microcontroller supervisor
collections micropython synthio
Plus any modules on the filesystem