Ускорение MicroPython

    MicroPython — реализация языка программирования 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-код.
    Поделиться публикацией

    Комментарии 11

      0
      По-моему, здесь все-таки уместнее писать «микроконтроллер», а не «микропроцессор» (как-никак, у описываемых в статье штуковин и внешние интерфейсы есть, чего у процессоров нет). А в широком смысле к микропроцессорам и CPU относятся.
        0
        Согласен, исправил. Спасибо
        +1
        Обычно, когда мы измеряем время реакции, нас очень интересует, помимо среднего значения, максимальное, поскольку именно оно дает границы применимости. Хорошо бы дополнить этим параметром.
          0
          Дополнил максимальные времена отклика вариантов программ.
          +1
          Огромное спасибо автору за свежие идеи! Используем micropython для управления промустановками и в 95% случаев его достаточно. Теперь применимость расширим.
            +1

            Можете поделиться, какие микроконтроллеры с micropython используете и для каких конкретно целей

              0
              Пока используем pyboard 1.1 с офиц сайта и стандартный экран к нему. Интерфейс — сенсорный экран + аппаратные кнопки (родной экран очень маленький, палец закрывает элементы интерфейса). Управляем маломощными лабораторными и промышленными установками, на которых хватает шаговых/сервошаговых приводов. Ввод-вывод через Овен-овские блоки (Modbus через RS485).
            –2
            ох сейчас меня обмажут минусами но я не могу промолчать.

            извращаться на змеином с МК это как толкать машину руками и радоваться что она хорошо катится.
            не говоря уже о том, что писать что либо на языке где все зависит от табуляции тот еще ссюр.
            как язык костылей и поддержки, быстрой автоматизации и внедрения на сервере, для скриптов да идеально. для МК нет.

            но меня унесло, это ESP. прекрасный чип от китайских друзей. с подобным количеством кода вы можете заставить тактировать выводной пин с хоть с частотой обработки процессора
            про вшитые ШИМ-релизации вообще молчу.
            Вы вызываете напрямую регистры и радуетесь ускорению процесса, закрывая глаза на то что в рамках какого-то комплексного кода а не просто оторванная от реальности моргалка пипном в производительности вы потеряете из-за тех петель что навернет интерпритатор переводя с микропитона на машинный.
            если уж начали опускаться на низкий уровень и дергать регистры, то делайте это качественно и до конца.
            А то, опять пример с машиной, вы завели машину но управляете ей с заднего сиденья шваброй. не спорю это возможно и при достаточны навыках даже заткнете за пояс любого посредственного автомобилиста, но самой печали ситуации это не убавит.
              +1
              На мой взгляд, если проводить аналогии с автомобилями… то использование MicroPython вместо С/C++/Asm, лучше представлять как использование некой продвинутой системы помощи водителю, а не швабры)

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

              Прогресс не стоит на месте, и производительность таких решений часто бывает более чем приемлема, особенно с учетом гораздо большей скорости разработки программ на питоне чем на С/C++/Asm.
                +1
                Про толкать машину уже написали вам.

                писать что либо на языке где все зависит от табуляции тот еще ссюр


                А на это скажу, что примерно так же относился к табуляциям питона, пока сам не начал писать на нем. Сейчас, спустя годы, имею стойкое мнение, что это одна из самых ценных находок человечества. Не писать begin/end-ы, а заменить их отступами.
                0
                alter_fritz а вы не могли бы дополнить статью аналогичными тестами на Lua/NodeMCU? Там такого количества возможностей для оптимизации нет. Только просто код и скомпилированный код. Но вроде как код луа гораздо ближе к машинному, интересно, как его быстродействие с uPython будет конкурировать.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое