Программируем микроконтроллеры ESP32 и STM32 на C# (nanoFramework)

dotnet nanoFramework

.NET nanoFramework — это бесплатная платформа с открытым исходным кодом, основанная на .NET и предназначена для малых встраиваемых устройств, микроконтроллеров. С её помощью можно разрабатывать различные устройства для Интернета вещей, носимые устройства, научные приборы, робототехнические устройства, можно создавать прототипы и даже использовать на промышленном оборудовании. В первой части мы познакомились с платформой .NET nanoFramework, её архитектурой, основными возможностями, посмотрели примеры программного кода. Теперь перейдем к практике, установим nanoFramework на микроконтроллеры серии ESP32 и STM32, напишем первый «Hello World!», поработаем с аппаратными интерфейсами, и оценим переносимость кода с «большого» .NET на платформу nanoFramework.


Сейчас на рынке представлено большое количество самых разнообразных микроконтроллеров. Одна из самых известных на сегодняшний день это платформа Arduino, которая использует язык программирования C/C++. По степени удобства, легкости, простоте, nanoFramework ничуть не уступает платформе Arduino. Несмотря на небольшой возраст, nanoFramework уже работает на многих микроконтроллерах, с поддержкой отладки кода и точками останова в удобной IDE MS Visual Studio. nanoFramework просто идеальное решение для существующих .NET разработчиков, которые не хотят останавливаться на достигнутом и им тоже интересна разработка для Интернета вещей (IoT). Ведь все, что необходимо знать и уметь для программирования на nanoFramework, они уже знают и умеют. Программный код, библиотеки, с большой степени переносимы один-к-одному, только необходимо учитывать малый объем RAM и низкую производительность процессора.

nanoFramework Visual Studio 2019
Отладка кода приложения на nanoFramework в Visual Studio 2019

Платформа nanoFramework отлично подходит и для сферы обучения. Нет ничего лучше, чем научиться разрабатывать приложения на C# для микроконтроллеров, где у тебя нет гигабайтов оперативной памяти и n-количества ядер процессора, вот тут начинаешь серьезно задумываться над оптимизацией алгоритмов.

И конечно nanoFramework подойдет всем тем, кто стремится к творчеству и хочет сделать свою жизнь более интересной и разнообразной, добавить в неё автоматизацию, роботов, и ИИ. Финансовые затраты для начала работы с nanoFramework составят не более стоимости чашки «кофа» в Starbucks (при условии покупки микроконтроллера ESP32). Для разработки используется бесплатная версия Visual Studio 2019 Community Edition. Все что от вас требуется, это терпение и упорство.

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

nanoFramework logo
Логотипы nanoFramework

Рассмотрим некоторые микроконтроллеры для платформы nanoFramework.


Поддерживаемые устройства делятся на две категории: основные платы и поддерживаемые сообществом (дополнительная ссылка — GitHub nanoframework/nf-Community-Targets). В зависимости от устройства будут доступны все или только некоторые сборки (.NET Assemblies). При выборе обращайте внимание на поддерживаемые функции, такие как:   Gpio, Spi, I2c, Pwm, Adc, Dac, Serial, OneWire, CAN, Events, SWO, Networking, Large Heap и UI. Например, работа с графическими дисплеями в серии модулей ESP32 будет доступна только на тех, в состав которых входит память PSRAM (Pseudo static RAM).

Модуль ESP-WROOM-32  — самое простое и доступное решение для начала осваивания nanoFramework. На основе данного модуля построена отладочная плата ESP32 DevKit v1, стоимостью до $3.65. В основе модуля чип SoC ESP32-D0WDQ6 от компании Espressif, в него входит 2-ядерный 32-битный процессор Tensilica Xtensa LX6 с блоками памяти ROM на 448 КБ и SRAM на 520 КБ. На кристалле расположены беспроводные модули Wi-Fi/Bluetooth, датчик Холла и сенсор температуры, доступны интерфейсы: ШИМ, АЦП, ЦАП, I2C, SPI, UART/Serial, I2S.  Дополнительно модуль содержит Flash-память на 4 МБ.

nanoFramework ESP32 DevKit
Отладочная плата ESP32 DevKit v1

При выборе модуля, возможно, вы встретите обозначение не ESP-WROOM-32, а
ESP32-WROOM-32U или ESP32-WROOM-32D. Разница заключается в следующем:

  • ESP32-WROOM-32U — модуль ESP-WROOM-32 с подключением внешней антенны;
  • ESP32-WROOM-32D — модуль ESP-WROOM-32 c встроенной антенной в виде змейки на самой плате.


Важно! Обычно на отладочной плате с модулем ESP-WROOM-32 размещают две кнопки EN и Boot. Кнопка EN — перезагрузка, Boot — ручной перевод микроконтроллера в режим загрузки прошивки. При нажатие кнопки Boot, контакт GPIO0 на микроконтроллере замыкается на Землю (GND), и в терминале можно увидеть следующий текст:

ets Jun 8 2016 00:22:57
rst:0x1 (POWERON_RESET),boot:0x7 (DOWNLOAD_BOOT(UART0/UART1/SDIO_REI_REO_V2))
waiting for download


ESP32 ESP-WROOM-32
Кнопки EN и Boot на плате с модулем ESP-WROOM-32

У некоторых пользователей модулей ESP-WROOM-32 без кнопки Boot, исходя из сообщений на форумах,  возникают проблемы с переключением микроконтроллера в режим прошивки, поэтому лучше приобретайте модули с наличием кнопки Boot на плате. Но это касается модулей первой версии/ревизии, в третьей ревизии для прошивки нажимать кнопку Boot не обязательно. Далее этот момент будет подробно рассмотрен в пункте выбора прошивки.

Недавно команда разработчиков nanoFramework сообщила, что следующие прошивки для устройств ESP32 будут основаны на системе сборки ESP-IDF 4.3.1. Это означает поддержку микроконтроллеров всей серии ESP32, включая: ESP32_S2, ESP32_S3, ESP32_C3 (RISC-V). Новая система сборки позволяет легко добавлять новые библиотеки и функции в nanoFramework, которые доступны или предоставляются ESP-IDF. Теперь у двухъядерных чипов ESP32 задействованы оба ядра для работы кода. Был успешно перенесен компонент mbedTLS из ESP-IDF без потери оптимизации и использования аппаратных блоков (криптографические вычисления, генерации случайных чисел и т. д.). Конфигурация сети, профили Wi-Fi, CA корневые сертификаты и сертификаты устройств X509 теперь доступны для использования также на всей серии ESP32 устройств.

Если вам не хватает количества контактов GPIO, то можно выбрать один из микроконтроллеров поддерживаемым сообществом — STM32. Например, отладочная плата ST Nucleo64 F411RE на базе SoC Cortex-M4@100 MHz с блоками памяти ROM на 512 КБ и SRAM на 128 КБ. Дополнительно на плате размещены контакты для подключения Arduino-совместимых модулей. По стоимости плата уже существенно дороже ESP32, на торговых площадках продается от $29 и выше.

ST Nucleo64 F411RE
Плата ST Nucleo64 F411RE


В связи с малыми аппаратными возможностями микроконтроллеров, в отличие от .NET IoT, у каждого класса библиотек есть соответствующий Nuget пакет. Все пакеты добавляются с помощью системы Nuget, как это принято в .NET Core. Рассмотрим подробно библиотеки классов для представления возможностей платформы. Все библиотеки делятся на общие для всех устройств и на специальные, предназначенные для конкретных микроконтроллеров или серии устройств. Классы имеющие название, например System.Device.Gpio совместимы с .NET IoT для Linux, а с названием Windows.Device.Gpio совместимы с Windows IoT Core. Полный список библиотек в документации
Class Libraries. Documentation — nanoFramework.

Библиотеки .NET nanoFramework (xlsx, pdf)

Таблица библиотек .NET nanoFramework


Многие датчики, используемые для подключения к Arduino, также поддерживаются платформой, среди них: акселерометры, газоанализаторы, замер уровня освещения, барометры, термометры, инфракрасные датчики, гироскопы, компасы, драйверы двигателей, NFC-модули и т.д.

Полный список поддерживаемых датчиком представлен в документации List and category of devices. Devices — nanoFramework. С примерами GitHub nanoFramework.IoT.Device/devices.


Для разработки приложений для nanoFramework потребуется Microsoft Visual Studio Community 2019 Version 16.11, установленная платформа .NET, ОС Windows 10 (в Windows 7 не открывается панель Device Explorer). Расширение устанавливается стандартно, через меню: Extensions → Manage Extensions. Название расширения: .NET nanoFramework VS2019 Extension.

Extension nanoFramework
Расширение nanoFramework для Microsoft Visual Studio

После установки расширения появится окно Device Explorer (меню View → Other Windows → Device Explorer). В этом окне наиболее важные всего три кнопки: Ping Device, Device Capabilities, Erase App.

nanoFramework Device Explorer
Панель Device Explorer расширения nanoFramework

Команды:

  • Ping Device — проверяет доступность подключенного устройства/микроконтроллера;
  • Device Capabilities — выводит в консоль список возможностей устройства;
  • Erase App — удаляет загруженное приложение (nanoFramework остается на месте).


Для возможности исполнения .NET кода на микроконтроллере, необходимо его прошить соответствующей прошивкой nanoFramework. Прошивка осуществляется с помощью утилиты nanoFirmwareFlasher. Установим драйвера для микроконтроллера, утилиту и прошьем устройство.

Шаг 1 — Установка драйвера для моста CP2102 USB to UART


Если при подключение отладочной платы ESP32 DevKit v1 по USB, вы видите что в Диспетчере устройств  микроконтроллер находится в узле Другие устройства, значит необходимо установить драйвер.

Device Manager ESP32
Диспетчер устройств

Загружаем драйвер с официального сайта CP210x USB to UART Bridge VCP Drivers и устанавливаем. После установки драйвера, микроконтроллер доступен на порту COM3.

Device Manager ESP32
Диспетчер устройств

Шаг 2 — Установка утилиты nanoFirmwareFlasher


Теперь устанавливаем утилиту nanoFirmwareFlasher, для этого запускаем командную строку от имени Администратора и выполняем команду:

dotnet tool install -g nanoff


Для вызова справки выполнить команду:

nanoff --help


Обновление утилиты выполняется следующей командой:

dotnet tool update -g nanoff

Шаг 3 — Вывод идентификационной информации для отладочной платы


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

nanoff --platform esp32 --serialport COM3 --devicedetails


где параметр --platform указывает на Target платформы, --serialport — номер COM порта, в данном случае COM3.

Результат выполнения команды:

nanoFramework ESP32
Идентификационная информация отладочной платы

Когда будет появляться фиолетовая строка »*** Hold down the BOOT/FLASH button in ESP32 board ***», то в этот момент необходимо нажать на плате кнопку Boot, иначе команда не выполнится.

Из вывода видим, что на плате установлен чип ESP32-D0WDQ6 с 4 Мб Flash памяти, на борту есть WiFi и BT.

Шаг 4 — Выбор прошивки


Теперь переходим к самому сложному шагу. Для серии ESP32 существует несколько различных прошивок (ESP32 Firmware versions). Espressif выпускает чипы SoC, затем на их основе формирует модули. Обратимся к описанию ESP32-S Series на официальном сайте. Модули серии ESP32 выпускаются на базе чипов:

  • ESP32-D0WD-V3 (третья версия/ревизия);
  • ESP32-D0WD (первая версия/ревизия — старый вариант);
  • ESP32-D0WDQ6 (первая версия/ревизия — старый вариант);
  • и другие.


Чипы ESP32-D0WD и ESP32-D0WDQ6 по функциональности ничем не отличаются, различие заключается только в форм-факторе самого чипа.

Рассмотрим различия между модулями. Существую модули версии ESP32-WROOM и ESP32-WROVER, эти модули выпускаются как на базе ESP32-D0WD-V3, так и на ESP32-D0WD. Модуль ESP32-WROVER отличается от ESP32-WROOM только наличием памяти PSRAM. PSRAM — это дополнительный напаянный чип RAM помимо встроенной памяти. Обычно указывается в описание к плате и бывает размером 2 или 8 Мб. Дополнительная оперативная память необходима, если подключаете дисплей для вывода UI используя WPF  (поддерживается в модулях ESP-WROVER-KIT с LCD ILI9341 на SPI интерфейсе).

Доступные прошивки (target) nanoFramework:

  • ESP32_REV0 — (DEV) подходит для всех плат ESP32 без поддержки PSRAM;
  • ESP32_REV3 — (DEV) подходит для всех плат ESP32 с чипом ESP32 версии/ревизии 3 без поддержки PSRAM;
  • ESP32_PSRAM_REV0 — (DEV) подходит для всех плат ESP32 с поддержкой PSRAM;
  • ESP32_PSRAM_REV3 — (DEV) подходит для всех плат ESP32 с чипом ESP32 версии/ревизии 3 и поддержкой PSRAM;
  • ESP32_WROOM_32 — подходит для всех плат ESP32 c поддержкой PSRAM, но не поддерживает Bluetooth BLE из-за ограничений памяти в разделе IRAM, вызванных исправлениями PSRAM;
  • ESP32_WROOM_32_BLE — подходит для всех плат ESP32, поддерживает Bluetooth BLE, но PSRAM отключен;
  • ESP32_WROOM_32_V3_BLE — подходит для всех плат ESP32 с чипом ESP32 версии/ревизии 3, одновременно поддерживается PSRAM и Bluetooth BLE;
  • ESP32_PSRAM_REV3_ILI9341 — (упоминается в документации, но прошивка не найдена) подходит для всех плат ESP32 с чипом ESP32 версии/ревизии 3 и поддержкой PSRAM, включает драйвер для LCD ILI9341;
  • ESP32_PSRAM_REV3_ILI9342 — (упоминается в документации, но прошивка не найдена) подходит для всех плат ESP32 с чипом ESP32 версии/ревизии 3 и поддержкой PSRAM, включает драйвер для LCD ILI9342;
  • ESP_WROVER_KIT — специальный вариант для отладочной платы ESP WROVER KIT. Включает в себя функции UI, поддержку PSRAM и драйвер для SPI LCD ILI9341;
  • ESP32_PICO — подходит для всех плат ESP32 с ESP32 PICO без поддержки PSRAM, например ESP32-PICO-KIT и M5Stack ATOM;
  • ESP32_PICO_ST7735S — (упоминается в документации, но прошивка не найдена) подходит для платы ESP32 PICO, включает драйвер для LCD ST7735S;
  • ESP32_PICO_ST7789V — (упоминается в документации, но прошивка не найдена) подходит платы для M5Stick C Plus, включает драйвер для LCD ST7789V;
  • ESP32_REV0_ILI9342 — (упоминается в документации, но прошивка не найдена) подходит платы для M5Stack, включает драйвер для ILI9342;
  • ESP32_LILYGO — специальный вариант для отладочной платы LilyGO ESP32, включает поддержку Ethernet PHY.


Перенесем из документации сводную таблицу основных прошивок с описанием опций:
Рассмотрим информацию о чипе модуля ESP32 DevKit v1 (скриншот в предыдущем шаге):

  • Отладочная плата построена на чипе ESP32-D0WDQ6 первой ревизии;
  • Включена поддержка Wi-Fi, BT;
  • Модуль PSRAM недоступен.


Исходя из полученной информации подойдут прошивки: ESP32_REV0 и ESP32_WROOM_32_BLE. Из двух прошивок выбираем ESP32_WROOM_32_BLE т.к. поддержка Bluetooth BLE будет не лишней.

Если вы не знаете какую прошивку выбрать, то подойдет универсальная прошивка »ESP32_REV0» и »ESP32_PSRAM_REV0».

Полный каталог прошивок представлен в Repository: nanoframework-images. Список прошивок только для серии Espressif ESP32 Series. Общий список прошивок GitHub nanoframework/nf-interpreter.

Шаг 5 — Прошивка ESP-WROOM-32


Для прошивки необходимо подключить отладочную плату и выполнить команду:

nanoff --update --target ESP32_WROOM_32_BLE --serialport COM3


где параметр --target указывает на идентификатор прошивки, --serialport — номер COM порта, а данном случае COM3.

Результат выполнения команды:

nanoFramework firmware ESP-WROOM-32
Прошивка ESP-WROOM-32

Если прошивка не загрузится с репозитория, то можно повторить операцию или её загрузить со страницы ESP32_WROOM_32_BLE, и распаковать, например по пути: «c:\nanofw». В архиве будет несколько файлов:

nanoFramework firmware ESP-WROOM-32
Распакованная прошивка для ESP-WROOM-32

Для прошивки устройства, используя локальную прошивку, выполнить команду:

nanoff --update --clrfile "C:\nanofw\nanoCLR.bin" --serialport COM3


где параметр --clrfile путь к файлу nanoCLR.bin, --serialport — номер COM порта, а данном случае COM3.

Не забываем во время прошивки нажимать на кнопку «Boot». Устройство готово для загрузки .NET программ!


Запустим Visual Studio 2019 Community Edition и откроем окно Device Explorer. Выполним команду — Device Capabilities. В результате выполнения команды в консоль будет выведена информация о возможностях микроконтроллера.

nanoFramework ESP-WROOM-32
Информация о возможностях ESP-WROOM-32

Полный список вывода команды Device Capabilities для ESP-WROOM-32
System Information
HAL build info: nanoFramework running @ ESP32
Target: ESP32_WROOM_32_BLE
Platform: ESP32

Firmware build Info:
Date: Aug 25 2021
Type: MinSizeRel build with IDF v3.3.5
CLR Version: 1.7.0.618
Compiler: GNU ARM GCC v5.2.0

OEM Product codes (vendor, model, SKU): 0, 0, 0

Serial Numbers (module, system):
00000000000000000000000000000000
0000000000000000

Target capabilities:
Has nanoBooter: NO
IFU capable: NO
Has proprietary bootloader: YES

AppDomains:

Assemblies:

Native Assemblies:
mscorlib v100.5.0.12, checksum 0×132BDB6F
nanoFramework.Runtime.Native v100.0.8.0, checksum 0×2307A8F3
nanoFramework.Hardware.Esp32 v100.0.7.2, checksum 0×1B75B894
nanoFramework.Hardware.Esp32.Rmt v100.0.3.0, checksum 0×9A53BB44
nanoFramework.Hardware.Esp32.Ble v100.0.0.0, checksum 0×1B75B894
nanoFramework.Devices.OneWire v100.0.3.4, checksum 0xA5C172BD
nanoFramework.Networking.Sntp v100.0.4.4, checksum 0xE2D9BDED
nanoFramework.ResourceManager v100.0.0.1, checksum 0xDCD7DF4D
nanoFramework.System.Collections v100.0.0.1, checksum 0×5A31313D
nanoFramework.System.Text v100.0.0.1, checksum 0×8E6EB73D
nanoFramework.Runtime.Events v100.0.8.0, checksum 0×0EAB00C9
EventSink v1.0.0.0, checksum 0xF32F4C3E
System.IO.FileSystem v1.0.0.0, checksum 0×3112D24C
System.Math v100.0.5.2, checksum 0xC9E0AB13
System.Net v100.1.3.4, checksum 0xC74796C2
Windows.Devices.Adc v100.1.3.3, checksum 0xCA03579A
System.Device.Dac v100.0.0.6, checksum 0×02B3E860
System.Device.Gpio v100.1.0.4, checksum 0xB6D0ACC1
Windows.Devices.Gpio v100.1.2.2, checksum 0xC41539BE
Windows.Devices.I2c v100.2.0.2, checksum 0×79EDBF71
System.Device.I2c v100.0.0.1, checksum 0xFA806D33
Windows.Devices.Pwm v100.1.3.3, checksum 0xBA2E2251
Windows.Devices.SerialCommunication v100.1.1.2, checksum 0×34BAF06E
System.IO.Ports v100.1.1.3, checksum 0×61B8380C
Windows.Devices.Spi v100.1.4.2, checksum 0×360239F1
System.Device.Spi v100.1.0.0, checksum 0×48031DC5
Windows.Devices.Wifi v100.0.6.2, checksum 0xA94A849E
Windows.Storage v100.0.2.0, checksum 0×954A4192

++++++++++++++++++++++++++++++++
++ Memory Map ++
++++++++++++++++++++++++++++++++
Type Start Size
++++++++++++++++++++++++++++++++
RAM 0×3ffe436c 0×00016800
FLASH 0×00000000 0×00400000

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++ Flash Sector Map ++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Region Start Blocks Bytes/Block Usage
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
0 0×00010000 1 0×180000 nanoCLR
1 0×00190000 1 0×230000 Deployment
2 0×003C0000 1 0×040000 Configuration

+++++++++++++++++++++++++++++++++++++++++++++++++++
++ Storage Usage Map ++
+++++++++++++++++++++++++++++++++++++++++++++++++++
Start Size (kB) Usage
+++++++++++++++++++++++++++++++++++++++++++++++++++
0×003C0000 0×040000 (256kB) Configuration
0×00010000 0×180000 (1536kB) nanoCLR
0×00190000 0×230000 (2240kB) Deployment

Deployment Map
Empty


Из полученных данных наиболее важен список — Native Assemblies. Данный список содержит сборки, которые мы можем использовать в своем проекте, все остальные библиотеки необходимо включать в проект в виде исходного кода. Дополнительно можно сформировать прошивку со своими нативными сборками на C++, но как это сделать будет в продолжении.

Создадим новое приложение, выберем тип проекта: Blank Application (nanoFramework).

nanoFramework Blank Application
Шаблон проекта Blank Application (nanoFramework)

Шаблонный проект выведет отладочное сообщение в консоль.

nanoFramework Blank Application
Шаблонный проект nanoFramework

Запустим проект на отладку

debugging nanoFramework
Отладка проекта nanoFramework

В окне отладки будет выведено сообщение: «Hello from nanoFramework!».

Отлично, первое приложение запущено! Далее запустим стандартную программу мигания светодиодом.


На плате ESP32 DevKit v1 размещён встроенный светодиод, подключенный к контакту GPIO2. Запустим программу Blink, аналогичную как на Arduino. Для этого создадим новый проект nanoframework-esp32-blink, и добавим Nuget-пакет: nanoFramework.System.Device.Gpio. Исходный код возьмем из проекта GitHub nanoframework/Samples — Blinky.

Файл Program.cs.

public class Program
{
  private static GpioController s_GpioController;
  public static void Main()
    {
      s_GpioController = new GpioController();
      // ESP32 DevKit: 4 is a valid GPIO pin in, some boards 
      // like Xiuxin ESP32 may require GPIO Pin 2 instead.
      GpioPin led = s_GpioController.OpenPin(2,PinMode.Output);
      led.Write(PinValue.Low);
      while (true)
      {
        led.Write(PinValue.High);
        Thread.Sleep(1000);
        led.Write(PinValue.Low);
        Thread.Sleep(1000);
      }
    }
}


Разберем пример:

  • s_GpioController = new GpioController () — инициализирует объект контроллера доступа к GPIO;
  • GpioPin led = s_GpioController.OpenPin (2, PinMode.Output) — метод OpenPin переводит контакт GPIO2 в режим вывода;
  • led.Write (PinValue.Low) — подает логический »0» на контакт;
  • led.Write (PinValue.High) — логическая »1», подано напряжение в 3.3V.


Демонстрация работы программного кода:
Любое устройство взаимодействует с внешним миром и нуждается в управлении, теперь добавим кнопку и реализуем новую логику.
К микроконтроллеру подключим кнопку и реализуем следующую логику: встроенный светодиод загорается и остается в положение включено при удержании нажатой кнопки. При отпускание кнопки светодиод гаснет. Светодиод будем использовать тот же контакт GPIO2. Кнопку подключим к контакту GPIO25, на плате обозначен как »D25». Создадим новый проект nanoframework-esp32-button, и добавим Nuget-пакет: nanoFramework.System.Device.Gpio.

nanoFramework ESP32 STM32
Отладочная плата ESP32 DevKit v1 с подключенной кнопкой

Схема подключения кнопки к контакту D25 отладочной платы ESP32 DevKit v1
ESP32 DevKit button


Файл Program.cs.

using System;
using System.Diagnostics;
using System.Threading;
using System.Device.Gpio;

namespace nanoframework_esp32_button
{   
  public class Program
  {
    //Board: ESP32 DevKit
    static GpioController s_GpioController;
    static int s_BluePinNumber=2;    
    static int s_UserButtonPinNumber=25;
    public static void Main()
    {
      s_GpioController = new GpioController();
      //setup blue LED
      s_GpioController.OpenPin(s_BluePinNumber, PinMode.Output);
      s_GpioController.Write(s_BluePinNumber, PinValue.Low);
      //setup user button      
      s_GpioController.OpenPin(s_UserButtonPinNumber, PinMode.Input);
      //s_GpioController.OpenPin(s_UserButtonPinNumber, PinMode.InputPullUp);
      //Event registration
      s_GpioController.RegisterCallbackForPinValueChangedEvent(
        s_UserButtonPinNumber,
        PinEventTypes.Falling | PinEventTypes.Rising,
        UserButton_ValueChanged);      
      //Infinite
      Thread.Sleep(Timeout.Infinite);      
    }

    private static void UserButton_ValueChanged(object sender, PinValueChangedEventArgs e)
    {
      // read Gpio pin value from event
      Debug.WriteLine("USER BUTTON (event) : " + e.ChangeType.ToString());
      Debug.WriteLine("USER BUTTON (event) : " + ((((int)e.ChangeType) == 1) ? "Rising" : "Falling"));
      //if (e.ChangeType != PinEventTypes.Rising) //for DFRobot
      if (e.ChangeType == PinEventTypes.Rising)
      {
        s_GpioController.Write(s_BluePinNumber, PinValue.High);
      }
      else
      {
        s_GpioController.Write(s_BluePinNumber, PinValue.Low);
      }
    }
  }
}


Разберем пример:

  • s_GpioController.OpenPin (s_UserButtonPinNumber, PinMode.Input) — открывает контакт кнопки и выставляет режим работы на ввод (сигнал поступает от кнопки). Если необходимо исключить считывание внешних наводок, то можно использовать подтяжку к питанию PinMode.InputPullUp (не все контакты ввода поддерживают);
  • s_GpioController.RegisterCallbackForPinValueChangedEvent — обработка прерывания реализуется путем добавление Callback на изменение состояние контакта. Callback регистрируется в контроллере GPIO. Указываем номер контакта —  s_UserButtonPinNumber, событие срабатывание — Falling (изменение с »1» на »0») или Rising (изменение с »0» на »1»), название функции обработки — UserButton_ValueChanged.


Демонстрация работы программного кода:
Основной принцип работы с nanoFramework понятен, теперь вернемся к микроконтроллеру ST Nucleo64 F411RE и прошьем его средой nanoFramework, и запустим простую программу использующую светодиод и кнопку.

Шаг 1 — Подключение ST Nucleo64 F411RE


До подключение микроконтроллеров STM32 необходимо установить драйвер для отладчика/программатора ST-Link, для этого устанавливаем драйвер ST-LINK USB driver signed for Windows 7–10 и подключаем устройство. После подключения в Диспетчере устройств должно появиться устройство ST-Link Debug.

Device Manager STM32
Диспетчер устройств

Выполним команду обнаружения устройств ST-Link:

nanoff --listjtag


Результат выполнения команды:

Device Manager STM32 ST-Link
Обнаруженные устройства ST-Link

Если плата появилась в списке, то можно переходить к следующему шагу.

Шаг 2 — Обновление прошивки ST-Link


Если мы попытаемся загрузить nanoFramework в микроконтроллер, то возможно появится подобная ошибка, которая говорит о необходимости обновления прошивки ST-Link Debug.

update ST-Link Debug
Сообщение о необходимости обновления прошивки ST-Link Debug

Загружаем и запускаем STM32 ST-LINK utility. Выбираем в меню ST-LINK → Firmware update.

update ST-Link Debug
STM32 ST-LINK utility

Нажимаем на кнопку Device Connect, затем Yes >>>>

update ST-Link Debug
STM32 ST-LINK utility

Если прошивка успешно загрузиться будет сообщение Update succesfull.

update ST-Link Debug
STM32 ST-LINK utility

Если на этом этапе возникнут трудности, то можете посмотреть видео-инструкцию ST Link Update — Web learning.

Шаг 3 — Прошивка ST Nucleo64 F411RE


Для прошивки необходимо выполнить команду:

nanoff --update --target ST_NUCLEO64_F411RE_NF --jtag


где параметр --target указывает на идентификатор прошивки ST_NUCLEO64_F411RE_NF.

nanoFramework ST Nucleo64 F411RE
Прошивка ST Nucleo64 F411RE

Теперь запустим Visual Studio Community и выполним команду Device Capabilities.

nanoFramework ST Nucleo64 F411RE
Информация о возможностях ST Nucleo64 F411RE

Полный список вывода команды Device Capabilities для ST Nucleo64 F411RE
System Information
HAL build info: nanoCLR running @ ST_NUCLEO64_F411RE_NF
Target: ST_NUCLEO64_F411RE_NF
Platform: STM32F4

Firmware build Info:
Date: Oct 22 2021
Type: MinSizeRel build with ChibiOS v21.6.0.6
CLR Version: 1.7.0.96
Compiler: GNU ARM GCC v9.3.1

OEM Product codes (vendor, model, SKU): 0, 0, 0

Serial Numbers (module, system):
00000000000000000000000000000000
0000000000000000

Target capabilities:
Has nanoBooter: YES
nanoBooter: v21845.21845.21845.21845
IFU capable: NO
Has proprietary bootloader: NO

AppDomains:

Assemblies:
nanoframework_stm32_button, 1.0.0.0
mscorlib, 1.10.5.4
nanoFramework.Runtime.Events, 1.9.1.3
Windows.Devices.Gpio, 1.5.2.3

Native Assemblies:
mscorlib v100.5.0.12, checksum 0×132BDB6F
nanoFramework.Runtime.Native v100.0.8.0, checksum 0×2307A8F3
nanoFramework.Hardware.Stm32 v100.0.4.4, checksum 0×0874B6FE
nanoFramework.ResourceManager v100.0.0.1, checksum 0xDCD7DF4D
nanoFramework.System.Collections v100.0.0.1, checksum 0×5A31313D
nanoFramework.System.Text v100.0.0.1, checksum 0×8E6EB73D
nanoFramework.Runtime.Events v100.0.8.0, checksum 0×0EAB00C9
EventSink v1.0.0.0, checksum 0xF32F4C3E
System.Math v100.0.5.2, checksum 0xC9E0AB13
Windows.Devices.Adc v100.1.3.3, checksum 0xCA03579A
Windows.Devices.Gpio v100.1.2.2, checksum 0xC41539BE
Windows.Devices.I2c v100.2.0.2, checksum 0×79EDBF71
System.Device.I2c v100.0.0.1, checksum 0xFA806D33
Windows.Devices.SerialCommunication v100.1.1.2, checksum 0×34BAF06E
System.IO.Ports v100.1.2.0, checksum 0×564F2452
Windows.Devices.Spi v100.1.4.2, checksum 0×360239F1
System.Device.Spi v100.1.0.0, checksum 0×48031DC5

++++++++++++++++++++++++++++++++
++ Memory Map ++
++++++++++++++++++++++++++++++++
Type Start Size
++++++++++++++++++++++++++++++++
RAM 0×20000000 0×00020000
FLASH 0×08000000 0×00080000

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++ Flash Sector Map ++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Region Start Blocks Bytes/Block Usage
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
0 0×08000000 1 0×004000 nanoBooter
1 0×08004000 3 0×004000 nanoCLR
2 0×08010000 1 0×010000 nanoCLR
3 0×08020000 1 0×020000 nanoCLR
4 0×08040000 2 0×020000 Deployment

+++++++++++++++++++++++++++++++++++++++++++++++++++
++ Storage Usage Map ++
+++++++++++++++++++++++++++++++++++++++++++++++++++
Start Size (kB) Usage
+++++++++++++++++++++++++++++++++++++++++++++++++++
0×08000000 0×004000 (16kB) nanoBooter
0×08004000 0×03C000 (240kB) nanoCLR
0×08040000 0×040000 (256kB) Deployment

Deployment Map
Empty


Из полученных данных видно, что nanoCLR работает на ChibiOS v21.6.
В отличие от ESP32, на плате дополнительно ещё размещена кнопка, которую можно программировать под свои задачи. Светодиод на плате обозначен меткой LD2 (Green LED), кнопка — B1 (USER). Добавим немного другую логику работы отличную от примера для ESP32, встроенный светодиод загорается и выключается по нажатию на кнопку.

ST Nucleo64 F411RE led button
Светодиод LD2 (Green LED) и кнопка B1 USER на плате ST Nucleo64 F411RE

Создадим новый проект nanoframework-stm32-button, и добавим Nuget-пакет: nanoFramework.Windows.Devices.Gpio. Обратите внимание, пакет отличается от предыдущего примера, название — nanoFramework.Windows.*. Как было сказано выше, API библиотек в которых присутствует Windows.*, совпадают с .NET на Windows IoT Core, которая работает на Raspberry Pi. Поэтому возьмем готовый код Push button — Raspberry Pi, с сайта MS. Из изменений в коде будут только другие номера контактов (c Raspberry Pi не совпадают).

Светодиод LD2 (Green LED) подключен к контакту Arduino D13, что соответствует STM32 I/O PA5 (pin 21). Контакт кнопки  B1 USER соответствует STM32 I/O PC13 (pin 2). В STM32 принято обращение к контактам по названию. Контакт PA5, где P — port, A-литера порта, 5 — номер контакта. Поэтому в программный код дополнительно будет добавлена функция конвертации буквенного обозначения контактов в номерной — int PinNumber (char port, byte pin). Описание контактов GitHub nanoframework/nf-Community-Targets/ChibiOS/ST_NUCLEO64_F411RE_NF/.

Файл Program.cs.

using System;
using System.Diagnostics;
using System.Threading;
using Windows.Devices.Gpio;

namespace nanoframework_stm32_button
{
  public class Program
  {
    private static int LED_PIN;
    private static int BUTTON_PIN;
    private static GpioPin ledPin;
    private static GpioPin buttonPin;    
    private static GpioPinValue ledPinValue = GpioPinValue.High;
    public static void Main()
    {      
      // ST Nucleo64 F411RE: PA5 is LED_GREEN
      LED_PIN = PinNumber('A', 5);
      // ST Nucleo64 F411RE: PC13 is B1_USER
      BUTTON_PIN = PinNumber('C', 13);
      //Next as .NET for Raspberry Pi, Windows IoT Core
      //Controller
      var gpio = new GpioController();
      //Init
      ledPin = gpio.OpenPin(LED_PIN);
      ledPin.SetDriveMode(GpioPinDriveMode.Output);
      buttonPin = gpio.OpenPin(BUTTON_PIN);
      buttonPin.SetDriveMode(GpioPinDriveMode.Input);
      // Initialize LED to the OFF state by first writing a HIGH value
      // We write HIGH because the LED is wired in a active LOW configuration
      ledPin.Write(ledPinValue);      
      // Check if input pull-up resistors are supported
      if (buttonPin.IsDriveModeSupported(GpioPinDriveMode.InputPullUp))
        buttonPin.SetDriveMode(GpioPinDriveMode.InputPullUp);
      else
        buttonPin.SetDriveMode(GpioPinDriveMode.Input);
      // Set a debounce timeout to filter out switch bounce noise from a button press
      buttonPin.DebounceTimeout = TimeSpan.FromMilliseconds(50);
      // Register for the ValueChanged event so our buttonPin_ValueChanged 
      // function is called when the button is pressed
      buttonPin.ValueChanged += buttonPin_ValueChanged;
      //Infinite
      Thread.Sleep(Timeout.Infinite);
    }

    static void buttonPin_ValueChanged(object sender, GpioPinValueChangedEventArgs e)
    {
      Debug.WriteLine("USER BUTTON (event) : " + ((e.Edge ==GpioPinEdge.RisingEdge)? "RisingEdge" : "FallingEdge"));      
      // toggle the state of the LED every time the button is pressed
      if (e.Edge == GpioPinEdge.FallingEdge)
      {
        ledPinValue = (ledPinValue == GpioPinValue.Low) ?
          GpioPinValue.High : GpioPinValue.Low;
        ledPin.Write(ledPinValue);
      }
    }
      
    static int PinNumber(char port, byte pin)
    {
      if (port < 'A' || port > 'J')
        throw new ArgumentException();
      return ((port - 'A') * 16) + pin;
    }
  }
}


Разберем пример:

  • int PinNumber (char port, byte pin) — функция конвертации буквенного обозначения контактов в номерной;
  • gpio.OpenPin (LED_PIN) — открывает контакт для его использования;
  • ledPin.SetDriveMode (GpioPinDriveMode.Output) — выставление режим работы контакта на — вывод GpioPinDriveMode.Output или ввод GpioPinDriveMode.Input;
  • ledPin.Write (ledPinValue) — подает логический »0«GpioPinValue.Low или »1»(GpioPinValue.High) на контакт;
  • buttonPin.IsDriveModeSupported (GpioPinDriveMode.InputPullUp) — проверка наличия подтягивающего резистора (pull-up);
  • buttonPin.SetDriveMode (GpioPinDriveMode.InputPullUp) — включение режима работы контакта на ввод с поддержкой pull-up;
  • buttonPin.DebounceTimeout — интервал в течение которого не будут регистрироваться события (необходимо для исключения дребезга контактов);
  • buttonPin.ValueChanged — подпись на событие изменения состояния;
  • void buttonPin_ValueChanged — функция, которая вызывается при изменение состояния кнопки. Свойство e.Edge принимает значение GpioPinEdge.RisingEdge (изменение с »0» на »1»), или GpioPinEdge.FallingEdge (изменение с »1» на »0»).


Демонстрация работы программного кода:
Модуль BME280 фирмы Bosch Sensortec предназначен для измерения атмосферного давления, температуры и влажности. По сравнению с первыми датчиками серии (BMP085 и BMP180), он имеет лучшие характеристики и меньшие размеры. Отличие от датчика BMP280 — наличие гигрометра, что позволяет измерять относительную влажность воздуха и создать на его основе маленькую метеостанцию.

Назначение контактов:

  • VCC — питание модуля 3.3 В или 5 В;
  • GND —  Ground;
  • SCL — линия тактирования (Serial CLock);
  • SDA — линия данных (Serial Data).


Модуль работает по двухпроводному интерфейсу I2C, адрес по умолчанию 0×76. Данный датчик уже был использован в проекте Метеостанция на Banana Pi M64 (Linux, C#, Docker, RabbitMQ, AvaloniaUI), поэтому детальнее ознакомится с характеристиками и шиной I2C можно по вышеуказанной ссылке. Подключим датчик BME280 к отладочной плате ESP32 DevKit v1.

ESP32 DevKit BME280
Отладочная плата ESP32 DevKit v1 с датчиком BME280

Схема подключения BME280 к шине I2C отладочной платы ESP32 DevKit v1
ESP32 DevKit BME280


Создадим новый проект nanoframework-esp32-bme280, и добавим для работы с шиной I2C Nuget-пакет: nanoFramework.System.Device.I2c. Драйвер для работы с BME280 возьмем с каталога драйверов nanoFramework.IoT.Device — BMxx80 Device Family. Перенесем драйвер в папку /libs/. Дополнительно команда разработчиков nanoFramework для работы с физическими величинами используют библиотеку UnitsNet.

Файл Program.cs. Инициализация датчика BME280.

// when connecting to an ESP32 device, need to configure the I2C GPIOs
// used for the bus
Configuration.SetPinFunction(21, DeviceFunction.I2C1_DATA);
Configuration.SetPinFunction(22, DeviceFunction.I2C1_CLOCK);
// bus id on the MCU
const int busId = 1;
I2cConnectionSettings i2cSettings = new(busId, Bme280.SecondaryI2cAddress);
  
using I2cDevice i2cDevice = I2cDevice.Create(i2cSettings);
using Bme280 bme80 = new Bme280(i2cDevice)
{
  // set higher sampling
  TemperatureSampling = Sampling.LowPower,
  PressureSampling = Sampling.UltraHighResolution,
  HumiditySampling = Sampling.Standard,
};

// set this to the current sea level pressure in the area for correct altitude readings
Pressure defaultSeaLevelPressure = WeatherHelper.MeanSeaLevel;


Разберем пример:

  • Configuration.SetPinFunction (21, DeviceFunction.I2C1_DATA) — функция из пространства имен nanoFramework.Hardware.Esp32. В связи с работой nanoFramework на различных микроконтроллерах, например для работы с I2C, требуется дополнительное  конфигурирования контактов. В данном случае для шины I2C с идентификатором »1», передача данных DATA определяется на GPIO21;
  • busId = 1 — идентификатор шины I2C — 1;
  • new I2cConnectionSettings (busId, Bme280.SecondaryI2cAddress) — создание конфигурации устройства I2C, указывается идентификатор шины I2C и адрес устройства (0×76);
  • I2cDevice.Create (i2cSettings) — создание I2C устройства;
  • new Bme280(i2cDevice) — инициализация датчика BME280;
  • TemperatureSampling, PressureSampling, HumiditySampling — указание точности измерений физических величин;
  • WeatherHelper.MeanSeaLevel — для определения уровня высоты указывается точка отсчета от уровня моря.


Файл Program.cs. Чтение данных с датчика BME280.

// Perform a synchronous measurement
var readResult = bme80.Read();

// Note that if you already have the pressure value and the temperature, you could also calculate altitude by using
// var altValue = WeatherHelper.CalculateAltitude(preValue, defaultSeaLevelPressure, tempValue) which would be more performant.
bme80.TryReadAltitude(defaultSeaLevelPressure, out var altValue);

Debug.WriteLine($"Temperature: {readResult.Temperature.DegreesCelsius}\u00B0C");
Debug.WriteLine($"Pressure: {readResult.Pressure.Hectopascals}hPa");
Debug.WriteLine($"Altitude: {altValue.Meters}m");
Debug.WriteLine($"Relative humidity: {readResult.Humidity.Percent}%");


Разберем пример:

  • bme80.Read () — чтение показаний датчика;
  • bme80.TryReadAltitude — получение значения высоты над уровнем моря;
  • readResult.Temperature, readResult.Pressure, readResult.Humidity — получение данных температуры, давления, влажности.


Запустим проект:

nanoFramework BME280
Чтение данных с датчика BME280 на nanoFramework

Для вывода данных планировал

© Habrahabr.ru