3.1. Микроконтроллеры

OpenMV Cam работает на микроконтроллере (MCU): едином чипе, который объединяет процессор, оперативную память (RAM), хранилище программ (флеш-память) и набор периферийных устройств – аппаратных блоков для взаимодействия с внешним миром.

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

MicroPython оборачивает эти регистры в классы внутри модуля machine. machine.Pin(...) возвращает объект, управляющий выводом общего назначения для ввода/вывода (GPIO) – проводом, на котором чип может удерживать высокий уровень (около 3,3 В) или низкий уровень (около 0 В), либо считывать одно из этих двух состояний, когда вывод задаётся чем-то внешним. machine.ADC(...) предоставляет доступ к аналого-цифровому преобразователю, который измеряет напряжение на выводе и выдаёт его в виде числа. machine.UART(...) управляет универсальным асинхронным приёмопередатчиком (UART) – периферийным устройством, которое отправляет и принимает байты по одному биту за раз по паре проводов: TX (передача) и RX (приём). Другие классы охватывают остальные периферийные устройства. Скрипт считывает и записывает объекты Python; MicroPython преобразует каждое обращение в соответствующие чтения и записи регистров, а они перемещают биты по физическим проводам.

Контур одного чипа, содержащий помеченные блоки -- процессор, RAM, флеш-память и ряд периферийных блоков (GPIO, ADC, таймер/PWM, UART/SPI/I2C, CAN), соединённых внутренней шиной, со стрелками от каждого периферийного устройства, выходящими из чипа к меткам физических выводов.

MCU объединяет процессор, память и периферийные устройства в едином чипе. Каждое периферийное устройство доступно из Python через класс в модуле machine.

3.1.1. Главный цикл

Почти каждая программа для микроконтроллера имеет одну и ту же структуру: однократная настройка в начале скрипта (импорт модулей, конфигурация выводов, открытие шин), а затем бесконечный цикл while True: в конце. Внутри цикла программа считывает входы, принимает решения и обновляет выходы снова и снова. Цикл и есть программа; когда скрипт завершается, устройство перестаёт что-либо делать.

# setup, runs once
from machine import Pin
led = Pin("P0", Pin.OUT)

# main loop, runs forever
while True:
    led.value(1)
    # ... do work ...
    led.value(0)
    # ... do other work ...

Эта структура – однократная настройка, а затем бесконечный цикл – называется паттерном главного цикла. Всё дальнейшее посвящено тому, что находится внутри него.

3.1.2. Управление в реальном времени

Настольная программа работает наряду со множеством других. Операционная система распределяет её работу между одним или несколькими потоками – независимыми потоками выполнения, между которыми она переключается миллисекунда за миллисекундой. Когда один поток ожидает ввода-вывода (диск, сеть, перемещение мыши пользователем), ОС передаёт процессор другому. Программа в основном управляется событиями: оконный менеджер вызывает ваш код, когда поступает ввод, библиотека HTTP возобновляет ваш код, когда в сокет приходят байты. Нечто более крупное вызывает вас.

Программа для микроконтроллера устроена противоположным образом. По умолчанию здесь нет ни операционной системы, ни планировщика, ни другого потока. Только что показанный главный цикл – единственный цикл. Периферийные устройства вызывают прерывания или выставляют флаги состояния; цикл опрашивает их или напрямую обрабатывает прерывания. Если цикл застревает в time.sleep_ms(1000), устройство ничего не делает в течение этой секунды; нет другого потока, который заполнил бы паузу.

Из этого вытекают два следствия, применимых повсюду:

  • Время реально. Двукратное чтение вывода в плотном цикле занимает микросекунды; сон в течение десяти миллисекунд означает десять миллисекунд, в течение которых не происходит ничего другого. Ответом на это является паттерн неблокирующего отсчёта времени.

  • Аппаратура реальна. Установка machine.Pin.value в 1 подаёт примерно 3,3 В на физический провод; установка в 0 подаёт туда примерно 0 В. Другие части схемы немедленно видят это напряжение – включая любые компоненты, которые вывод может повредить, если задан неправильно.