Ускорение MicroPython

в 5:33, , рубрики: esp8266, IoT, micropython, python, программирование микроконтроллеров

MicroPython — реализация языка программирования Python для микроконтроллеров, даёт возможность аудитории этого языка, используя знакомый синтаксис и принципы программирования работать с небольшими вычислительными устройствами.

В своей работе я использую MicroPython для прототипирования, быстрой проверки идей и для создания небольших стендов. Благодаря REPL и простому синтаксису MicroPython также отлично подходит для DIY проектов и для обучения программированию.

Когда речь заходит о взаимодействии компьютеров с реальным миром, меня всегда интересует скорость их взаимодействия. В некоторых случаях использования микропроцессорной техники, например в сфере интернета вещей, скорость реакции устройства не так важна. Нет особой разницы когда включится сирена сигнализации: через 10 микросекунд после обнаружения движения или через 10 миллисекунд.

Но в некоторых аспектах, скорость работы и время реакции важно и встаёт вопрос о целесообразности использования MicroPython. Поэтому я провел небольшое исследование, на которое меня вдохновило видео с выступления создателя MicroPython Damien George. Мне стало интересно как быстро программа, написанная на Micropython будет реагировать на входное воздействие.

Подопытным устройством будет микроконтроллер ESP8266, на плате NodeMcu с MicroPython версии esp8266-2018511-v1.9.4 на борту.

Ускорение MicroPython - 1

Я буду нажимать на кнопку и регистрировать на осциллографе разницу во времени между нажатием и появлением 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)

Типичная осциллограмма при такой программе выглядит так:

Ускорение MicroPython - 2

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

Ускорение MicroPython - 3

В среднем время реакции около 310 микросекунд, не очень быстро, но для некоторых применений вполне приемлемо.

Тест №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)

И картина получается следующая:

Ускорение MicroPython - 4

Ускорение MicroPython - 5

Использование прерываний даёт прирост в скорости примерно в 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()

Типичная картина ответа на осциллограмме:

Ускорение MicroPython - 6

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

Ускорение MicroPython - 7

Тест №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()

И как результат, отклик заметно ускорился:

Ускорение MicroPython - 8

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

Ускорение MicroPython - 9

Ускорение MicroPython - 10

Если и этого мало, то можно воспользоваться ассемблерными вставками через декоратор @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)

И осциллограмма реакции:

Ускорение MicroPython - 11

И второй тест через 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()

И осциллограмма при втором тесте:

Ускорение MicroPython - 12

Среднее время реакции при двух тестах:

Ускорение MicroPython - 13

Ускорение реакции достигается за счёт более быстрого обнаружения входного воздействия во втором тесте.

Заключение

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

Автор: alter_fritz

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js