Внимание! Статья была отредактирована в ноябре 2023 года (последнее изменение 28 июля 2024г). Были исправленны битые ссылки, логи и скриншоты STM32CubeProgrammer были приведены к версии 2.15. Кроме этого, был добавлен новый раздел "BlackPill в 2023 году, альтернативные платы WeAct", где рассмотренна современная ситуация с прошивками микропитона для BlackPill. Также был добавлен легкий экскурс по программированию на микропитоне на примере написания драйвера дисплея SSD1306 с I2C интерфейсом. Кроме этого было затронуто асинхронное программирование на микропитоне. Также был добавлен обзор альтернативных плат WeAct в форм-факторе Blackpill на чипах: AT32F403ACGU7, RP2040 и STM32WB55. Старую версию статьи можно найти по следующему адресу: "перейти по ссылке".
Изначально статья была посвящена плате Blackpill на чипе STM32F411, и немного была затронута тема микропитона. Немного, потому-что это казалось скорее игрушкой для студентов и не более того. За последние три года произошли какие-то тектонические сдвиги в микроэлектронике, и ситуация поменялась.
Таким образом, я вижу с одной стороны дрифт в сторону от продукции ST, а с другой - микроконтроллеров в сторону SoC. Микропитон же похоже пытается стать Java для микроконтроллеров. Так или иначе байткод-интерпретатор там уже имеется.
Еще один аспект - микропитон выполняет роль интерактивной операционной системы для микроконтроллеров. Там есть дисковая система, командная строка и возможность запускать скрипты с диска. Т.о. микропитон позволяет работать с периферией и аппаратными интерфесами в диалоговом режиме.
Хочу заметить, что вопрос прошивки модулей ESP8266/ESP32 микропитоном рассматривалась в статье про EPS8266: "Установка прошивки MicroPython".
Полезная документация по теме статьи:
Содержание:
I. Обзор платы BlackPill на чипе STM32F411CE
II. Основы работы c MicroPython
III. BlackPill в 2023 году, альтернативные платы WeAct
IV. Микропитон в 2024 году
Примерно в 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 году это все уже не так однозначно/. Кроме того, на плате имеется посадочное место под флешку.
Существенным отличием от 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------------------------------------------------------------------- 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) загрузчика:
- 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
- Method 2: When the power is off, hold down the BOOT0 key, and release the BOOT0 at 0.5s after the power is on
- 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
- Serial Port Mode: Connect PA9 and PA10 of core board with USB serial port
- 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/ и перезагружаем правила командой из под рута:
# udevadm 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" будет стоять одно число, то будет очищен соответствующий сектор:
Компания 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 килобайта.
На 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(<n>) 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();
Установка SPI флешки позволит преодолеть досадное ограничение в 45 килобайт под вашу программу и дополнительные модули. Здесь: https://github.com/mcauser/WEACT_F411CEU6 утверждается, что платы успешно тестировались с флешками:
Замечу, что официальной поддержки 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 МБ. Ее даже можно открыть в файловом браузере:
Совсем не обязательно пользоваться готовыми прошивками микропитона, их можно собирать самому. Это может понадобиться, если вы решите поставить флешку на 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" появится размер нашей флешки.
Прежде всего, как я уже говорил ранее (возможно кто-то читает не все подряд с начала до конца), что бы управлять периферией из 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г.).
Примечание из 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.
Если присмотреться, то увидим, что появились версии прошивок для плат с кварцем 8 МГц. Сейчас в магазине WeAct на али продаются три ревизии плат: 1) без флешки с кварцем на 8 МГц; 2) без флешки с кварцем на 25 МГц; 3) с флешкой на 8 МБ и кварцем 25 МГц. Я подождал, пока выйдет версия микропитона 1.23 и собрал прошивки для плат с кварцем 25МГц:
1) с внутренной флешкой ссылка
2) c флешкой на 8 МБ ссылка
Changelog на релиз можно посмотреть на гите проекта - ссылка
Спустя три года давайте посмотрим, что изменилось в микропитоне для 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. А в остальном они в принципе одинаковы.
На мой взгляд, сфера применения микропитона - это платы типа 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.pySTM32F411CE имеет 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КБ занимает шрифт. Так что без внешней флешки, пожалуй, обойтись будет трудно.
В принципе библиотеку можно развивать. Например можно добавить функцию очистки (заливки) отдельной строки дисплея, можно добавлять шрифты и попытаться сделать ее более универсальной, добавив для этого поддержку других типов дисплея.
Модули могут быть не только на микропитоне, но и на Си и даже на ассемблере. Но тут есть некоторые нюансы. Такой модуль собирается вместе с прошивкой, т.е. нужно пересобирать прошивку. Модуль будет занимать место на флеш-памяти микроконтроллера, а не на внешней флешке. А память у 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
Мне хотелось бы совсем немного коснуться темы асинхронного программирования в микропитоне. По этой причине я буду больше болтать, и мало кодить.
Итак, асинхронное программирование - это когда несколько функций работают работают как-бы параллельно. "Как-бы" здесь означает, что параллельность не настоящая, а условная. Его еще называют неблокирующим программированием, и оно применяется в программировании веб-приложений на микропитоне, в частности на 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()
Как казалось бы логично следовало сделать.
Для тех, кто кто хочет поподробнее познакомится с асинхронным программированием, я привожу пару ссылок на хабр, где можно прокачаться по этой тематике:
Так как статья посвящена плате 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, нет файловой системы, нет работы с периферией, нет даже чисел с плавающей запятой, в общем только интерпретатор:
MicroPython v1.20.0-497-ge86a5bfee-dirty on 2023-10-27; minimal with artery_403rc Type "help()" for more information. >>> help() Welcome to MicroPython! For online docs please visit http://docs.micropython.org/ 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, exit or do a soft reset CTRL-E -- on a blank line, enter paste mode For further help on a specific object, type help(obj) >>> import gc >>> gc.mem_free() 79552 >>> gc.mem_alloc() 736 >>> import sys >>> sys.implementation (name='micropython', version=(1, 20, 0), _machine='minimal with artery_403rc') >>> for i in range(5): ... print("Hello") ... Hello Hello Hello Hello Hello >>> for i in range(600000): ... pass ... 06:30:35.120-> 06:30:37.346-> >06:30:37.347-> >> 06:31:09.974-> pri06:31:09.975-> nt(2+2) 406:31:09.976-> >>> print(2+2) 4 >>> print(2/2) Traceback (most recent call last): File "<stdin>", in <module> TypeError: >>>
Если судить по времени выполнения цикла, то микропитон на чипе AT32F403RC работает раза в полтора быстрее, следовательно, в AT32F403ACGU7 он будет работать раза в два быстрее. Не знаю, насколько принципиально, конечно.
Если ситуация с минимальной версией микропитона понятна, то с полноценной версией все несколько удручающе. В упомянутом топике, один из контрибьюторов микропитона заявил, что после как того вы собрали микропитон под свою платформу, все остальное вы должны писать сами. Пока у меня не получается даже добавить поддержу чисел с плавающей запятой, система виснет при выполнении операции деления. Кроме того, совсем непонятно, зачем вообще делать порт микропитона под AT32F403? Eсть у Artery чипы совместимые c STM32F4xx. Стоят они так же, как и 403-й.
Тем не менее, я считаю, что чип AT32F403 очень удачный, т.к. STM32F103 очень популярный микроконтроллер, и хорошо что сейчас можно перенести проекты c него на более продвинутый AT32F403.
В ассортименте компании 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() Welcome to MicroPython! For online help please visit https://micropython.org/help/. For access to the hardware use the 'machine' module. RP2 specific commands are in the 'rp2' module. Quick overview of some objects: machine.Pin(pin) -- get a pin, eg machine.Pin(0) machine.Pin(pin, m, [p]) -- get a pin and configure it for IO mode m, pull mode p methods: init(..), value([v]), high(), low(), irq(handler) machine.ADC(pin) -- make an analog object from a pin methods: read_u16() machine.PWM(pin) -- make a PWM object from a pin methods: deinit(), freq([f]), duty_u16([d]), duty_ns([d]) machine.I2C(id) -- create an I2C object (id=0,1) methods: readfrom(addr, buf, stop=True), writeto(addr, buf, stop=True) readfrom_mem(addr, memaddr, arg), writeto_mem(addr, memaddr, arg) machine.SPI(id, baudrate=1000000) -- create an SPI object (id=0,1) methods: read(nbytes, write=0x00), write(buf), write_readinto(wr_buf, rd_buf) machine.Timer(freq, callback) -- create a software timer object eg: machine.Timer(freq=1, callback=lambda t:print(t)) Pins are numbered 0-29, and 26-29 have ADC capabilities Pin IO modes are: Pin.IN, Pin.OUT, Pin.ALT Pin pull modes are: Pin.PULL_UP, Pin.PULL_DOWN Useful control commands: 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')
__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
Результат выполнения теста:
Test result: Integer addition test result: 5.56 s Float addition test result: 10.93 s Integer multiplication test result: 5.95 s Float multiplication test result: 10.95 s Integer division test result: 5.91 s Float division test result: 11.57 s 1000 digit Pi calculation result: 1.02 s 5000 digit Pi calculation result: 25.92 s
Если ориентироваться на время вычисления 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) и бенчмарк после нового выполнения показал следующий результат:
Test result: Integer addition test result: 2.90 s Float addition test result: 5.69 s Integer multiplication test result: 3.10 s Float multiplication test result: 5.70 s Integer division test result: 3.08 s Float division test result: 6.02 s 1000 digit Pi calculation result: 0.55 s 5000 digit Pi calculation result: 13.48 s
Как можно видеть, производительность работы микроконтроллера увеличилась в два раза, и стала приближаться к 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
В прошлом году я начал портирование микропитона на микроконтроллеры AT32F403/AT32F403A. После портирования минимальной сборки микропитона я взял паузу, но сейчас у меня появилось время на проект, и я могу уже что-то показать.
1) Я решил проблему с вещественными числами. Теперь в прошивке есть аппаратная поддержка чисел с плавающей запятой одинарной точности и длинных целых чисел. Прошивка больше на зависает на операции деления ;)
2) После того, как я добавил реализацию чисел с плавающей запятой (не все прошивки микропитона ее имеют, минимальная прошивка собирается без них), я принялся добавлять модули, которые не связаны с аппаратной периферией. В частности это модули вроде "math" и "сmath", хотя в необходимости последнего я не уверен, это математические функции для комплексных чисел.
Сейчас размер прошивки немного более 150 килобайт.
3) Далее я собираюсь добавлять модули реализующие управление аппаратными интерфейсами. Я планирую полностью их переработать, т.к. дисплей SSD1306 на микропитоне работал безобразно медленно, и полагаю, что со всем другим дела обстоят похожим образом. Т.к. микропитон кроссплатформенный проект, для работы с периферией он имеет два уровня абстракции. Первый уровень общий для всех, его функции имеют префикс "mp_hal_". Второй уровень абстракции - это HAL библиотека для STM32 микроконтроллеров. Я собираюсь отвязать порт AT32 от библиотеки HAL фирмы ST и переделать все на регистрах. Т.о. порт будет полностью отвязан от ветки STM32.
4) Если предыдущий пункт мне удастся реализовать, я потрачу какое-то время, что-бы перенести наработки на Blackpill на F411.
5) Опять же, если все будет удачно, то я буду смотреть, как можно реализовать файловую систему на внутренней флешке.
6) После этого я бы хотел добавить внешний носитель на SD карте или внешней флешке.
7) В последнюю очередь, я бы хотел добавить композитный USB интерфейс на последовательный порт и флешку, как сделано в blackpill.
Как я уже говорил, мне уже есть что показать. Первый аппаратный модуль который принялся портировать - это модуль "time". Он использует таймер SysTick и использует его прерывание работающее на частоте 1кГц. В обработчике прерывания к счетчику миллисекунд добавляется по единице. Функция ticks_ms(), которую я буду использовать, возвращает количество этих миллисекунд. Еще в данном модуле есть функции задержек типа delay. Задержки в микросекундах организуются очень примерно, на обычном счетчике. Задержки в миллисекундах организуются в зависимости от того, разрешены прерывания или нет. Если они разрешены, то задержка реализуется через счетчик на таймере SysTick и операции WFI. Если прерывания не разрешены, то опять же используется программный счетчик.
Наличие функции time_ms() дает возможность оценить время выполнения той или иной функции, и можно будет на глаз оценить быстродействие чипа. Частота 240 МГц, согласитесь выглядит привлекательно.
Для начала я запустил функцию tst() на простом счетчике:
>>> import time >>> def tst(): t1=time.ticks_ms() for i in range(600000): pass t2=time.ticks_ms() print(t2-t1) >>> >>> tst() >>> 1070
Та же функция на Blackpill выдает значение 2526, что в принципе соответствует отношению частот: 240/96=2.5. 1070*2.5=2675. Raspberry Pico на частоте 133 МГц 3130, но там Cortex M0+, а у нас M4.
Т.к. прошивка "сырая", мне показалось, что будет не лишним проконтролировать корректность времени выполнения функции. Я переподключился к микроконтроллеру через монитор последовательного порта, и включил отображение времени приема и отправки сообщений, после чего вызвал функцию tst() из монитора:
Если смотреть на последние строки, то считаем 2919-1852=1067 мс, что сопоставимо с тем результатом, что выдала функция в микропитоне - 1071 мс.
Мне стало интересно, и я взял с полки плату с чипом stm32f407zet6 и собрал под него микропитон
https://github.com/mcauser/BLACK_F407ZE
Чип stm32f407zet6 работает на частоте 168МГц, это так же Cortex-M4, и результат работы функции на этом чипе дал результат 1472 мс:
Однако, данная функция на счетчике не счетчике не может считаться полноправным тестом. Как можно было убедиться, единственное что она показывает - частоту процессора.
Я уже упоминал бенчмарк для микропитона основанный на вычислении числа Пи:
"https://raw.githubusercontent.com/shaoziyang/micropython_benchmarks/master/1.9.4-479/benchmark.py"
Из за отсутствия файловой системы на на микроконтроллере, я не мог перекинуть его на флешку и запустить на выполнение. Но я мог скопировать функции вычисления числа Пи через консоль, и запустить их. Речь идет о этих функциях:
def pi(places=100): # 3 + 3*(1/24) + 3*(1/24)*(9/80) + 3*(1/24)*(9/80)*(25/168) # The numerators 1, 9, 25, ... are given by (2x + 1) ^ 2 # The denominators 24, 80, 168 are given by (16x^2 -24x + 8) extra = 8 one = 10 ** (places+extra) t, c, n, na, d, da = 3*one, 3*one, 1, 0, 0, 24 while t > 1: n, na, d, da = n+na, na+8, d+da, da+32 t = t * n // d c += t return c // (10 ** extra) def pi_test(n = 5000): t1 = time.ticks_ms() t = pi(n) t2 = time.ticks_ms() r = time.ticks_diff(t2, t1)/1000 print(' Pi', n, 'digit calculation: ', r, 's') return '%.2f'%r
Первая функция вычисляет нужное количество знаков числа Пи, вторая же замеряет время требуемое на работу первой.
У нас есть таблица с результатами выполнения теста для основных типов микроконтроллеров, и тесты которые я запускал для Blackpill, Raspberry Pico и ESP8266/ESP32S2 совпадали с этими значениями:
Теперь я запустил тест на stm32f407zet6 и получил следующее:
Десять с половиной секунд для вычисления 5000 знаков числа Пи, это совпадает с результатом PYBV10 который на STM32F405RG микроконтроллере, также работающем на частоте 168 МГц.
Если же запустить тест на вычисление 1000 знаков на at32f403acgu7, то:
результат в полсекунды неплохой, но вот сообщения от сборщика мусора garbage collection
настораживает. На других микроконтроллеров такого не было. И на результате при подсчете 5000 знаков видно уже заметное торможение:Тринадцать секунд это тоже не плохо, но мы должны были получить результат около восьми секунд, как на ESP32 который работает также на частоте 240 МГц.
Вообще, когда я заказывал at32f403acgu7, я ожидал производительности на уровне H743, который работает на 400МГц. Все-таки у at32f403acgu7 заявлено 256 КБ zero-wait флеш память, и прирост производительности по сравнению с STM32, где флеш-память работает на более низкой частоте нежели ядро.
Я практически уверен, что сборщик мусора виноват в низком результате выполнения теста, и это означает, что с работой данного модуля придется разбираться.
На самом деле, функцию подсчета числа пи можно запустить на обычном питоне. Там тоже есть модуль time, в котором есть функция time(), с помощью которой можно оценить производительность системы.
Как можно видеть, на компьютере, вычисление 5000 знаков числа Пи занимает 50мс, что вряд ли кого-то удивит. Но таким способом мы можем оценить производительность роутерных чипов, на которые можно поставить OpenWrt, и где есть как правило и питон и микропитон.
Я хочу показать, что сфера применения микропитона не ограничивается микроконтроллерами.
На полке у меня нашелся древний роутер F@ST 2804 перепрошитый в OpenWrt/LEDE v19.07.10. Роутер имеет чип BCM6318 с частотой 333 МГц, архитектура MIPS_32 big endian, флешка на 8 МБ и ОЗУ 64 МБ.
Несмотря на то, что роутер считается страшной древностью, с точки зрения микроконтроллеров, это очень достойный чип. Но там установлена полноценная операционная система, т.е. это не "bare metal", и сказать заранее о производительности чипа довольно сложно.
Я установил на роутер микропитон, т.к. он легче чем сам питон, и запустил функцию tst() на счетчике:
Результат 0.75 секунды не сильно отличается от 1.07 секунды на at32f403acgu7. Но вот тест на вычисление 5000 знаков числа Пи, показал неожиданно хорошие результаты:
4.8 секунды - это на уровне H743 c его 4.6 секундами. При том, что H743 - это топовый микроконтроллер, а роутер мне отдали за шоколадку.
Я сейчас не хочу углубляться в тему роутеров, если вы являетесь обладателем платы с чипом AT32F403ACGU7, то чтобы оценить его возможности, вы пока можете воспользоваться готовой прошивкой с микропитоном на основе RT-Thread, о которой речь пойдет ниже.
Кто ищет тот найдет, и поиском по ключевым словам "AT32" и "Micropython" я нашел таки на сайте Artery аппнот: Micropython Based On AT32 RTT (альтернативная ссылка).
Аппнот написан на смеси китайского и английского языков, и из него следует, что если у вас есть желание использовать микропитон на чипах Artery, то следует установить RT-Thread (сокращенно RTT) и там микропитон доступен с виде одного из компонента.
Иначе говоря, у нас имеется порт микропитона для RT-Thread: https://github.com/RT-Thread-packages/micropython и мы можем собрать RTT с микропитонам в качестве опции.
Сразу хочу сказать, что на много рассчитывать не стоит. Во-первых это не микропитон, а rt-thread с микропитоном. Следовательно, быстродействие будет не очень. Там нет USB, нужно подключаться к плате через USB-UART преобразователь, но самое главное, там нет даже внутренной флешки. Т.е. ни одного скрипта вы туда не запишете. Есть только интерпретатор и пока все.
Мне не очень хочется говорить что-то про RT-Thread (на самом деле ничего про него не знаю, столкнулся в первый раз), это RTOS сделанная китайцами для китайцев. В RT-Studio я собрал проект с микропитоном для фирменной платы AT32-403A-START:
На плате AT32-403A-START установлен 100-пиновый чип, у нас же всего 48 ног, но тем не менее это работает. И там и там используется 8 МГц кварц. Готовую прошивку скачать можно скачать по ссылке:
at32f403a_micropython_rt-thread.binПро способы прошивки чипов Artery я писал в "Прошивка и отладка китайского ARM микроконтроллера AT32F403RC", но в случае с AT32F403ACGU7 появилась сложность, что DAP-Link с ним очень глючно работает, хотя с AT32F403RC все всегда гладко. Поэтому, если в двух словах, потребуется китайский ST-LINK и pyocd. Если последний еще не установлен, то быстренько ставим:
$ python3 -m pip install -U pyocd
В каталог с прошивкой кидаем файл "pyocd.yaml" следующего содержания:
pack: - ./Keil.AT32F4xx_DFP.1.3.2.pack
Далее скачиваем и кидаем в этот же каталог пак с поддержкой чипов AT32 ссылка.
На плате AT32F403ACGU7 сбоку выведено 5 контактов для отладчика. Крайний, R-контакт не трогайте, все работает и без него. К остальным подключайте ST-LINK.
На UART1, это пины PA9 (TX) и PA10 (RX), подключите UART преобразователь, на забудьте соединить землю UART преобразователя и платы.
Теперь подключаем все это дело к компьютеру, и прошиваем командой:
$ pyocd load --target at32f403acgu7 ./at32f403a_micropython_rt-thread.bin
Загрузка занимает где-то секунд двадцать:
У меня прошивка уже была залита в микроконтроллер и поэтому в логе вывело erased 0 sectors, programmed 0 pages. У вас же все должно быть по честному. После заливки прошивки, микроконтролер перезагрузиться и начнет мигать синий светодиод на плате, я туда пошил мигалку для PC13, на котором висит светодиод на плате WeAct.
Открываем порт UART преобразователя командой:
$ screen /dev/ttyUSB0 115200
После жмем ресет на плате и попадаем в консоль RT-Thread FinSH
\ | /
- RT - Thread Operating System
/ | \ 4.1.0 build Jun 11 2024 11:33:07
2006 - 2022 Copyright by RT-Thread team
[I/sal.skt] Socket Abstraction Layer initialize success.
msh />
Здесь можно ввести команду help, чтобы вывести длинный список доступных команд:
msh /> help
RT-Thread shell commands:
ifconfig - list the information of all network interfaces
ping - ping network host
dns - list and set the information of dns
netstat - list the information of TCP / IP
clear - clear the terminal screen
version - show RT-Thread version information
list_thread - list thread
list_sem - list semaphore in system
list_event - list event in system
list_mutex - list mutex in system
list_mailbox - list mail box in system
list_msgqueue - list message queue in system
list_memheap - list memory heap in system
list_mempool - list memory pool in system
list_timer - list timer in system
list_device - list device in system
help - RT-Thread shell help.
ps - List threads in the system.
free - Show the memory usage in the system.
ls - List information about the FILEs.
cp - Copy SOURCE to DEST.
mv - Rename SOURCE to DEST.
cat - Concatenate FILE(s)
rm - Remove(unlink) the FILE(s).
cd - Change the shell working directory.
pwd - Print the name of the current working directory.
mkdir - Create the DIRECTORY.
mkfs - format disk with file system
mount - mount <device> <mountpoint> <fstype>
umount - Unmount device from file system
df - disk free
echo - echo string to file
tail - print the last N - lines data of the given file
date - get date and time or set (local timezone) [year month day hour min sec]
adc - adc function
pwm_enable - pwm_enable <pwm_dev> <channel/-channel>
pwm_disable - pwm_disable <pwm_dev> <channel/-channel>
pwm_set - pwm_set <pwm_dev> <channel> <period> <pulse>
pwm_get - pwm_get <pwm_dev> <channel>
list_fd - list file descriptor
python - MicroPython: `python [file.py]` execute python script
Нас буде интересовать последняя команда. Вводим слово "python" и попадаем в консоль микропитона:
msh /> python
The stack (tshell) size for executing MicroPython must be >= 8192
MicroPython v1.13-148-ged7ddd4 on 2020-11-03; Universal python platform with RT-Thread
Type "help()" for more information.
>>>
И вот, мы запустили микропитон на китайском AT32F403A:
>>> help('modules') __main__ micropython uhashlib uselect builtins pyb uheapq userfunc cmath rtthread uio ustruct frozentest ubinascii ujson usys gc ucollections uos utime machine uctypes urandom utimeq math uerrno ure uzlib Plus any modules on the filesystem >>> gc.mem_free() Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name not defined >>> import gc >>> gc.mem_free() 7120 >>> gc.mem_alloc() 928 >>> import sys >>> sys.implementation (name='micropython', version=(1, 13, 0), mpy=517) >>> import pyb >>> pyb.info() --------------------------------------------- RT-Thread --------------------------------------------- --------------------------------------------- qstr: n_pool=0 n_qstr=0 n_str_data_bytes=0 n_total_bytes=0 --------------------------------------------- GC: 7936 total 1280 : 6656 1=44 2=14 m=8 >>> import os >>> os.listdir('/') [] >>> fs_stat = os.statvfs('/') Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: no such attribute >>> import machine >>> machine.reset() Not implement on ../packages/micropython-v1.13.0/port/modules/machine/modmachine.c:118, Please add for your board! >>>
Здесь функции gc.mem_free() и gc.mem_alloc() выводят довольно мало доступной памяти. Это полагаю объясняется тем, что при сборке прошивки под микропитон было выделено 8КБ динамической памяти, а не вся память, как это было бы при прошивке чистым микропитоном.
py.info() выводит удручающее мало информации. machine.reset() не реализована, как полагаю и функции работы с внутренной флешкой.
Я попробовал запустить тест на счетчике:
>>> import time >>> def tst(): t1=time.ticks_ms() for i in range(600000): pass t2=time.ticks_ms() print(t2-t1) >>> >>> tst() >>> 3214
И цифра в 3214 показывает результат на уровне STM32F411 с 96 МГц и Raspberry Pico c 133 МГц, что для AT32F403A с частотой 240 МГц конечно же мало.
В принципе, это все, что пока можно сделать, т.к. пока я не придумал как сделать внутреннюю флешку. RT-Thread интересная штука, мне понравилась, там много чего есть, например готовые файловые системы. Но чтобы их использовать, нужно иметь блочное устройство, которое для каждого контроллера нужно писать индивидуально (это только мои догадки). В общем, изучение этого вопроса потребует времени.
Не так давно, где-то в мае 2024г, а ассортименте магазина WeAct на али, появилась очередная плата в форм-факторе Bluepill на чипе STM32WB55CGU6 c беспроводными интерфейсами BLE 5.x, ZigBee, Thread и Mutter:
"Живая" плата WeAct STM32WB55CGU6 выглядит следующим образом. Вид сверху:
Обратите внимание, что количество пинов меньше, чем у blackpill. На плате имеется две кнопки: RST и BOOT, один светодиод на PE4, USB разъем, и разъем под SWD интерфейс. Причем последний расположен посередине платы, т.к. на правом краю платы размещена PCB-антенна. Вид снизу:
Видимо для пользовательской кнопки и SPI-флешки места на плате не нашлось.
Несмотря на то, что сама плата является новинкой, чип STM32WB55CGU6 таковым не является. Ему где-то лет пять, и ему на смену уже выпущена серия STM32WBA с 100 Мгц ядром Cortex-M33, который является условно говоря преемником Cortex M4, и который специально спроектирован с учетом применения в сфере IoT. Но вот доступная печатная плата с STM32WB55CGU6 появилась только сейчас. До этого был доступен только фирменный комплект P-NUCLEO-WB55, которым все и пользовались.
В данной главе я хочу поделиться опытом установки на данную плату микропитона, bluetooth-стека и запуска демонстрационного bluetooth приложения для проверки работоспособности BLE, т.к. на странице магазина WeAct ничего не сказано о поддержке платы WeAct STM32WB55CGU6 микропитоном.
STM32WBх5 - это серия двухядерных чипов, где одно ядро Cortex-M4 - доступно вам для программирования, а второе на Cortex-M0 работает с радиочастью. Прошивки на второе ядро заливаются в виде blob'ов, т.е. бинарных шифрованных файлов. Чип имеет 1024 КБ флеш-памяти и 256 КБ ОЗУ, которые объеденны в одно адресное пространство и разбиты по регионам для каждого ядра. имеется также 'shared" область в ОЗУ для обменна данными между ядрами.
В настоящее время, из всего этого списка, для прошивки bluetooth стека в защищенную область флеш-памяти нам понадобится пункт 4 - "AN5185 - How to use STMicroelectronics firmware upgrade services for STM32WB MCUs", где firmware upgrade service везде в сети обозначается как FUS.
Еще хочется упомянуть пошаговое руководство на французском языке, которое заменило мне чтение всего вышеупомянутого, и благодаря которому я таки сумел заставить работать bluetooth на микропитоне: "Reprogrammer la pile BLE d’un SoC STM32WB sur une carte NUCLEO-WB avec STM32CubeProgrammer". Все руководство читать не обязательно, достаточно будет последнего абзаца:
SET PROG="C:\Program Files\STMicroelectronics\STM32Cube\STM32CubeProgrammer\bin\STM32_Programmer_CLI.exe" %PROG% -c port=swd -startfus %PROG% -c port=swd -fwdelete %PROG% -c port=swd -fwupgrade "C:\stm32wb5x_FUS_fw_for_fus_0_5_3.bin" 0x080EC000 firstinstall=0 %PROG% -c port=swd -fwupgrade "C:\stm32wb5x_BLE_HCILayer_fw.bin" 0x080E0000 firstinstall=1 %PROG% -c port=swd -startwirelessstack %PROG% -c port=swd -e all %PROG% -c port=swd mode=UR -d "c:\fw_upy_nucl_wb55_1.19.1.hex" 0x08000000 -v
Собственно этим мы и будем руководствоваться.
Итак, FUS - это сервис обновления прошивки радиочасти, что работает на ядре Cortex-M0. Повторяю, что все прошивки радиочасти шифрованные, и поэтому для перепрошивки радиочасти требуется запускать FUS, который проверит контрольные суммы прошивки радочасти, цифровую подпись ST, затем он дешифрует данный blob, и запишет в защищенную от считывания область флеш-памяти. Поэтому, чтобы вы не собирались делать с микроконтроллером, хотя бы один раз, следует прошить радиочасть с помощью FUS, и только потом вы можете делать все остальное!
Если посмотреть на скрипт компоновщика проекта микропитон для STM32WB55, то распределение памяти будет следующее:
/* Specify the memory areas */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K /* sectors 0-127 */ FLASH_APP (rx) : ORIGIN = 0x08008000, LENGTH = 480K /* sectors 8-127 */ FLASH_FS (r) : ORIGIN = 0x08080000, LENGTH = 256K /* sectors 128-191 */ RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K /* SRAM1 */ RAM2A (xrw) : ORIGIN = 0x20030000, LENGTH = 10K /* SRAM2A */ RAM2B (xrw) : ORIGIN = 0x20038000, LENGTH = 10K /* SRAM2B */ }
Здесь половина флеш-памяти 512 КБ, отведена на прошивку (стоковая прошивка весит 390 КБ), 256 КБ отведено под внутреннюю флешку, остальные 256 КБ якобы никак не используются. На самом деле, коследние сектора отведены под прошивку радиочасти и FUS. В настоящий момент я еще только изучаю документацию, и для мегабайтных чипов флеш-память расположена по адресам 0х08000000 - 0х080FFFFF. Т.е адрес 0х080FFFFF является крайним. Прошивка FUS начинается с адреса 0х080EC000. Прошивка радочасти, в зависимости от профиля может располагаться по разным адресам. мы будем использовать "stm32wb5x_BLE_HCILayer_fw.bin", которая шьется по адресу 080E0000. Т.е. теоретически, мы можем использовать оставшиеся 1024К-128К= 896 КБ флеш-памяти.
Что касается ОЗУ, то тут все попроще. Нам доступно 192 КБ, остальная память отведена под радиочасть. Еще имеется такое понятие как Shared RAM - это область ОЗУ выделенное для обменна данными между ядрами.
Итак, с теорией разобрались, осталось предупредить, что для работы потребуется ST-Link v2, а к самой плате нужно будет припаять коннектор под SWD разъем, т.к. при работе через USB, CubeProgrammer постоянно вылетал с ошибкой сегментации. Я пробовал использовать Windows версию, но там просто все висло. Поэтому все действия будут осуществляться через ST-Link.
Еще интересный момент, сейчас везде пишут, что stm32wb55 поддерживает BLE 5.4. На самом деле, на момент разработки чипа спецификация BLE 5.4 еще не была выпущена, и если посмотреть на официальные обучающие видео ST, то там заявлено BLE 5.2
Если верить информации на сайте www.bluetooth.com, спецификация 5.4 была опубликована 31 января 2023 года. Так что, версия BLE вашего чипа может зависеть от его ревизии.
Итак, при первом подключении платы к компьютеру по USB, в dmesg я получил сообщение о подключении CDC устройства:
И когда я запустил монитор последовательного порта, там был посекундный счетчик:
Еще мигал синий светодиод на плате, и это как бы все. Впрочем, для проверки работоспособности платы этого достаточно, но я бы хотел убедиться в работоспособности радиочасти.
<Мне было неохота припаивать SWD разъем, поэтому я по аналогии с blackpill, я зажал кнопки boot и rst и в dmesg появился лог DFU загрузчика:
usb 6-1: new full-speed USB device number 4 using ohci-pci usb 6-1: New USB device found, idVendor=0483, idProduct=df11, bcdDevice= 2.00 usb 6-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 6-1: Product: DFU in FS Mode usb 6-1: Manufacturer: STMicroelectronics usb 6-1: SerialNumber: 206630804242
Запускаем STM32CubeProgrammer, выбираем USB порт, и подключаемся к чипу:
Или через консольную версию:
$ ~/mydev/tools/STM32CubeProgrammer/bin/STM32_Programmer_CLI -c port=usb1 ------------------------------------------------------------------- STM32CubeProgrammer v2.15.0 ------------------------------------------------------------------- USB speed : Full Speed (12MBit/s) Manuf. ID : STMicroelectronics Product ID : DFU in FS Mode SN : 206630804242 DFU protocol: 1.1 Board : -- Device ID : 0x0495 Device name : STM32WB5x/35xx Flash size : 1 MBytes Device type : MCU Revision ID : -- Device CPU : Cortex-M4
к сожалению, когда я стал работать c FUS, STM32CubeProgrammer стал у меня вылетать с ошибкой сегментации. Поэтому пришлось припаять гребенку на место разъема SWD, и все остальные действия я выполнял через ST-Link.
1. Во-первых, после подключения к чипу, в крайней левой колонке появится значок "wireless", щелкнув по которому, мы попадем в "Firmware Upgrade Services", т.е. FUS:
В секции "WB commands" все четыре кнопки команд должны быть активны. Когда я подключался через USB DFU, активна была только верхняя кнопка "Start Wireless Stack". Следует щелкнуть по кнопке "Start FUS". Может появиться сообщение об ошибке, ничего страшного, закройте сообщение с щелкните еще раз, пока не появиться об успешном выполнении команды.
После этого следует считать статус FUS с помощью кнопки "Read FUS in..", в результате должны заполниться "серые" поля "FUS version", "FUS status" и т.д.
На этом этапе можно отключится от чипа, и закрыть STM32CubeProgrammer. Далее будем использовать консольную версию. И сейчас нам понадобятся блобы BLE стека и последней версии FUS. Они имеются в пакете STM32CubeWB Скачать их можно c сайта ST либо с гитхаба (альтернативная ссылка).
Интересующие нас блобы лежат в каталоге "Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x":
Здесь нас будут интересовать "stm32wb5x_FUS_fw.bin" - FUS, "stm32wb5x_BLE_HCILayer_fw.bin" - легковесная прошивка BLE стека, "Release_Notes.html" - документ, где прописаны адреса загрузки блобов (ну прямо как для ESP8266). Открываем последний документ, ищем свой чип и адреса для нужных нам блобов:
Я документацию конечно не читал, но по названиям блобов можно сделать вывод, что какой стек в микроконтроллер прошить, такой он и будет протокол поддерживать. Т.е. не обязательно прошивать все протоколы, если тебе нужно что-то одно. Удобно.
Ок, теперь у нас есть все необходимое, подключаем плату через ST-Link к компьютеру и работаем.
Читаем версию FUS микроконтроллера. Какая-нибудь обязательно должна быть, иначе ничего сделать не получится.
$ ~/mydev/tools/STM32CubeProgrammer/bin/STM32_Programmer_CLI -c port=swd mode=HOTPLUG -r32 0x20030030 4 ------------------------------------------------------------------- STM32CubeProgrammer v2.15.0 ------------------------------------------------------------------- ST-LINK SN : 1200220015000059334D524E ST-LINK FW : V2J29S7 Board : -- Voltage : 3,28V SWD freq : 4000 KHz Connect mode: Hot Plug Reset mode : Software reset Device ID : 0x495 Revision ID : Rev Y Device name : STM32WB5x/35xx Flash size : 1 MBytes Device type : MCU Device CPU : Cortex-M4 BL Version : 0xD5 Debug in Low Power mode enabled Reading 32-bit memory content Size : 4 Bytes Address: : 0x20030030 0x20030030 : 01020000
Здесь цифра 01020000 означает версию 1.2. Если у вас будет версия FUS 0.5.3, то вам нужен будет FUS - "stm32wb5x_FUS_fw_for_fus_0_5_3.bin". Если версия меньше 1.2, то FUS - "stm32wb5x_FUS_fw.bin", если версия FUS 1.2 или старше, то тут или ничего не делать или поискать версию посвежее (на тот момент, когда вы это читаете, версия 1.2 возможно уже устареет).
Запускаем FUS:
$ ~/mydev/tools/STM32CubeProgrammer/bin/STM32_Programmer_CLI -c port=swd -startfus ------------------------------------------------------------------- STM32CubeProgrammer v2.15.0 ------------------------------------------------------------------- ST-LINK SN : 1200220015000059334D524E ST-LINK FW : V2J29S7 Board : -- Voltage : 3,28V SWD freq : 4000 KHz Connect mode: Normal Reset mode : Software reset Device ID : 0x495 Revision ID : Rev Y Device name : STM32WB5x/35xx Flash size : 1 MBytes Device type : MCU Device CPU : Cortex-M4 BL Version : 0xD5 Debug in Low Power mode enabled Warning: Option Byte: nSWboot0, value: 0x0, was not modified. Warning: Option Byte: nboot0, value: 0x1, was not modified. Warning: Option Byte: nboot1, value: 0x1, was not modified. Warning: Option Bytes are unchanged, Data won't be downloaded Time elapsed during option Bytes configuration: 00:00:00.000 Succeeded to set nSWboot0=0 nboot1=1 nboot0=1 Memory Programming ... Opening and parsing file: 0x495_FUS_Operator.bin File : 0x495_FUS_Operator.bin Size : 8,00 KB Address : 0x08000000 Erasing memory corresponding to segment 0: Erasing internal memory sectors [0 1] Download in Progress: [==================================================] 100% File download complete Time elapsed during download operation: 00:00:00.922 Application is running, Please Hold on... Reconnecting... Reconnected ! Reconnecting... Reconnected ! StartFus activated successfully FUS_STATE_IDLE startfus command execution finished
Получили "successfully", значит идем дальше. Удаляем текущий стек:
$ ~/mydev/tools/STM32CubeProgrammer/bin/STM32_Programmer_CLI -c port=swd -fwdelete ------------------------------------------------------------------- STM32CubeProgrammer v2.15.0 ------------------------------------------------------------------- ST-LINK SN : 1200220015000059334D524E ST-LINK FW : V2J29S7 Board : -- Voltage : 3,28V SWD freq : 4000 KHz Connect mode: Normal Reset mode : Software reset Device ID : 0x495 Revision ID : Rev Y Device name : STM32WB5x/35xx Flash size : 1 MBytes Device type : MCU Device CPU : Cortex-M4 BL Version : 0xD5 Debug in Low Power mode enabled Warning: Option Byte: nSWboot0, value: 0x0, was not modified. Warning: Option Byte: nboot0, value: 0x1, was not modified. Warning: Option Byte: nboot1, value: 0x1, was not modified. Warning: Option Bytes are unchanged, Data won't be downloaded Time elapsed during option Bytes configuration: 00:00:00.001 Succeeded to set nSWboot0=0 nboot1=1 nboot0=1 Memory Programming ... Opening and parsing file: 0x495_FUS_Operator.bin File : 0x495_FUS_Operator.bin Size : 8,00 KB Address : 0x08000000 Erasing memory corresponding to segment 0: Erasing internal memory sectors [0 1] Download in Progress: [==================================================] 100% File download complete Time elapsed during download operation: 00:00:00.923 Application is running, Please Hold on... Reconnecting... Reconnected ! Firmware delete Success fwdelete command execution finished
Обновляем FUS, если в этом есть необходимость. Адрес загрузки берём из таблицы в "Release_Notes.html":
$ ~/mydev/tools/STM32CubeProgrammer/bin/STM32_Programmer_CLI -c port=swd -fwupgrade ./stm32wb5x_FUS_fw.bin 0x080EC000 firstinstall=0 ------------------------------------------------------------------- STM32CubeProgrammer v2.15.0 ------------------------------------------------------------------- ST-LINK SN : 1200220015000059334D524E ST-LINK FW : V2J29S7 Board : -- Voltage : 3,27V SWD freq : 4000 KHz Connect mode: Normal Reset mode : Software reset Device ID : 0x495 Revision ID : Rev Y Device name : STM32WB5x/35xx Flash size : 1 MBytes Device type : MCU Device CPU : Cortex-M4 BL Version : 0xD5 Debug in Low Power mode enabled Old wireless stack delete ... Warning: Option Byte: nSWboot0, value: 0x0, was not modified. Warning: Option Byte: nboot0, value: 0x1, was not modified. Warning: Option Byte: nboot1, value: 0x1, was not modified. Warning: Option Bytes are unchanged, Data won't be downloaded Time elapsed during option Bytes configuration: 00:00:00.001 Succeeded to set nSWboot0=0 nboot1=1 nboot0=1 Memory Programming ... Opening and parsing file: 0x495_FUS_Operator.bin File : 0x495_FUS_Operator.bin Size : 8,00 KB Address : 0x08000000 Erasing memory corresponding to segment 0: Erasing internal memory sectors [0 1] Download in Progress: [==================================================] 100% File download complete Time elapsed during download operation: 00:00:00.922 Application is running, Please Hold on... Reconnecting... Reconnected ! Warning: FUS_STATE_IMG_NOT_FOUND, Flash already empty ! Firmware delete Success Download firmware image at address 0x80EC000 ... Memory Programming ... Opening and parsing file: stm32wb5x_FUS_fw.bin File : stm32wb5x_FUS_fw.bin Size : 23,92 KB Address : 0x080EC000 Erasing memory corresponding to segment 0: Erasing internal memory sectors [236 241] Download in Progress: [==================================================] 100% File download complete Time elapsed during download operation: 00:00:01.335 Firmware Upgrade process started ... Application is running, Please Hold on... Reconnecting... Reconnected ! Reconnecting... Reconnected ! Firmware Upgrade Success
Прошиваем BLE стек, адрес 0x080E0000 также берем из "Release_Notes.html"
$ ~/mydev/tools/STM32CubeProgrammer/bin/STM32_Programmer_CLI -c port=swd -fwupgrade ./stm32wb5x_BLE_HCILayer_fw.bin 0x080E0000 firstinstall=1 ------------------------------------------------------------------- STM32CubeProgrammer v2.15.0 ------------------------------------------------------------------- ST-LINK SN : 1200220015000059334D524E ST-LINK FW : V2J29S7 Board : -- Voltage : 3,28V SWD freq : 4000 KHz Connect mode: Normal Reset mode : Software reset Device ID : 0x495 Revision ID : Rev Y Device name : STM32WB5x/35xx Flash size : 1 MBytes Device type : MCU Device CPU : Cortex-M4 BL Version : 0xD5 Debug in Low Power mode enabled Download firmware image at address 0x80E0000 ... Memory Programming ... Opening and parsing file: stm32wb5x_BLE_HCILayer_fw.bin File : stm32wb5x_BLE_HCILayer_fw.bin Size : 77,24 KB Address : 0x080E0000 Erasing memory corresponding to segment 0: Erasing internal memory sectors [224 243] Download in Progress: [==================================================] 100% File download complete Time elapsed during download operation: 00:00:02.757 Firmware Upgrade process started ... Warning: Option Byte: nSWboot0, value: 0x0, was not modified. Warning: Option Byte: nboot0, value: 0x1, was not modified. Warning: Option Byte: nboot1, value: 0x1, was not modified. Warning: Option Bytes are unchanged, Data won't be downloaded Time elapsed during option Bytes configuration: 00:00:00.001 Succeed to set nSWboot0=0 nboot1=1 nboot0=1 Memory Programming ... Opening and parsing file: 0x495_FUS_Operator.bin File : 0x495_FUS_Operator.bin Size : 8,00 KB Address : 0x08000000 Erasing memory corresponding to segment 0: Erasing internal memory sectors [0 1] Download in Progress: File download complete Time elapsed during download operation: 00:00:00.923 Application is running, Please Hold on... Reconnecting... Reconnected ! Reconnecting... Reconnected ! Firmware Upgrade Success
Активируем установленный стек:
$~/mydev/tools/STM32CubeProgrammer/bin/STM32_Programmer_CLI -c port=swd -startwirelessstack ------------------------------------------------------------------- STM32CubeProgrammer v2.15.0 ------------------------------------------------------------------- ST-LINK SN : 1200220015000059334D524E ST-LINK FW : V2J29S7 Board : -- Voltage : 3,28V SWD freq : 4000 KHz Connect mode: Normal Reset mode : Software reset Device ID : 0x495 Revision ID : Rev Y Device name : STM32WB5x/35xx Flash size : 1 MBytes Device type : MCU Device CPU : Cortex-M4 BL Version : 0xD5 Debug in Low Power mode enabled Warning: Option Byte: nSWboot0, value: 0x0, was not modified. Warning: Option Byte: nboot0, value: 0x1, was not modified. Warning: Option Byte: nboot1, value: 0x1, was not modified. Warning: Option Bytes are unchanged, Data won't be downloaded Time elapsed during option Bytes configuration: 00:00:00.000 Succeeded to set nSWboot0=0 nboot1=1 nboot0=1 Memory Programming ... Opening and parsing file: 0x495_FUS_Operator.bin File : 0x495_FUS_Operator.bin Size : 8,00 KB Address : 0x08000000 Erasing memory corresponding to segment 0: Erasing internal memory sectors [0 1] Download in Progress: [==================================================] 100% File download complete Time elapsed during download operation: 00:00:00.922 Application is running, Please Hold on... Reconnecting... Reconnected ! FusStartWS activated successfully startwirelessStack command execution finished
BLE мы поставили, больше к этому возвращаться не нет необходимисти. Достаточно установить его один раз. Стек Осталось прошить микропитон. Очищаем флеш-память:
$ ~/mydev/tools/STM32CubeProgrammer/bin/STM32_Programmer_CLI -c port=swd -e all ------------------------------------------------------------------- STM32CubeProgrammer v2.15.0 ------------------------------------------------------------------- ST-LINK SN : 1200220015000059334D524E ST-LINK FW : V2J29S7 Board : -- Voltage : 3,28V SWD freq : 4000 KHz Connect mode: Normal Reset mode : Software reset Device ID : 0x495 Revision ID : Rev Y Device name : STM32WB5x/35xx Flash size : 1 MBytes Device type : MCU Device CPU : Cortex-M4 BL Version : 0xD5 Debug in Low Power mode enabled Mass erase ... Mass erase successfully achieved
На настоящий момент, единственной платой на чипе STM32WB55, которую поддерживает микропитон, является упомянутый ранее комплект P-NUCLEO-WB55, который состоит из самой платы на чипе STM32WB55RG и USB dongle на чипе STM32WB55СG. Т.е. на том же самом чипе, что и у нас плате WeAct STM32WB55CGU6. Поэтому, если вы хотите поставить микропитон на плату WeAct, то следует зайти на страницу загрузки MicroPython downloads , выбрать там USBDONGLE_W55 и оттуда скачать последнюю прошивку (альтернативная ссылка для v1.23).
В частности, сейчас это:
$ wget https://micropython.org/resources/firmware/USBDONGLE_WB55-20240602-v1.23.0.hex -O firmware.hex
--2024-07-10 08:59:16-- https://micropython.org/resources/firmware/USBDONGLE_WB55-20240602-v1.23.0.hex
Распознаётся micropython.org (micropython.org)… 176.58.119.26
Подключение к micropython.org (micropython.org)|176.58.119.26|:443... соединение установлено.
HTTP-запрос отправлен. Ожидание ответа… 200 OK
Длина: 1098202 (1,0M) [application/octet-stream]
Сохранение в: «firmware.hex»
firmware.hex 100%[================================>] 1,05M 1,29MB/s за 0,8s
2024-07-10 08:59:17 (1,29 MB/s) - «firmware.hex» сохранён [1098202/1098202]
Давайте оценим на глаз:
$ arm-none-eabi-size ./firmware.hex
text data bss dec hex filename
0 390420 0 390420 5f514 ./firmware.hex
Размер прошивки 390 КБ, это не очень много. Прошивка ESP8266 с сетевыми возможностями висит около 600 КБ. Но это больше, чем 330КБ для Blackpill. Ставим прошивку с микропитоном:
$ ~/mydev/tools/STM32CubeProgrammer/bin/STM32_Programmer_CLI -c port=swd -d ./firmware.hex -hardRst ------------------------------------------------------------------- STM32CubeProgrammer v2.15.0 ------------------------------------------------------------------- ST-LINK SN : 1200220015000059334D524E ST-LINK FW : V2J29S7 Board : -- Voltage : 3,28V SWD freq : 4000 KHz Connect mode: Normal Reset mode : Software reset Device ID : 0x495 Revision ID : Rev Y Device name : STM32WB5x/35xx Flash size : 1 MBytes Device type : MCU Device CPU : Cortex-M4 BL Version : 0xD5 Debug in Low Power mode enabled Memory Programming ... Opening and parsing file: firmware.hex File : firmware.hex Size : 381,27 KB Address : 0x08000000 Erasing memory corresponding to segment 0: Erasing internal memory sectors [0 95] Download in Progress: [==================================================] 100% File download complete Time elapsed during download operation: 00:00:09.850 Hard reset is performed
Отключаем чип от ST-Link, и подключаем его компьютеру по USB. Смотрим лог:
$ sudo dmesg --notime |tail -n20
usb 6-1: New USB device found, idVendor=f055, idProduct=9800, bcdDevice= 2.00
usb 6-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 6-1: Product: Pyboard Virtual Comm Port in FS Mode
usb 6-1: Manufacturer: MicroPython
usb 6-1: SerialNumber: 206630804242
usb-storage 6-1:1.0: USB Mass Storage device detected
scsi host8: usb-storage 6-1:1.0
usbcore: registered new interface driver usb-storage
cdc_acm 6-1:1.1: ttyACM0: USB ACM device
usbcore: registered new interface driver cdc_acm
cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters
usbcore: registered new interface driver uas
scsi 8:0:0:0: Direct-Access MicroPy pyboard Flash 1.00 PQ: 0 ANSI: 2
sd 8:0:0:0: [sdb] 768 512-byte logical blocks: (393 kB/384 KiB)
sd 8:0:0:0: [sdb] Write Protect is off
sd 8:0:0:0: [sdb] Mode Sense: 03 00 00 00
sd 8:0:0:0: [sdb] No Caching mode page found
sd 8:0:0:0: [sdb] Assuming drive cache: write through
sdb: sdb1
sd 8:0:0:0: [sdb] Attached SCSI removable disk
Появилась флешка и CDC устройство. Запускаем screen командой
$ screen /dev/ttyACM0 115200,cs
и заходим в REPL микропитона.Следующими командами:
>>> import bluetooth >>> ble=bluetooth.BLE() >>> ble.active(1)
проверяем работу BLE стека:
Если в ответ возвращает "True", то все отлично и радиочасть работает. Если вернет ошибку типа:
>>> import bluetooth >>> ble=bluetooth.BLE() >>> ble.active(1) Traceback (most recent call last): File "<stdin>", line 1, in <module> OSError: [Errno 110] ETIMEDOUT
то значит, что где-то вы допустили ошибку. Ок, давайте немного осмотримся. Список модулей:
>>> help('modules')
__main__ bluetooth io re
_asyncio builtins json select
_onewire cmath machine socket
array collections math stm
asyncio/__init__ deflate micropython struct
asyncio/core dht network sys
asyncio/event errno onewire time
asyncio/funcs framebuf os uasyncio
asyncio/lock gc platform uctypes
asyncio/stream hashlib pyb vfs
binascii heapq random
Plus any modules on the filesystem
Появился модуль bluetooth:
>>> import bluetooth >>> help(bluetooth) object <module 'bluetooth'> is of type module __name__ -- bluetooth BLE -- <class 'BLE'> UUID -- <class 'UUID'> FLAG_READ -- 2 FLAG_WRITE -- 8 FLAG_NOTIFY -- 16 FLAG_INDICATE -- 32 FLAG_WRITE_NO_RESPONSE -- 4 >>> help(bluetooth.BLE) object <class 'BLE'> is of type type active -- <function> config -- <function> irq -- <function> gap_advertise -- <function> gap_connect -- <function> gap_scan -- <function> gap_disconnect -- <function> gap_pair -- <function> gap_passkey -- <function> gatts_register_services -- <function> gatts_read -- <function> gatts_write -- <function> gatts_notify -- <function> gatts_indicate -- <function> gatts_set_buffer -- <function> gattc_discover_services -- <function> gattc_discover_characteristics -- <function> gattc_discover_descriptors -- <function> gattc_read -- <function> gattc_write -- <function> gattc_exchange_mtu -- <function> l2cap_listen -- <function> l2cap_connect -- <function> l2cap_disconnect -- <function> l2cap_send -- <function> l2cap_recvinto -- <function>
Попробуем оценить свободное место на внутренней флешке. pyb.info() тут не работает:
>>> import os >>> os.listdir('/') ['flash'] >>> os.listdir('/flash/') ['boot.py', 'main.py', 'pybcdc.inf', 'README.txt'] >>> 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 244,224 - Free Space 239,104
Имеем примерно 240 КБ. Туже цифру мы получим, если смонтируем флешку:
$ df -h /dev/sdb1
Файловая система Размер Использовано Дост Использовано% Cмонтировано в
/dev/sdb1 239K 11K 228K 5% /run/media/flanker/PYBFLASH
Смотрим количество свободной памяти:
>>> import gc >>> gc.mem_free() 146048
Тест частоты:
>>> import time >>> paste mode; Ctrl-C to cancel, Ctrl-D to finish === def tst(): === t1=time.ticks_ms() === for i in range(600000): === pass === t2=time.ticks_ms() === print(t2-t1) === >>> tst() 3485
Повторяю, что чип Low-Power, поэтому по производительности он будет где-то на уровне F103. Результаты выполнения бенчмарка:
Test result: Integer addition test result: 7.52 s Float addition test result: 19.00 s Integer multiplication test result: 7.95 s Float multiplication test result: 18.92 s Integer division test result: 8.03 s Float division test result: 19.08 s 1000 digit Pi calculation result: 1.57 s 5000 digit Pi calculation result: 26.81 s
Если сравнивать с другим Low Power чипом STM32L432 с частотой 80 МГц, то наша плата показывает неожиданно хорошие результаты при меньшей частоте.
Теперь нам надо подключиться телефоном к чипу по bluetooth протоколу. В проекте микропитона есть примеры программ, в том числе и для работы с блютузом. Они доступны на по адресу: "https://github.com/micropython/micropython/tree/master/examples/bluetooth"
Файлы "ble_advertising.py", "ble_uart_peripheral.py", "ble_uart_repl.py" нужно будет перекинуть на микроконтроллер с помощью ampy. Мне не хочется вдаваться в то, как это все работает, т.к. про это пришут довольно объемные книги, я просто показываю "как готовить".
На телефон нужно будет поставить программу установки последовательного соединения по bluetooth Serial Bluetooth Terminal или аналогичную:Теперь заходим в REPL микропитона и запускаем bluetooth-пример командами:
>>> import ble_uart_repl >>> ble_uart_repl.start()
Теперь в телефоне запускаем "Serial Bluetooth Terminal", открываем вкладку BLE, жмем "scan" и должно появиться mpy-repl устройство:
Тапаем по устройству, и попадаем repl микропитона:
И если у вас все получилось, то можно считать, что квест по установке микропитона и BLE-стека на STM32WB55CGU6 вы успешно прошли))
Но это еще не все. Есть подводный камень. После всех этих манипуляций, у меня пропала возможность попасть в DFU загрузчик по USB. Если у вас будет наблюдаться такая же проблема, то подключитесь к чипу через ST-Link и проверьте состояние флага "nSWBOOT0" в Option Bytes. Если галочка снята, установите ее. Сохраните Option Bytes и переподключитесь к чипу по USB. Возможность вызова DFU загрузчика должна снова появиться.
В завершение добавлю, что я собрал прошивку специально под плату WeAct_WB55. Там минимальные изменения, по сравнению со стоковой прошивкой. Убрана кнопка, которой на плате WeAct нет, и светодиод LED1 выведен на PE4. Т.е. единственный профит, то что можно помигать светодиодом через команду:
>>> pyb.LED(1).toggle()
В том числе через bluetooth:
Еще при подключении платы к USB синий светодиод коротко мигает. Регионы флеш-памяти пока двигать не стал, не вижу необходимости. Скачать прошивку с микропитоном версии 1.24_preview для платы WeAct_WB55 можно по следующей ссылке.
Как я уже упоминал в самом начале статьи, в микропитоне имеется байт-код интерпретатор и байт-код компилятор. Служит это для того, чтобы ваш код на микропитоне работал быстрее, а места на флеш-памяти занимал меньше. Как это работает?
Для преобразования в байт-код используется компилятор mpy-cross, который является частью проекта микропитон. В частности этот компилятор используется для компиляции некоторых модулей микропитона которые изначально написаны на питоне. При сборке прошивки микропитона можно увидеть такие строки:
MPY asyncio/__init__.py MPY asyncio/core.py MPY asyncio/event.py MPY asyncio/funcs.py MPY asyncio/lock.py MPY asyncio/stream.py MPY uasyncio.py MPY dht.py MPY onewire.py MPY lcd160cr.py
Здесь как раз работает байт-код компилятор, и соответственно данные модули включаются в прошивку в виде байт-кода. Если вспомнить инструкцию по сборке прошивки микропитона для Blackpill то там, перед сборкой самой прошивки, предварительно требуется собрать mpy-cross:
Для демонстрации работы с байт-кодом, я буду использовать blackpill с последней самосборной прошивкой 1.24_preview:
>>> import sys > >> sys.implementation (name='micropython', version=(1, 24, 0, 'preview'), _machine='WeAct Studio Core with STM32F411CE', _mpy=7942)
Важный момент: версии байт-код компилятора и байт-код интерпретатора должны совпадать. Смотрим версию байт-код интерпретатора:
> >> sys.implementation._mpy & 0xff 6
и компилятора:
$ ./mpy-cross --version
MicroPython v1.24.0-preview.100.g358e501e7 on 2024-07-10; mpy-cross emitting mpy v6.3
Не будем формалистами, и допустим, что 6 равно 6.3. Если верить официальной документации, то 6.3 это версии микропитона 1.20 и выше. Для экспериментов возьмем счетчик на пустом цикле:
import time as t def loop(): t1=t.ticks_ms() for i in range(600000): pass t2=t.ticks_ms() print(t2-t1)
Файл весит 110 байт и на blackpill выполняется за 2.4 сек:
>>> import tst >>> tst.loop() 2414
На компьютере компилирует модуль в байт-код:
$ ./mpy-cross ./tst.py
На выходе получаем файл "tst.mpy" размером 113 байт. В каталоге tools микропитона имеется утилита "mpy-tool.py", с помощью которой мы можем заглянуть внутрь полученного байт-кода:
$ mpy-tool.py -xd ./tst.mpy
...
бла бла бла
...
prelude: (6, 0, 0, 0, 0, 0)
args: []
line info: 60:27:25:2b:27
12:05 LOAD_GLOBAL t
14:04 LOAD_METHOD ticks_ms
36:00 CALL_METHOD 0
c0 STORE_FAST 0
80 LOAD_CONST_SMALL_INT 0
42:44 JUMP 4
57 DUP_TOP
c1 STORE_FAST 1
81 LOAD_CONST_SMALL_INT 1
e5 BINARY_OP 14 __iadd__
57 DUP_TOP
22:a4:cf:40 LOAD_CONST_SMALL_INT 600000
d7 BINARY_OP 0 __lt__
43:34 POP_JUMP_IF_TRUE -12
59 POP_TOP
12:05 LOAD_GLOBAL t
14:04 LOAD_METHOD ticks_ms
36:00 CALL_METHOD 0
c2 STORE_FAST 2
12:06 LOAD_GLOBAL print
b2 LOAD_FAST 2
b0 LOAD_FAST 0
f3 BINARY_OP 28 __sub__
34:01 CALL_FUNCTION 1
59 POP_TOP
51 LOAD_CONST_NONE
63 RETURN_VALUE
Программа на микропитоне преобразовалась в такую вот программу. Переименовываем модуль "tst.mpy" в "btst.mpy" и закидываем его в микроконтроллер и проверяем:
>>> import btst >>> btst.loop() 2414
Вообще ничего не поменялось. Т.е. можно сделать вывод, что байт-код - это тот же микропитон только записанный по другому.
Служебное слово "@micropyton.native" должно преобразовать наш модуль в якобы нативный код:
import time as t @micropython.native def loop(): t1=t.ticks_ms() for i in range(600000): pass t2=t.ticks_ms() print(t2-t1)
компилируем:
$ ./mpy-cross -march=armv7emsp ./tst.py
На выходе получаем файл весом уже 321 байт. Хотя на ассемблере такая программа влезет в дюжину байтов. Если мы опять воспользуемся утилитой "mpy-tool.py":
$ mpy-tool.py -xd ./tst.mpy
то увидим следующее:
mpy_source_file: ./tst.mpy source_file: ./tst.py header: 4d:06:1f:1f qstr_table[7]: ./tst.py <module> time loop ticks_ms t print obj_table: [mp_fun_table] simple_name: <module> raw bytecode: 16 08:04:01:46:80:51:1b:02:16:05:32:00:16:03:51:63 prelude: (2, 0, 0, 0, 0, 0) args: [] line info: 46 80 LOAD_CONST_SMALL_INT 0 51 LOAD_CONST_NONE 1b:02 IMPORT_NAME time 16:05 STORE_NAME t 32:00 MAKE_FUNCTION 0 16:03 STORE_NAME loop 51 LOAD_CONST_NONE 63 RETURN_VALUE children: ['loop'] simple_name: loop raw data: 253 00:00:00:00:fe:b5:9c:b0:47:68:be:68:ff:68:3f:68:14:90:06:20:17:90:14:a8:d7:f8:b4:40:a0:47:14:98 ... prelude: (6, 0, 0, 0, 0, 0) args: [] line info: b'' 00:00:00:00:fe:b5:9c:b0:47:68:be:68:ff:68:3f:68 14:90:06:20:17:90:14:a8:d7:f8:b4:40:a0:47:14:98 40:68:40:68:7b:69:98:47:15:90:00:28:00:f0:0e:80 00:a8:d7:f8:80:30:98:47:00:28:00:f0:07:80:15:98 7b:69:98:47:01:98:d7:f8:88:30:98:47:1e:9c:1d:9d 70:89:fb:69:98:47:19:aa:31:89:bb:6a:98:47:19:aa 00:20:00:21:3b:6f:98:47:04:46:01:21:19:91:00:f0 09:b8:19:98:05:46:19:90:03:22:19:99:0e:20:bb:6c 98:47:19:90:19:98:44:f6:81:72:c0:f2:12:02:01:46 19:90:00:20:bb:6c:98:47:3b:6c:98:47:00:28:e8:d1 70:89:fb:69:98:47:19:aa:31:89:bb:6a:98:47:19:aa 00:20:00:21:3b:6f:98:47:1c:90:b0:89:fb:69:98:47 19:90:1c:98:22:46:01:46:1c:20:bb:6c:98:47:1a:90 1a:aa:19:98:01:21:fb:6e:98:47:38:68:13:90:00:f0 00:b8:15:98:00:28:00:f0:05:80:7b:69:98:47:d7:f8 84:30:98:47:13:98:1c:b0:fe:bd children: []
Тут уже все поинтереснее, это уже больше походит на какой-то код.
Закинем теперь этот файл на blackpill, и запустим loop():
>>> import tst >>> tst.loop() 1157
Как можно видеть, такой цикл выполняется уже пошустрее. Чтобы оценить производительность, давайте бенчмарк преобразуем в нативный байткод, и посмотрим результаты его выполнения.
Во-первых, бенчмарк в байткоде весит почти в два раза легче, чем в виде текстовой программы:
$ ls -l bench.*py
-rw-r--r-- 1 flanker users 3666 июл 28 06:33 bench.mpy
-rw-r--r-- 1 flanker users 5531 июл 28 06:32 bench.py
Это полезное свойство для тех микроконтроллеров, где немного места на флешке. Результаты выполнения такого теста были следующими:
Test result: Integer addition test result: 5.66 s Float addition test result: 13.30 s Integer multiplication test result: 5.85 s Float multiplication test result: 13.42 s Integer division test result: 5.87 s Float division test result: 13.69 s 1000 digit Pi calculation result: 0.65 s 5000 digit Pi calculation result: 16.46 s
Это почти не отличается от предыдущих результатов. Тогда 5 тысяч знаков числа пи вычислялись за 17.84 секунды, теперь за 16.46 секунд.
Такой результат может выглядеть парадоксально на первый взгляд, но у меня есть объяснение. В программе большую часть кода выполняют функции микропитона, которые реализованы в прошивке, а из бенчмарка происходит лишь обращение к ним. Поэтому результаты обоих тестов практически совпадают.
Т.о. байткод ускорит лишь те алгоритмы, которые реализованы в самом модуле.
Теперь поговорим о чем-то более полезном. Драйвер SSD1306 состоит из двух файлов, которые суммарно весят около 10 килобайт. Выполнениее полной заливки (очистки) дисплея происходит за 67 мс. После компиляции драйвера в байткод, его размер сжимается в три раза до 3 КБ:
7172 байт cybercafe_16x8.py 2402 байт ssd1306_i2c.py 1824 байт cybercafe_16x8.mpy 1066 байт ssd1306_i2c.mpy
Но вот, что касается производительности, здесь мы ничего не получим:
MicroPython v1.24.0-preview.100.g358e501e7 on 2024-07-20; WeAct Studio Core with STM32F411CE
Type "help()" for more information.
>>> import time
>>> import ssd1306_i2c
>>> lcd=ssd1306_i2c.SSD1306()
>>> lcd.power_off()
>>> lcd.power_on()
>>> lcd.fill(0)
>>>
paste mode; Ctrl-C to cancel, Ctrl-D to finish
=== def lcdTST():
=== t1=time.ticks_ms()
=== lcd.fill(0xaa)
=== t2=time.ticks_ms()
=== print(t2-t1)
=== lcd.fill(0x0)
===
>>> lcdTST()
67
>>>
Функция заливки выполняется за те же 67 мс.
Подводя итог, с байт-код программой на микропитоне можно обращаться также как и с обычным модулем. Его можно размещать на флешке, или если прошивка собирается без файловой системы, что вероятно на чипах с малым объемом флеш-памяти, то такой модуль можно собрать вместе с прошивкой. Даже если прошивка собирается без MPY модуля, то микропитоновские функции все-равно можно включить в прошивку через функции do_str():
do_str(demo_single_input, MP_PARSE_SINGLE_INPUT); do_str(demo_file_input, MP_PARSE_FILE_INPUT);
где "demo_single_input" - это однострочник, а "demo_file_input" - это уже полноценная программа на микропитоне, которая размещается в Си-программе как константа:
static const char *demo_single_input = "print('hello world!', list(x + 1 for x in range(10)), end='eol\\n')"; static const char *demo_file_input = "import micropython\n" "\n" "print(dir(micropython))\n" "\n" "for i in range(10):\n" " print('iter {:08}'.format(i))";
Т.е. я хочу сказать, что собирать и использовать прошивки микропитона можно и для чипов с малым объем флеш-памяти. Отказавшись от файловой системы все равно можно использовать модули написанные на микропитоне.