Зеркало сайта: vivacious-stockings-frog.cyclic.app

Плата STM32F411CE BlackPill + MicroPython. Быстрый старт

разделы: STM32 , дата: 24 июля 2020г.

Внимание! Статья была отредактирована в ноябре 2023 года (последнее изменение 05.дек.2023г). Были исправленны битые ссылки, логи и скриншоты STM32CubeProgrammer были приведены к версии 2.15. Кроме этого, был добавлен новый раздел "BlackPill в 2023 году, альтернативные платы WeAct", где рассмотренна современная ситуация с прошивками микропитона для BlackPill. Также был добавлен легкий экскурс по программированию на микропитоне на примере написания драйвера дисплея SSD1306 с I2C интерфейсом. Кроме этого было затронуто асинхронное программирование на микропитоне. Также был добавлен обзор альтернативных плат WeAct в форм-факторе Blackpill на чипах: AT32F403ACGU7 и RP2040. Старую версию статьи можно найти по следующему адресу: "перейти по ссылке".

Изначально статья была посвящена плате Blackpill на чипе STM32F411, и немного была затронута тема микропитона. Немного, потому-что это казалось скорее игрушкой для студентов и не более того. За последние три года произошли какие-то тектонические сдвиги в микроэлектронике, и ситуация поменялась.

  1. Во-первых, фирма Espressif выбросила на рынок целую коробку новых чипов, с очень привлекательными характеристиками. А для ESP микропитон вполне достойная среда программирования, т.к. писать что-то на SDK - сомнительное удовольствие.
  2. Китайцы выпустили целый вагон своих чипов, и микроконтроллеры с объемом флеш-памяти 1 МБ перестали быть экзотикой.
  3. Появился очень интересный RP2040 с возможностью установки микропитона.

Таким образом, я вижу с одной стороны дрифт в сторону от продукции ST, а с другой - микроконтроллеров в сторону SoC. Микропитон же похоже пытается стать Java для микроконтроллеров. Так или иначе байткод-интерпретатор там уже имеется.

Еще один аспект - микропитон выполняет роль интерактивной операционной системы для микроконтроллеров. Там есть дисковая система, командная строка и возможность запускать скрипты с диска. Т.о. микропитон позволяет работать с периферией и аппаратными интерфесами в диалоговом режиме.

Хочу заметить, что вопрос прошивки модулей ESP8266/ESP32 микропитоном рассматривалась в статье про EPS8266: "Установка прошивки MicroPython".

Полезная документация по теме статьи:

  1. STM32F411xC/E Reference Manual (RM0383)
  2. User Manual "STM32CubeProgrammer software description" (UM2237)
  3. Аккаунт компании WeACT на github'e с документацией на плату MiniF4-STM32F4x1
  4. Документация по MicroPython

Содержание:

I. Обзор платы BlackPill на чипе STM32F411CE

  1. Плата WeACT с чипом STM32F411CEU6 ака "Black Pill V2.0"
  2. Использование флешера STM32CubeProgrammer
  3. Прошивка MicroPython
  4. Загрузка программы Blink.py для микропитона, и основы работы с REPL
  5. Установка SPI флешки на плату WeACT STM32F411CEU6
  6. Сборка микропитона из исходников

II. Основы работы c MicroPython

  1. Работа с MicroPython в интерактивной системе REPL
  2. Использование редакторов VS Code и Atom в качестве IDE для MicroPython

III. BlackPill в 2023 году, альтернативные платы WeAct

  1. Микропитон на Blackpill - STM32F411CE в 2023 году
  2. Прикручиваем I2C дисплей SSD1306 128x32
  3. Сборка прошивки микропитона с модулем ulab
  4. Асинхронное программирование на микропитоне: сопрогрммы(coroutines)
  5. Альтернативная плата WeAct Blackpill на китайском чипе AT32F403ACGU7
  6. Плата WeAct RP2040 с микропитоном и внешней флешкой

1) Плата WeACT с чипом STM32F411CEU6 ака "Black Pill V2.0"

Примерно в 2019 году на али появились недорогие платы в форм-факторе "Blue Pill", с чипам STM32F401CC и STM32F411CE. Это чипы с ядрами ARM Cortex®-M4, которые имеют аппаратную поддержку чисел с плавающей запятой одинарной точности и блоком DSP инструкций. На тот момент это были самые доступные платы для тех, кто хочет оценить возможности микроконтролеров на ядрах ARM Cortex®-M4. Чип STM32F401CC имеет 256 КБайт флеш-памяти, 64 КБайт ОЗУ и может работать на частотах до 84 МГц включительно. Чип STM32F411CE имеет 512 КБайт флеш-памяти, 128 КБайт ОЗУ и он может работать на частоте до 100 МГц.

Сперва хочется сказать пару слов о чипе STM32F411CE. Если посмотреть на ключевые особенности (key features) данного микроконтроллера, что заявленны на сайте ST https://www.st.com/en/microcontrollers-microprocessors/stm32f411ce.html, то увидим следующее:

Arm® 32-bit Cortex®-M4 CPU with FPU, Adaptive real-time accelerator (ART Accelerator™) allowing 0-wait state execution from Flash memory, frequency up to 100 MHz

Здесь нам обещаются рабочие частоты до 100МГц включительно, некий ART-ускоритель (проприетарная технология ST), который позволит работать с флеш-памятью на частоте CPU, т.е. с "0-wait state" задержкой (читай "без задержки"). Ранее, я уже указывал на то, что флеш-память микроконтроллеров STM работает на меньшей частоте нежели ЦПУ и это существенно сказывается на работу небольших циклов. Если при линейном, т.е. последовательном выполнении программы, данное обстоятельство компенсируется двумя (для i-code bus и d-code bus) 128-битными буферами, то в случае ветвления конвейер сбрасывается, и процессору приходится ожидать флеш-память. В случае 0-wait state задержки ничего бы ждать не приходилось. Однако давайте заглянем в документацию на чип: STM32F411xC/E Reference Manual (RM0383):

Как видно, при частоте ЦПУ в 100 МГц и питании чипа 3.3 Вольт, нам придется работать с 3-wait state задержкой. Формально, вы конечно можете работать с 0-wait state задержкой на частотах порядка 16МГц или даже 24МГц.

Теперь поглядим на схему тактирования микроконтролера:

STM32F411CE имеет три коэффициента PLL: M, N и P. При тактировании от кварца 25МГц, если мы хотим использовать USB, через коэффициенты M, N и P мы можем выставить максимальную частоту ЦПУ не более 96 МГц.

Перейдем непосредственно к платам. Я заказывал свою плату с чипом STM32F411CEU6 пошлой осенью. Выглядит она так:

Вид снизу:

Заметьте, что снизу моей платы стоит номер версии платы - v1.3. Сейчас уже продаются более новые версии плат. На данный момент актуальная ревизия - это V3.1. Наиболее полную информацию об этой плате, так и о многих других, что продаются на ebay и ali, можно посмотреть на вики проекта STM32Duino , в разделе "Generic STM32F4 boards". Там эти платы обозначены как "WeAct Black Pill V2.0", и про них там имеется отдельная страничка WeAct Black Pill V2.0. На фотографиях там версия платы уже V2.0.

У одного из продавцов на али я нашел Changelog версий плат:

Платы версий начиная с 1.3 и выше, имеют три кнопки "BOOT", "FLASH" и "KEY". Один светодиод на питании, и один на выводе PC13. Два кварца, один на 25 MHz для HSE, и часовой кварц на LSE. Штыревой коннектор 2.5мм для отладчика ST-Link V2 и разъем USB-C. На обратной стороне платы имеются контактные площадки для впайки SPI-флешки.

У компании WeAct имеется аккаунт на github: https://github.com/WeActStudio/WeActStudio.MiniSTM32F4x1, там также выложен Changelog, правда местами с китайскими иероглифами. Также у них там написано, что платы с чипом STM32F401CC, т.е. что с 256KБ флеш-памяти, признаны устаревшими, и заменены платами с чипом STM32F401CE, т.е. с 512KБ флеш-памяти. Я правда не нашел таких плат на али (Сейчас магазин WeAct вообще не продает платы с чипом STM32F401. Примечание из 2023г.), но имейте ввиду, если собираетесь использовать на плате микропитон, прошивка с ним может не влезть на чипы с 256KБ флеш-памяти.

В разделе документации, имеется распиновка плат:

Еще:

Там у них есть еще разные картинки, не поленитесь, зайдите и посмотрите.

В целом, я считаю, что преимущества платы с 411-м чипом - это невысокая цена (можно взять сразу десять), миниатюрность (большинство отладочных плат на чипах STM32F4xx выглядят монструозно), огромный объем флеш-памяти и приемлемый объем оперативной памяти (128 килобайт) /В 2023 году это все уже не так однозначно/. Кроме того, на плате имеется посадочное место под флешку.

2) Использование флешера STM32CubeProgrammer

Существенным отличием от BluePill, явлется встроенный в чипы STM32F401/STM32F411 DFU-загрузчик. Это означает, что вы сможете прошить свой микроконтролер без программатора, прямо через USB-шнурок. В случае с BluePill, для прошивки платы через USB, сначала нужно было ставить DFU-загрузчик от STM32Duino, а затем наблюдать, как после каждой загрузки вашей программы, USB-порт постоянно переключался c устройства Virtual Serial Port на DFU-загрузчик и обратно. Кроме того, тот загрузчик элементарно затирался, стоило только прошить что-либо через ST-Link. Теперь все эти траблы остались в прошлом. DFU-загрузчик прошит в ПЗУ и останется там при любых условиях. Активируется он только специальной комбинацией нажатия кнопок, а не автоматически при каждом старте как было у Blue Pill.

Через DFU загрузчик STM32F411 можно не только загрузить свою прошивку, но и выставить нужные Option Bytes, прочитать флеш-память микроконтролера и т.д. Т.е. через USB-шнурок вы теперь можете сделать все то, что раньше было доступно только из флешера "STM32 ST-Link Utility", который требовал наличия программатора.

Для работы с DFU-загрузчиком микроконтролера нам понадобится флешер STM32CubeProgrammer. Данный флешер по функционалу примерно соответствует STM32 ST-Link Utility, но при этом работает в разных операционных системах, включая Linux.

На официальном сайте ST имеется книжка с документацией к программе: User Manual "STM32CubeProgrammer software description" (UM2237) . Настоятельно рекомендую полистать ее. Она кратенькая (80 страниц), и довольно доходчивая.

Скачать STM32CubeProgrammer можно также с официального сайта ST - https://www.st.com/en/development-tools/stm32cubeprog.html. (Альтернативные ссылки, версия 2.15 для Linux. для win32, для win64). После распаковки скачанного архива для Linux, перед нами появятся следующие файлы:

jre
SetupSTM32CubeProgrammer-2.15.0.exe
etupSTM32CubeProgrammer-2.15.0.linux

Здесь перед нами два инсталлятора, для Windows и для Linux. Давайте потыкаем в линуксовый инсталлятор:

$ file ./SetupSTM32CubeProgrammer-2.15.0.linux
./SetupSTM32CubeProgrammer-2.15.0.linux: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=bcc7be207b463b7b004b10f7078d9d2be84d3902, with debug_info, not stripped

Даем права на выполнение:

$ chmod +x ./SetupSTM32CubeProgrammer-2.15.0.linux

После этого, от пользователя запускаем инсталлятор. Инсталлятор написан на Java, также как и графическая утилита STM32CubeProgrammer, поэтому выглядят они соответствуще.

Программа STM32CubeProgrammer существует в двух ипостасях: в виде графической тулзы, и в виде утилиты с консольным, ака CLI-интерфейсом. Это означает, что теоретически мы можем использовать STM32CubeProgrammer_CLI в качестве флешера для его вызова из Makefile.

Интерфейс графической STM32CubeProgrammer версии 2.15 выглядит так:

В связи с текущей ситуацией, я бы рекомендовал при первом запуске отключить отправку телеметрических данных. Делается это через меню "знак вопроса -> User preference":

Полностью отключить приложению доступ в сеть можно выставив на настройках прокси localhost - 127.0.0.1:80.

Интерфейс консольной программы - это довольно длинный список опций, я спрятал его под спойлер:

   -------------------------------------------------------------------
                        STM32CubeProgrammer v2.15.0
   -------------------------------------------------------------------



STM32_Programmer_CLI.exe [command_1] [Arguments_1][[command_2] [Arguments_2]...]

Generic commands:

 -?, -h, --help         : Show this help
 -c,     --connect      : Establish connection to the device
     <port=<PortName>   : Interface identifier. ex COM1, /dev/ttyS0, usb1,
                          JTAG, SWD...)
 USB port optional parameters:
     [sn=<serialNumber>]   : Serial number of the usb dfu
     [PID=<Product ID>]    : Product ID. ex: 0xA38F, etc, default 0xDF11
     [VID=<Vendor ID>]     : Vendor ID. ex: 0x0389, etc, default x0483
 UART port optional parameters:
     [br=<baudrate>]    : Baudrate. ex: 115200, 9600, etc, default 115200
     [P=<parity>]       : Parity bit, value in {NONE,ODD,EVEN}, default EVEN
     [db=<data_bits>]   : Data bit, value in {6, 7, 8} ..., default 8
     [sb=<stop_bits>]   : Stop bit, value in {1, 1.5, 2} ..., default 1
     [fc=<flowControl>] : Flow control
                          Value in {OFF,Hardware,Software} ..., default OFF
     rts=<status>       : low or high
     dtr=<status>       : low or high
                          Not supported for STM32MP
     [noinit=noinit_bit]: Set No Init bits, value in {0,1} ..., default 0
     [console]          : Enter UART console mode
 JTAG/SWD debug port optional parameters:
     [freq=<frequency>] : Frequency in KHz. Default frequencies:
                          4000 SWD 9000 JTAG with STLINKv2
                          24000 SWD 21333 with STLINKv3
     [index=<index>]    : Index of the debug probe. default index 0
     [sn=<serialNumber>]: Serial Number of the debug probe
     [ap=<accessPort>]  : Access Port index to connect to. default ap 0
     [mode=<mode>]      : Connection mode. Value in {UR/HOTPLUG/NORMAL/POWERDOWN}
                          default mode: NORMAL
     [reset=<mode>]     : Reset modes: SWrst/HWrst/Crst. Default mode: SWrst
                          Reset mode with UR connection mode is HWrst
     [shared]           : Enable shared mode allowing connection of two or more
                          instances of STM32CubeProgrammer or other debugger
                          to the same ST-LINK probe.
     [tcpport=<Port>]   : Port used for running ST-Link Server, default 7184
     [LPM]              : Enable debug in Low Power mode(default mode)
     [dLPM]             : Disable debug in Low Power mode
     [getAuthID]        : Get device identification (Option only for STM32U5 series)
     [speed=<Reliable/fast>]: Choose flashing  Reliable/fast (Option only for STM32U5 series)
 SPI port optional parameters:
     [br=<baudrate>]    : Baudrate.
     [cpha=<cpha_val>]  : 1Edge or 2Edge. default 1Edge
     [cpol=<cpol_val>]  : low or high
     [crc=<crc_val>]    : enable or disable (0/1).
     [crcpol=<crc_pol>] : crc polynom value.
     [datasize=<size>]  : 8bit/16bit
     [direction=<val>]  : Direction: 2LFullDuplex/2LRxOnly/1LRx/1LTx
     [firstbit=<val>]   : First Bit: MSB/LSB
     [frameformat=<val>]: Frame Format: Motorola/TI
     [mode=<val>]       : Mode: master/slave
     [nss=<val>]        : NSS: soft/hard
     [nsspulse=<val>]   : NSS pulse: Pulse/NoPulse
     [delay=<val>]      : Delay: Delay/NoDelay, delay of few microseconds
     [noinit=noinit_bit]: Set No Init bits, value in {0,1} ..., default 0
 CAN port optional parameters:
     [br=<rbaudrate>]   : Baudrate : 125, 250, 500, 1000 Kbps, default 125
     [mode=<canmode>]   : CAN Mode : NORMAL, LOOPBACK..., default NORMAL
     [ide=<type>]       : CAN Type : STANDARD or EXTENDED, default STANDARD
     [rtr=<format>]     : Frame Format: DATA or REMOTE, default DATA
     [fifo=<afifo>]     : Msg Receive : FIFO0 or FIFO1, default FIFO0
     [fm=<fmode]        : Filter Mode : MASK or LIST, default MASK
     [fs=<fscale>]      : Filter Scale: 16 or 32, default 32
     [fe=<fenable>]     : Filter Activation : ENABLE or DISABLE, default ENABLE
     [fbn=<fbanknb>]    : Filter Bank Number : 0 to 13, default 0
     [noinit=noinit_bit]: Set No Init bits, value in {0,1} ..., default 0
 I2C port optional parameters:
     [add=<ownadd>]     : Slave address : address in hex format
     [br=<sbaudrate>]   : Baudrate : 100 or 400 Kbps, default 400
     [sm=<smode>]       : Speed Mode : STANDARD or FAST, default FAST
     [am=<addmode>]     : Address Mode : 7 or 10 bits, default 7
     [af=<afilter>]     : Analog filter : ENABLE or DISABLE, default ENABLE
     [df=<dfilter>]     : Digital filter : ENABLE or DISABLE, default DISABLE
     [dnf=<dnfilter>]   : Digital noise filter : 0 to 15, default 0
     [rt=<rtime>]       : Rise time : 0-1000(STANDARD), 0-300(FAST), default 0
     [ft=<ftime>]       : Fall time : 0-300 (STANDARD), 0-300(FAST), default 0
     [noinit=noinit_bit]: Set No Init bits, value in {0,1} ..., default 0
 -version, --version    : Displays the tool's version
 -l,     --list         : List all available communication interfaces
     <uart>             : UART interface
     <usb>              : USB interface
     <st-link>          : st-link interface
     <stlink>
     <st-link-only>     : Enumarte st-link list without connecting and intruse the target
     <stlink-only>        Access port number is set to zero.
     <shared>           : Allowing to list all boards connected to other instance(s)
     <stlink-shared>      of STM32CubeProgrammer where the shared mode is enabled.
 -q,     --quietMode    : Enable quiet mode. No progress bar displayed
 -log,   --log          : Store the detailed output in log file
     [<file_Path.log>]  : Path of the log file,
                          default path = $HOME/.STM32Programmer/trace.log
 -vb,    --verbosity    : Specify verbosity level
     <Level>            : Verbosity level, value in {1, 2, 3}
 -y, --yes              : Ignore confirmation prompt message

Available commands for STM32 MCU:

 --skipErase            : Skip sector erase before programming
 -sl,    --safelib      : Add a segment into a firmware file (elf,bin
                          hex,srec and s19) containing computed CRC values
                          To use only with the safety lib component
     <file_path>        : File path to be modified
     <start_address>    : Flash memory start address
     <end_address>      : Flash memory end address
     <slice_size>       : Size of data per CRC value
     <pattern>          : Optional pattern from 0x0 to 0xFF. Default pattern 0x00
 -ms,    --mergesbsfu   : Add a binary header and a sbsfu segment to an elf file
     <elf_file_path>    : File path to be modified
     <header_file_path> : Header file path
     <sbsfu_file_path>  : SBSFU file path
 -e,     --erase        : Erase memory pages/sectors devices:
                          Not supported for STM32MP
     [all]              : Erase all sectors
     [<sectorsCodes>]   : Erase the specified sectors identified by sectors
                          codes. ex: 0, 1, 2 to erase sectors 0, 1 and 2
                          for EEPROM : ed1 & ed2
     [<[start end]>]    : Erase the specified sectors starting from
                          start code to end code, ex: -e [5 10]
 -w,     --write
 -d,     --download     : Download the content of a file into device memory
     <file_path>        : File path name to be downloaded: (bin, hex, srec, s19
                          elf, stm32 or tsv file)
     [<address>]        : Start address of download
 -w64                   : Write a 64-bits data into device memory
     <address>          : Start address of download
     <64-bit_data>      : 64-bit data to be downloaded
                          values should not be separated by space
 -w32                   : Write a 32-bits data into device memory
     <address>          : Start address of download
     <32-bit_data>      : 32-bit data to be downloaded
                          values should be separated by space
 -w16                   : Write a 16-bits data into device memory
     <address>          : Start address of download
     <16-bit_data>      : 16-bit data to be downloaded
                          values should be separated by space
 -w8                    : Write a 8-bits data into device memory
     <address>          : Start address of download
     <8-bit_data>       : 8-bit data to be downloaded
                          values should be separated by space
 -v,     --verify       : Verify if the programming operation is achieved
                          successfully
 -nv,    --noverify     : Do not verify if the programming operation is achieved
                          successfully, used with -w32/-w16/-w8 commands
 -checksum, --checksum  : Memory checksum calculation
     <address>          : Optional, Start address
     <size>             : Optional, Size of memory region
                        : If the parameters <address> and <size> are not indicated, the tool will calculate the full internal Flash
 -r32                   : Read a 32-bit data from device memory
     <address>          : Read start address
     <size>             : Size of data
 -r16                   : Read a 16-bit data from device memory
     <address>          : Read start address
     <size>             : Size of data
 -r8                    : Read a 8-bit data from device memory
     <address>          : Read start address
     <size>             : Size of data
 -rst                   : Reset system
 -hardRst               : Hardware reset
                          Available only with JTAG/SWD debug port
 -halt                  : Halt core
 -run                   : Run core
 -step                  : Step core
                          Available only with JTAG/SWD debug port
 -score                 : Get core status
                          Available only with JTAG/SWD debug port
 -coreReg               : Read/Write core registers
     [<core_register>]    R0/../R15/PC/LR/PSP/MSP/XPSR/APSR/IPSR/EPSR/
                          PRIMASK/BASEPRI/FAULTMASK/CONTROL
     [core_reg=<value>]   value in case of write opration
                          Note: multiple registers can be handled at once
                          Available only with JTAG/SWD debug port
 -r,     --read
 -u,     --upload       : Upload the device memory content to a .bin/.hex/.srec/.s19 file
     <address>          : Start address of read and upload
     <size>             : Size of memory content to be read
     <file_path>        : file path with .bin/.hex/.srec/.s19 extension

 -el,     --extload     : Select a custom external memory-loader for JTAG/SWD
     <file_path>        : External memory-loader file path
 -elbl,   --extloadbl   : Select a custom external memory-loader for Bootloader interface (SFIx only)
     <file_path>        : External memory-loader file path
 -s,     --start
 -g,     --go           : Run the code at the specified address.
     [<address>]        : Start address
 -rdu,   --readunprotect: Remove memory's Read Protection by shifting the RDP
                          level from level 1 to level 0.

 -tzenreg,   --tzenregression: Remove TrustZone Protection by disabling the TZEN
                           from 1 to 0.

 -ob,    --optionbytes  : This command allows the user to manipulate the device
                          's OptionBytes by displaying or modifying them.
      [displ]           : This option allows the user to display the whole set
                          of Option Bytes.
      [unlockchip]      : This option allows the user to unlock the chip in case
                          of bad Option Bytes already programmed.
      [OptByte=<value>] : This option allows the user to program the given
                          Option Byte.

 -force_no_da, --force_no_da
                        : Froce OB programming if the DA is not already configured.
                          Available only for STM32H50x devices

 -w32dbgmcu             : Write a 32-bits data into DBGMCU DBG AUTH HOST register
     <32-bit_data>        32-bit data to be downloaded, only for STM32H5xx devices.

 -lockRDP1              : Lock RDP level 1
     <first_half>       : First 32-bit of password
     <second_half>      : Second 32-bit of password
 -unlockRDP1            : Unlock RDP level 1
     <first_half>       : First 32-bit of password
     <second_half>      : Second 32-bit of password
 -lockRDP2              : Lock RDP level 2
     <first_half>       : First 32-bit of password
     <second_half>      : Second 32-bit of password
 -unlockRDP2            : Unlock RDP level 2
     <first_half>       : First 32-bit of password
     <second_half>      : Second 32-bit of password
 -ssigfoxc              : Save the chip Certificate,
                          supported for STM32WL devices
     <file_path>        : Certificate file path

 -wsigfoxc              : Write the Sigfox credential,
                          supported for STM32WL devices
     <file_path>        : Certificate file path (binary, header)
     <address>          : start address for write

 -fillmemory            : Fill memory with the given pattern from the chosen address.
     <start_address>    : Start address for write. The address 0x08000000 is used by default
     [size=<value>]     : Size of the data to write
     [pattern=<value>]  : The pattern value to write.
     [dataWidth=8|16|32]: The filling data size:
                          8 bits is selected by default.
 -blankcheck            : Verifies that the STM32 Flash memory is blank.
                          If the Flash memory is not blank, the first address with data is highlighted in a message.
 -fchecksum, --file-checksum  : File checksum calculation
     <file_path>              : File path

Beta commands:

 -regdump                     : Read and dump Core and MCU registers
     <file_path.log>          : Log file path
     [choice=<number>]        : Device number from the list of compatible devices (optional), this list
                                is displayed if the command is performed without this optional argument
 -hf                          : Hard fault analyzer
                                Helps to identify system faults that occur when the CPU
                                is driven into a fault condition by the application code.

 -pwr, --power                : Perform power ON/OFF, only for STLINK supporting this capability
     <on/off>                 : Select the power type
     [index=<number>]         : Index of the debug probe.
Available commands for STM32 MPU:

 -c,     --connect      : Establish connection to the device
     <port=<PortName>   : Interface identifer. ex COM1, /dev/ttyS0, usb1)
 USB port optional parameters:
     [sn=<serialNum>]   : Serial number of the usb dfu
     [serial]           : Activate USB serial Gadget for MPU devices
 UART port optional parameters:
     [br=<baudrate>]    : Baudrate. ex: 115200, 9600, etc, default 115200
     [P=<parity>]       : Parity bit, value in {NONE,ODD,EVEN}, default NONE
     [db=<data_bits>]   : Data bit, value in {6, 7, 8} ..., default 8
     [sb=<stop_bits>]   : Stop bit, value in {1, 1.5, 2} ..., default 1
     [fc=<flowControl>] : Flow control (Not yet supported for STM32MP)
                          Value in {OFF,Hardware,Software} ..., default OFF
     [noinit=noinit_bit]: Set No Init bits, value in {0,1} ..., default 0
 -s,     --start
 -g,     --go           : Run the code at the specified partition ID.
     [<partitionID>]    : Partition ID
                          If not specified, last loaded partition
                          will be started

     [<startAdress>]    : Start address
                          If not specified, last loaded segment address
     [<noack>]          : No acknowledgment required
                          If not specified, acknowledgment will be required

 -detach                : Send detach command to DFU

 -wb                    : Write blob

     <blob_file_path>   : Blob file path
 -pmic                  : Program PMIC NVM

     <PMIC file_path>   : PMIC file_path
 -gc, --getcertificate  : Get the chip Certificate,
                          supported for devices with security features
     <file_path>        : Certificate file path into which the chip
                          certificate will be uploaded
     <tfa-ssp-path>     : Input TFA SSP signed firmware path. Used only with STM32MP devices

 -p,     --phaseID      : Display the current partition ID to be loaded

 -w,     --write
 -d,     --download     : Download the content of a file into device memory
     <file_path>        : File path name to be downloaded: (bin, stm32 file
     <partition_id>     : Partition ID to be downloaded
 -rp,    --readPart     : Upload a memory partion ID content to a .bin file
     <partionID>        : Partion to be read
     [<offset address>] : Offset address of read and upload
     <size>             : Size of partion content to be read
     <file_path>        : Binary file path

 -ssp, --ssp                  : Program an SSP file
     <ssp_file_path>          : SSP file path to be programmed, bin or ssp extension
     <ssp-fw-path>            : SSP signed firmware path
     [hsm=0|1]                : Set user option for HSM use
                                value in {0 (do not use HSM), 1 (use HSM)}
                                Default value : hsm=0
     <lic_path|slot=slotID>   : Path to the license file (if hsm=0)
                                or reader slot ID if HSM is used (hsm=1)
 -tm                          : Force timeout
     <value>                  : Number of milliseconds
 -rst                         : Reset USB device

  OTP structure v1 commands :
 -otp displ             : This command allows the user to display all or parts
                          of the OTP structure
      [upper]           : Displays the loaded upper OTP shadow registers
                          values and status
      [lower]           : Displays the loaded lower OTP shadow registers
                          values and status
      [ctrl]            : Displays the loaded BSEC control registers

 -otp program           : This command allows the user to program SAFMEM
                          memory by modifying the OTP words
      [wordID=<value>]  : This field contains the OTP word number
      [value=<value>]   : Loads value into the chosen OTP word
      [sha_rsl=<value>] : Loads value into the corresponding shadow read
                          sticky lock bit
      [sha_wsl=<value>] : Loads value into the corresponding shadow write
                          sticky lock bit
      [sl=<value>]      : Loads value into the corresponding programming sticky
                          lock bit
      [pl=<value>]      : Loads value into the corresponding programming perma-
                          nent lock bit

 -otp fwrite            : This command allows to program a binary file
      [lock]            : {Optional} indicate the operation type, update or permanent lock
      <bin_path>        : Binary file path, 32-bits aligned data
      [wordid=<id>]     : OTP word number in hex/dec format, start word of program

  OTP structure v2 commands :
 -otp displ             : This command allows the user to display all or parts
                          of the OTP structure
      [word=<id>]       : {Optional} display a specific OTP registers {values and status}
                          Up to 96 OTP words [0 to 95], id value in hex/dec format

 -otp write             : This command allows to fuse or update OTP words
                          Up to 96 OTP words [0 to 95] at the same command line
      [lock]            : {Optional} indicate the operation type, update or permanent lock
      [word=<id>]       : This field contains the OTP word number in hex/dec format
      [value=<value>]   : Value to be written in hex format

 -otp lock              : This command allows to fuse permanently OTP words
                          Up to 96 OTP words [0 to 95] at the same command line
      [word=<id>]       : This field contains the OTP word number in hex/dec format

 -otp fwrite            : This command allows to program a binary file
      [lock]            : {Optional} indicate the operation type, update or permanent lock
      <bin_path>        : Binary file path, 32-bits aligned data
      [word=<id>]       : OTP word number in hex/dec format, start word of program

MCU Secure programming specific commands:

 -installipmodule, --installipmodule : Install ip module file
     <file_path>               : Path of smu file to be programmed
     [hsm=0|1]                 : Set user option for HSM use
                                 value in {0 (do not use HSM), 1 (use HSM)}
                                 Default value : hsm=0
     <lic_path|slot=slotID>   : Path to the license file (if hsm=0)
                                or reader slot ID if HSM is used (hsm=1)
                               note that if it is the case of global license,
                               please use hsm  = 0 with license path.
     [<address>]              : Destination address of the smu module
 -updateipmodule, --updateipmodule : update ip module.
     <file_path>              : Path of SMU file containing the update.
     [<address>]              : Destination address of the smu module
 -sfi, --sfi                  : Program an sfi file
     [<protocol=Ptype>]       : Protocol type to be used : static/live
                                Only static protocol is supported so far
                                Default value static
     <file_path>              : Path of sfi file to be programmed
     [hsm=0|1]                : Set user option for HSM use
                                value in {0 (do not use HSM), 1 (use HSM)}
                                Default value : hsm=0
     <lic_path|slot=slotID>   : Path to the SFI license file (if hsm=0)
                                or reader slot ID if HSM is used (hsm=1)
   [<licMod_path>|slot=slotID]: list of the integrated SMI license files paths
                                if HSM is not used (hsm=0)
                                Or readers slot IDs list if HSM is used (hsm=1)
                                Used only in combined case
                                the list order should correspond to
                                modules integration order within the SFI file

 -ssfi, --ssfi                : Program an ssfi file
     <ssfi_file_path>              : Path of ssfi file to be programmed
     <rsse_file_path>              : RSSe file path

 -smi, --smi                  : Program an smi file
     <protocol=Ptype>         : Protocol type to be used : static/live
                                Only static protocol is supported so far
                                Default value static
     <file_path>              : Path of smi file to be programmed
     [hsm=0|1]                : Set user option for HSM use
                                value in {0 (do not use HSM), 1 (use HSM)}
                                Default value : hsm=0
     [<address>]              : Destination address of the smi module
                                needed only for relocatable SMI
     <lic_path|slot=slotID>   : Path to the SMI license file (if hsm=0)
                                or reader slot ID if HSM is used (hsm=1)

 -rsse, --rsse                : Set the RSSe file path,
                                supported for devices with security extension
     <file_path>              : RSSe file path

 -mcsv, --mcsv                : Set the MCSV file path indicating SFI's modules configuration
                                supported for STM32H5xx devices
     <file_path>              : MCSV file path with .mcsv extension

 -a, --abort                  : This command allows the user
                                to clean a not properly finished process.
                                The currently ongoing operation will stop
                                and the system will return to idle state

 -dsecurity                   : Disable the security for STM32WL

 -setdefaultob                : Set default Option Bytes for STM32WL

 -rssgetversion, --rssgetversion : get the version of RSS
HSM related commands:
 -hsmgetinfo                : Read the HSM available information
     [slot=<SlotID>]        : Slot ID of the Smart Card Reader
                              Default value: slot=1 (the PC integrated SC reader)
 -hsmgetcounter             : Read the current value of the license counter
     [slot=<SlotID>]        : Slot ID of the Smart Card Reader
                              Default value: slot=1 (the PC integrated SC reader)
 -hsmgetfwid                : Read the Firmware/Module Identifier
     [slot=<SlotID>]        : Slot ID of the Smart Card Reader
                              Default value: slot=1 (the PC integrated SC reader)
 -hsmgetstatus              : Read the current card life-cycle state
     [slot=<SlotID>]        : Slot ID of the Smart Card Reader
                              Default value: slot=1 (the PC integrated SC reader)
 -hsmgetlicense             : Get a license for the current chip
                              if counter is not null
     <file_path>            : File path into which the received license
                              will be stored
     [slot=<SlotID>]        : Slot ID of the Smart Card Reader
                              Default value: slot=1 (the PC integrated SC reader)
     [protocol=<Ptype>]     : Protocol type to be used : static/live
                              Only static protocol is supported so far
                              Default value static
     <tfa-ssp-path>         : Input TFA SSP signed firmware path. Used only for STM32MP devices

 -hsmgetlicensefromcertifbin, -hsmglfcb : Get a license for the input certificate
                                          if counter is not null
     <certif_file_path.bin>             : Input certificate file path
     <license_file_path.bin>            : File path into which the received license
                                          will be stored
     [slot=<SlotID>]                    : Slot ID of the Smart Card Reader
                                          Default value: slot=1 (the PC integrated SC reader)
     [protocol=<Ptype>]                 : Protocol type to be used : static/live
                                          Only static protocol is supported so far
                                          Default value static

STM32WBxx specific commands:

 -getuid64              : Read the device UID
 -fusgetstate           : Read the FUS state
 -fusopgetversion       : Read the FUS Operator version
 -antirollback          : Perform the antirollback operation (Only on Bootloader interface)
 -startfus              : Perform the startfus operation
  Firmware Upgrade commands:
 -fwdelete              : Delete the BLE stack firmware
 -fwupgrade             : Upgrade the BLE stack firmware or the FUS firmware
     <file_path>        : New firmware image file path
     <address>          : Start address of download
     [firstinstall=0|1] : 1 if it is a first install otherwise 0
                          optional, Default value: firstinstall=0
     [startstack=0|1]   : 1 to start the stack after the upgrade otherwise 0
                          optional, Default value: startstack=1
      -v                : Verify if the download operation is achieved
                          successfully before starting upgrade
 -startwirelessstack    : Start the wireless stack
  Key management commands:
 -authkeyupdate         : Authentication key update
     <file_path>        : New authentication key file path.
                        : This is the public key generated by
                        : STM32TrustedPackageCreator using -sign command.
 -authkeylock           : Authentication key lock
                        : Once locked, it's no longuer possible to change it
                        : using authkeyupdate command
 -wusrkey               : Write user key
     <file_path>        : User key file path
     <keytype=1|2|3>    : User key type, values : 1, 2 or 3.
                        : 1 = simple key, 2 = master key, 3 = encrypted key.
Serial Wire Viewer specific commands:

 -swv                       : Printf via SWO
     <freq=<frequency>>     : System Clock in MHz
     <portnumber=0-31|all>  : ITM port number, values : 0-31, or all for All ports
     [<file_Path.log>]      : Path of the SWV log file (optional),
                            : default path = $USER_HOME/STMicroelectronics/STM32Programmer/SWV_Log/swv.log
      -RA                   : Start the reception of swv automatically
 -startswv                  :  Printf via SWO & Start the reception of swv automatically
Script Manager command:

 -script                    : Start the execution of Script File
 <file_Path.prg>            : Path of the script file (.prg)
Provisionning Command:

 -sdp                    : Start the OBKey Provisioning
 <File_Path>             : OBKey File Path
Provisioning with password :

 -pwd                    : Start Provisioning with password
 value=<password_value>           : Password value
 path=<password_path>             : location of password file to be used in Debug Authentication
Debug Authentication options:

      [debugauth=<1|2>]               : to choose starting authentication(1) or discovery(2)
      [pwd=<password_file_path>]      : required in case of authentication with password
      [key=<key_file_path>]           : required in case of authentication with certificate
      [cert=<certificate_file_path>]  : required in case of authentication with certificate
      [per=<requested_permission>]    : required in case of authentication with certificate. Possible values are a/b/c/d/e/f/g/h :

        (a) Full Regression
        (b) Partial Regression
        (c) Debug Secure L3
        (d) Debug Secure L2
        (e) Debug Secure L1
        (f) Debug Non Secure L3
        (g) Debug Non Secure L2
        (h) Debug Non Secure L1

Хотя список опций CLI интерфейса довольно внушительный, нас касаться будет только та часть, что входит в раздел STM32 MCU.

Теперь подключим плату через USB-C шнурок к компьютеру. На github'е нам предлагается следующие способы для активации DFU (Device Firmware Upgrade) загрузчика:

  1. Method 1: When the power is on, press the BOOT0 key and the reset key, then release the reset key, and release the BOOT0 key after 0.5 seconds
  2. Method 2: When the power is off, hold down the BOOT0 key, and release the BOOT0 at 0.5s after the power is on
  3. DFU Mode: Use the data line to connect to the computer. If there is an unrecognized problem, you can heat the chip appropriately (25°C) and then re-enter the ISP mode
  4. Serial Port Mode: Connect PA9 and PA10 of core board with USB serial port
  5. Soft: STM32CubeProg。

Нам предлагается два варианта. Второй вариант реализуется через отключение микроконтроллера от компьютера, он нам не подходит, т.к. это неудобно.

Если следовать первому варианту (метод 1), то сначала следует зажать кнопки BOOT0 и RESET, затем сперва отпустить кнопку RESET и через полсекунды так же отпустить кнопку BOOT0. Если все было сделано правильно, то в dmesg появится соответствующий лог:

usb 6-1: new full-speed USB device number 5 using ohci-pci
usb 6-1: New USB device found, idVendor=0483, idProduct=df11, bcdDevice=22.00
usb 6-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 6-1: Product: STM32  BOOTLOADER
usb 6-1: Manufacturer: STMicroelectronics
usb 6-1: SerialNumber: 389C37823039

У меня в микроконтроллер была залита простая мигалка (заливал сам). Когда я зажимал кнопки BOOT0 и RESET, и входил в режим загрузчика, то светодиод на плате переставал мигать. Чтобы выйти из режима загрузчика, достаточно начать RESET, светодиод снова начнет мигать (если вы не снесете прошивку в процессе, конечно).

Чтобы начать работать с DFU-загрузчиком в Linux, нужно сначала установить udev-правила. В каталоге с программой имеются несколько правил:

nostromo:~: ls -l /usr/local/STMicroelectronics/STM32Cube/STM32CubeProgrammer/Drivers/rules/
итого 28
-rw-r--r-- 1 flanker users 215 апр 11  2019 49-stlinkv1.rules
-rw-r--r-- 1 flanker users 604 авг  7  2019 49-stlinkv2-1.rules
-rw-r--r-- 1 flanker users 480 авг  7  2019 49-stlinkv2.rules
-rw-r--r-- 1 flanker users 862 авг  7  2019 49-stlinkv3.rules
-rw-r--r-- 1 flanker users  97 авг  7  2019 50-usb-conf.rules
-rw-r--r-- 1 flanker users 217 авг  7  2019 Readme.txt
-rw-r--r-- 1 flanker users   5 авг  7  2019 version.txt

Правила для ST-Link у меня уже есть, а вот правило 50-usb-conf.rules потребуется установить. Посмотрим, что там:

$ cat  /usr/local/STMicroelectronics/STM32Cube/STM32CubeProgrammer/Drivers/rules/50-usb-conf.rules 
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", GROUP="users", MODE="0666"

Номера idVendor и idProduct соответствуют аналогичным номерам из нашего лога dmesg, следовательно - это то самое правило, которое нам и нужно. Копируем это udev-правило в /etc/udev/rules.d/ и перезагружаем правила командой из под рута:

# devadm control --reload-rules && udevadm trigger

Подключаем плату вновь (или перезагружаем если уже подключен) к компьютеру и заново активируем режим DFU-загрузчика.

Переключаемся на графическую тулзу. В вертикальной правой панели нужно будет нажать на кнопку обновления:

После этого там должен появиться порт USB1. После этого в выпадающем списке нужно выбрать тип соединения USB, и нажать на расположенную рядом желтую кнопку "Connect". Если все пройдет удачно, то у вас должно рабочее окно программы выглядеть как-то так:

Далее сориентироваться думаю не составит труда. Через программу можно загрузить прошивку в микроконтроллер, изменить Option Bytes (вкладка [OB]), посмотреть содержимое флеш-памяти в режиме дамп-вьювера. Ластик внизу позволяет очистить флеш от прошивки. Вкладка CPU доступна только при подключении через ST-Link. Хочу обратить внимание на переключение "Verbosity Level". Переключая его уровни можно регулировать уровень отладочной информации в окне лога.

Теперь вернемся к версии флешера с консольным интерфейсом. Запустим его с параметром "-l":

$ /usr/local/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/STM32_Programmer_CLI -l
      -------------------------------------------------------------------
                        STM32CubeProgrammer v2.15.0                  
      -------------------------------------------------------------------

=====  DFU Interface   =====

Total number of available STM32 device in DFU mode: 1

  Device Index           : USB1
  USB Bus Number         : 006
  USB Address Number     : 001
  Product ID             : STM32  BOOTLOADER
  Serial number          : 389C37823039
  Firmware version       : 0x011a
  Device ID              : 0x0431


===== STLink Interface =====
Error: No ST-Link detected!

Нам вывело наш подключенный через DFU микроконтроллер. Попробуем к нему подключиться:

$ /usr/local/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/STM32_Programmer_CLI -c port=usb1
      -------------------------------------------------------------------
                        STM32CubeProgrammer v2.15.0                  
      -------------------------------------------------------------------



USB speed   : Full Speed (12MBit/s)
Manuf. ID   : STMicroelectronics
Product ID  : STM32  BOOTLOADER
SN          : 389C37823039
FW version  : 0x011a
Device ID   : 0x0431
Device name : STM32F411xC/E
Flash size  : 512 KBytes (default)
Device type : MCU
Device CPU  : Cortex-M4

Через опцию -vb можно регулировать уровнем отладочной информации. Например, с опцией -vb 2 та же команда выдаст такой лог:

$ /usr/local/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/STM32_Programmer_CLI -vb 2 -c port=usb1
      -------------------------------------------------------------------
                        STM32CubeProgrammer v2.15.0                  
      -------------------------------------------------------------------



USB speed   : Full Speed (12MBit/s)
Manuf. ID   : STMicroelectronics
Product ID  : STM32  BOOTLOADER
SN          : 389C37823039
DFU protocol: 1.1
Board       : --
Device ID   : 0x0431
sending an abort request
setting the address pointer to address: 0x08000000
sending an abort request
setting the address pointer to address: 0x08000000
setting the address pointer to address: 0x1fffc008
receiving packet nbr: 0
sending an abort request
UpLoading data
Database: Config 0 is active.
sending an abort request
setting the address pointer to address: 0x08000000
setting the address pointer to address: 0x1fffc008
receiving packet nbr: 0
sending an abort request
UpLoading data
Database: Config 0 is active.
Device name : STM32F411xC/E
Flash size  : 512 KBytes (default)
Device type : MCU
Revision ID : --  
Device CPU  : Cortex-M4

В предыдущий раз (в 2020 году), когда я пытался подключиться к микроконтроллеру через ST-Link, то получил сообщение об ошибке, говорящее, что прошивка моего ST-Link устарела и не совместима с программой STM32CubeProgrammer:

$ /usr/local/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/STM32_Programmer_CLI -l
      -------------------------------------------------------------------
                        STM32CubeProgrammer v2.4.0                  
      -------------------------------------------------------------------

=====  DFU Interface   =====

No STM32 device in DFU mode connected

===== STLink Interface =====
Error: Old ST-LINK firmware version. Upgrade ST-LINK firmware

-------- Connected ST-LINK Probes List --------

ST-Link Probe 0 :
   ST-LINK SN  : 56FF6E064966504930072087
   ST-LINK FW  : V2J27S6
-----------------------------------------------

=====  UART Interface  =====

Total number of serial ports available: 1

Port: ttyS0
Location: /dev/ttyS0
Description: N/A
Manufacturer: N/A

В сети можно найти видеоролики о том, как через графическую тулзу прошивка ST-Link обновляется прямо тут же в STM32CubeProgrammer. Но видимо в Linux это не работает так просто. При попытке обновления прошивки из STM32CubeProgrammer я получил еще одно сообщение об ошибке:

Теперь уже мной ST-Link не в DFU-режиме! Опять пришлось все делать по старинке, т.е. скачать c сайта st.com утилиту "ST-Link Firmware Upgrade" и обновить прошивку из виртуалки:

Последний раз я обновлял прошивку ST-Link в 2016 году, тогда это была версия V2.J27.S6. Также как и тогда, обновление прошивки из виртуалки под Linux прошло успешно.

После этого, препятствия для подключения микроконтроллера к флешеру исчезнут.

Если в микроконтроллере имеется прошивка без поддержки отладки, как например в прошивке микропитона, то перед подключением к программе STM32CubeProgrammer (т.е. перед нажатием кнопки "Connect" в графической тулзе или перед нажатием Enter при вводе команды в случае использования консольной утилиты) необходимо будет будет нажать и отпустить кнопку Reset на плате, и примерно через полсекунды после отпускания кнопки подключаться программой. Второй способ заключается опять же в поочередном нажимании кнопок Reset и Boot.

В целом, каких-то преимуществ, по сравнению с подключением через USB-шнурок, в использовании ST-Link я не увидел.

В 2022 году мой STLink приказал долго жить, я менял чип и заново заливал в него прошивку отладчика. Об этом я писал в "Реанимация ST-Link V2". Тогда я поставил не самую свежую прошивку V2J32S7, и давайте посмотрим, сможет ли она работать с STM32CubeProgrammer:

Как видите, никаких проблем не возникло. Еще интересный вопрос, работа STM32CubeProgrammer с STLink'ами на китайских чипах. Аналогично тому, как производители чипов PL2303 и FT232RL боролись с китайскими клонами, можно ожидать, что ST будет действовать похожим способом. За все клоны не скажу, но вот мой экземпляр на APM32 чипе купленный в мае 2022 года работает без сучка и задорины:

Любопытно, что версия прошивки клона еще более ранняя, нежели на моем восстановленном STLink.

Флеш-память микроконтроллера STM32F411CEU6 разбита на следующие сектора:

Очистка флеш-памяти через CLI-интерфейс осуществляется с помощью ключа "-e". Этот ключ с параметром "all" будет очищать все сектора. Но мы можем выбрать определенные сектора для очистки, в этом случае следует указать их через запятую. Или же мы можем задать диапазон секторов, для этого их номера достаточно будет разделить пробелом. В случае если после "-e" будет стоять одно число, то будет очищен соответствующий сектор:

3) Прошивка MicroPython

Компания WeAct предлагает свои платы как бюджетную платформу для MircoPython. Оригинальная pyboard v1.1 c чипом STM32F405 стоит прилично - 33.9 фунтов + доставка.

Готовую прошивку с микропитоном для микроконтроллера STM32F411CE можно скачать с гитхаба компании WeACT https://github.com/WeActStudio/WeActStudio.MiniSTM32F4x1/tree/master/SDK/STM32F411CEU6/MicroPython/firmwares-v1.12:

Там есть прошивки для плат с различными флешками. Внешняя флешка позволит вам размешать ваш код на микропитоне непосредственно на этой самой флешке и быть т.о. по-сути не ограниченным в свободном пространстве. В противном случае вам придется довольствоваться 45 килобайтами под собственный код, т.к. остальное место будет занимать микропитон. Т.к. на нашей плате пока никакой флешки не установлено, нам нужна будет прошивка для "internal_rom":

Скачиваем raw-файл с прошивкой:

$ wget https://github.com/WeActStudio/WeActStudio.MiniSTM32F4x1/raw/master/SDK/STM32F411CEU6/MicroPython/firmwares-v1.12/firmware_internal_rom_stm32f411_v1.12-540.hex

Проверяем:

$ arm-none-eabi-size ./firmware_internal_rom_stm32f411_v1.12-540.hex 
   text    data     bss     dec     hex filename
      0  322404       0  322404   4eb64 ./firmware_internal_rom_stm32f411_v1.12-540.hex

Прошивка весит ~320КБ. Прошиваем с помощью команды:

$ STM32_Programmer_CLI -c port=swd -w /tmp/firmware_internal_rom_stm32f411_v1.12-540.hex

Отстыковываем ST-Link, если прошивка проводилась через него, а не через USB-шнурок, и подключаем плату к USB. В dmesg пойдет такой лог:

[32070.940976] usb 4-2: New USB device found, idVendor=f055, idProduct=9800, bcdDevice= 2.00
[32070.940983] usb 4-2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[32070.940988] usb 4-2: Product: Pyboard Virtual Comm Port in FS Mode
[32070.940991] usb 4-2: Manufacturer: MicroPython
[32070.940994] usb 4-2: SerialNumber: 389C37823039
[32071.352314] usb-storage 4-2:1.0: USB Mass Storage device detected
[32071.353469] scsi host8: usb-storage 4-2:1.0
[32071.353558] usbcore: registered new interface driver usb-storage
[32071.359817] cdc_acm 4-2:1.1: ttyACM0: USB ACM device
[32071.361341] usbcore: registered new interface driver cdc_acm
[32071.361344] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters
[32071.368434] usbcore: registered new interface driver uas
[32072.363046] scsi 8:0:0:0: Direct-Access     MicroPy  pyboard Flash    1.00 PQ: 0 ANSI: 2
[32072.374982] sd 8:0:0:0: [sdb] 384 512-byte logical blocks: (197 kB/192 KiB)
[32072.382022] sd 8:0:0:0: [sdb] Write Protect is off
[32072.382029] sd 8:0:0:0: [sdb] Mode Sense: 03 00 00 00
[32072.389035] sd 8:0:0:0: [sdb] No Caching mode page found
[32072.389042] sd 8:0:0:0: [sdb] Assuming drive cache: write through
[32072.443023]  sdb: sdb1
[32072.494018] sd 8:0:0:0: [sdb] Attached SCSI removable disk

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

$ df  -h /mnt/tmp
Файловая система Размер Использовано  Дост Использовано% Cмонтировано в
/dev/sdb1           47K         5,0K   42K           11% /mnt/tmp

$ ls -l  /mnt/tmp
итого 5
-rwxr-xr-x 1 root root  528 янв  1  2015 README.txt
-rwxr-xr-x 1 root root  366 янв  1  2015 boot.py
-rwxr-xr-x 1 root root   34 янв  1  2015 main.py
-rwxr-xr-x 1 root root 2999 янв  1  2015 pybcdc.inf

Здесь файл с расширением INF - это драйвер последовательно порта для Windows, его можно сразу стереть, освободится место. README.txt это хелло-файл:

This is a MicroPython board

You can get started right away by writing your Python code in 'main.py'.

For a serial prompt:
 - Windows: you need to go to 'Device manager', right click on the unknown device,
   then update the driver software, using the 'pybcdc.inf' file found on this drive.
   Then use a terminal program like Hyperterminal or putty.
 - Mac OS X: use the command: screen /dev/tty.usbmodem*
 - Linux: use the command: screen /dev/ttyACM0

Please visit http://micropython.org/help/ for further help.

Его тоже можно стереть.

boot.py - это надо думать автозагузочный файл:

# boot.py -- run on boot-up
# can run arbitrary Python, but best to keep it minimal

import machine
import pyb
pyb.country('US') # ISO 3166-1 Alpha-2 code, eg US, GB, DE, AU
#pyb.main('main.py') # main script to run after this one
#pyb.usb_mode('VCP+MSC') # act as a serial and a storage device
#pyb.usb_mode('VCP+HID') # act as a serial device and a mouse

И main.py это пустой файл для вашего кода:

# main.py -- put your code here!

Напоминаю, что для своего кода у вас есть всего 45 килобайта.

4) Загрузка программы Blink.py для микропитона, и основы работы с REPL

На github'е компании WeAct имеется парочка примеров для микропитона: https://github.com/WeActTC/MiniF4-STM32F4x1/tree/master/SDK/MicroPython_Example. Нас будет интересовать содержимое Blink.py:

import pyb, micropython

micropython.alloc_emergency_exception_buf(100)

class Foo(object):
    def __init__(self, timer, led):
        self.led = led
        timer.callback(self.cb)
    def cb(self, tim):
        self.led.toggle()

blue = Foo(pyb.Timer(1, freq=2), pyb.LED(1)) # LED(1) -> PC13

Размещаем эту программу в main.py, размонтируем флешку, жмем на кнопку Reset на плате, и наслаждаемся миганием синего светодиода.

Через последовательный порт у нас также есть доступ к режиму интерпретатора REPL. Там мы вручную сможем помигать светодиодом, но для этого сначала придется отчистить main.py от размещенного там ранее кода, и перезагрузить микроконтроллер, т.к. способа остановить через REPL запущенную из флешки программу, я пока не нашел. Итак входим к REPL с помощью команды:

$ screen /dev/ttyACM0

Нас должно встретить сообщение навроде этого:

MicroPython v1.12-540-gdd65eb920-dirty on 2020-07-05; WeAct_Core with STM32F411CE
Type "help()" for more information.
>>>

Словечко dirty в версии прошивки мне заранее не нравится ;) Вводим команду help() для вывода справки, и получаем следующий вывод:

Welcome to MicroPython!

For online help please visit http://micropython.org/help/.

Quick overview of commands for the board:
  pyb.info()    -- print some general information
  pyb.delay(n)  -- wait for n milliseconds
  pyb.millis()  -- get number of milliseconds since hard reset
  pyb.Switch()  -- create a switch object
                   Switch methods: (), callback(f)
  pyb.LED(n)    -- create an LED object for LED n (n=1,2,3,4)
                   LED methods: on(), off(), toggle(), intensity()
  pyb.Pin(pin)  -- get a pin, eg pyb.Pin('X1')
  pyb.Pin(pin, m, [p]) -- get a pin and configure it for IO mode m, pull mode p
                   Pin methods: init(..), value([v]), high(), low()
  pyb.ExtInt(pin, m, p, callback) -- create an external interrupt object
  pyb.ADC(pin)  -- make an analog object from a pin
                   ADC methods: read(), read_timed(buf, freq)
  pyb.DAC(port) -- make a DAC object
                   DAC methods: triangle(freq), write(n), write_timed(buf, freq)
  pyb.RTC()     -- make an RTC object; methods: datetime([val])
  pyb.rng()     -- get a 30-bit hardware random number
  pyb.Servo(n)  -- create Servo object for servo n (n=1,2,3,4)
                   Servo methods: calibration(..), angle([x, [t]]), speed([x, [t]])
  pyb.Accel()   -- create an Accelerometer object
                   Accelerometer methods: x(), y(), z(), tilt(), filtered_xyz()

Pins are numbered X1-X12, X17-X22, Y1-Y12, or by their MCU name
Pin IO modes are: pyb.Pin.IN, pyb.Pin.OUT_PP, pyb.Pin.OUT_OD
Pin pull modes are: pyb.Pin.PULL_NONE, pyb.Pin.PULL_UP, pyb.Pin.PULL_DOWN
Additional serial bus objects: pyb.I2C(n), pyb.SPI(n), pyb.UART(n)

Control commands:
  CTRL-A        -- on a blank line, enter raw REPL mode
  CTRL-B        -- on a blank line, enter normal REPL mode
  CTRL-C        -- interrupt a running program
  CTRL-D        -- on a blank line, do a soft reset of the board
  CTRL-E        -- on a blank line, enter paste mode

For further help on a specific object, type help(obj)
For a list of available modules, type help('modules')

Вводим команду help('modules') чтобы посмотреть список установленных по-умолчанию модулей:

>>> help('modules')
__main__          math              uasyncio/funcs    umachine
_onewire          micropython       uasyncio/lock     uos
_thread           network           uasyncio/stream   urandom
_uasyncio         onewire           ubinascii         ure
builtins          pyb               ucollections      uselect
cmath             stm               uctypes           usocket
dht               sys               uerrno            ustruct
framebuf          uarray            uhashlib          utime
gc                uasyncio/__init__ uheapq            utimeq
lcd160cr          uasyncio/core     uio               uzlib
lcd160cr_test     uasyncio/event    ujson
Plus any modules on the filesystem

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

Вводим команду pyb.info()

>>> pyb.info()
ID=4a006700:19513930:38373538
S=96000000
H=96000000
P1=24000000
P2=48000000
_etext=806b184
_sidata=806b18c
_sdata=20000000
_edata=2000001c
_sbss=2000001c
_ebss=20006494
_sstack=2001bff8
_estack=2001fff8
_ram_start=20000000
_heap_start=20006494
_heap_end=2001bff8
_ram_end=20020000
qstr:
  n_pool=1
  n_qstr=3
  n_str_data_bytes=28
  n_total_bytes=124
GC:
  86848 total
  1536 : 85312
  1=23 2=5 m=40
LFS free: 40960 bytes
THREAD: only main thread

Эта информация для нас пока ничего не значит. Что бы управлять светодиодом, следует вводить команды pyb.LED(1).on(), pyb.LED(1).off(), pyb.LED(1).toggle(). При этом синий светодиод должен включаться и выключаться соответственно:

Также обратите внимание на командные комбинации клавиш. CTRL + D, к примеру, перезагружает микроконтроллер. Примечание из 2023 года. Точнее, по Ctrl+D выполняется "soft reset", т.е. когда происходит выход из REPL, и главный цикл микропитона перезагружается, по метке "soft reset":

    // Run main.py (or whatever else a board configures at this stage).
    if (MICROPY_BOARD_RUN_MAIN_PY(&state) == BOARDCTRL_GOTO_SOFT_RESET_EXIT) {
        goto soft_reset_exit;
    }

    #if MICROPY_ENABLE_COMPILER
    // Main script is finished, so now go into REPL mode.
    // The REPL mode can change, or it can request a soft reset.
    for (;;) {
        if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) {
            if (pyexec_raw_repl() != 0) {
                break;
            }
        } else {
            if (pyexec_friendly_repl() != 0) {
                break;
            }
        }
    }
    #endif

soft_reset_exit:

    // soft reset

    MICROPY_BOARD_START_SOFT_RESET(&state);

    #if MICROPY_HW_ENABLE_STORAGE
    if (state.log_soft_reset) {
        mp_printf(&mp_plat_print, "MPY: sync filesystems\n");
    }
    storage_flush();

5) Установка SPI флешки на плату WeACT STM32F411CEU6

Установка SPI флешки позволит преодолеть досадное ограничение в 45 килобайт под вашу программу и дополнительные модули. Здесь: https://github.com/mcauser/WEACT_F411CEU6 утверждается, что платы успешно тестировались с флешками:

  1. Winbond W25Q32 (4 MByte)
  2. Winbond W25Q64 (8 MByte)
  3. Winbond W25Q128 (16 MByte)

Замечу, что официальной поддержки 16 МБайтной флешки нет, в ее случае придется микропитон собирать из исходников. У меня под рукой была флешка Winbond W25Q64FVSIG. Запаять ее было делом пяти минут:

Кроме флешки, там есть еще посадочное место под конденсатор С15 на 0.1 мкФ:

Посадочное место под SMD конденсатор размера 0603, но у меня под рукой были только 0805. Усилием воли, я поставил тот, что был в наличии, но сел он кривовато. Флешка будет работать и без конденсатора, но его лучше будет все-таки поставить.

После промывки платы от следов пайки и последующей сушки, на микроконтроллере следует обновить прошивку, до версии, которая предназначена для работы с флешками на 8МБ
https://github.com/WeActStudio/WeActStudio.MiniSTM32F4x1/tree/master/SDK/STM32F411CEU6/MicroPython/firmwares-v1.12:

Загружаем эту прошивку в чип:

Убираем ST-Link (если прошивка производилась через него) и подключаем плату через USB к компьютеру. В dmesg видим следующее:

[17748.129823] cdc_acm 4-2:1.1: ttyACM0: USB ACM device
[17748.131436] usbcore: registered new interface driver cdc_acm
[17748.131437] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters
[17748.136489] usb-storage 4-2:1.0: USB Mass Storage device detected
[17748.139756] scsi host8: usb-storage 4-2:1.0
[17748.139869] usbcore: registered new interface driver usb-storage
[17748.148136] usbcore: registered new interface driver uas
[17749.216339] scsi 8:0:0:0: Direct-Access     MicroPy  pyboard Flash    1.00 PQ: 0 ANSI: 2
[17749.227319] sd 8:0:0:0: [sdb] 16640 512-byte logical blocks: (8.52 MB/8.13 MiB)
[17749.234348] sd 8:0:0:0: [sdb] Write Protect is off
[17749.234355] sd 8:0:0:0: [sdb] Mode Sense: 03 00 00 00
[17749.241323] sd 8:0:0:0: [sdb] No Caching mode page found
[17749.241325] sd 8:0:0:0: [sdb] Assuming drive cache: write through
[17749.287331]  sdb: sdb1
[17749.316331] sd 8:0:0:0: [sdb] Attached SCSI removable disk

У нас появилась флешка на 8 МБ. Ее даже можно открыть в файловом браузере:

6) Сборка микропитона из исходников

Совсем не обязательно пользоваться готовыми прошивками микропитона, их можно собирать самому. Это может понадобиться, если вы решите поставить флешку на 16 МБ, или вам надо поставить микропитон на какую-то свою плату с микроконтроллером серии STM32F4xx. Или если спустя какое-то время, компания WeACT прекратит существование, и иного способа получить актуальную прошивку не будет.

На github'е https://github.com/WeActTC/MiniF4-STM32F4x1/tree/master/SDK/STM32F411CEU6/MicroPython/WeAct_F411CE выложен порядок сборки микропитона из исходников:

Следуя этим инструкциям, выполняем:

$ git clone https://github.com/micropython/micropython.git
$ cd micropython/
$ git submodule update --init
$ cd mpy-cross
$ make -j4
$ cd ../ports/stm32/boards
$ git clone https://github.com/WeActTC/WeAct_F411CE.git
$ cd ..

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

Согласно инструкции, в файле: ./boards/WeAct_F411CE/mpconfigboard.h находим строки:

#define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (1)
#define MICROPY_HW_SPIFLASH_SIZE_BITS (32 * 1024 * 1024)

и заменяем их на следующие:

#define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0)
#define MICROPY_HW_SPIFLASH_SIZE_BITS (64 * 1024 * 1024)

Компилируем прошивку:

$ make -j4

После недолгой компиляции, мы получим прошивку в файле ./build-PYBV10/firmware.hex

$ arm-none-eabi-size ./build-PYBV10/firmware.hex 
   text    data     bss     dec     hex filename
      0  356852       0  356852   571f4 ./build-PYBV10/firmware.hex

Прошиваем ее в микроконтроллер, затем подключаем через USB к компьютеру, и заходим в REPL. Самым очевидным признаком того, что мы используем самосборную прошивку, будет изменившееся приветствие. Появится дата нашей сборки, замечаем, что версия микропитона сменилась с 1.12-540 до 1.12-648. Кроме того, из имени сборки исчезло слово "dirty":

Если ввести команду pyb.info(), то в строке "LFS free" появится размер нашей флешки.

7) Работа с MicroPython в интерактивной системе REPL

Прежде всего, как я уже говорил ранее (возможно кто-то читает не все подряд с начала до конца), что бы управлять периферией из REPL (Read-Eval-Print Loop), нам следует очистить от программы файл main.py на флешке и перезапустить микроконтролер. Последнее можно сделать прямо из REPL нажав комбинацию клавиш CTRL+D.

В REPL можно вводить свою программу нажав комбинацию CTRL+E. Допустим только ввод, редактировать текст нельзя, даже затереть последние напечатанные символы не получится. Поэтому если вы вводите большой текст, то пишите его обычном редакторе, а затем используйте "Copy & Paste". Кроме CTRL+C и CTRL+V, мы можем использовать более хардкорное решение.

Т.к. мы уже работаем в screen, который является консольным мультиплексором, то мы можем использовать его встроенные возможности. Все команды в screen посылаются хоткеями "CTRL+Мета+Клавиша_команды". В качестве Мета-клавишы по-умолчанию выступает Alt. Т.о. жмем "CTL+ALT+C" и открываем новую консоль, между которыми можно переключаться через "CTL+ALT+SPACE". В новой консоли, в любом консольном редакторе вводим текст программы и выделяем его через "CTL+ALT+[". После последнего хоткея подводим курсор к началу выделения и нажатием пробела начинаем его выделять. Далее ведем курсор до окончания выделения и еще одним нажатием на пробел, заканчиваем выделение.

Внизу появится сообщение, что столько-то символов было скопировано в буфер. Далее снова переключаемся на REPL, вводим "CTR+E", затем "CTL+ALT+]", и наш текст вставился без малейшей ошибки.

Комбинация REPL "CTL+C" отменяет ввод программы. Комбинация "CTL+D" запускает программу на выполнение. Выполнения программы можно прервать комбинацией "CTL+C".

Теперь давайте напишем что-то полезное. Сделаем мигалку на обычном счетчике, и на нем приблизительно измерим быстродействие нашей MicroPython системы. Вводим в REPL такую программу:

while True:
    for i in range(60000):
        pass
    pyb.LED(1).toggle()

Запускаем ее комбинацией "CTL+D", и судя по частоте мигания синего светодиода, я бы сказал, что быстродействие нашего кода на python где-то на уровне нативного кода Arduino на 16-мегагерцовой меге. Но никто же не ждал чуда? Частоту работу микроконтроллере можно менять через команду: machine.freq(). Введите ее с пустыми скобками, чтобы узнать допустимые значения. Но микроконтроллер по умолчанию работает на частоте 96МГц, т.е. на максимальной производительности.

Можно использовать функцию pyb.delay() для указания задержки к миллисекундах:

import pyb

while True:
    pyb.delay(1000)
    pyb.LED(1).toggle()

Следующий способ помигать светодиодом, на этот раз с помощью таймера, приведен как-раз в документации по этим таймерам - class Timer – control internal timers:

import pyb

tim = pyb.Timer(1)              # create a timer object using timer 1
tim.init(freq=1)                # trigger at 1Hz
tim.callback(lambda t:pyb.LED(1).toggle())

Здесь ключевое слово lambda несет то же значение, что в C++, т.е. указывает на безымянную функцию. Если то же самое сделать через именованную функцию, то получится немного длиннее:

import pyb

def blink(timer):               # we will receive the timer object when being called
    pyb.LED(1).toggle()         # make blink
tim = pyb.Timer(1, freq=2)      # create a timer object using timer 1, trigger at 2Hz
tim.callback(blink)             # set the callback to our blink function

В качестве еще одного примера работы с таймерами можно рассмотреть реализацию бенчмарка, который я описывал в 2018 году: Простой бенчмарк на операции деления:

import pyb

def blink(timer):               # we will receive the timer object when being called
    global ms
    if (ms>0):
        ms = ms-1
tim = pyb.Timer(1, freq=1000)   # create a timer object using timer 1, trigger at 1000Hz
ms=50
tim.callback(blink)             # set the callback to our blink function
div_count=0
while (ms>0):
    result=ms/int(314)
    div_count=div_count+1
tim.callback(None)              # stop timer
print('div_count= ',div_count)

И ожидаемо, что результат его работы покажет производительность даже меньшую чем в Arduino:

Программу можно записать и другим способом. Например:

import pyb

class check(object):
    def __init__(self,ms=50):
        self.ms=ms
        self.timer=pyb.Timer(1,freq=1000)
    def cb(self, tim):
        if (self.ms>0):
            self.ms-=1
    def __call__(self):
        self.div_count=0
        self.timer.callback(self.cb)
        while (self.ms>0):
            self.div_count+=1
        self.timer.callback(None)
        return self.div_count

r = check()
print(r())

Любопытно, что такая, казалось бы, более замороченная программа, работает почти в два раза быстрее предыдущей:

Кстати, раз уж мы используем прерывания, а метод def cb(self, tim) в данном примере является обработчиком прерывания, в документации к микропитону есть рекомендация вставлять следующие строки в начало программы, для возможности генерации сообщения об ошибке, если они возникнут в обработчике прерывания:

import micropython

micropython.alloc_emergency_exception_buf(100)

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

import pyb

def check(ms):
    div_count=0
    start=pyb.millis()
    while (pyb.elapsed_millis(start) < ms):
        div_count+=1
    return div_count

print(check(50))

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

Теперь, допустим нам нужно помигать светодиодом на произвольном пине. Для этого нужно обратиться к пину по его номеру, а не через LED(1).

Сначала зададим режим работы порта как PUSH PULL:

>>> blu=machine.Pin(pyb.Pin.cpu.C13,machine.Pin.OUT)

Теперь включаем светодиод:

>>> blu.off()

нет, это не ошибка, именно off(). Или выключаем:

>>> blu.on()

А можно и так. Включаем:

>>> blu.value(0)

Выключаем:

>>> blu.value(1)

Или меняем состояние:

>>> blu.value(not blu.value())

А сейчас самое время вспомнить, что на плате имеется кнопка "Key" висящая на PA0. Мы можем читать ее состояние, и использовать светодиод для индикации состояния кнопки. Для начала сделаем это самым простым способом - методом опроса состояния кнопки.

import pyb, machine

led=machine.Pin(pyb.Pin.cpu.C13,machine.Pin.OUT)
btn=machine.Pin(pyb.Pin.cpu.A0,machine.Pin.IN)

while True:
    led.value(btn.value())
    pyb.delay(100)

Теперь, когда мы будем нажимать кнопку "Key", синий светодиод будет загораться.

Переделать программу на внешнем прерывании не составляет большого труда. В данном случае у меня она получилось такой:

from machine import Pin
import pyb, micropython

micropython.alloc_emergency_exception_buf(100)

btn=Pin(pyb.Pin.cpu.A0,Pin.IN)

def btn_irq(pin):
    pyb.LED(1).toggle()
    global interrupt_pin
    interrupt_pin=pin
    print("press: ",interrupt_pin)

btn.irq(trigger=Pin.IRQ_RISING,handler=btn_irq)

while True:
    pyb.delay(100)

При каждом нажатии на кнопку "KEY" синий светодиод будет зажигаться или гаснуть, при этом в консоль пойдет идентификатор пина с кнопкой:

Замечу, что при нажатии на кнопку отсутствовал дребезг контакта, поэтому ставить какие-то задержки, для подавления дребезга, в обработчик внешнего прерывания я не стал (а зря, дребезг все-таки есть. примечание 2023г.).

8) Использование редакторов VS Code и Atom в качестве IDE для MicroPython

Примечание из 2023 года. В настоящее время актуальной версией Pymark является вторая, которую мне не удалось заставить работать должным образом. Поэтому решением был откат не версию описанную ниже. Подробнее об этом я писал в: "ESP8266 + Micropython + VSCode: основы работы".

Для редакторов кода VS Code и Atom возможна установка плагина Pymakr, который превращает работу в них, в приятную альтернативу использованию программы screen. Полагаю, что наиболее актуально это будет для пользователей Windows, у которых программы screen под рукой как правило нет, да и работать в консоли они не любят. Плагин Pymakr предоставляет вам консоль REPL, и позволяет автоматизировать оправку вашей программы в REPL на исполнение. Но Pymakr написан был для работы с платой Pyboard, и "из коробки" с нашей платой WeACT работать не будет. Его нужно будет настраивать. В принципе, это займет всего пару минут.

Проше всего, как мне показалось, настроить VS Code. Для начала нужно будет установить этот самый плагин Pymakr:

Затем надо будет заглянуть в лог dmesg и посмотреть, как там записан производитель (Manufacturer) последовательного порта.

[ 1709.050627] usb 4-2: New USB device found, idVendor=f055, idProduct=9800, bcdDevice= 2.00
[ 1709.050634] usb 4-2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 1709.050639] usb 4-2: Product: Pyboard Virtual Comm Port in FS Mode
[ 1709.050642] usb 4-2: Manufacturer: MicroPython
[ 1709.050645] usb 4-2: SerialNumber: 389C37823039
[ 1709.466521] usb-storage 4-2:1.0: USB Mass Storage device detected
[ 1709.466639] scsi host8: usb-storage 4-2:1.0
[ 1709.466747] usbcore: registered new interface driver usb-storage
[ 1709.474206] cdc_acm 4-2:1.1: ttyACM0: USB ACM device
[ 1709.475847] usbcore: registered new interface driver cdc_acm
[ 1709.475850] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters
[ 1709.500918] usbcore: registered new interface driver uas
[ 1710.529689] scsi 8:0:0:0: Direct-Access     MicroPy  pyboard Flash    1.00 PQ: 0 ANSI: 2
[ 1710.541684] sd 8:0:0:0: [sdb] 16640 512-byte logical blocks: (8.52 MB/8.13 MiB)
[ 1710.548668] sd 8:0:0:0: [sdb] Write Protect is off
[ 1710.548676] sd 8:0:0:0: [sdb] Mode Sense: 03 00 00 00
[ 1710.555673] sd 8:0:0:0: [sdb] No Caching mode page found
[ 1710.555680] sd 8:0:0:0: [sdb] Assuming drive cache: write through
[ 1710.607667]  sdb: sdb1
[ 1710.668669] sd 8:0:0:0: [sdb] Attached SCSI removable disk

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

После установки Pymakr сразу же откроется файл с настройками плагина - pymakr.json. Но даже если он не откроется автоматически, его можно найти в ~/.config/Code/User/pymakr.json. В этом файле настроек, нам нужно будет поменять адрес устройства с 192.168.4.1 на наш порт: "/dev/ttyACM0". Кроме этого добавить нашего производителя в список поддерживаемых для автосоединения. При этом автосоединения лучше будет сбросить в false, это только мешает.

После этого достаточно будет подключить плату к компьютеру и щелкнуть внизу по "Pymakr Console". В окне терминала появиться консоль REPL. Щелчок по "Run" запустит вашу программу в REPL.

Настройка Atom'а аналогична. Для начала нужно будет установить плагин Pymakr, после чего сразу зайти в настройки плагина:

В настройках нужно будет также изменить адрес устройства на свой порт, сбросить флажок с автосоединения, и добавить своего производителя в общий список:

После этого сверху, на окне Pymakr, нужно щелкнуть по "Connect Device", и из выпадающего списка выбрать свой порт. В окне появиться консоль REPL:

Клик по треугольнику слева загрузит вашу программу на выполнение в REPL.

9) Микропитон на Blackpill - STM32F411CE в 2023 году

Спустя три года давайте посмотрим, что изменилось в микропитоне для Blackpill на чипе stm32f411ce. Если зайдем на гит компании WeAct, то можно будет увидеть:

последний коммит был сделан два года назад, где они обновили прошивку микропитона до версии 1.14.

Т.о. если мы хотим получить актуальную прошивку, а сейчас это версия 1.20, то нам придется ее собирать из исходников. Я собрал две версии прошивки микропитона для blackpill без SPI-флешки (скачать) и для флешки на 8МБ (скачать).

Размер прошивки ~330кБ при том, что доступная нам флеш-память "всего" 512кБ:

$ ./firmware.elf 
   text    data     bss     dec     hex filename
 331236      36   27616  358888   579e8 ./firmware.elf

Для нас вполне сгодится версия прошивки без поддержки внешней флешки:

$ wget  "https://www.dropbox.com/scl/fi/ipaw4x8qdy95wvh8k5gpv/blackpill-micropyhon_1.20_internal_flash.hex?rlkey=pp1hmw9nw6cot1475kyuvxo7n&dl=1" -O ./micropython_int.hex 

Проверяем контрольную сумму:

$ sha1sum ./micropython_int.hex 
60b8866f227c9cad2ce131c5bc4e318ee857c298  ./micropython_int.hex

Если все совпадает, то подключаем плату по USB к компьютеру, входим в режим загрузчика зажав для этого кнопки "boot" и "reset" на плате, и прошиваем:

$ STM32_Programmer_CLI -c port=usb1 -w ./micropython_int.hex

Перезагружаем Blackpill нажатием на кнопку "reset" и заходим в REPL через последовательный порт. Там нас должно встретить сообщение:

MicroPython v1.20.0-489-ga3862e726 on 2023-09-24; WeAct Studio Core with STM32F411CE
Type "help()" for more information.

Запрашиваем список доступных модулей:

>>> help('modules')
__main__          binascii          io                re
_asyncio          builtins          json              select
_onewire          cmath             machine           socket
_thread           collections       math              stm
array             deflate           micropython       struct
asyncio/__init__  dht               network           sys
asyncio/core      errno             onewire           time
asyncio/event     framebuf          os                uasyncio
asyncio/funcs     gc                platform          uctypes
asyncio/lock      hashlib           pyb
asyncio/stream    heapq             random
Plus any modules on the filesystem
>>>

Напомню, что на микропитоне 1.12 был следующий набор модулей:

>>> help('modules')
__main__          math              uasyncio/funcs    umachine
_onewire          micropython       uasyncio/lock     uos
_thread           network           uasyncio/stream   urandom
_uasyncio         onewire           ubinascii         ure
builtins          pyb               ucollections      uselect
cmath             stm               uctypes           usocket
dht               sys               uerrno            ustruct
framebuf          uarray            uhashlib          utime
gc                uasyncio/__init__ uheapq            utimeq
lcd160cr          uasyncio/core     uio               uzlib
lcd160cr_test     uasyncio/event    ujson
Plus any modules on the filesystem
>>>

Пропали бесполезные модули "lcd160cr" и "lcd160cr", вряд ли у кого были эти дисплеи продаваемые фирменным магазином micropython. Остальные модули лишились префикса "u" в своем названии, модуль "uzlib" подозреваю, что был переименован в deflate (это название алгоритма сжатия используемого в zip).

Смотрим, что у нас нас на флешке:

>>> import os
>>> os.listdir('/')
['flash']
>>> os.listdir('/flash/')
['boot.py', 'main.py', 'pybcdc.inf', 'README.txt']
>>> 

"pybcdd.inf" - это драйвер CDC устройства для Windows, "main.py" пустой, а в "boot.py" находится следующее:

# boot.py -- run on boot to configure USB and filesystem
# Put app code in main.py

import machine
import pyb
#pyb.main('main.py') # main script to run after this one
#pyb.usb_mode('VCP+MSC') # act as a serial and a storage device
#pyb.usb_mode('VCP+HID') # act as a serial device and a mouse
#import network
#network.country('US') # ISO 3166-1 Alpha-2 code, eg US, GB, DE, AU or XX for worldwide
#network.hostname('...') # DHCP/mDNS hostname

В общем ничего такого, о чем бы стоило сожалеть. Делаем "format c:" ?

В документации микропитона сказано, что он поддерживает несколько файловых систем, а именно: "FAT, littlefs v1 и littlefs v2." и приведена табличка реализации их в конкретных железках:

Для STM32 заявлена только FAT, однако прошивка для BlackPill собирается с поддержкой littlefs v2, о чем свидетельствует соответствующий флаг компиляции проекта:

# MicroPython settings
MICROPY_VFS_LFS2 = 1

LittleFS это вроде бы часть проекта MBED_OS, простая файловая система для микроконтроллеров. Почитать о ней можно в статье на хабре: LittleFS – компактная и экономичная файловая система для ARM микроконтроллеров в составе mbed os. Быстрый старт. Я не уверен, что ваш компьютер на Windows или Linux будет монтировать флешку с этой файловой системой, возможно через FUSE драйвер в случае Linux. Я предлагаю не усложнять себе жизнь и воспользоваться FAT. Выполняем форматирование согласно приведенной в документации инструкции:

>>> import os, pyb
>>> os.umount('/flash')
>>> os.VfsFat.mkfs(pyb.Flash(start=0))
>>> s.mount(pyb.Flash(start=0), '/flash')
>>> os.chdir('/flash')
>>> os.listdir('/flash')
[]

Теперь при запросе "pyb.info()", микропитон выдаст размер свободного пространства: "LFS free: 48128 bytes"

Данную цифру можно получить и по другому. В статье про ESP8266: "ссылка", я показывал как можно узнать место на флешке используя универсальный модуль "os":

>>> import os
>>> fs_stat = os.statvfs('/flash/')
>>> fs_size = fs_stat[0] * fs_stat[2]
>>> fs_free = fs_stat[0] * fs_stat[3]
>>> print("File System Size {:,} - Free Space {:,}".format(fs_size, fs_free))
File System Size 48,128 - Free Space 47,616

В той же статье я упоминал бенчмарк - тест производительности микроконтроллера. Можно попробовать запустить его на Blackpill. Для этого скачиваем программу бенчмарка:

$ wget https://raw.githubusercontent.com/shaoziyang/micropython_benchmarks/master/1.9.4-479/benchmark.py -O main.py

Чтобы не возиться с капризным Pymakr будем загружать файлы на микроконтроллер с помощью консольной утилиты ampy (можно просто смонтировать флешку микопитона и закинуть туда файлы). Поставить ее можно через pip:

$ pip install --user adafruit-ampy

Загружаем бенчмарк:

$ ampy -p /dev/ttyACM0 put ./main.py

После чего перезагружаем микроконтроллер и подключаемся терминальной программой. Результат который выдал у меня тест :

Test result:
  Integer addition test result:  5.94 s
  Float addition test result:  13.76 s
  Integer multiplication test result:  6.13 s
  Float multiplication test result:  13.83 s
  Integer division test result:  6.15 s
  Float division test result:  14.06 s
  1000 digit Pi calculation result:  1.15 s
  5000 digit Pi calculation result:  17.84 s

В принципе, цифры совпадают с табличными для STM32F411:

Если сравнивать вычисление числа "Пи", то Blackpill получается, что в два раза быстрее чем ESP8266. А в остальном они в принципе одинаковы.

10) Прикручиваем I2C дисплей SSD1306 128x32

На мой взгляд, сфера применения микропитона - это платы типа ESP8266/ESP32, у которых имеется сетевой интерфейс, где есть вместительная флешка и требуется писать веб-приложения.

Однако при некоторой доработке, от микропитона можно извлечь пользу и на платах STM32, превратив их в отладочное средство для разнообразной периферии. В частности, уже сейчас с его помощью можно работать с I2C устройствами в диалоговом режиме. Я продемонстрирую это на примере дисплея SSD1306.

Этот дисплей уже бегло рассматривался в статье про работу с монохромными дисплями на stm32f103c8(bluepill): "Дисплей SSD1306". Сказать про него что-то новое очень сложно, в сети имеются тысячи страниц с его описанием.

Чтобы данная глава была хоть сколь-нибудь полезной, я возьму "узкий" SSD1306 дисплей с разрешением 128х32 пикселей на I2C шине. Из-за небольших размеров у него нормально читаются лишь две строки с текстом, что требует шрифта высотой в 16 пикселей. Таких шрифтов немного, чаще всего используется увеличение шрифта 5x7 в два раза по вертикали. Я же в данном примере буду использовать шрифт "Cybercafe" 16x8 пикселей, который рассматривался в вышеупомянутой статье.

Но прежде чем начать что-то делать, хочу обратить внимание на то, что в прошивку микропитона для ESP8266 уже входит готовый драйвер SSD1306, возможно вам захочется посмотреть на его код. По крайней мере я туда поглядывал, когда писал свой модуль:

https://github.com/micropython/micropython-lib/blob/e025c843b60e93689f0f991d753010bb5bd6a722/micropython/drivers/display/ssd1306/ssd1306.py

STM32F411CE имеет I2C интерфейс на выводах PB6 (тактовый сигнал CLK) и PB7 (передача данных - SDA):

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

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

I2C класс содержат модули "machine". Вики на него доступна по следующей ссылке:

class I2C – a two-wire serial protocol

Но нас будет интересовать класс I2C из модуля "pyb". На него также имеется документация в форме вики:

class I2C – a two-wire serial protocol

После подключения дисплея к микроконтроллеру, подключаем его к компьютеру, входим в диалоговый режим REPL, где последовательно вводим следующие команды:

>>> from pyb import I2C
>>> i2c = I2C(1, I2C.MASTER, baudrate=400000)
>>> i2c.scan()

В ответ на последнюю команду на должно напечатать "[60]". Это I2C адрес нашего дисплея, признак того микроконтроллер видит его. Что бы получить адрес в шестнадцатеричном виде, мы можем ввести команду "print(hex(60))", либо использовать цикл:

>>> ard=i2c.scan()
>>> for i in ard:
...     print(hex(i))

И в ответ мы получим число 0x3C:

Статус готовности дисплея к работе можно также проверить командой

>>> i2c.is_ready(0x3c)

В ответ мы должны получить "True".

Инициализация дисплея SSD1306 состоит из 25 команд. Запишем их в виде массива:

>>> import array
>>> init = array.array('B',[0xAE,0xD5,0x80,0xA8,0x1F,0xD3,0x00,0x00,0x8D,0x14,0x20,0x00,0xA1,0xC8,0xDA,0x02,0x81,0x8F,0xD9,0xF1,0xDB,0x40,0xA4,0xA6,0xAF])

В соответствии с протоколом работы по I2C шине для дисплея SSD1306, при отсылке команды на дисплей, мы должны перед командой посылать ноль. Это можно сделать с помощью команды mem_write(). Введем функцию отправки команды на дисплей:

>>> def send_cmd(value):
...     i2c.mem_write(value,0x3c,0)

Проверить функцию "send_cmd()" можно вызвав ее с параметром 0xae(выключение дисплея):


Если микропитон не выдал ошибку, значит мы на верном пути. Давайте определим функцию инициализации дисплея SSD1306:

>>> def power_on():
...     for it in init:
...         send_cmd(it)

Вызываем функцию и дисплей должен включиться показав характерное "звездное небо"


Аналогично можно ввести функцию для выключения дисплея:

>>> def power_off():
...    send_cmd(0xae)

Для отправки данных нам понадобится соответствующая функция:

>>> def send_data(value):
...     i2c.mem_write(value,0x3c,0x40)

Также понадобится последовательность команд для установки указателя памяти дисплея на начало:

>>> term = array.array('B',[0x22,0,0xff,0x21,0,127])

После этого пишем функцию заливки дисплея:

>>> def fill(value):
...     for it in term:
...         send_cmd(it)
...     for i in range(512):
...         send_data(value)

Для очистки дисплея SSD1306 функцию fill() следует вызвать с нулем в качестве праметра. Любое другое значение заливает дисплей соответствующей маской:


При заливке или очистке дисплея можно заметить, что процесс осуществляется не мгновенно, как было на Bluepill, а занимает некоторое время. Т.е. циклы на микропитоны довольно неторопливые.

Далее, для вывода текстовой информации на дисплей нам понадобится файл шрифтов. Через REPL его вводить будет утомительно, поэтому на гитбахе я нашел проект для микропитона содержащий знаменитый шрифт 5х7:

https://github.com/jeffmer/micropython-ili9341/blob/master/glcdfont.py
альтернативная ссылка

Данный файл следует закинуть на флешку микроконтроллера. Шрифт записан в следующем формате:

_font = \
  b'\x00\x00\x00\x00\x00'\
  b'\x3E\x5B\x4F\x5B\x3E'\
  b'\x3E\x6B\x4F\x6B\x3E'\
  b'\x1C\x3E\x7C\x3E\x1C'\
  ...
  бла-бла-бла
  ...
  b'\x00\x3C\x3C\x3C\x3C'\
  b'\x00\x00\x00\x00\x00'

Данный исходник содержит в том числе и функции для работы с данным шрифтом, поэтому советую туда заглянуть. Итак, подключаем файл "glcdfont.py"

>>> import glcdfont

Далее нам нужно получить указатель на массив "_font". Делается это так:

>>> buffer = memoryview(glcdfont._font)

чтобы получить нужный символ, воспользуемся нехитрой функцией:

def get_ch(ch):
    offset = ch*5
    buf = bytearray(6)
    buf[0] = 0
    buf[1:]=buffer[offset:offset+5]
    return buf, 6

Для проверки результата, введем вспомогательную функцию печати массива:

def print_sym(value):
    a,b=get_ch(value)
    for i in a:
        print(hex(i))

Проверяем:

Отлично, процесс в общих чертах мы освоили. Теперь проделаем все тоже самое, но уже со шрифтом Cybercafe. Т.к. шрифт имеет 16 пикселей по высоте, то каждый шрифт выводится в две строки. При размерности дисплея 32 пикселя по вертикали мы получаем две рабочие строки.

C помочью редактора "sed" я преобразовал шрифт из Си-формата, в питоновский формат. Из шрифта я убрал первые 32 символа ASCII, которые вряд ли кому-то пригодятся. Символы псевдографики 128-255 я тоже удалил, они странные скажем так. Остался привычный набор: латиница + цифры, всего 96 символов.

Действуя по аналогии, импортируем шрифт Cybercafe и берем на него указатель:

>>> import cybercafe_8x16
>>> bf = memoryview(cybercafe_8x16.cybercafe_font_8x16)

Функция получения нужной литеры:

>>> def get_char_cybercafe(ch):
...     offset = ch*16
...     buf = bytearray(16)
...     buf[0:]=bf[offset:offset+16]
...     return buf, 16

функция для проверки работы (отладки) функции get_char_cybercafe():

>>> def print_sym_cybercafe(value):
...     a,b=get_char_cybercafe(value-0x20)
...     for i in range(b):
...         print(hex(a[i]), end = " ")

Проверяем работу последней функции:

Текст будем выводить без использования фрейм-буфера, поэтому нам понадобится функция установки положения указателя памяти (курсора) для дисплея SSD1306:

def set_hw_cursor(x,y):
    send_cmd(0x22)
    send_cmd(y)
    send_cmd(0xff)
    send_cmd(0x21)
    send_cmd(x)
    send_cmd(127)

Печать одного символа будет осуществляться такой функцией:

def cybercafe_send_char(x,y,ch):
    if ch >= 0x20:
        a,b=get_char_cybercafe(ch-0x20)
    else:
        return
    set_hw_cursor(x,y)
    for i in range(8):
        send_data(a[i])
    set_hw_cursor(x,y+1)
    for i in range(8,16):
        send_data(a[i])

Функция в качестве параметра принимает ASCII-код символа, т.е. для вывода символа "С" нужно ввести что-то вроде:

>>> cybercafe_send_char(32,0,0x43)

Дело осталось за малым. Функция печати строки:

>>> def print_str(x,y,str):
    l=len(str)
    print(l)
    for i in range(l):
        j=x+i*8
        cybercafe_send_char(j,y,ord(str[i]))

Здесь функция ord() преобразует символ из типа "char" а тип "int". Выведем на дисплей какой-нибудь текст. Ну например:

>>> fill(0)
>>> print_str(32,0,"MicroPython")
>>> print_str(0,2,"Blackpill WeAct")

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

В принципе осталось упаковать весь написанный код в какую-нибудь библиотеку, чтобы каждый раз не вводить все вручную. У меня библиотека вышла на пару файлов. В файле "cybercafe_16x8.py " содержится шрифт, а в файле "ssd1306_i2c.py" код работы с дисплеем. Библиотека небольшая, она может печатать только строки. Ее размер где-то на восемьдесят строк кода:

Пример использования библиотеки:

>>> import ssd1306_i2c as ssd1306
>>> i2c = ssd1306.SSD1306()
>>> i2c.power_on()
>>> i2c.fill(0)
>>> i2c.print_str(10,0,"MicroPython")
>>> i2c.print_str(0,1,"Blackpill WeAct")

Единственное изменение которое я сделал, в функции "print_str()" номер строки задается числом или 0 или 1, т.к. дисплей фактически двухстрочный. Вышеприведенный код можно поместить в "main.py" и тогда, при включении микроконтроллера, на дисплее сразу будет отображаться заданный текст. Можно попробовать прикрутить RTC, и сделать еще одни часы))

Чтобы напечатать число, неважно, целое или дробное, нужно воспользоваться функцией "str()" для преобразования числа в строку. На скриншоте я привел небольшой пример печати дробных чисел на экране дисплея:

Еще бросается в глаза тот факт, что библиотека занимает 10КБ памяти на флешке, из них 7КБ занимает шрифт. Так что без внешней флешки, пожалуй, обойтись будет трудно.

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

11) Сборка прошивки микропитона с модулем ulab

Модули могут быть не только на микропитоне, но и на Си и даже на ассемблере. Но тут есть некоторые нюансы. Такой модуль собирается вместе с прошивкой, т.е. нужно пересобирать прошивку. Модуль будет занимать место на флеш-памяти микроконтроллера, а не на внешней флешке. А память у Blackpill F411 "всего" 512КБ.

В качестве примера сборки микропитона со сторонним модулем, я бы хотел взять ulab. Это порт модулей numpy и scipy для MicroPython and CircuitPython.Процесс сборки следующий.

Для сборки нужно скачать в некий каталог микропитон с поддержкой WeAct Blackpill платы и модуль ulab. Этот этап простой, я напомню нужные команды, для микропитона:

$ git clone https://github.com/micropython/micropython.git
$ cd micropython/
$ git submodule update --init
$ cd mpy-cross
$ make -j4
$ cd ../ports/stm32/boards
$ git clone https://github.com/WeActTC/WeAct_F411CE.git
$ cd ../../../../

скачивание исходников ulab:

$ git clone https://github.com/v923z/micropython-ulab.git ulab

Нам потребуется поддержка внешней флешки, т.к. прошивка займет практически всю флеш-память STM32F411CE. Поэтому перед сборкой микропитона нужно не забыть в "micropython/ports/stm32/boards/WeAct_F411CE/mpconfigboard.h" найти и поменять строки:

#define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (1)
#define MICROPY_HW_SPIFLASH_SIZE_BITS (32 * 1024 * 1024)

на следующие (если флешка на 8МБ, если флешка на 4МБ, то вторую строку менять не нужно):

#define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0)
#define MICROPY_HW_SPIFLASH_SIZE_BITS (64 * 1024 * 1024)

Осталось собрать прошивку, но так просто это сделать не получится.

На домашней странице проекта "ulab" приведены инструкции для сборки прошивки микропитона с модулем. Для этого переходим в каталог "micropython/ports/stm32/" и запускаем компиляцию командой:

$ make BOARD=WeAct_F411CE USER_C_MODULES=../../../ulab all

Cборка в принципе осуществляется успешно, но на этапе линковки вылетает ошибка:

LINK build-WeAct_F411CE/firmware.elf
/usr/local/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/ld: build-WeAct_F411CE/firmware.elf section `.text' will not fit in region `FLASH_TEXT'
/usr/local/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/ld: region `FLASH_TEXT' overflowed by 28744 bytes
collect2: error: ld returned 1 exit status
make: *** [Makefile:659: build-WeAct_F411CE/firmware.elf] Ошибка 1

Прошивка не влазит в регион "`FLASH_TEXT". Проведенное небольшое расследование показало, что проблема можно решить, если расширить регион FLASH_TEXT за счет региона FLASH_FS, а для файловой системы использовать внешнюю флешку. Если открыть скрипт компоновщика "boards/stm32f411.ld" то увидим:

MEMORY
{   
    FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 512K /* entire flash */
    FLASH_ISR (rx)  : ORIGIN = 0x08000000, LENGTH = 16K /* sector 0 */
    FLASH_FS (rx)   : ORIGIN = 0x08004000, LENGTH = 64K /* sectors 1,2,3,4: 16k+16k+16k+16k(of 64k)=64k */
    FLASH_TEXT (rx) : ORIGIN = 0x08020000, LENGTH = 384K /* sectors 5,6,7 are 128K */
    RAM (xrw)       : ORIGIN = 0x20000000, LENGTH = 112K
    FS_CACHE (xrw)  : ORIGIN = 0x2001c000, LENGTH = 16K
}

Достаточно будет исправить эту таблицу следующим образом:

MEMORY
{   
    FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 512K /* entire flash */
    FLASH_ISR (rx)  : ORIGIN = 0x08000000, LENGTH = 16K /* sector 0 */
    FLASH_FS (rx)   : ORIGIN = 0x08004000, LENGTH = 16K /* sectors 1 =16k */
    FLASH_TEXT (rx) : ORIGIN = 0x0800C000, LENGTH = 464K /* sectors 3,4,5,6,7 are 16K + 64K + 128K + 128K + 128K =464K */
    RAM (xrw)       : ORIGIN = 0x20000000, LENGTH = 112K
    FS_CACHE (xrw)  : ORIGIN = 0x20008000, LENGTH = 16K /* sector 2 =16K */
}

Здесь практически ничего не остается под файловую систему, поэтому внешняя флешка явлется необходимой.

После внесения изменений делаем:

$ make clean

После чего снова пытаемся собрать прошивку:

$ make BOARD=WeAct_F411CE USER_C_MODULES=../../../ulab all

По окончании сборки прошивки нам выдаст:

LINK build-WeAct_F411CE/firmware.elf
   text    data     bss     dec     hex filename
 436628      76   31680  468384   725a0 build-WeAct_F411CE/firmware.elf
GEN build-WeAct_F411CE/firmware0.bin
GEN build-WeAct_F411CE/firmware1.bin
GEN build-WeAct_F411CE/firmware.dfu
GEN build-WeAct_F411CE/firmware.hex

Прошивка вышла на 436704 байт, т.е. мы забили флеш-память микроконтроллера практически под завязку. Прошиваем микроконтроллера прошивкой "build-WeAct_F411CE/firmware.hex", заходим терминалом на микроконтроллер и видим такое приветствие:

MicroPython v1.22.0-preview.12.g516385c4c on 2023-10-14; WeAct Studio Core with STM32F411CE
Type "help()" for more information.

Если теперь вывести информацию по модулям, то получим:

>>> help('modules')
__main__          binascii          io                re
_asyncio          builtins          json              select
_onewire          cmath             machine           socket
_thread           collections       math              stm
array             deflate           micropython       struct
asyncio/__init__  dht               network           sys
asyncio/core      errno             onewire           time
asyncio/event     framebuf          os                uasyncio
asyncio/funcs     gc                platform          uctypes
asyncio/lock      hashlib           pyb               ulab
asyncio/stream    heapq             random

Как можно видеть, добавился модуль "ulab". Запрашиваем свободное пространство:

>>> import os
>>> fs_stat = os.statvfs('/flash/')
>>> fs_size = fs_stat[0] * fs_stat[2]
>>> fs_free = fs_stat[0] * fs_stat[3]
>>> print("File System Size {:,} - Free Space {:,}".format(fs_size, fs_free))
File System Size 8,368,128 - Free Space 8,335,360

Восемь мегабайт доступно, что в общем неплохо.

Документация по модулю ulab лежит здесь https://micropython-ulab.readthedocs.io/en/latest/

Давайте попробуем выполнить пример из документации:

from ulab import numpy as np
from ulab import scipy as spy


x = np.array([1, 2, 3])
spy.special.erf(x)

на что получим ответ:

array([0.8427008, 0.9953223, 0.9999779], dtype=float32)

Чтобы получить длинный список доступных функций модуля numpy, следует вести команду:

>>> help(np)

Аналогичным образом можно собрать какие-то свои модули. Но главный затык в том, что памяти в 512КБ нам едва хватает. В конечном счете это сведется к формуле: "что бы добавить в прошивку что-то нужное, нужно выкинуть их нее что-то ненужное".

Если кого-то интересует данная прошивка (повторяю, что она для платы с внешней флешкой на 8МБ), то ее можно скачать с ЯндексДиска или c DropBox.

контрольная сумма:

$ sha1sum ./stm32f411ce_micropython_ulab.hex 
277de60bee6185de1eecffd5ae6c1736af01013c  ./stm32f411ce_micropython_ulab.hex

Для ESP8266 модуль ulab собрать не получилось, но если кого-то интересует, готовую прошивку с данным модулем и микропитоном 1.12 можно скачать здесь:
https://gitlab.com/rcolistete/micropython-samples/-/tree/master/ESP8266/Firmware/v1.12_with_ulab/ulab_v0.54.0

Альтернативные ссылки с: "Яндекс Диска", "DropBox"

Контрольная сумма:

$ sha1sum /tmp/esp8266_ulab_v1.12-658-g3bab891e5_2020-07-26.bin 
c0c5b74eeb09a3acf9bba74589d09082b75d69c9  /tmp/esp8266_ulab_v1.12-658-g3bab891e5_2020-07-26.bin

Также данный модуль у меня успешно собрался для Linux-версии микропитона:

>>> help('modules')
__main__          btree             io                select
_asyncio          builtins          json              socket
_thread           cmath             machine           ssl
argparse          collections       math              struct
array             cryptolib         micropython       sys
asyncio/__init__  deflate           mip/__init__      termios
asyncio/core      errno             mip/__main__      time
asyncio/event     ffi               os                uasyncio
asyncio/funcs     framebuf          platform          uctypes
asyncio/lock      gc                random            ulab
asyncio/stream    hashlib           re                websocket
binascii          heapq             requests/__init__
Plus any modules on the filesystem

12) Асинхронное программирование на микропитоне: сопрогрммы(coroutines)

Мне хотелось бы совсем немного коснуться темы асинхронного программирования в микропитоне. По этой причине я буду больше болтать, и мало кодить.

Итак, асинхронное программирование - это когда несколько функций работают работают как-бы параллельно. "Как-бы" здесь означает, что параллельность не настоящая, а условная. Его еще называют неблокирующим программированием, и оно применяется в программировании веб-приложений на микропитоне, в частности на ESP8266/ESP32. Но сейчас я хочу поговорить о работе с периферией в асинхронном режиме.

Главный вопрос касательно асинхронного программирования, формулируется на наверно тремя латинскими буквами: "WTF?", т.е. для чего все это надо? В самом деле, если в программировании веб-приложений без неблокирующих функций никак не обойтись, то при работе с периферией это кажется бесмысленным усложненим.

Асинхронное программирование нужно для того же, для чего нужна RTOS, т.е. для квалифицированной организации работы микроконтроллера с периферией. Все наверное помнят пример из Arduino: "Blink без функции delay()", по которому можно организовать неблокирующее переключение состояния GPIO, и по аналогии с которым можно организовать работу и с другой периферией. Но это работает лишь до поры до времени. При работе с большим количеством сложной периферии, организация работы главного цикла превращается в адский труд, что толкает на: а) либо написание своего планировщика; б) либо на использовании какой-либо RTOS.

Асинхронное программирование является аналогом RTOS для микропитона.

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

Функции которые могут выполняться в асинхронном режиме, называются сопрограммами, или корутинами (англицизм от coroutines). Такая функция должна содержать вызов асинхронной функции, с которой может работать планировщик микропитона. Асинхронные функции содержатся в модуле "asyncio":

>>> help(asyncio)
object <module 'asyncio' from 'asyncio/__init__.py'> is of type module
  sys -- <module 'sys'>
  Task -- <class 'Task'>
  create_task -- <function create_task at 0x200071c0>
  run -- <function run at 0x20007040>
  __name__ -- asyncio
  current_task -- <function current_task at 0x200071d0>
  __version__ -- (3, 0, 0)
  __getattr__ -- <function __getattr__ at 0x20006d00>
  sleep_ms -- <function sleep_ms at 0x20006f30>
  sleep -- <function sleep at 0x20006f70>
  ticks_add -- <function>
  TaskQueue -- <class 'TaskQueue'>
  ticks_diff -- <function>
  SingletonGenerator -- <class 'SingletonGenerator'>
  _attrs -- {'open_connection': 'stream', 'Event': 'event', 'StreamReader': 'stream', 'StreamWriter': 'stream', 'gather': 'funcs', 'wait_for_ms': 'funcs', 'wait_for': 'funcs', 'Lock': 'lock', 'ThreadSafeFlag': 'event', 'start_server': 'stream'}
  TimeoutError -- <class 'TimeoutError'>
  __file__ -- asyncio/__init__.py
  select -- <module 'select'>
  get_event_loop -- <function get_event_loop at 0x20007350>
  Loop -- <class 'Loop'>
  CancelledError -- <class 'CancelledError'>
  new_event_loop -- <function new_event_loop at 0x200071e0>
  IOQueue -- <class 'IOQueue'>
  __path__ -- .frozen/asyncio
  run_until_complete -- <function run_until_complete at 0x20007020>
  ticks -- <function>
  core -- <module 'asyncio.core' from 'asyncio/core.py'>

Сейчас нас будут интересовать лишь функции: sleep(), create_task() и run().

В официальной документации на модуль "asyncio": asyncio — asynchronous I/O scheduler имеется небольшой пример как можно параллельно помигать двумя светодиодами на pyboard:

На Blackpill светодиод лишь только один, зато теперь есть дисплей SSD1306 (если у вас нет дисплея, замените функции работы с дисплеем на print("бла-бла")). Поэтому будем мигать светодиодом и дисплеем.

Итак. Подключаем нужные модули:

import pyb
import asyncio
import ssd1306_i2c as ssd1306
lcd = ssd1306.SSD1306()

Пишем асинхронную функцию мигания светодиодом:

async def async_blink():
    while(True):
        await asyncio.sleep(1)
        pyb.LED(1).toggle()
        await asyncio.sleep(1)
        pyb.LED(1).toggle()

Здесь используется асинхронный вариант sleep(). Кроме того, повторяющийся код здесь не просто так, об этом ниже.

Асинхронный функция мигания дисплеем SSD1306:

async def async_lcd_blink():
    lcd.power_off()
    lcd.power_on()
    lcd.fill(0)
    while(True):
        await asyncio.sleep(1)
        lcd.fill(0)
        await asyncio.sleep(1)
        lcd.fill(0xff)

Здесь первые три функции выполняют сброс дисплея. Также используется асинхронный вариант sleep().

На этом этапе можно будет запустить эти функции для тестирования:

asyncio.run(async_blink())

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

async def blink_loop():
    asyncio.create_task(async_blink())
    asyncio.create_task(async_lcd_blink())
    await asyncio.sleep(10)

И вот теперь запустим рабочий цикл командой:

asyncio.run(blink_loop())

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

async def async_blink():
    while(True):
        await asyncio.sleep(1)
        pyb.LED(1).toggle()

Как казалось бы логично следовало сделать.

Для тех, кто кто хочет поподробнее познакомится с асинхронным программированием, я привожу пару ссылок на хабр, где можно прокачаться по этой тематике:

  1. Асинхронный python без головной боли (часть 1)
  2. Использование asyncio для создания асинхронных драйверов устройств на MicroPython v.1.12 (перевод)

13) Альтернативная плата WeAct Blackpill на китайском чипе AT32F403ACGU7

Так как статья посвящена плате Blackpill_F411, хочу рассказать про альтернативную плату на китайском чипе AT32F403ACGU7. История этой платы следующая.

Если кто не помнит, то в 2021 году разразился кризис с поставками электронных компонентов, цены на чипы поднялись в разы, и они просто стали исчезать из продажи. В качестве альтернативы, народ стал обращать внимание на изделия китайского чип-прома. Я тоже не стал исключением, и летом 2022-го года, наблюдал на али такую историю. Весной 2022 года, компания WeAct в своем магазине на али начала предлагать плату BlackPill в двух вариантах: на чипах STM32F411CEU6 и AT32F403ACGU7. Последний чип заявлялся как "улучшенный/advanced" вариант STM32. Причем стоил он рублей на сто дешевле.

Выглядит она так:

Как говорится, найдите десять отличий. Летом же, вообще, из магазина WeAct совсем исчезла возможность выбрать BlackPill на чипе STM32, остался лишь вариант на AT32. Сам магазин это объяснил тем, что цена на STM32F411 стала неоправдано большой. Но выглядело это все так, что в лоте одного товара стали продавать другой. Многие люди не разобравшись заказывали данные платы и потом не знали что с ними делать. В конечном счете, в августе, магазин вернул BlackPill на чипе F411 на свое место, а плату на чипе AT32F403ACGU7 выделил в отдельный лот. Вроде бы такая не очень красивая история получилась, но разговор не об этом.

Т.к. я в то время заинтересовался именно чипами Artery, для меня это была возможность купить готовую плату с данным чипом за недорого, и как владелец, я могу определенно ответить на вопрос, является данная плата аналогом Blackpill? Ответ скорее НЕТ. И вот почему.

Чипы действительно похожи по характеристикам. Характеристики периферии у них практически один в один, у китайского чипа ОЗУ поменьше - 96 КБ, зато больше флеш-панять 1024КБ. Частота ядра побольше 240МГц, но в целом, это все тот же старый добрый Соrtex-M4.

Однако, AT32F403A - это STM32F103 с ядром Соrtex-M4, т.е. вся периферия у него совместима с F103, и НЕсовместима с F4хх. Другими словами, микропитон вы на этом чипе не запустите, т.к. микропитона для F103 не существует. Причина этого простая - маленький размер флеш-памяти и ОЗУ у данных чипов. Хотя сам проект был бы скорее всего заинтересован в создании порта на ультра-бюджетную Bluepill. Я тщательно изучил ситуацию, и нашел следующее:

Первый проект у меня собрался, но на AT32F403ACGU7 не запустился. Обсуждение на форуме закончилось ничем.

Ситуация несколько удручающая, т.к. бюджетный AT32F403, обладающий быстрым ядром и флеш-памятью в 1024 КБ мог бы стать неплохой платформой для микропитона. Аналоги платы pyboard на 405-м чипе чипе стоят в разы дороже.

У микропитона имеется HowTo по сборке минимального порта: "Porting MicroPython", и руководствуясь этим HowTo, я собрал минимальный порт микропитона для AT32F403:

Здесь нет USB, нет файловой системы, нет работы с периферией, нет даже чисел с плавающей запятой, в общем только интерпретатор:

Если судить по времени выполнения цикла, то микропитон на чипе AT32F403RC работает раза в полтора быстрее, следовательно, в AT32F403ACGU7 он будет работать раза в два быстрее. Не знаю, насколько принципиально, конечно.

Если ситуация с минимальной версией микропитона понятна, то с полноценной версией все несколько удручающе. В упомянутом топике, один из контрибьюторов микропитона заявил, что после как того вы собрали микропитон под свою платформу, все остальное вы должны писать сами. Пока у меня не получается даже добавить поддержу чисел с плавающей запятой, система виснет при выполнении операции деления. Кроме того, совсем непонятно, зачем вообще делать порт микропитона под AT32F403? Eсть у Artery чипы совместимые c STM32F4xx. Стоят они так же, как и 403-й.

Тем не менее, я считаю, что чип AT32F403 очень удачный, т.к. STM32F103 очень популярный микроконтроллер, и хорошо что сейчас можно перенести проекты c него на более продвинутый AT32F403.

14) Плата WeAct RP2040 с микропитоном и внешней флешкой

В ассортименте компании WeAct имеется еще одна похожая на Blackpill плата - RP2040. И хотя это совсем не STM32, она примерно в одной категории с Blackpill, и было бы кстати произвести их сравнение.

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

Имеем ядро Cortex-M0 c операцией деления, частотой 133 МГц и ОЗУ объемом 264 КБ. Чип RP2040 двухядерный и там еще есть некие PIO блоки, на которых можно организовать собственный аппаратный интерфейс. Сама плата стоит дешевле чем Blackpill на F411, и она продается с уже впаянной флешкой. Так что недостатка флеш-памяти, как на Blackpill, мы испытывать не будем.

То, что используется внешняя флеш-памят означает, что коммерческое применение этому чипу не грозит, в виду того, что прошивку коммерческого устройства попросту "спилят" конкуренты. Данный чип специально делали для простого народа.

Мой экземпляр платы выглядит следующим образом. Вид сверху:

Вид снизу:

Я брал вариант с флешкой на 2 мегабайта, именно такой вариант предлагает оригинальная плата PicoPi, и это разумное решение.

Подключаем плату к компьютеру и в логе dmesg видим следующее:

usb 6-1: new full-speed USB device number 3 using ohci-pci
usb 6-1: New USB device found, idVendor=2e8a, idProduct=0005, bcdDevice= 1.00
usb 6-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 6-1: Product: Board in FS mode
usb 6-1: Manufacturer: MicroPython
usb 6-1: SerialNumber: e66724b79b104d21
cdc_acm 6-1:1.0: ttyACM0: USB ACM device
usbcore: registered new interface driver cdc_acm
cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters

У нас появляется один cdc_acm интерфейс, и больше ничего. Никакой флешки, как было на Blackpill. На самой плате у моем случае мигал синий светодиод, а если открыть терминал, то там работал беспрерывный счетчик:

Какого-то способа сбросить счетчик я не нашел, на Ctrl+D, Ctrl+C терминал никак не реагировал. Ок. Отключаем плату от компьютера и вновь подключаем, предварительно зажав кнопку BOOT на плате. В этот раз плата определяется уже как флешка:

usbcore: registered new interface driver usb-storage
usbcore: registered new interface driver uas
scsi 6:0:0:0: Direct-Access     RPI      RP2              3    PQ: 0 ANSI: 2
sd 6:0:0:0: [sdg] 262144 512-byte logical blocks: (134 MB/128 MiB)
sd 6:0:0:0: [sdg] Write Protect is off
sd 6:0:0:0: [sdg] Mode Sense: 03 00 00 00
sd 6:0:0:0: [sdg] No Caching mode page found
sd 6:0:0:0: [sdg] Assuming drive cache: write through
 sdg: sdg1
sd 6:0:0:0: [sdg] Attached SCSI removable disk

Флешка аж на целых 128МБ. При монтировании флешки, там окажется два файла:

-r--r--r-- 1 flanker users 241 сен  5  2008 INDEX.HTM
-r--r--r-- 1 flanker users  62 сен  5  2008 INFO_UF2.TXT

Странный размер файлов и разрешение только на операции чтения. Потыкаем палочкой?

$ file /run/media/flanker/RPI-RP2/*
/run/media/flanker/RPI-RP2/INDEX.HTM:    HTML document, ASCII text, with no line terminators
/run/media/flanker/RPI-RP2/INFO_UF2.TXT: ASCII text

Читаем:

$ cat /run/media/flanker/RPI-RP2/*
<html><head><meta http-equiv="refresh" content="0;URL='https://raspberrypi.com/device/RP2?version=E0C9125B0D9B'"/></head><body>Redirecting to <a href='https://raspberrypi.com/device/RP2?version=E0C9125B0D9B'>raspberrypi.com</a></body></html>UF2 Bootloader v3.0
Model: Raspberry Pi RP2
Board-ID: RPI-RP2

Одним словом, ничего похожего на прошивку или программу. Поэтому идем на гит компании WeAct: "https://github.com/WeActStudio/WeActStudio.RP2040CoreBoard/tree/main/SDK/micropython/", где скачиваем прошивку с микропитоном под свою флешку:

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

Ок, мы в системе. Давайте осмотримся.

>>> help('modules')
__main__          gc                uasyncio/funcs    uos
_boot             machine           uasyncio/lock     urandom
_onewire          math              uasyncio/stream   ure
_rp2              micropython       ubinascii         uselect
_thread           onewire           ucollections      ustruct
_uasyncio         rp2               uctypes           usys
builtins          uarray            uerrno            utime
cmath             uasyncio/__init__ uhashlib          uzlib
ds18x20           uasyncio/core     uio
framebuf          uasyncio/event    ujson
Plus any modules on the filesystem

Осматриваем встроенную файловую систему:

>>> import uos as os
>>> os.listdir('/')
['main.py']
>>> file = open("main.py","r")
>>> file.read()
'from machine import Pin,PWM\r\nimport utime\r\nLED = PWM(Pin(25))\r\nn = 0\r\n\r\nwhile True:\r\n    LED.duty_u16(abs(32000-n*1000))\r\n    n = (n+4)%64\r\n    print(n)\r\n    utime.sleep(0.1)'
>>> file.close()

Т.о. содержимое "main.py" выглядит так:

from machine import Pin,PWM
import utime
LED = PWM(Pin(25))
nn = 0
while True:
    LED.duty_u16(abs(32000-n*1000))
    n = (n+4)%64
    print(n)
    utime.sleep(0.1)

Т.е. синий светодиод у нас на 25-м пине, собственно на реверсе платы так и написано. Смотрим, сколько у нас свободного пространства на внутренней флешке:

>>> import uos
>>> fs_stat = uos.statvfs('/')
>>> fs_size = fs_stat[0] * fs_stat[2]
>>> fs_free = fs_stat[0] * fs_stat[3]
>>> print("File System Size {:,} - Free Space {:,}".format(fs_size, fs_free))
File System Size 1,441,792 - Free Space 1,433,600

Почти полтора мегабайта свободно. Для микроконтролера без сетевого интерфейса, это больше чем достаточно. Смотрим сколько свободной ОЗУ:

>>> import gc
>>> gc.mem_free()
181280

181 КБ для бюджетного микроконтроллера, это неплохо. Небольной тест производительности на скорости выполнения пустого цикла:

>>> def tst():
     t1=time.ticks_ms()
     for i in range(600000):
         pass
     t2=time.ticks_ms()
     print(t2-t1)
>>>
>>> tst()
>>> 3130

Три секунды - это примерно на уровне Blackpill с его частотой 96 МГц.

Версия микропитона которую я скачал с гита WeAct была 1.17 от 2 сентября 2021 года. На официальном сайте микропитона можно скачать актуальную версию, т.к. плата официально поддерживается. Прошивки доступны по адресу: "https://micropython.org/download/WEACTSTUDIO/"

Закидываем прошивку на микроконтроллер, и после его перезагрузки, подключаемся к нему через терминальную программу:

MicroPython v1.22.0-preview.70.g1cf3085c5 on 2023-10-31; WeAct Studio RP2040 with RP2040
Type "help()" for more information.

В этот раз никакие счетчики нас встречать не будут, это видимо было ноу-хау парней из WeAct.

Выводим список модулей:

>>> help('modules')
__main__          asyncio/lock      framebuf          platform
_asyncio          asyncio/stream    gc                random
_boot             binascii          hashlib           re
_boot_fat         board             heapq             rp2
_onewire          builtins          io                select
_rp2              cmath             json              struct
_thread           collections       machine           sys
array             cryptolib         math              time
asyncio/__init__  deflate           micropython       uasyncio
asyncio/core      dht               neopixel          uctypes
asyncio/event     ds18x20           onewire
asyncio/funcs     errno             os
Plus any modules on the filesystem

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

>>> import uos
>>> fs_stat = uos.statvfs('/')
>>> fs_size = fs_stat[0] * fs_stat[2]
>>> fs_free = fs_stat[0] * fs_stat[3]
>>> print("File System Size {:,} - Free Space {:,}".format(fs_size, fs_free))
File System Size 15,728,640 - Free Space 15,720,448

свободная динамическая память микропитона:

>>>> gc.mem_free()
218016

В качестве теста попробуем запустить бенчмарк. Скачиваем его:

$ wget https://raw.githubusercontent.com/shaoziyang/micropython_benchmarks/master/1.9.4-479/benchmark.py -O main.py

Загружаем бенчмарк на микроконтроллер:

$ ampy -p /dev/ttyACM0 put ./main.py

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

Если ориентироваться на время вычисления 5000 знаков числа Пи, то результат скорее ближе к Blackpill, там 19 секунд. ESP8266 показывает время в 40 секунд.

На сайте микропитона имеется шпаргалка по работе с платами на чипе RP2040: "Quick reference for the RP2", и в самом начале там приведена команда, с помощью которой можно повысить частоту микроконтроллера до 240 МГц:

>>> import machine
>>> machine.freq()          # get the current frequency of the CPU
>>> machine.freq(240000000) # set the CPU frequency to 240 MHz

А давайте проверим. Выше я приводил функцию подсчета времени выполнения пустого цикла, запустим ее еще раз на новой частоте:

>>> tst()
1266

Результат показал двухкратный рост производительности. Тогда я перезапустил микроконтроллер по "Ctrl+D" (Soft Reset) и бенчмарк после нового выполнения показал следующий результат:

Как можно видеть, производительность работы микроконтроллера увеличилась в два раза, и стала приближаться к PYB10 на stm32f405, который вычисляет 5000 знаков числа Пи за 10.8 секунд. На мой взгляд, за эти деньги, это очень хорошая производительность.

Теперь давайте посмотрим, как обстоят дела с поддержкой I2C протокола. Нам понадобится методичка (всего 52 страницы) по работе с RP2040 на микропитоне: "Raspberry Pi Pico Python SDK: A MicroPython environment for RP2040 microcontrollers" (альтернативная ссылка). В главе 3.6 имеется пример работы с I2C интерфейсом:

Наша проблема заключается в том, что в Blackpill мы использовали реализацию I2C из модуля "pyb", что для плат STM32, здесь же придется использовать реализацию I2C из модуля "machine". Т.е. просто взять код от Blackpill и скопировать, не получится, т.к. там и там используются разные функции для работы с I2C.

I2C интерфейс у RP2040 висит на пинах 8(SDA) и 9(SCL):

Подключаем к этим пинам дисплей SSD1306, заходим в терминальную программу, и вводим:

from machine import Pin, I2C
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=100000)
i2c.scan()

В ответ должны получить "[60]", т.е. 0x3C. Т.к. далее все-равно потребуется логический анализатор, я покажу как выглядит сканирование шины:

Запросы посылаются примерно с интервалом в 1мс. Программа по порядку возрастания перебирает адреса устройств, и на большинство запросов получает NASK:

Однако, на запрос 0х3С наш дисплей, если он подключен к шине, ответит "ASK":

Здесь вроде бы все в порядке. Сейчас нужно узнать, какие функции для работы с I2C имеются в модуле "machine". В документации приведен следующий пример работы с I2C:

from machine import I2C i2c = I2C(freq=400000) # create I2C peripheral at frequency of 400kHz # depending on the port, extra parameters may be required # to select the peripheral and/or pins to use i2c.scan() # scan for peripherals, returning a list of 7-bit addresses i2c.writeto(42, b'123') # write 3 bytes to peripheral with 7-bit address 42 i2c.readfrom(42, 4) # read 4 bytes from peripheral with 7-bit address 42 i2c.readfrom_mem(42, 8, 3) # read 3 bytes from memory of peripheral 42, # starting at memory-address 8 in the peripheral i2c.writeto_mem(42, 2, b'\x10') # write 1 byte to memory of peripheral 42 # starting at address 2 in the peripheral

Функция "writeto" пишет массив данных на определенное устройство, она нам не подходит. А вот функция writeto_mem() может подойти. С ее помощью определяем функцию передачи команды:

def send_cmd(value):
     i2c.writeto_mem(0x3c, 0, chr(value))

Далее пишем код инициализации дисплея:

def power_on():
    for it in init:
        send_cmd(it)

import array
init = array.array('B',[0xAE,0xD5,0x80,0xA8,0x1F,0xD3,0x00,0x00,0x8D,0x14,0x20,0x00,0xA1,0xC8,0xDA,0x02,0x81,0x8F,0xD9,0xF1,0xDB,0x40,0xA4,0xA6,0xAF])        

И вызываем "power_on()":


Но тут нас будет ждать фиаско. Если посмотрим на дисплей то увидим следующее:

Рабочая область дисплея сократилась в два раза. При этом дисплей воспринимает команды 0xAF/0xAE (включение/выключение), в него можно записывать данные обычным способом, делать заливку, очистку и пр. Кстати, скорость работы с дисплеем под RP2040 в несколько раз ниже, чем с Blackpill.

Итак, процедура инициализации проходит некорректно. Семь бед, один ответ - подключаем логический анализатор и смотрим на датаграмму инициализации:


Всего 25 пакетов на CLK линии, как и должно быть, но пакеты не одинаковые по размеру, хотя должны быть таковыми. Если посмотреть на датаграмму повнимательнее, то первое что бросается в глаза, то что при передаче (подчеркнуто красным) некоторых команд, появлется лишний байт:


Т.к. такое происходит только со значениями более 127, я предположил что вина лежит на функции chr(), которая видимо выдает юникод на данные значения. Поэтому было решено написать функцию инициализации традиционно, в столбик из 25-и команд. Вручную такое постоянно делать не интересно, поэтому пришлось запустить VSCode:

В этот раз инициализация работает как следует. Но это костыльное решение никак не спасает, оно лишь подтвердило догадку. Для отправки данных все равно потребуется передавать значения в диапазоне [0-255]. Поэтому пришлось немного погуглить и решение было найдено в тестовых примерах микропитона:
https://github.com/micropython/micropython/blob/master/tests/wipy/i2c.py

Таким образом, корректные функции передачи команд и данных будут выглядеть так:

    def send_cmd(self,value):
        var = bytearray(1)
        var[0]=value
        self.i2c.writeto_mem(I2C_ADR, 0, var)

    def send_data(self,value):
        var = bytearray(1)
        var[0]=value
        self.i2c.writeto_mem(I2C_ADR, 0x40, var)

Остальное - дело техники, я переписал драйвер дисплея SSD1306 под RP2040, пример его использования аналогичен:

import ssd1306_i2c as ssd1306
lcd = ssd1306.SSD1306()
lcd.fill(0)
lcd.fill(0xaa)
lcd.power_off()
lcd.power_on()
lcd.fill(0)
lcd.print_str(10,0,"MicroPython")
lcd.print_str(30,1,"RP2040 WeAct")

Результат выглядит как-то так:

Такая вот плата на микроконтроллере RP2040, на мой взгляд очень интересная. И сравнивая с Blackpill, возможно каждый для себя сделает какие-то свои выводы.

Что касается сборки микропитона для RP2040, то тут все делается аналогично плате Blackpill, для сборки нужен только компилятор:


Актуальные версии драйверов для SSD1306 можно скачать с портала GitLab по ссылке: https://gitlab.com/flank1er/micropython-snippets