19
Содержание статьи¶
- С чего все началось?
- А чем эта плата лучше?
- И что, только официальная плата?
- Подготовка к работе
- Прошивка контроллера
- Взаимодействие с платой
- Начинаем разработку
- Hello world
- Радужный мир
- Монитор. Рисование, письмо и каллиграфия
- Настраиваем Wi-Fi и управляем через сайт
- Управление моторами
- Интернет вещей
- Заключение
Шутники говорят, что после трудового дня за компьютером типичный программист едет домой, садится за ПК и таким образом отдыхает. А ведь истина на самом деле куда ужаснее этой шутки: многие из нас, приходя с работы, посвящают оставшееся до сна время... программированию микроконтроллеров. Обывателям не понять, но 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
Подготовка к работе¶
Перед тем как писать программы, нужно настроить плату, установить на нее прошивку, а также установить на компьютер необходимые программы.
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
Для подключения по Serial есть разные программы. Для Windows можно использовать PuTTY или TeraTerm. Для Linux — picocom или minicom. В качестве кросс-платформенного решения можно использовать монитор порта Arduino IDE. Главное — правильно определить порт и указать скорость передачи данных 115200.
picocom /dev/ttyUSB0 -b115200
Кроме этого, уже создано и выложено на GitHub несколько программ, облегчающих разработку, например EsPy. Кроме Serial-порта, он включает в себя редактор Python-файлов с подсветкой синтаксиса, а также файловый менеджер, позволяющий скачивать и загружать файлы на ESP.
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
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
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
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.