- PVSM.RU - https://www.pvsm.ru -

Быстрый старт с ARM Mbed: разработка на современных микроконтроллерах для начинающих

Привет.

Традиционным уникальным преимуществом платформы Arduino называлось (да и сейчас иногда называется, хотя это уже неверно — и мы поговорим, почему) опускание порога входа в микроконтроллерную разработку до уровня базовых знаний C/C++ и электроники в маштабе «подключить светодиод в нужной полярности».

Спросите примерно у любого активного сторонника Arduino — и вам быстро объяснят, что можно, конечно, писать под STM32 или nRF52, но выгоды в том реальной никакой, зато вас ждут бессонные ночи над сотнями страниц даташитов и бесконечные простыни функций с длинными непонятными названиями.

Заслуги Arduino в снижении порога вхождения действительно трудно переоценить — эта платформа появилась на свет в середине нулевых годов, а после 2010 завоевала серьёзную популярность среди любителей. Особых альтернатив на тот момент ей не было — процессоры на ядрах Cortex-M только появились, по сравнению с AVR они были довольно сложны даже для профессиональных разработчиков, а отладочные платы у большинства вендоров стоили от сотни долларов и выше (и в общем в индустрии ценник за отладку на 5-долларовом контроллере в $500 никого сильно не удивлял).

Однако большая проблема Arduino в том, что её развитие за минувшие 10+ лет более всего напоминает некоторые модели АвтоВАЗа:

Быстрый старт с ARM Mbed: разработка на современных микроконтроллерах для начинающих - 1

Так как дальше я планирую длинное вступление, то сейчас, чтобы вы представляли, в чём будет заключаться практическая часть, я приведу полный текст программы, включающий инициализацию процессора STM32 и мигание светодиодом. Программа написана для ОС ARM Mbed:

#include "mbed.h"
DigitalOut myled(LED1);

int main() {
    while(1) {
        myled = 1; // LED is ON
        wait(0.2); // 200 ms
        myled = 0; // LED is OFF
        wait(1.0); // 1 sec
    }
}

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

Дело в том, что в мире встраиваемой разработки с 2010 года произошло… многое. AVR, как и вообще 8-битные контроллеры, практически умерли — на 2017 год суммарная доля последних в разработках составляла 12 % (данные опроса разработчиков Aspencore), причём она делилась как минимум на три семейства: AVR, PIC и STM8. Фактически, основное их применение сейчас — замена мелкой логики, периферийные контроллеры с минимальным объёмом мозгов и т.п.

Для серии ATMega в этих 12 % места совсем мало — они избыточны в качестве вспомогательных и не могут конкурировать с 32-битными Cortex-M по соотношению цена/характеристики (STM32F030 с 16-64 КБ флэша и 4-8 КБ ОЗУ стоит в России мелким оптом 30-50 рублей). По сути, атмеги в каких-то проектах остались только по историческим причинам.

Быстрый старт с ARM Mbed: разработка на современных микроконтроллерах для начинающих - 2
64-битные процессоры — это, очевидно, старшие Cortex-A и интелы в тяжёлых проектах

Случилось много нового и в средах разработки. Помните, я выше писал, что на старте Cortex-M отпугивали многих разработчиков своей сложностью? Помимо самой по себе развесистой программной инициализации железа (одна только корректная инициализация тактирования, чтобы процессор вообще запустился — это полстраницы кода), главной проблемой стал низкий уровень совместимости разных моделей друг с другом. Если один AVR менялся на другой иногда вообще без правки кода, то для смены STM32F1 даже на STM32L1 придётся поправить изрядно, а уж на какой-нибудь Infineon или NXP…

В пределах одного вендора проблема решалась выпуском набора библиотек, абстрагирующих софт от железа — так, STMicro на данный момент сделала их аж три штуки: SPL, HAL и LL. Однако популярность таких библиотек не всегда была так высока, как хотелось бы вендорам, а кроме того, они с очевидностью были привязаны к конкретному вендору.

Эта проблема начала решаться с появление микроконтроллерных операционных систем.

При всех словах о необходимости экономии ресурсов ОС оказались настолько удобны в разработке, что сейчас большинство проектов делается с их использованием. Впрочем, это вполне ожидаемо — при работе над крупным проектом, даже если вы не будете использовать готовую ОС, вы в итоге напишете для себя набор подсистем, фактически эту ОС составляющих. Просто потому, что они нужны (нет, по опыту людей, пишущих статьи о том, как сделать многозадачность средствами цикла loop(), я в курсе, что кому-то и не нужны — но это будет дискуссия о том, что кому-то и велосипед с круглыми обрезиненными колёсами не нужен, когда и на деревянных квадратных в принципе как-то можно ездить).

Микроконтроллерные ОС и что они дают

С точки зрения программиста ОС — это не только большой полосатый мух, но и набор сервисов «из коробки», серьёзно облегчающих ему жизнь:

  • HAL — абстрагирование от железа. В ОС очень чётко и недвусмысленно разделено взаимодействие с железом и пользовательский код. Это позволяет, во-первых, легко переносить проекты между разными контроллерами (например, у нас большая, увесистая прошивка без каких-либо модификаций собирается под STM32L072, STM32L151 и STM32L451 — достаточно просто указать, какая плата нужна сейчас), во-вторых, при работе над проектом нескольких человек разделять между ними обязанности в соответствии с навыками и квалификацией (логику приложения, например, может писать человек, имеющий крайне смутное понятие о работе с регистрами в STM32, при этом низкоуровневая часть проекта будет развиваться другим человеком параллельно, и они не будут друг другу мешать). В некоторых ОС абстрагируется даже доступ к внешним устройствам — например, в RIOT есть API под названием SAUL, которым можно пользоваться для доступа к сенсорами; в этом случае автору кода верхнего уровня будет всё равно, какой конкретно сенсор там подключён где-то внизу.
  • Многозадачность — любой серьёзный проект представляет собой набор конкурирующих и кооперирующихся задач, которые исполняются в разные периоды времени, периодически или по каким-либо событиям. Современные ОС позволяют легко выделять такие задачи в отдельные потоки, не зависящие от других потоков и обладающие собственными стеками, назначать им приоритет выполнения и отслеживать их работу.
  • Таймеры — события часто привязываются к конкретному времени, и поэтому обязательно нужны часы. Аппаратные таймеры контроллера для регистрации событий в многозадачной ОС подходят плохо в силу ограниченности своего числа, поэтому ОС предоставляют собственные таймеры. Это подсистемы, работающие на базе одного из аппаратных таймеров, и позволяющие запрограммировать практически неограниченное количество событий с точностью, равной дискретности используемого таймера. Так как подсистема работает на базе прерываний аппаратного таймера, то взаимное влияние событий ограничено собственно только пересечением обработчиков прерываний.
  • IPC — межпроцессное сообщение. Так как различные задачи работают не в вакууме, а общаются между собой, то ОС предоставляет средства такого общения, от простых семафоров и мьютексов, позволяющих, например, притормозить один поток в ожидании, пока другой освободит нужную периферию или получит нужные данные, до сообщений, в которых можно передавать данные и которые сами по себе могут являться триггером для переключения ОС из потока-отправителя в поток-получатель (так, например, делается выход из обработчика прерывания: если у вас есть тяжёлая процедура, которая должна запускаться по прерыванию, то из прерывания вы просто отправляете сообщение в собственно поток с этой процедурой, который выполняется уже в обычном контексте, не мешая работе системы).
  • Наборы стандартных библиотек и функций — помимо API работы с самим микроконтроллером, ОС может предоставлять вам доступ к стандартным библиотекам, в том числе сторонней разработки. Это могут быть простые, но востребованные процедуры типа форматирования чисел между разными представлениями, процедуры шифрования и расчёта контрольных сумм, а также большие сторонние библиотеки, например, сетевые стеки, файловые системы и так далее, адаптированные под API данной ОС.
  • Наборы драйверов — многие ОС предоставляют также «из коробки» наборы драйверов для внешних по отношению к контроллеру датчиков и систем.

В целом, благодаря ОС программирование микроконтроллеров становится всё ближе к написанию софта под большие ПК — даже API местами очень похоже на старый добрый POSIX.

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

  • FreeRTOS [1] — строго говоря, вообще не ОС, а только ядро ОС. Обвес к нему — на усмотрение разработчика. Самая популярная на данный момент микроконтроллерная ОС (но это не значит, что вам надо использовать именно её)
  • Contiki OS [2] — старая универстетская разработка, получившая известность благодаря сетевым возможностям, в частности, стеку 6LoWPAN. По нынешним меркам тяжеловесна, а многие концепции давно устарели (например, там нет нормальной по современным понятиям реализаци многозадачности). Сейчас переписывается под названием Contiki NG [3] — из проекта выкидывают разный хлам, но общая концепция не меняется.
  • RIOT OS [4] — молодая университетская разработка, претендующая на место Contiki. Поддерживает кучу процессоров разных архитектур (включая даже старшие ATMega) и бурно развивается. Легковесна и понятна. Написана на C. Многого местами не хватает, но проста в освоении, поддержке и доработке под свои нужды. Оптимальна, на мой взгляд, в качестве учебной для профильных студентов.
  • ARM Mbed [5] — коммерческая (но опенсорсная, Apache 2.0) ОС разработки самой ARM Holdings. Была немного печальна в версии 2.0, но стремительно рванула ввысь в новой ветке 5.x. Написана на C++ и имеет достаточно понятный и простой API. Поддерживает гору различных плат, и в общем и целом делает это хорошо. У части вендоров есть собственные команды, занимающиеся поддержкой отладочных плат этого вендора в Mbed.
  • TI-RTOS [6] — собственная разработка Texas Instruments, работает примерно со всем, что TI когда-либо выпустила, ставится из коробки сразу с поддержкой примерно всего, но установка занимает пару часов и уменьшает ваш диск на несколько гигабайт. На мой вкус, API чрезмерно тяжеловесен.

Во многих случаях ОС не привязана к какой-либо среде разработки, а в качестве тулчейна обычно используется типовой arm-none-eabi-gcc. Так, RIOT изначально сделан на Makefile'ах, а потому с ним можно работать хоть из Visual Studio, хоть из командной строки. ARM Mbed имеет собственную систему сборки на питоне (mbed-cli [7]), а также может быть быстро и практически автоматически настроен в PlatformIO [8], равно как и экспортирован через mbed-cli в проекты для Keil uVision или те же Makefiles.

То же самое касается и отладчиков — слова, не слышанного в мире Arduino. Как правило, любая платформа позволяет использовать старый добрый gdb в связке с любым нравящимся вам JTAG/SWD-отладчиком.

Этот страшный входной порог

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

А ничего. Его уже давно нет. Все перечисленные выше плюшки, сервисы и возможности с точки зрения кривой обучения получаются практически бесплатно.

Например, с RIOT OS «высокий входной порог» от нулевого знакомства с микроконтроллерами до запуска первой программы выглядит так:

  1. Скачать и поставить MinGW, arm-none-eabi-gcc и GNU make (для пользователей Windows 10 первый пункт не нужен, достаточно из Магазина поставить свежую Ubuntu)
  2. Скачать и распаковать последний релиз RIOT с Гитхаба [9]
  3. cd RIOT/examples/hello-world
  4. make BOARD=nucleo-l152re
  5. залить полученный HEX-файл любым способом на Nucleo (в свежих версиях ST-Link это можно сделать просто киданием его на виртуальный диск)

Вся программа, если вы заглянете в main.c, выглядит так:

#include <stdio.h>

int main(void)
{
    puts("Hello World!");

    printf("You are running RIOT on a(n) %s board.n", RIOT_BOARD);
    printf("This board features a(n) %s MCU.n", RIOT_MCU);

    return 0;
}

Видите эту страшную инициализацию процессора? Бессонные ночи, проведённые над даташитами? Бесконечной длины вызовы функций SPL? Ассемблер, наконец?

Вот и я не вижу.

В принципе, это был сложный путь. На более простом вообще ничего локально устанавливать не надо — ARM Mbed предоставляет возможность компилировать всё прямо в онлайне.

Давайте сделаем метеостанцию

(не то чтобы я любил метеостанции, просто я сейчас сижу дома с температурой, и помимо всяких сугубо специализированных вещей под разные b2b-проекты, у меня тут из дженерика под рукой только Nucleo-L152RE да несколько плат с BME280 и OPT3001)

Быстрый старт с ARM Mbed: разработка на современных микроконтроллерах для начинающих - 3

1) Регистрируемся на mbed.com [10].

2) Жмём там кнопочку «Compiler» и попадаем в онлайн-среду разработки. В правом верхнем углу в ней есть кнопочка выбора платы, с которой мы будем работать. Жмём её, во всплывшем окошке жмём «Add board» и выбираем ту версию Nucleo, которая у нас имеется (у меня это L152RE, у вас может быть и другая, а может быть и вообще не Nucleo, это неважно). Вернувшись в окно с проектами, выбираем в той же менюшке нашу Nucleo как текущую плату.

Быстрый старт с ARM Mbed: разработка на современных микроконтроллерах для начинающих - 4

3) Жмём слева вверху New → New Program. Mbed предлагает нам сразу какую-то гору образцов, но по крайней мере для Nucleo-L152 все они притащат с собой старую версию ОС. Мы же хотим новую, свежую, вышедшую три дня назад 5.9.5, поэтому выберем самое простое — «Empty program».

Для дальнейшей работы нам потребуются две вещи — во-первых, подключить собственно исходники mbed, во-вторых, создать и чем-нибудь наполнить файл main.cpp. Для подключения исходников надо не только указать #include, но и собственно импортировать в проект библиотеку mbed-os.

Для этого:

  • Жмём «Import», в открывшемся разделе справа вверху жмём малозаметную ссылку «Click here to import from URL», суём ей вот эту ссылку [11] (при всём страшном виде, это просто ссылка на наиболее свежий релиз Mbed, которую мы взяли со страницы релизов [12])
  • Тыкаем радиобаттон «Library»
  • В качестве целевого проекта уже сам подставился наш проект
  • Тыкаем «Import»

Быстрый старт с ARM Mbed: разработка на современных микроконтроллерах для начинающих - 5

Теперь жмём на имени проекта правой кнопкой → «New file» → создаём файл main.cpp. Заполняем его тут же самым тривиальным образом:

#include "mbed.h"

DigitalOut led(LED1);

int main()
{
    printf("Hello World !n");

    while(1) {
        wait(1); // 1 second
        led = !led; // Toggle LED
    }
}

(LED1 уже определён в описании платы — это собственно единственный имеющийся на Nucleo управляемый пользователем светодиод)

4) Жмём Ctrl-D (или кнопку «Compile»), ждём секунд десять-пятнадцать, скачиваем получившийся файл. Подключаем Nucleo к USB-порту (мощная засада: на Nucleo стоят разъёмы mini-USB, шнурок для которых есть уже не у всех и не всегда), обнаруживаем на компьютере новый диск размером 540 КБ с названием «Node_L152RE». Внутри него лежит файл MBED.HTM, недвусмысленно намекающий, зачем этот диск нужен.

На самом деле, конечно, любой кинутый на него BIN- или HEX-файл автоматически прошивается в контроллер, с Mbed это напрямую не связано, просто такой интерфейс программирования.

Кидаем туда скачанный файл. Nucleo моргает светодиодиком программатора, а потом начинает размеренно моргать светодиодом на плате — тем самым LED1.

5) Теперь нам надо добавить датчик BME280, ибо метеостанция у нас или что? Пятнадцать секунд поиска приводят нас к библиотеке с его поддержкой [13], на странице которой надо жамкнуть «Import Library» (библиотеки, конечно, бывают разные, и всегда стоит смотреть внутрь — но пока опустим эти детали). При импорте не забываем отметить, что это Library, и импортировать её надо в наш текущий проект («Target path»).

Быстрый старт с ARM Mbed: разработка на современных микроконтроллерах для начинающих - 6

Тут же лежит пример использования библиотеки [14], в котором можно вкратце посмотреть, как ей пользоваться.

6) Добавляем обращение к библиотеке в свой код, заодно убирая из него всякий хеллоуворлд:

#include "mbed.h"
#include "BME280.h"

DigitalOut led(LED1);
BME280 sensor_bme(D14, D15);

int main()
{
    while(1) {
        wait(1); // 1 second
        led = !led; // Toggle LED
        printf("%2.2f degC, %04.2f hPa, %2.2f %%rn", sensor_bme.getTemperature(), sensor_bme.getPressure(), sensor_bme.getHumidity());
    }
}

В качестве ножек я, ничтоже сумняшеся, напрямую указал пины, на которые мне в моей Nucleo будет удобно ткнуть I2C-датчик. Вообще, т.к. они прямо на плате подписаны как SDA и SCL (хотя вообще у STM32L1 несколько портов I2C, и каждый можно повесить на несколько вариантов ножек), то, скорее всего, дефайны I2C_SDA и I2C_SCL указали бы на них же. Но мне сейчас тяжело думать о настолько сложных материях.

7) Подключаем датчик. Четыре провода — +3,3 В, земля, SDA, SCL. Так как и STM32, и датчик 3,3-вольтовые, думать о согласовании уровней не надо.

Быстрый старт с ARM Mbed: разработка на современных микроконтроллерах для начинающих - 7

8) Снова жмём «Compile», скачиваем новый BIN-файл и кидаем его в Nucleo.

8) ??????

(на самом деле на этом пункте мы открываем свою любимую терминалку — я обычно использую Termite [15], и цепляем её на виртуальный порт, соответствующий Nucleo; в современном компьютере это с большой вероятностью будет единственный наличествующий COM-порт. Скорость выставляем 9600 бит/с)

9) PROFIT!!!

Быстрый старт с ARM Mbed: разработка на современных микроконтроллерах для начинающих - 8

Работает. Но тут мы, как хорошие программисты, не удовлетворяемся идеей «если индусский код работает, то не так уж он и плох», и смотрим, что мы хотим улучшить.

Глобально — наверное, две вещи. Во-первых, код, крутящийся в while(1) с 1-секундной задержкой — это очень плохая концепция, так как и задержка там не 1 секунда, а 1 секунда плюс время выполнения кода, и мало ли что с другими периодами мы захотим тут ещё запустить.

Во-вторых, 9600 бит/с — это как-то грустно, 2018 год на дворе, давайте хотя бы 115200.

С портом всё просто: надо создать свой экземпляр класса Serial, указать ему нужную скорость, а потом печатать через него.

Serial pc(SERIAL_TX, SERIAL_RX);
pc.baud(115200);

С избавлением от while(1) тоже, впрочем, больших проблем не возникает. В Mbed есть такая штука, как очередь событий (EventQueue [16]), события в которой могут вызываться через заданный промежуток времени однократно (метод call) или постоянно (метод call_every), а результатом их вызова будет выполнение той или иной функции.

Оформляем в код:

#include "mbed.h"
#include "BME280.h"

DigitalOut led(LED1);
BME280 sensor_bme(D14, D15);
EventQueue eventQueue(/* event count */ 50 * EVENTS_EVENT_SIZE);

void printTemperature(void)
{
    printf("%2.2f degC, %04.2f hPa, %2.2f %%rn", sensor_bme.getTemperature(), sensor_bme.getPressure(), sensor_bme.getHumidity());
    led = !led; // Toggle LED
}

int main()
{
    eventQueue.call_every(1000, printTemperature); // run every 1000 ms
    eventQueue.dispatch_forever();
    
    return 0;
}

Собираем, запускаем. Работает точно так же, как и раньше (что уже неплохо), но — теперь мы получили решение, которое можно практически неограниченно и без каких-либо проблем масштабировать. Мы совершенно спокойно можем накидать в eventQueue других событий с другими периодами (главное — следить, чтобы одномоментно у нас более 50 событий не жило), и до тех пор, пока их время выполнения не начнёт тупо пересекаться, нам вообще не надо беспокоиться о том, как они взаимодействуют.

Почему?

Потому что, в отличие от запихивания всего в один while(1) с тщательно выверенными задержками, они не взаимодействуют.

Ладно, теперь давайте добавим сюда OPT3001. Не то чтобы измерение освещённости было обязательной чертой метеостанции, но раз уж у меня есть на столе этот датчик…

С OPT3001 мы имеем некоторую проблему — это очень хороший (и точностью, и особенно энергопотреблением), но не сильно популярный по сравнению с тем же TSL2561 среди самодельщиков датчик, поэтому готовую библиотечку для него прямо в Mbed найти не получается. Я бы сказал, что драйвер для него и самому написать несложно (я писал), но давайте сунемся в гугль — и первой ссылкой обнаружим https://github.com/ashok-rao/mbed-OPT3001 [17].

Снова кликаем Import, выбираем в заголовке малозаметную ссылку «Click here to import from URL», вставляем URL проекта на гитхабе, не забываем тыцнуть пункт «Library» — ну и собственно «Import».

На самом деле нет. Астанавитес.

На самом деле этот драйвер написан настолько на коленке, что его использовать вообще не стоит ни в каком случае. Как однажды сказал великий писатель, «открыв OPT3001.cpp, кровавые слёзы потекли из глаз моих», поэтому я отложил ненадолго все важные дела и быстренько набросал более пристойный драйвер [18] (то есть, теперь я уже дважды писатель драйвера OPT3001).

Далее по известному уже пути: Import → Click here to import from URL → github.com/olegart/mbed_opt3001 [18] → Library → Target Path = наш проект → Import.

Добавляем в проект остатки кода:

#include "mbed.h"
#include "BME280.h"
#include "OPT3001.h"

DigitalOut led(LED1);
BME280 sensor_bme(D14, D15);
OPT3001 sensor_opt(D14, D15);
EventQueue eventQueue(/* event count */ 50 * EVENTS_EVENT_SIZE);
Serial pc(SERIAL_TX, SERIAL_RX);

void printTemperature(void)
{
    pc.printf("%2.2f degC, %04.2f hPa, %2.2f %%rn", sensor_bme.getTemperature(), sensor_bme.getPressure(), sensor_bme.getHumidity());
    
    pc.printf("%ld luxrn", sensor_opt.readSensor());
    
    led = !led; // Toggle LED
}

int main()
{
    pc.baud(115200);
    
    eventQueue.call_every(1000, printTemperature); // run every 1000 ms
    eventQueue.dispatch_forever();
    
    return 0;
}

Compile, сохраняем файл, кидаем его в Nucleo, не забываем переключить терминалку на 115200…

Быстрый старт с ARM Mbed: разработка на современных микроконтроллерах для начинающих - 9

PROFIT!!!

Бонус: энергосбережение

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

Среди вещей, о которых мы не задумывались, есть такая штука, как энергопотребление. Иногда о нём задумываться, однако, приходится.

Специально для его измерения на Nucleo есть джампер «IDD», сняв который, можно воткнуться туда мультиметром и посмотреть, что же у нас происходит (хотя вообще говоря, мультиметр — плохой инструмент для измерения энергопотребления микроконтроллеров, при быстрых скачках потребления он фиксирует в основном погоду на Марсе).

Быстрый старт с ARM Mbed: разработка на современных микроконтроллерах для начинающих - 10

На моём L152 ток получается в районе 10 мА, немного гуляя туда-сюда в зависимости от свечения LED1, который мы по привычке всё ещё дёргаем (он потребляет 2-3 мА). Как-то довольно негуманно для системы, которая большую часть времени ничего не делает, вы не находите?..

В данном случае нам мешает та самая очередь сообщений, которой мы недавно радовались. Дело в том, что она позволяет задавать тайминги с миллисекундной точностью, а потому работает на быстром аппаратном таймере процессора, выключающемся при уходе последнего в сон — говоря иначе, если процессор заснёт, наш eventQueue.call_every заснёт тоже, причём навсегда. На STM32L151 нет как такового специального быстрого таймера, тикающего во сне — точнее, на некоторых моделях процессора его можно вытащить из обычных часов RTC (начиная с L151CB-A и выше, регистр RTC_SSR), но в общем случае — нет.

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

Но ведь нам же здесь и не нужны миллисекундные интервалы, у нас всё равно данные снимаются раз в секунду? И ведь сообщения нам надо обрабатывать, только когда процессор проснулся, потому что если не проснулся — откуда там сообщения?

Поэтому мы смело берём штуку под названием LowPowerTicker [19], работающую на RTC, и начинаем переписывать код на ней — благо поменять надо всего несколько строчек. Запустив LowPowerTicker, можно сказать системе sleep() — и она уснёт глубоким сном (ну или, точнее, тем, каким позволяют другие модули — здесь есть нюансы, например, какой-то модуль, ломающийся во сне, может сон на время своей работы запретить).

Но есть одна проблема: вызовы тикера выполняются в прерывании, а в прерывании нельзя делать какие-то долгие вещи — у нас же чтение датчиков очень, очень долгое (у OPT3001, например, от запроса до ответа проходит 100 мс). С printf всё ещё хуже — он ещё и громоздкий, так что в прерывании может тупо не хватить стека. Поэтому нам надо из прерывания послать сигнал функции, собственно читающей датчики и печатающей значение так, чтобы она выполнилась уже в обычном контексте.

Грубым методом было бы устанавливать в прерывании переменную, а в основном контексте читать значение этой переменной в while(1), и если вдруг она взвелась — вызывать чтение датчиков. Но мы же ведь с вами не ардуинщики какие, чтобы в while(1) запихивать осмысленный код?

Внезапно, поможет нам в этом тот же самый EventQueue, который мы только что отбросили.

#include "mbed.h"
#include "BME280.h"
#include "OPT3001.h"

DigitalOut led(LED1);
BME280 sensor_bme(D14, D15);
OPT3001 sensor_opt(D14, D15);
EventQueue eventQueue(/* event count */ 50 * EVENTS_EVENT_SIZE);
Serial pc(SERIAL_TX, SERIAL_RX);
LowPowerTicker lpTicker;

void printTemperature(void)
{
    pc.printf("%2.2f degC, %04.2f hPa, %2.2f %%rn", sensor_bme.getTemperature(), sensor_bme.getPressure(), sensor_bme.getHumidity());
    pc.printf("%ld luxrn", sensor_opt.readSensor());        
    led = !led; // Toggle LED
}

void tickerIRQ (void)
{
    eventQueue.call(printTemperature);
}

int main()
{
    pc.baud(115200);
    
    lpTicker.attach(tickerIRQ, 1); // every second
    
    while(1)
    {
        eventQueue.dispatch(0);
        sleep();
    }
}

Что мы здесь сделали? Да в общем ничего сложного: добавили Low Power Ticker, который раз в секунду выдаёт прерывание. По прерыванию контроллер просыпается, обработчик прерывания зовёт eventQueue.call, который должен вытащить собственно тяжёлую работу из обработчика во внешнюю функцию (сонливый процессор тут ему не мешает, т.к. у него нет никакой задержки, в течение которой процессор мог бы заснуть — call подразумевает немедленное выполнение). Сам по себе .call ничего бы не сделал, но в основном теле программы у нас снова появился while(1), в котором всего две строки — метод dispatch для очереди, обрабатывающий сообщения в ней, и sleep(), уводящий процессор в сон до следующего прерывания.

Всё.

Заснули → прерывание → проснулись → cal → вышли из прерывания → dispatch → сняли показания с датчиков → sleep() до следующего щелчка тикера.

PROFIT???

Быстрый старт с ARM Mbed: разработка на современных микроконтроллерах для начинающих - 11

PROFIT!!!

(на самом деле, 0,57 мА потребления — это очень много, хотя и в пару десятков раз меньше прежнего. Но в Mbed, скорее всего, не учтён неприятный нюанс с процессорами STM32L1: у них по умолчанию ножки стоят в режиме Digital In без подтяжки, а надо бы Analog In или подтяжку включить, иначе триггеры Шмитта на входе этих ножек довольно прилично жрут из-за спонтанных переключений из-за наводок. Кроме того, в Nucleo ток может утекать через ножки JTAG в сторону встроенного на плату программатор-отладчика)

И всё это мы получили в общем-то без особых усилий. На самом деле, даже ещё не добравшись до «настоящей RTOS» с её процессами, семафорами, сообщениями и прочими вкусностями.

Единственной платой стало разве что потребление ресурсов — 55,1 КБ флэша и 9,2 КБ ОЗУ (можно посмотреть, жамкнув «Build details» в логе с сообщением об успешной сборке проекта), но и это для современных контроллеров — не то чтобы гигантские объёмы, даже совершенно типовой 2-долларовый STM32F103CB имеет 128 КБ первого и 20 КБ второго. В принципе же минимальный проект на микроконтроллере и ОС из тех, с которыми работал я, жил на STM32F030F4P6 с 16 КБ флэша и 4 КБ ОЗУ — на RIOT OS со слегка подрезанными крыльями.

Кроме того, в данном случае мы вообще никак не экономили ресурсы, в то время как в приличном обществе за использование float внутри printf на микроконтроллере разработчика просто молча выводят за дверь и расстреливают в коридоре. Ничего при этом не говорят — все и так понимают, за что [20]. Даже malloc лучше, malloc в некоторых обстоятельствах всё же можно простить.

P.S. Для ленивых — написанный здесь код сразу в репозитарии Mbed [21].

P.P.S. Детали для сборки: Nucleo-L152RE [22] (любая другая Nucleo / Discovery тоже подойдёт, проверьте только, что она есть в списке плат [23] — но там, похоже, все есть), BME280 [24] (внимание: на Али эти платки дешевле стоимости самого чипа не только потому, что китайцы щедрые, но и потому, что на них лепят втрое более дешёвый BMP280, который внешне неотличим). Где вам взять OPT3001 ближе Али — не знаю, если не считать родной девкит TI за три тысячи рублей.

Заключение

Я не хочу сказать, что в мире микроконтроллерных RTOS всё безоблачно, а каждая строчка кода сама собой сочится благодатью — тот же Mbed до приемлемого, с моей точки зрения, состояния дошёл где-то в последнюю пару лет, в RIOT только в релизе 2018.07 исправили часть старых болячек (а часть так и не исправили), и так далее.

Однако идею о том, что только Arduino «обеспечивает минимальный порог входа», а все остальные платформы — это обязательное ковыряние в странных библиотеках, сотнях страниц даташитов и ассемблере, может разделять только человек, последнее десятилетие пробывший где-то в очень удалённых от цивилизации местах (в не столь удалённых Интернет, говорят, и то уже провели).

Абсолютно ничего сложного в том, чтобы начать работать с современными контроллерами в современной среде разработки, нет. Более того, при этом вы получаете массу вкусных, полезных, и главное — грамотно реализованных вещей, радикально ускоряющих и упрощающих разработку. Нет, я не про библиотеки «мигания светодиодом», хотя и их тоже достаточно — я про таймеры, многозадачность, сообщения, сервисы и всё остальное, что не имеет никакого смысла в 2018 году нашей эры писать руками, потому что всё это уже написано до вас и, скорее всего, сильно лучше, чем когда-либо напишете вы.

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

Да, можно извернуться и за несколько дней сваять на ардуине кособокое граблеобразное подобие того, что пользователи современных платформ получают из коробки в элегантном отглаженном виде. Можно даже написать на этом какой-то осмысленный проект, который рано или поздно ценой серьёзных затраченных усилий удастся заставить работать и при этом не вываливаться из ресурсов типового AVR.

Но…

Быстрый старт с ARM Mbed: разработка на современных микроконтроллерах для начинающих - 12

Зачем?!

Посмотрите на календарь, 2018 год на дворе! Вам настолько себя не жалко?

Автор: Олег Артамонов

Источник [25]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/arduino/289518

Ссылки в тексте:

[1] FreeRTOS: https://www.freertos.org/

[2] Contiki OS: http://www.contiki-os.org/

[3] Contiki NG: https://github.com/contiki-ng/contiki-ng

[4] RIOT OS: https://riot-os.org/

[5] ARM Mbed: https://www.mbed.com/en/

[6] TI-RTOS: http://processors.wiki.ti.com/index.php/TI-RTOS

[7] mbed-cli: https://github.com/ARMmbed/mbed-cli

[8] настроен в PlatformIO: https://habr.com/post/358682/

[9] с Гитхаба: https://github.com/RIOT-OS/RIOT/releases

[10] mbed.com: http://mbed.com

[11] вот эту ссылку: https://github.com/ARMmbed/mbed-os/commit/f8b140f8d7cb226e41486c5df66ac4f3ce699219

[12] страницы релизов: https://github.com/ARMmbed/mbed-os/releases/tag/mbed-os-5.9.5

[13] библиотеке с его поддержкой: https://os.mbed.com/components/BME280-Combined-humidity-and-pressure-se/

[14] пример использования библиотеки: https://os.mbed.com/users/MACRUM/code/BME280_Hello/file/710f0b2843e4/main.cpp/

[15] Termite: https://www.compuphase.com/software_termite.htm

[16] EventQueue: https://os.mbed.com/docs/v5.6/tutorials/the-eventqueue-api.html

[17] https://github.com/ashok-rao/mbed-OPT3001: https://github.com/ashok-rao/mbed-OPT3001

[18] более пристойный драйвер: https://github.com/olegart/mbed_opt3001

[19] LowPowerTicker: https://os.mbed.com/docs/v5.8/reference/lowpowerticker.html

[20] все и так понимают, за что: https://os.mbed.com/blog/entry/Reducing-memory-usage-with-a-custom-prin/

[21] в репозитарии Mbed: https://os.mbed.com/users/olegart/code/

[22] Nucleo-L152RE: https://www.electronshik.ru/item/ST/NUCLEO-L152RE

[23] списке плат: https://os.mbed.com/platforms/?tvend=10

[24] BME280: https://www.electronshik.ru/item/LCTECH/BME280%20atmospheric%20pressure%20temperature%20humidity%20sensor%20module

[25] Источник: https://habr.com/post/420435/?utm_source=habrahabr&utm_medium=rss&utm_campaign=420435