始めに
知り合いがSTM32L4A6を使ったボードを自作し、これにMicroPythonを入れて動かして欲しいと頼まれたので、動かすまでにやったことの備忘録です。といっても、ほとんどこちらを参考にさせていただきました。
MicroPythonビルド環境構築
こちらを参考
自作ボードにMicroPythonを対応させる
今回、自作ボード用にMicroPythonに追加したフォルダ、ファイルは以下の通り。
ports/stm32/
├ boards/
│ └ STM32L4a6GDISC/ -> new folder
│ ├ mpconfigboard.h -> new file
│ ├ mpconfigboard.mk -> new file
│ ├ pins.csv -> new file
│ └ stm32l4xx_hal_conf.h -> new file
├ stm32l4a6_af.csv -> new file
└ stm32l4a6xg.ld -> new file
自作ボードの回路図・ピンアサ、チップのデータシート、すでにMicroPythonでサポートされている自分と似たチップのデータを参考にこれらのファイルを作成、編集します。
stm32l4a6xg.ld (リンカスクリプト)
チップのデータシートのMemory mappingを参考に記述します。
今回は、同チップシリーズのSTM32L496のMemory mappingと見比べたところ、同じメモリ配置であったため、すでにMicroPythonプロジェクトに存在するstm32l496xg.ldをそのままコピーしました。
/*
GNU linker script for STM32L4A6XG
*/
/* Specify the memory areas */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 612K /* sectors 0-305 */
FLASH_FS (r) : ORIGIN = 0x08099000, LENGTH = 412K /* sectors 306-511 412 KiB */
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 320K /* SRAM1, 256K + SRAM2, 64K */
}
/* produce a link error if there is not this amount of RAM for these sections */
_minimum_stack_size = 2K;
_minimum_heap_size = 16K;
/* Define the stack. The stack is full descending so begins just above last byte of RAM,
or bottom of FS cache.. Note that EABI requires the stack to be 8-byte aligned for a call. */
/* RAM extents for the garbage collector */
_ram_start = ORIGIN(RAM);
_ram_end = ORIGIN(RAM) + LENGTH(RAM);
_ram_fs_cache_end = _ram_end;
_ram_fs_cache_start = _ram_fs_cache_end - 2K; /* fs cache = 2K */
_estack = _ram_fs_cache_start - _estack_reserve;
_sstack = _estack - 16K; /* stack = 16K */
_heap_start = _ebss; /* heap starts just after statically allocated memory */
_heap_end = _sstack; /* bss + heap = 302K, tunable by adjusting stack size */
_flash_fs_start = ORIGIN(FLASH_FS);
_flash_fs_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS);
stm32l4a6_af.csv (Alternate Functionsリスト)
チップのデータシートのAlternate functionテーブルとpin definitionsテーブルを参考に記述します。
今回は、同チップシリーズのSTM32L496のデータシートと見比べたところ、テーブルの内容が同一のものであったため、既に存在するstm32l496_af.csvをそのままコピーしました。
pins.csv
自作ボードの回路図を見ながら、各ピンの名前と信号線の対応を記述します。
後ほど、MicroPython上からは以下のように使うことができます。
from machine import Pin
cs = Pin("SPI2_NSS", Pin.OUT)
cs.on()
mpconfigboard.h (ボードのコンフィグ)
自作ボードの回路図、他のボードのmpconfigboard.hを参考にしながら記述します。
今回はこのようになりました。
# define MICROPY_HW_BOARD_NAME "HOGE_BOARD"
# define MICROPY_HW_MCU_NAME "STM32L4A6"
# define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0)
# define MICROPY_HW_ENABLE_RNG (1)
# define MICROPY_HW_ENABLE_RTC (1)
# define MICROPY_HW_ENABLE_TIMER (1)
# define MICROPY_HW_ENABLE_SDCARD (1)
// MSI is used and is 4MHz,
// Resulting core frequency is 80MHz:
# define MICROPY_HW_CLK_PLLM (1)
# define MICROPY_HW_CLK_PLLN (40)
# define MICROPY_HW_CLK_PLLP (RCC_PLLP_DIV7)
# define MICROPY_HW_CLK_PLLR (RCC_PLLR_DIV2)
# define MICROPY_HW_CLK_PLLQ (RCC_PLLQ_DIV2)
# define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_4
// The board has an external 32kHz crystal
# define MICROPY_HW_RTC_USE_LSE (1)
// USART config
# define MICROPY_HW_UART1_TX (pin_A9)
# define MICROPY_HW_UART1_RX (pin_A10)
# define MICROPY_HW_UART2_TX (pin_A2)
# define MICROPY_HW_UART2_RX (pin_A3)
// USART 1 is connected to the virtual com port on the ST-LINK
# define MICROPY_HW_UART_REPL PYB_UART_1
# define MICROPY_HW_UART_REPL_BAUD 115200
// I2C buses
# define MICROPY_HW_I2C1_SCL (pin_B6)
# define MICROPY_HW_I2C1_SDA (pin_B7)
# define MICROPY_HW_I2C2_SCL (pin_B10)
# define MICROPY_HW_I2C2_SDA (pin_B11)
// SPI buses
# define MICROPY_HW_SPI1_NSS (pin_A4)
# define MICROPY_HW_SPI1_SCK (pin_A5)
# define MICROPY_HW_SPI1_MISO (pin_A6)
# define MICROPY_HW_SPI1_MOSI (pin_A7)
# define MICROPY_HW_SPI2_NSS (pin_B12)
# define MICROPY_HW_SPI2_SCK (pin_B13)
# define MICROPY_HW_SPI2_MISO (pin_B14)
# define MICROPY_HW_SPI2_MOSI (pin_B15)
// LED (The orange LED is controlled over MFX)
# define MICROPY_HW_LED1 (pin_B0)
# define MICROPY_HW_LED2 (pin_B1)
# define MICROPY_HW_LED_ON(pin) (mp_hal_pin_low(pin))
# define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_high(pin))
// SD card
# define MICROPY_HW_SDCARD_SDMMC (1)
# define MICROPY_HW_SDCARD_CK (pin_C12)
# define MICROPY_HW_SDCARD_CMD (pin_D2)
# define MICROPY_HW_SDCARD_D0 (pin_C8)
# define MICROPY_HW_SDCARD_D1 (pin_C9)
# define MICROPY_HW_SDCARD_D2 (pin_C10)
# define MICROPY_HW_SDCARD_D3 (pin_C11)
# define MICROPY_HW_SDCARD_DETECT_PIN (pin_A8)
# define MICROPY_HW_SDCARD_DETECT_PULL (GPIO_PULLUP)
# define MICROPY_HW_SDCARD_DETECT_PRESENT (GPIO_PIN_RESET)
mpconfigboard.mk
MCU_SERIES = l4
CMSIS_MCU = STM32L4A6xx
AF_FILE = boards/stm32l4a6_af.csv
LD_FILES = boards/stm32l4a6xg.ld boards/common_basic.ld
OPENOCD_CONFIG = boards/openocd_stm32l4.cfg
stm32l4xx_hal_conf.h
こちらもSTM32L496用のものをそのまま流用しました。
/* This file is part of the MicroPython project, http://micropython.org/
* The MIT License (MIT)
* Copyright (c) 2019 Damien P. George
*/
# ifndef MICROPY_INCLUDED_STM32L4XX_HAL_CONF_H
# define MICROPY_INCLUDED_STM32L4XX_HAL_CONF_H
# include "boards/stm32l4xx_hal_conf_base.h"
// Oscillator values in Hz
# define HSE_VALUE (8000000)
# define LSE_VALUE (32768)
# define EXTERNAL_SAI1_CLOCK_VALUE (48000)
# define EXTERNAL_SAI2_CLOCK_VALUE (48000)
// Oscillator timeouts in ms
# define HSE_STARTUP_TIMEOUT (100)
# define LSE_STARTUP_TIMEOUT (5000)
# endif // MICROPY_INCLUDED_STM32L4XX_HAL_CONF_H
その他 ports/stm32/adc.c
上記ファイルを追加し、ビルドを実行したところ、adc.cでエラーが発生したので、必要箇所に、
defined(STM32L4A6xx)
を追記しました。
ビルド
ports/stm32ディレクトリに移動し、
$ make BOARD=STM32L4a6GDISC
作成されたbuild-STM32L4a6GDISC/firmware.hexをチップに書き込むと、無事MicroPythonが起動しました。
おまけ
SDカードへのアクセス
SD内のファイルを確認
>>> import os
>>> os.listdir("/sd")
['appl.mot', 'kernel_config.txt', 'System Volume Information', 'hello.py', 'tmp', 'spi.py', 'spi_bmx160.py']
>>>
SD内のPythonスクリプトを実行
>>> exec(open("/sd/hello.py").read())
Hello World
>>>
SPI通信でセンサの情報を取得する
自作ボードにはボッシュ社のBMX160 9軸センサが載っており、チップとはSPIでつながっています。MicroPythonからセンサの情報を取得するスクリプトは以下のようになります。
from struct import unpack
import time
from machine import SPI, Pin
WRITE_BIT = 0 << 7
READ_BIT = 1 << 7
CHIP_ID_ADDRESS = 0x00
MAG_CONF_ADDRESS = 0x44
MAG_IF_0_ADDRESS = 0x4C
MAG_IF_1_ADDRESS = 0x4D
MAG_IF_2_ADDRESS = 0x4E
MAG_IF_3_ADDRESS = 0x4F
CMD_REG_ADDRESS = 0x7E
DATA_START_ADDRESS = 0x04
DATA_END_ADDRESS = 0x1A
DATA_NUM = DATA_END_ADDRESS - DATA_START_ADDRESS + 1
spi = SPI(2)
cs = Pin("SPI2_NSS", Pin.OUT)
cs.on()
time.sleep(0.5)
def write_spi(reg_address: int, write_data: int) -> None:
send_data = WRITE_BIT | reg_address
buf = bytearray([send_data, write_data])
print(f"Write: {buf}")
cs.off()
spi.write(buf)
cs.on()
time.sleep(0.1)
def read_spi_1byte(reg_address: int) -> None:
send_data = READ_BIT | reg_address
print(f"Send: 0x{send_data:x}")
cs.off()
recv = spi.read(2, send_data)
cs.on()
print(f"Recv: 0x{recv[1]:x}\n")
def read_spi_multiple(start_address: int, nbytes: int) -> bytearray:
write_data = READ_BIT | start_address
cs.off()
read_buf = spi.read(nbytes + 1, write_data)
cs.on()
return read_buf[1:]
def convert_mag_data(mag_data_byte: bytearray) -> float:
mag_data_raw = unpack("<h", mag_data_byte)[0]
return mag_data_raw / 16.0
def convert_gyro_data(gyro_data_byte: bytearray) -> float:
gyro_data_raw = unpack("<h", gyro_data_byte)[0]
return gyro_data_raw * 61.0 / 1000
def convert_acc_data(acc_data_byte: bytearray) -> float:
acc_data_raw = unpack("<h", acc_data_byte)[0]
return acc_data_raw * 2 / 0x7FFF
def convert_sensor_time(sensor_time_byte: bytearray) -> float:
sensor_time_raw = int.from_bytes(sensor_time_byte, "little")
return sensor_time_raw * 39 / 1000000
print("CHIP ID")
read_spi_1byte(CHIP_ID_ADDRESS)
write_spi(CMD_REG_ADDRESS, 0x11)
write_spi(CMD_REG_ADDRESS, 0x15)
write_spi(CMD_REG_ADDRESS, 0x19)
write_spi(MAG_IF_0_ADDRESS, 0x80)
write_spi(MAG_IF_3_ADDRESS, 0x01)
write_spi(MAG_IF_2_ADDRESS, 0x4B)
write_spi(MAG_IF_3_ADDRESS, 0x01)
write_spi(MAG_IF_2_ADDRESS, 0x51)
write_spi(MAG_IF_3_ADDRESS, 0x0E)
write_spi(MAG_IF_2_ADDRESS, 0x52)
write_spi(MAG_IF_3_ADDRESS, 0x02)
write_spi(MAG_IF_2_ADDRESS, 0x4C)
write_spi(MAG_IF_1_ADDRESS, 0x42)
write_spi(MAG_CONF_ADDRESS, 0x05)
write_spi(MAG_IF_0_ADDRESS, 0x00)
write_spi(CMD_REG_ADDRESS, 0x1A)
while True:
data_bytearray = read_spi_multiple(DATA_START_ADDRESS, DATA_NUM)
mag_x = convert_mag_data(data_bytearray[0:2])
mag_y = convert_mag_data(data_bytearray[2:4])
mag_z = convert_mag_data(data_bytearray[4:6])
gyro_x = convert_gyro_data(data_bytearray[8:10])
gyro_y = convert_gyro_data(data_bytearray[10:12])
gyro_z = convert_gyro_data(data_bytearray[12:14])
acc_x = convert_acc_data(data_bytearray[14:16])
acc_y = convert_acc_data(data_bytearray[16:18])
acc_z = convert_acc_data(data_bytearray[18:20])
sensor_time = convert_sensor_time(data_bytearray[20:23])
print(f"Mag X: {mag_x:.2f}, Mag Y: {mag_y:.2f}, Mag Z: {mag_z:.2f} [μT]")
print(
f"Gyro X: {gyro_x:.2f}, Gyro Y: {gyro_y:.2f}, Gyro Z: {gyro_z:.2f} [deg/s]"
)
print(f"Acc X: {acc_x:.2f}, Acc Y: {acc_y:.2f}, Acc Z: {acc_z:.2f} [g]")
print(f"Sensor time: {sensor_time:.3f} [sec]")
print(data_bytearray)
print("")
time.sleep(1)
実行すると1秒間隔でセンサの情報を取ってくれました。
Mag X: 29.56, Mag Y: -4.94, Mag Z: -4.19 [μT]
Gyro X: -0.12, Gyro Y: 0.18, Gyro Z: 0.06 [deg/s]
Acc X: 0.99, Acc Y: -0.02, Acc Z: 0.05 [g]
Sensor time: 156.778 [sec]
b'\xd9\x01\xb1\xff\xbd\xff\rZ\xfe\xff\x03\x00\x01\x00)?\xc5\xfe\x03\x03\xecV='
Mag X: 30.56, Mag Y: -5.94, Mag Z: -3.81 [μT]
Gyro X: -0.06, Gyro Y: 0.18, Gyro Z: 0.18 [deg/s]
Acc X: 0.99, Acc Y: -0.02, Acc Z: 0.05 [g]
Sensor time: 157.816 [sec]
b'\xe9\x01\xa1\xff\xc3\xff\rZ\xff\xff\x03\x00\x03\x00$?\xc4\xfe\xe5\x02\xe7\xbe='
参考URL
- https://blog.boochow.com/article/mp-stm-nucleo-l432kc.html
- https://www.st.com/ja/microcontrollers-microprocessors/stm32l4x6.html
- https://ja.wikipedia.org/wiki/MicroPython
- https://github.com/micropython/micropython
- https://docs.micropython.org/en/latest/develop/gettingstarted.html
- https://www.st.com/resource/en/datasheet/DM00284207.pdf
- https://www.mouser.jp/new/bosch/bosch-sensortec-bmx160/