MicroPython — реализация языка программирования Python для микроконтроллеров, даёт возможность аудитории этого языка, используя знакомый синтаксис и принципы программирования работать с небольшими вычислительными устройствами.
В своей работе я использую MicroPython для прототипирования, быстрой проверки идей и для создания небольших стендов. Благодаря REPL и простому синтаксису MicroPython также отлично подходит для DIY проектов и для обучения программированию.
Когда речь заходит о взаимодействии компьютеров с реальным миром, меня всегда интересует скорость их взаимодействия. В некоторых случаях использования микропроцессорной техники, например в сфере интернета вещей, скорость реакции устройства не так важна. Нет особой разницы когда включится сирена сигнализации: через 10 микросекунд после обнаружения движения или через 10 миллисекунд.
Но в некоторых аспектах, скорость работы и время реакции важно и встаёт вопрос о целесообразности использования MicroPython. Поэтому я провел небольшое исследование, на которое меня вдохновило видео с выступления создателя MicroPython Damien George. Мне стало интересно как быстро программа, написанная на Micropython будет реагировать на входное воздействие.
Подопытным устройством будет микроконтроллер ESP8266, на плате NodeMcu с MicroPython версии esp8266-2018511-v1.9.4 на борту.

Я буду нажимать на кнопку и регистрировать на осциллографе разницу во времени между нажатием и появлением 3.3 В на другой ножке микропроцессора. Каждое измерение делается 15 раз, берётся среднее (проиллюстрировано на графиках) и рассчитывается стандартное отклонение (чёрная полоса на графиках).
Если решать эту задачу «в лоб», то программа выглядит довольно тривиально:
Типичная осциллограмма при такой программе выглядит так:

Здесь и на других осциллограммах «синий» сигнал — пин с кнопкой, «зелёный» ответный пин. При 15 повторениях получается такая картина:

В среднем время реакции около 310 микросекунд, максимальное — 356 мкс, не очень быстро, но для некоторых применений вполне приемлемо.
Ускорить стандартный код «из коробки» можно через обработку прерываний.
И картина получается следующая:


а максимальное время ответа 306 мкс.
Использование прерываний даёт прирост в скорости примерно в 20%, но при этом даёт довольно большой разброс во времени ответа.
Если полученных скоростей не хватает, то следующий шаг — воспользоваться конструкцией @micropython.native, что даёт возможность преобразования питоновкого кода в нативный машинный код. Но при этом есть некоторые ограничения.
Вариант кода:
Типичная картина ответа на осциллограмме:

По сравнению с предыдущим способом ускорение почти в два раза:

Наибольшее время ответа 128 мкс.
Следующим этапом в поисках «быстрого» MicroPython — использование конструкции @micropython.viper и обращение непосредственно к регистрам микропроцессора (адреса регистров можно найти тут.
И как результат, отклик заметно ускорился:

Время отклика получается очень не большим и не поддаётся сравнению с другими способами (максимум 820 нс):


Если и этого мало, то можно воспользоваться ассемблерными вставками через декоратор @micropython.asm_thumb. При таком способе питона особо не остаётся (и теряются высокоуровневые преимущества Python) и если нужны более высокие скорости лучше использовать другие аппаратные средства, например FPGA (где Python тоже может пригодиться см. тут и тут).
В случае если есть потребность передать много информации после некоторого события можно использовать последовательный интерфейс UART.
Возьму для примера два варианта реализации.
Первый через обработку прерываний:
И осциллограмма реакции:

Максимальное время ответа 248 мкс.
И второй тест через viper:
И осциллограмма при втором тесте:

Максимальное время отклика при таком коде 71 мкс.
Среднее время реакции при двух тестах:

Ускорение реакции достигается за счёт более быстрого обнаружения входного воздействия во втором тесте.
MicroPython позволяет при программировании микроконтроллеров пользоваться характерными для языков высокого уровня вещами (ООП, обработка исключений, list и dict comprahansions и пр.), а в случае необходимости заметно ускорить «классический» Python-код.
В своей работе я использую MicroPython для прототипирования, быстрой проверки идей и для создания небольших стендов. Благодаря REPL и простому синтаксису MicroPython также отлично подходит для DIY проектов и для обучения программированию.
Когда речь заходит о взаимодействии компьютеров с реальным миром, меня всегда интересует скорость их взаимодействия. В некоторых случаях использования микропроцессорной техники, например в сфере интернета вещей, скорость реакции устройства не так важна. Нет особой разницы когда включится сирена сигнализации: через 10 микросекунд после обнаружения движения или через 10 миллисекунд.
Но в некоторых аспектах, скорость работы и время реакции важно и встаёт вопрос о целесообразности использования MicroPython. Поэтому я провел небольшое исследование, на которое меня вдохновило видео с выступления создателя MicroPython Damien George. Мне стало интересно как быстро программа, написанная на Micropython будет реагировать на входное воздействие.
Подопытным устройством будет микроконтроллер ESP8266, на плате NodeMcu с MicroPython версии esp8266-2018511-v1.9.4 на борту.

Я буду нажимать на кнопку и регистрировать на осциллографе разницу во времени между нажатием и появлением 3.3 В на другой ножке микропроцессора. Каждое измерение делается 15 раз, берётся среднее (проиллюстрировано на графиках) и рассчитывается стандартное отклонение (чёрная полоса на графиках).
Тест №1.
Если решать эту задачу «в лоб», то программа выглядит довольно тривиально:
import machine import time o = machine.Pin(5, machine.Pin.OUT) #D1 out i = machine.Pin(4, machine.Pin.IN) #D2 in while 1: if i.value(): o.value(1) time.sleep(0.1) o.value(0)
Типичная осциллограмма при такой программе выглядит так:

Здесь и на других осциллограммах «синий» сигнал — пин с кнопкой, «зелёный» ответный пин. При 15 повторениях получается такая картина:

В среднем время реакции около 310 микросекунд, максимальное — 356 мкс, не очень быстро, но для некоторых применений вполне приемлемо.
Тест №2
Ускорить стандартный код «из коробки» можно через обработку прерываний.
import machine import time o = machine.Pin(5, machine.Pin.OUT) #D1 out i = machine.Pin(4, machine.Pin.IN) #D2 in def f(_): o.value(1) time.sleep(0.1) o.value(0) i.irq(trigger=machine.Pin.IRQ_RISING, handler=f)
И картина получается следующая:


а максимальное время ответа 306 мкс.
Использование прерываний даёт прирост в скорости примерно в 20%, но при этом даёт довольно большой разброс во времени ответа.
Тест №3
Если полученных скоростей не хватает, то следующий шаг — воспользоваться конструкцией @micropython.native, что даёт возможность преобразования питоновкого кода в нативный машинный код. Но при этом есть некоторые ограничения.
Вариант кода:
import machine import time o = machine.Pin(5, machine.Pin.OUT) #D1 out i = machine.Pin(4, machine.Pin.IN) #D2 in @micropython.native def f(): while 1: if i.value(): o.value(1) time.sleep(0.1) o.value(0) f()
Типичная картина ответа на осциллограмме:

По сравнению с предыдущим способом ускорение почти в два раза:

Наибольшее время ответа 128 мкс.
Тест №4
Следующим этапом в поисках «быстрого» MicroPython — использование конструкции @micropython.viper и обращение непосредственно к регистрам микропроцессора (адреса регистров можно найти тут.
import time @micropython.viper def f(): O = ptr32(0x60000300) # регистр GPIO ESP8266 while 1: s = ((O[6] & 0x10) >> 4) # считывание информации с 4 пина if s: O[1] = 0x20 #активизация 5 пина time.sleep(0.1) O[2] = 0x20 #деактивизация 5 пина f()
И как результат, отклик заметно ускорился:

Время отклика получается очень не большим и не поддаётся сравнению с другими способами (максимум 820 нс):


Если и этого мало, то можно воспользоваться ассемблерными вставками через декоратор @micropython.asm_thumb. При таком способе питона особо не остаётся (и теряются высокоуровневые преимущества Python) и если нужны более высокие скорости лучше использовать другие аппаратные средства, например FPGA (где Python тоже может пригодиться см. тут и тут).
UART
В случае если есть потребность передать много информации после некоторого события можно использовать последовательный интерфейс UART.
Возьму для примера два варианта реализации.
Первый через обработку прерываний:
import machine i = machine.Pin(4, machine.Pin.IN) #D2 in ua = machine.UART(1) ua.init(1000000) def f(_): ua.write(b'\x01') i.irq(trigger=machine.Pin.IRQ_RISING, handler=f)
И осциллограмма реакции:

Максимальное время ответа 248 мкс.
И второй тест через viper:
import machine import time i = machine.Pin(4, machine.Pin.IN) #D2 in ua = machine.UART(1) ua.init(1000000) @micropython.viper def f(): O = ptr32(0x60000300) while 1: if ((O[6] & 0x10) >> 4): ua.write(b'\x01') time.sleep(0.1) f()
И осциллограмма при втором тесте:

Максимальное время отклика при таком коде 71 мкс.
Среднее время реакции при двух тестах:

Ускорение реакции достигается за счёт более быстрого обнаружения входного воздействия во втором тесте.
Заключение
MicroPython позволяет при программировании микроконтроллеров пользоваться характерными для языков высокого уровня вещами (ООП, обработка исключений, list и dict comprahansions и пр.), а в случае необходимости заметно ускорить «классический» Python-код.
