На Хабре уже немало писали про обучающий микрокомпьютер BBC micro:bit, который в 2016 раздали всем британским школьникам, и сейчас он продаётся по $15. С прошлого года появились в продаже и micro:bit v2, в которых ОЗУ расширена с 16 КБ до 128 КБ. Неизменным остался форм-фактор: две кнопки для ввода, матрица 5х5 светодиодов для вывода, всё что сверх этого – подключайте через 25-контактный edge connector. Очевидно, что создатели задумывали micro:bit не как самостоятельное устройство, а как «мозг» для более сложного проекта со внешними датчиками, индикаторами, релюшками, сервоприводами и т.п. – этакий «детский Arduino».
Энтузиасты из Шэньчжэня, взявшие себе название KittenBot, решили заполнить пустующую нишу «обучающий микрокомпьютер, который как micro:bit, но со всем необходимым для нескучных проектов — уже внутри». Их плата MeowBit, выпущенная в 2018, стоит $40; сохраняет edge connector, совместимый с micro:bit; и добавляет четыре кнопки-«джойстик», полноцветный TFT-экран 160х128, динамик, и силиконовую оболочку с отсеком для аккумулятора – всё, что нужно для создания мини-«геймбоя» размером с кредитную карточку. У MeowBit 96 КБ ОЗУ – до выхода micro:bit v2 это было ещё одним его существенным превосходством – и 2 МБ флеш-памяти, по сравнению с 256 КБ у micro:bit v1 и 512 КБ у micro:bit v2. Игры для MeowBit можно писать на MakeCode Arcade (диалект Scratch от Microsoft), Kittenblock (собственный диалект Scratch от KittenBot), или на MicroPython. На сайте KittenBot есть туториалы п�� использованию этих трёх языков, но весьма бедные, и увы, только на китайском.

MicroPython создавался для тех, кто привык программировать микроконтроллеры на Си, но хотел быиспользовать при этом синтаксис Python. Процесс разработки организован соответствующим образом: после загрузки MicroPython выполняет файл main.py из флеш-памяти, затем запускает REPL (интерактивную оболочку) на последовательном интерфейсе. Единственный способ запустить новый скрипт – перезагрузка с новым main.py. Единственный способ удалить из памяти переменные и импорты, оставшиеся от отработавшего скрипта – перезагрузка. Единственный способ прервать выполнение скрипта, вошедшего в бесконечный цикл – перезагрузка. Вдобавок, стандартный вывод MicroPython направляется на последовательный интерфейс, а не на экран; чтобы напечатать что-либо на экране, вместо стандартного
Осознавая это, в 2017 нью-йоркский стартап Adafruit начал разработку собственного форка MicroPython, получившего название CircuitPython. Эта реализация выполняет main.py каждый раз, когда файл с таким названием копируется через USB, и затем «обнуляет» среду, так что переменные из main.py не мешают ни REPL, ни следующему запускаемому скрипту. Чтобы остановить выполнение скрипта, достаточно удалить main.py через USB. В MicroPython нужно проявлять осторожность, чтобы во флеш-память не писали одновременно скрипт и компьютер через USB, иначе файловая система могла повредиться. В CircuitPython же перезапись main.py во время его выполнения – основной сценарий использования, так что ради предосторожности нужно выбрать один из двух вариантов – доступ ко флешу из Python только на чтение и через USB на чтение и запись, либо из Python на чтение и запись и через USB только на чтение, без возможности перезаписи либо удаления main.py. И ещё одна приятная фича – что до перехода в графический режим стандартный вывод дублируется и на последовательный интерфейс, и на экран. С другой стороны, MicroPython позволяет писать на Python обработчики прерываний (с рядом ограничений – например, в них нельзя создавать/удалять объекты на куче и нельзя бросать исключения), а CircuitPython – не позволяет, и не собирается добавлять такую
возможность.
Разница в подходе проявляется и в API. MicroPython стремится к краткости, CircuitPython – к абстракции:
Во втором примере разница в подходе наиболее наглядна: MicroPython предоставляет очень простую и прозрачную модель «создал массив значений пикселей, отправил его целиком на экран», навязывающую программисту довольно неудобный формат RGB565, потому что именно с таким форматом работает экран MeowBit (ST7735). Недостаток этой модели в том, что буфер 160х128х2 занимает 40 КБ – почти всю память, остающуюся свободной после загрузки MicroPython. Держать два таких буфера и отображать их поочерёдно – нет никакой возможности.
С другой стороны, CircuitPython навязывает программисту многоуровневую абстракцию:
Самое удивительное в CircuitPython – то, что он занимает не больше памяти, чем MicroPython:
свежезагруженной в MeowBit программе остаётся для работы 55 КБ и 53 КБ соответственно. Одна из причин – то, что большинство стандартных модулей CircuitPython ожидаются во флеш-памяти отдельными файлами, и не загружаются, пока не востребованы. (Таков, например, модуль
Один из моментов, вызванный разницей в подходах, хотелось бы разобрать подробнее: сложно представить игру без музыки и/или звуковых эффектов, но если на время проигрывания звуков игра будет приостанавливаться (как в примерах выше с
В MicroPython можно напрямую перенести напрашивающееся низкоуровневое решение – обрабатывать прерывание от таймера: функция
CircuitPython же не позволяет писать обработчики прерываний, так что понадобится намного более высокоуровневая реализация.
Но теперь фоновый звук будет проигрываться не сам собой, а только при регулярном «дёрганьи» генератора. Это склоняет к тому, ��тобы и остальные игровые процессы реализовать
в виде генераторов; например, заставка игры, прокручивающаяся вверх-вниз до нажатия любой
кнопки, реализуется следующим образом:
С одной стороны, реализация на MicroPython даёт заметно более качественный звук, потому что обработчик прерывания вызывается точно в заданное время, тогда как в CircuitPython на время перерисовки экрана (порядка 0.15 с) звук «подвисает». С другой стороны, код на CircuitPython легче писать, легче отлаживать и легче расширять, а реализация игровых процессов в виде сопрограмм-генераторов естественна и в отрыве от требований ко звуку.

Энтузиасты из Шэньчжэня, взявшие себе название KittenBot, решили заполнить пустующую нишу «обучающий микрокомпьютер, который как micro:bit, но со всем необходимым для нескучных проектов — уже внутри». Их плата MeowBit, выпущенная в 2018, стоит $40; сохраняет edge connector, совместимый с micro:bit; и добавляет четыре кнопки-«джойстик», полноцветный TFT-экран 160х128, динамик, и силиконовую оболочку с отсеком для аккумулятора – всё, что нужно для создания мини-«геймбоя» размером с кредитную карточку. У MeowBit 96 КБ ОЗУ – до выхода micro:bit v2 это было ещё одним его существенным превосходством – и 2 МБ флеш-памяти, по сравнению с 256 КБ у micro:bit v1 и 512 КБ у micro:bit v2. Игры для MeowBit можно писать на MakeCode Arcade (диалект Scratch от Microsoft), Kittenblock (собственный диалект Scratch от KittenBot), или на MicroPython. На сайте KittenBot есть туториалы п�� использованию этих трёх языков, но весьма бедные, и увы, только на китайском.

MicroPython создавался для тех, кто привык программировать микроконтроллеры на Си, но хотел быиспользовать при этом синтаксис Python. Процесс разработки организован соответствующим образом: после загрузки MicroPython выполняет файл main.py из флеш-памяти, затем запускает REPL (интерактивную оболочку) на последовательном интерфейсе. Единственный способ запустить новый скрипт – перезагрузка с новым main.py. Единственный способ удалить из памяти переменные и импорты, оставшиеся от отработавшего скрипта – перезагрузка. Единственный способ прервать выполнение скрипта, вошедшего в бесконечный цикл – перезагрузка. Вдобавок, стандартный вывод MicroPython направляется на последовательный интерфейс, а не на экран; чтобы напечатать что-либо на экране, вместо стандартного
print
надо использовать FrameBuffer: fb.text(s, x, y); pyb.SCREEN().show(fb)
– с явным заданием координат, без автоматического разбиения на строки и без автоматической прокрутки экрана. Для разработки на Си всё это естественно, но программисты на Python привыкли к намного большему комфорту.Осознавая это, в 2017 нью-йоркский стартап Adafruit начал разработку собственного форка MicroPython, получившего название CircuitPython. Эта реализация выполняет main.py каждый раз, когда файл с таким названием копируется через USB, и затем «обнуляет» среду, так что переменные из main.py не мешают ни REPL, ни следующему запускаемому скрипту. Чтобы остановить выполнение скрипта, достаточно удалить main.py через USB. В MicroPython нужно проявлять осторожность, чтобы во флеш-память не писали одновременно скрипт и компьютер через USB, иначе файловая система могла повредиться. В CircuitPython же перезапись main.py во время его выполнения – основной сценарий использования, так что ради предосторожности нужно выбрать один из двух вариантов – доступ ко флешу из Python только на чтение и через USB на чтение и запись, либо из Python на чтение и запись и через USB только на чтение, без возможности перезаписи либо удаления main.py. И ещё одна приятная фича – что до перехода в графический режим стандартный вывод дублируется и на последовательный интерфейс, и на экран. С другой стороны, MicroPython позволяет писать на Python обработчики прерываний (с рядом ограничений – например, в них нельзя создавать/удалять объекты на куче и нельзя бросать исключения), а CircuitPython – не позволяет, и не собирается добавлять такую
возможность.
Разница в подходе проявляется и в API. MicroPython стремится к краткости, CircuitPython – к абстракции:
MicroPython | CircuitPython |
---|---|
|
|
|
|
|
|
С другой стороны, CircuitPython навязывает программисту многоуровневую абстракцию:
Bitmap
произвольной цветовой глубины, позволяющий сэкономить память, когда одновременно используемых цветов не так много; Palette
, превращающая значения Bitmap в конкретные значения цвета, задаваемые в стандартном 24-битном формате, и автоматически конвертируемые в тот формат, с которым работает экран; затем TileGrid
, позволяющая сдвигать и масштабировать несколько спрайтов как одно целое; и наконец Group
, позволяющая переключаться между «стопками спрайтов». Для простых задач, типа отрисовки графика функции, все эти дополнительные абстракции со��ершенно лишние; но для разработки игр, скорее всего, программисту на MicroPython пришлось бы самостоятельно разрабатывать нечто аналогичное этой иерархии абстракций.Самое удивительное в CircuitPython – то, что он занимает не больше памяти, чем MicroPython:
свежезагруженной в MeowBit программе остаётся для работы 55 КБ и 53 КБ соответственно. Одна из причин – то, что большинство стандартных модулей CircuitPython ожидаются во флеш-памяти отдельными файлами, и не загружаются, пока не востребованы. (Таков, например, модуль
adafruit_framebuf
, предоставляющий интерфейс стандартного framebuf
из MicroPython.) Полный набор стандартных внешних модулей занимает больше 2 МБ и даже не помещается целиком во флеш-память MeowBit.Один из моментов, вызванный разницей в подходах, хотелось бы разобрать подробнее: сложно представить игру без музыки и/или звуковых эффектов, но если на время проигрывания звуков игра будет приостанавливаться (как в примерах выше с
delay
и sleep
), то играть будет очень неудобно. Как же реализовать фоновый звук в двух вариантах Python?В MicroPython можно напрямую перенести напрашивающееся низкоуровневое решение – обрабатывать прерывание от таймера: функция
play
будет добавлять записи в список music
, а обработчик handle_music
будет обрабатывать их по одной. Ограничения MicroPython не позволяют укорачивать список music
прямо в handle_music
по мере обработки записей, так что придётся пользоваться более низкоуровневыми средствами: продвигать в обработчике указатель next
, и удалять из списка обработанные записи лишь при следующем вызове play
.# `tim` и `ch` как в примере выше
music = []
countdown = 0
next = 0
# понимает подмножество синтаксиса QBasic PLAY:
# https://en.wikibooks.org/wiki/QBasic/Appendix#PLAY
def play(m):
global music, next
music = music[next:]
next = 0
octave = 1
duration = 75
n = 0
while n < len(m):
note = m[n]
if note >= 'A' and note <= 'G':
freq = [440, 494, 262, 294, 330, 349, 392][ord(note)-ord('A')]
music.append((freq * 2 ** (octave-1), duration * 7 / 8))
music.append((0, duration / 8))
elif note == 'O':
n += 1
octave = int(m[n])
elif note == 'L':
n += 2
l = int(m[n-1:n+1])
duration = 1500 / l
n += 1
def handle_music(t):
global countdown, next
if countdown:
countdown -= 1
if countdown:
return
ch.pulse_width_percent(0)
if next < len(music):
(freq, countdown) = music[next]
next += 1
if freq:
tim.freq(freq)
ch.pulse_width_percent(50)
bg_tim = Timer(1, freq=1000)
bg_tim.callback(handle_music)
CircuitPython же не позволяет писать обработчики прерываний, так что понадобится намного более высокоуровневая реализация.
handle_music
из повторно вызываемого обработчика превращается в генератор – это ещё и упрощает логику кода: включение динамика, задержка, и выключение динамика теперь идут в коде последовательно, так что можно обойтись без глобального countdown
. Кроме того, генератор может сам удалять из music
обработанные записи, так что упрощается и функция play
.# `pwm` как в примере выше
def sleep(duration):
until = monotonic_ns() + duration
while monotonic_ns() < until:
yield
def handle_music():
global music
while True:
if music:
(freq, d) = music[0]
music = music[1:]
if freq:
pwm.frequency = int(freq)
pwm.duty_cycle = 2**15
yield from sleep(d * 1.e6)
pwm.duty_cycle = 0
yield
Но теперь фоновый звук будет проигрываться не сам собой, а только при регулярном «дёрганьи» генератора. Это склоняет к тому, ��тобы и остальные игровые процессы реализовать
в виде генераторов; например, заставка игры, прокручивающаяся вверх-вниз до нажатия любой
кнопки, реализуется следующим образом:
play("L08CDEDCDL04ECC")
def scroll():
while True:
while splash.y > max_scroll:
splash.y -= 1
yield from sleep(3.e8)
while splash.y < 0:
splash.y += 1
yield from sleep(3.e8)
def handle_input():
while all(button.value for button in buttons):
yield
for _ in zip(scroll(), handle_input(), handle_music()):
pass
С одной стороны, реализация на MicroPython даёт заметно более качественный звук, потому что обработчик прерывания вызывается точно в заданное время, тогда как в CircuitPython на время перерисовки экрана (порядка 0.15 с) звук «подвисает». С другой стороны, код на CircuitPython легче писать, легче отлаживать и легче расширять, а реализация игровых процессов в виде сопрограмм-генераторов естественна и в отрыве от требований ко звуку.
