Skip to content

19

Содержание статьи

Шутники говорят, что после трудового дня за компьютером типичный программист едет домой, садится за ПК и таким образом отдыхает. А ведь истина на самом деле куда ужаснее этой шутки: многие из нас, приходя с работы, посвящают оставшееся до сна время... программированию микроконтроллеров. 🙂 Обывателям не понять, но Arduino, Teensy или ESP — действительно очень неплохое хобби. Их единственный недостаток — необходимость программировать на достаточно низком уровне, если не на Assembler, то на Arduino C или Lua. Но теперь в списке ЯП для микроконтроллеров появился Python. Точнее, MicroPython. В этой статье я постараюсь максимально продемонстрировать его возможности.

С чего все началось?

Все началось с кампании на Kickstarter. Дэмьен Джордж (Damien George), разработчик из Англии, спроектировал микроконтроллерную плату, предназначенную специально для Python. И кампания «выстрелила». Изначально была заявлена сумма в 15 тысяч фунтов стерлингов, но в результате было собрано в шесть с половиной раз больше — 97 803 фунта стерлингов.

А чем эта плата лучше?

Автор проекта приводил целый ряд преимуществ своей платформы в сравнении с Raspberry Pi и Arduino:

  • Мощность — MP мощнее в сравнении с микроконтроллером Arduino, здесь используются 32-разрядные ARM-процессоры типа STM32F405 (168 МГц Cortex-M4, 1 Мбайт флеш-памяти, 192 Кбайт ОЗУ).

  • Простота в использовании — язык MicroPython основан на Python, но несколько упрощен, для того чтобы команды по управлению датчиками и моторами можно было писать буквально в несколько строк.

  • Отсутствие компилятора — чтобы запустить программу на платформе MicroPython, нет необходимости устанавливать на компьютер дополнительное ПО. Плата определяется ПК как обычный USB-накопитель — стоит закинуть на него текстовый файл с кодом и перезагрузить, программа тут же начнет исполняться. Для удобства все-таки можно установить на ПК эмулятор терминала, который дает возможность вписывать элементы кода с компьютера непосредственно на платформу. Если использовать его, тебе даже не придется перезагружать плату для проверки программы, каждая строка будет тут же исполняться микроконтроллером.

  • Низкая стоимость — в сравнении с Raspberry Pi платформа PyBoard несколько дешевле и, как следствие, доступнее.

  • Открытая платформа — так же как и Arduino, PyBoard — открытая платформа, все схемы будут находиться в открытом доступе, что подразумевает возможность спроектировать и создать подобную плату самому в зависимости от потребностей.

И что, только официальная плата?

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

  • для BBC micro:bit — британская разработка, позиционируется как официальное учебное пособие для уроков информатики;
  • Circuit Playground Express — разработка известной компании Adafruit. Это плата, включающая в себя светодиоды, датчики, пины и кнопки. По умолчанию программируется с помощью Microsoft MakeCode for Adafruit. Это блочный (похожий на Scratch) редактор «кода»;
  • ESP8266/ESP32 — одна из самых популярных плат для IoT-разработки. Ее можно было программировать на Arduino C и Lua. А сегодня мы попробуем установить на нее MicroPython.

Плата ESP8266

Плата ESP8266

Подготовка к работе

Перед тем как писать программы, нужно настроить плату, установить на нее прошивку, а также установить на компьютер необходимые программы.

INFO

Все примеры проверялись и тестировались на следующем оборудовании:

  • плата NodeMCU ESP8266-12E;
  • драйвер моторов L293D;
  • I2C-дисплей 0,96″ 128 × 64;
  • Adafruit NeoPixel Ring 16.

Прошивка контроллера

Для прошивки платы нам понадобится Python. Точнее, даже не он сам, а утилита esptool, распространяемая с помощью pip. Если у тебя установлен Python (неважно, какой версии), открой терминал (командную строку) и набери:

pip install esptool

После установки esptool надо сделать две вещи. Первое — скачать с официального сайта версию прошивки для ESP8266. И второе — определить адрес платы при подключении к компьютеру. Самый простой способ — подключиться к компьютеру, открыть Arduino IDE и посмотреть адрес в списке портов.

Для облегчения восприятия адрес платы в примере будет /dev/ttyUSB0, а файл прошивки переименован в esp8266.bin и лежит на рабочем столе.

Открываем терминал (командную строку) и переходим на рабочий стол:

cd Desktop

Форматируем флеш-память платы:

esptool.py --port /dev/ttyUSB0 erase_flash 

Если при форматировании возникли ошибки, значит, нужно включить режим прошивки вручную. Зажимаем на плате кнопки reset и flash. Затем отпускаем reset и, не отпуская flash, пытаемся отформатироваться еще раз.

И загружаем прошивку на плату:

esptool.py --port /dev/ttyUSB0 --baud 460800 write_flash --flash_size=detect 0 esp8266.bin

Взаимодействие с платой

Все взаимодействие с платой может происходить несколькими способами:

  • через Serial-порт;
  • через web-интерпретатор.

При подключении через Serial-порт пользователь в своем терминале (в своей командной строке) видит практически обычный интерпретатор Python.

Подключение через SerialPort

Подключение через SerialPort

Для подключения по Serial есть разные программы. Для Windows можно использовать PuTTY или TeraTerm. Для Linux — picocom или minicom. В качестве кросс-платформенного решения можно использовать монитор порта Arduino IDE. Главное — правильно определить порт и указать скорость передачи данных 115200.

picocom /dev/ttyUSB0 -b115200

Кроме этого, уже создано и выложено на GitHub несколько программ, облегчающих разработку, например EsPy. Кроме Serial-порта, он включает в себя редактор Python-файлов с подсветкой синтаксиса, а также файловый менеджер, позволяющий скачивать и загружать файлы на ESP.

EsPy IDE

EsPy IDE

Но все перечисленные способы хороши лишь тогда, когда у нас есть возможность напрямую подключиться к устройству с помощью кабеля. Но плата может быть интегрирована в какое-либо устройство, и разбирать его только для того, чтобы обновить программу, как-то неоптимально. Наверное, именно для таких случаев и был создан WebREPL. Это способ взаимодействия с платой через браузер с любого устройства, находящегося в той же локальной сети, если у платы нет статического IP, и с любого компьютера, если такой IP присутствует. Давай настроим WebREPL. Для этого необходимо, подключившись к плате, набрать

import webrepl_setup

Появится сообщение о статусе автозапуска WebREPL и вопрос, включить или выключить его автозапуск.

WebREPL daemon auto-start status: enabled

Would you like to (E)nable or (D)isable it running on boot?
(Empty line to quit)
>

После ввода q появляется сообщение о выставлении пароля доступа:

To enable WebREPL, you must set password for it
New password (4-9 chars):   

Вводим его, а затем подтверждаем. Теперь после перезагрузки мы сможем подключиться к плате по Wi-Fi.

Так как мы не настроили подключение платы к Wi-Fi-сети, она работает в качестве точки доступа. Имя Wi-Fi-сeти — MicroPython-******, где звездочками я заменил часть MAC-адреса. Подключаемся к ней (пароль — micropythoN).

Открываем WebREPL и нажимаем на Connect. После ввода пароля мы попадаем в тот же интерфейс, что и при прямом подключении. Кроме этого, в WebREPL есть интерфейс для загрузки файлов на плату и скачивания файлов на компьютер.

WebRERL

WebRERL

INFO

Среди файлов, загруженных на плату, есть стандартные:

  • boot.py — скрипт, который загружается первым при включении платы. Обычно в него вставляют функции для инициализации модулей, подключения к Wi-Fi и запуска WebREPL;
  • main.py — основной скрипт, который запускается сразу после выполнения boot.py, в него записывается основная программа.

Начинаем разработку

Hello world

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

import machine
import time

pin = machine.Pin(2,machine.Pin.OUT)

def dot_show():
    pin.off()
    time.sleep(1)
    pin.on()

def dash_show():
    pin.off()
    time.sleep(2)
    pin.on()

Hello_world = '**** * *-** *-** ---    *-- --- *-* *-** -**'

for i in Hello_world:
    if i=="*":
        dot_show()
    elif i=='-':
        dash_show()
    else:
        time.sleep(3)
    time.sleep(0.5)

Итак, что же происходит? Сначала подключаются библиотеки: стандартная Python-библиотека time и специализированная machine. Эта библиотека отвечает за взаимодействие с GPIO. Стандартный встроенный светодиод располагается на втором пине. Подключаем его и указываем, что он работает на выход. Если бы у нас был подключен какой-нибудь датчик, то мы бы указали режим работы IN.

Следующие две функции отвечают за включение и выключение светодиода на определенный интервал времени. Наверное, интересно, почему я сначала выключаю светодиод, а потом включаю? Мне тоже очень интересно, почему сигнал для данного светодиода инвертирован... Оставим это на совести китайских сборщиков. На самом деле команда pin.off() включает светодиод, а pin.on() — отключает.

Ну а дальше все просто: заносим в переменную Hello_world нашу строчку, записанную кодом Морзе, и, пробегаясь по ней, вызываем ту или иную функцию.

Радужный мир

Одна из стандартных библиотек, поставляемых с MicroPython, — библиотека NeoPixel. Она используется для работы с RGB-светодиодами, выпускаемыми компанией Adafruit. Для подключения нужно три пина. Один — земля, второй — питание (3,3 В), а третий необходим для управления. У меня это GPIO4, а ты можешь выбрать любой.

Схема подключения NeoPixel

Схема подключения NeoPixel

import machine, neopixel,time,urandom

my_neopixel_ring = neopixel.NeoPixel(machine.Pin(4), 16)

def color_all(neopixel_ring, color):
    for i in range(neopixel_ring.n):
        neopixel_ring[i] = color
    neopixel_ring.write()

def color_all_slow(neopixel_ring, color):
    for i in range(neopixel_ring.n):
        neopixel_ring[i] = color
        neopixel_ring.write()
        time.sleep(0.5)

def color_random(neopixel_ring):
    for i in range(neopixel_ring.n):
        color =  (urandom.getrandbits(8),urandom.getrandbits(8),urandom.getrandbits(8))
        neopixel_ring[i] = color
    neopixel_ring.write()

def disable(neopixel_ring):
    for i in range(neopixel_ring.n):
        neopixel_ring[i] = (0,0,0)
    neopixel_ring.write()

def show(neopixel_ring):
    RAINBOW_COLORS = [(255,0,0),(255, 128, 0),(255,255,0),(0,255,0),(0,255,255),(0,0,255), (255,0,255)]
    for i in RAINBOW_COLORS:
        color_all(neopixel_ring,i)
        time.sleep(0.5)
    time.sleep(5)
    disable(neopixel_ring)
    for i in RAINBOW_COLORS:
        color_all_slow(neopixel_ring,i)
        time.sleep(0.5)
    for i in range(100):
        color_random(neopixel_ring)
        time.sleep(0.5)
    disable(neopixel_ring)

Ух, сколько здесь всего! Начнем с подключения. Для создания объекта типа NeoPixel нужно указать два параметра: пин и количество светодиодов. Светодиоды в NeoPixel адресные, и можно подключать много модулей последовательно. По сути, это массив, в каждом элементе которого хранится кортеж определенного формата (RGB).

Функция color_all окрашивает все светодиоды в один цвет. Причем для визуального наблюдателя это происходит «мгновенно», а вот в функции color_all_slow включение будет происходить по одному светодиоду с задержкой в полсекунды. Это зависит от того, когда вызывается функция write(). Именно она отвечает за «проявление» цвета.

Следующая функция, color_random, окрашивает все светодиоды в разные случайные цвета. Именно здесь заметно отличие от версии Python, запускаемой на компьютере. Как бы мы сгенерировали случайный кортеж на компьютере:

import random
color = (random.randrage(256),random.randrage(256),random.randrage(256))

Но здесь нет модуля random. Зато есть urandom. C помощью функции getrandbits можно получить случайный набор бит определенной длины, то есть случайное число в диапазоне от нуля до двойки в какой-то степени. В данном случае — до восьми.

Для того чтобы выключить светодиод, необходимо задать ему цвет, равный (0,0,0). Ой. А как же яркость? Ведь когда цвет задается с помощью RGB, обычно присутствует такой параметр, как яркость (прозрачность). Здесь она задается с помощью обыкновенной математики:

  • (255,255,255) — белый на максимальной яркости;
  • (128,128,128) — белый с яркостью 50%;
  • (64,64,64) — белый с яркостью 25%.

Функция show — это просто демонстрационная функция, в которой показывается, как работают уже разобранные функции.

Монитор. Рисование, письмо и каллиграфия

Очень часто неохота лезть на устройство, чтобы посмотреть какие-то данные. Значит, их нужно куда-то выводить. Например, на экран. 🙂

from ssd1306 import SSD1306_I2C
import machine
from writer import Writer
import freesans20
import time

WIDTH = const(128)
HEIGHT = const(64)

pscl = machine.Pin(4, machine.Pin.OUT)
psda = machine.Pin(5, machine.Pin.OUT)
i2c = machine.I2C(scl=pscl, sda=psda)

ssd = SSD1306_I2C(WIDTH, HEIGHT, i2c, 0x3c)

ssd.fill(1)
ssd.show()
time.sleep(0.5)
ssd.fill(0)
ssd.show()

ssd.line(40, 0, 40, HEIGHT, 1)

square_side = 40
ssd.fill_rect(0, 0, square_side, square_side, 1)

ssd.text('Hello', 50, 10)

wri2 = Writer(ssd, freesans20, verbose=False) 
Writer.set_clip(True, True)
Writer.set_textpos(32, 64)
wri2.printstring('][akep\n')
ssd.show()

Для работы с монитором необходима библиотека ssd1306, ее можно загрузить с GitHub. Для подключения по протоколу I2C нам необходимо два пина, используем GPIO4 как scl, а GPIO5 как sda. Инициализируем I2C-подключение. Для проверки после инициализации переменной i2c можно вызвать функцию i2c.scan(). Если ты все правильно подключил, то в качестве результата будет список вида [60]. Это номер I2C-порта. Если увидел пустой список — значит, какая-то проблема с подключением. Что можно сделать с экраном?

  • Заполнить одним цветом: белым — ssd.fill(1) или черным — ssd.fill(0).
  • Обновить изображение на экране — ssd.show().
  • Нарисовать линию (x0,y0,x1,y1) толщиной t — ssd.line(x0,y0,x1,y1,t).
  • Нарисовать пиксель заданного цвета по координатам — ssd.pixel(x,y,c).
  • Нарисовать прямоугольник, задаваемый начальной точкой, длинами сторон и цветом — ssd.fill_rect(x0, y0, lenth, width, color).
  • Сделать надпись стандартным шрифтом — ssd.text('', x0, y0).

Но у стандартного шрифта есть свои недостатки, поэтому на GitHub уже можно найти способ создавать и использовать свои шрифты. Для этого надо создать свой шрифт с помощью font-to-py.py, загрузить созданный шрифт и модуль Writer на устройство и использовать, как продемонстрировано в примере.

Настраиваем Wi-Fi и управляем через сайт

Действительно, каждый раз подключаться к локальной сети платы неудобно. Но можно настроить плату так, чтобы она подключалась к уже созданной Wi-Fi-сети.

import network

sta_if = network.WLAN(network.STA_IF)

sta_if.active(True)

sta_if.connect('<your ESSID>', '<your password>')

sta_if.isconnected() ## True

sta_if.ifconfig() ##('192.168.0.2', '255.255.255.0', '192.168.0.1', '8.8.8.8')

Здесь показано, как подключиться к заданной Wi-Fi-сети путем последовательного выполнения команд. Но ведь это очень монотонно. Официальное руководство советует добавить следующую функцию в файл boot.py:

def do_connect():
    import network
    sta_if = network.WLAN(network.STA_IF)
    if not sta_if.isconnected():
        print('connecting to network...')
        sta_if.active(True)
        sta_if.connect('<essid>', '<password>')
        while not sta_if.isconnected():
            pass
    print('network config:', sta_if.ifconfig())

Отлично. Мы подключили нашу плату к интернету. Давай попробуем управлять светодиодом с помощью обычного сайта. Нам не понадобятся никакие модули вроде Flask или Djano. Только socket. Только хардкор.

import socket
import machine

html = """<!DOCTYPE html>
<html>
<head> <title> ESP8266 Controller </title> </head>
<form>
<H1>ESP8266 Controller</H1>
<button name="LED" value="ON" type="submit">ON</button><br>
<button name="LED" value="OFF" type="submit">OFF</button>
</form>
</html>
"""

pin = machine.Pin(2, machine.Pin.OUT)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 80))
s.listen(5)
while True:

    conn, addr = s.accept()
    print("Connected with " + str(addr))
    request = conn.recv(1024)
    request = str(request)
    LEDON = request.find('/?LED=ON')
    LEDOFF = request.find('/?LED=OFF')

    if LEDON == 6:
        print('TURN LED0 ON')
        pin.off()
    if LEDOFF == 6:
        print('TURN LED0 OFF')
        pin.on()
    response = html
    conn.send(response)
    conn.close()

В начале записан HTML-код странички. Там две кнопки — одна на включение, другая на выключение. По сути, они просто создают и отправляют POST-запросы на сервер.

После описания сайта опять подключаем встроенный светодиод, а затем создаем сервер. Задача сервера — отлавливать запросы и, если в них попадаются команды на включение/выключение светодиода, обрабатывать их.

Управление моторами

Мне кажется, что машинка на радиоуправлении была, есть или будет у всех (на этом моменте все, кто родился в начале восьмидесятых и раньше, начинают смахивать слезинки — ведь пределом их мечтаний был шагающий луноход без радиоуправления. 😉 — Прим. ред.). И многие начинают свой путь в DIY с создания такой игрушки. Это настолько популярное дело, что уже давно в продаже появились целые наборы, включающие в себя колесную платформу с креплениями под различные платы. Попробуем создать машинку, управляемую с помощью WebREPL.

Драйвер моторов L293D

Драйвер моторов L293D

from machine import Pin, PWM

pin1 = Pin(5, Pin.OUT)  # D1
pin2 = Pin(4, Pin.OUT)  # D2
pin3 = Pin(0, Pin.OUT)  # D3
pin4 = Pin(2, Pin.OUT)  # D4

BIN1 = PWM(pin1, freq=750)
BIN2 = PWM(pin3, freq=750)
AIN1 = PWM(pin2, freq=750)
AIN2 = PWM(pin4, freq=750)

speed = 700 

def stop_all():
    for each in (BIN1, BIN2, AIN1, AIN2):
        each.duty(0)

def B(tmp1,tmp2):
    BIN1.duty(tmp1)
    BIN2.duty(tmp2)

def A(tmp1,tmp2):
    AIN1.duty(tmp1)
    AIN2.duty(tmp2)

def forward():
    B(speed,0)
    A(speed,speed)

def backward():
    B(speed,speed)
    A(speed,0)

def left():
    B(speed,0)
    A(speed,0)

def right():
    B(speed,speed)
    A(speed,speed)

commands = {'w':forward,'s':backward,'a':left,'d':right,'s':stop_all}

while True:
    a = input().lower()
    try:
        commands[a]()
    except:
        pass

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

Интернет вещей

Как-то неловко вышло... ESP8266 позиционируется как плата для разработки в сфере IoT, а мы пока ничего не сделали. Одно из базовых понятий IoT — MQTT-протокол. Чтобы не тратить время на настройку своего MQTT-сервера, воспользуемся платформой Adafruit IO. А библиотека для работы с этим протоколом уже включена в сборку MicroPython и называется umqtt.

Перед непосредственно программированием необходимо настроить Adafruit IO. Регистрируемся, создаем новый feed и называем его enablefield. Затем создаем новый Dashboard и добавляем на него кнопку-переключатель. При добавлении следует указать имя созданного нами feed’a. Для подключения нам необходимо имя аккаунта и так называемый Active Key.

import network
from umqtt.simple import MQTTClient 
import machine


pin = machine.Pin(2, machine.Pin.OUT)


def sub_cb(topic, msg): 
   print(msg)
   if msg == b'ON':
      pin.off()
   elif msg == b'OFF':
      pin.on()

sta_if = network.WLAN(network.STA_IF)
sta_if.active(True)
sta_if.connect('<SSID>', '<PASSWORD>')

client = MQTTClient("my_device", "io.adafruit.com",user="<USER_LOGIN>", password="<API-KEY>", port=1883) 
client.set_callback(sub_cb) 
client.connect()
client.subscribe(topic="<USER_LOGIN>/feeds/<FEEDNAME>") 
while True: 
    client.wait_msg()

Для работы с внешним MQTT-сервером необходимо подключение к Wi-Fi-сети с доступом в интернет. Функция sub_cb(topic, msg) отвечает за то, что происходит, если в заданный топик приходит сообщение. После подключения к Wi-Fi создается подключение к MQTT-серверу. Описываются топики, на которые подписан клиент, и запускается бесконечное ожидание сообщений в топике.

Заключение

MicroPython — очень молодой проект, но он уже достаточно популярен и даже попал в список 30 Amazing Python Projects for the Past Year (v.2018). И мне кажется, что это только начало, ведь на GitHub уже немало проектов, написанных на MicroPython, а Adafruit даже разработала свою версию, предназначенную для обучения, — CircuitPython.

WWW

Back to top