STM32L4A6を使った自作ボードでMicroPythonを動かす

# 始めに
知り合いが[STM32L4A6](https://www.st.com/ja/microcontrollers-microprocessors/stm32l4x6.html)を使ったボードを自作し、これに[MicroPython](https://ja.wikipedia.org/wiki/MicroPython)を入れて動かして欲しいと頼まれたので、動かすまでにやったことの備忘録です。といっても、ほとんど[こちら](https://blog.boochow.com/article/mp-stm-nucleo-l432kc.html)を参考にさせていただきました。

# MicroPythonビルド環境構築
[こちら](https://docs.micropython.org/en/latest/develop/gettingstarted.html)を参考

# 自作ボードに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
```

自作ボードの回路図・ピンアサ、[チップのデータシート](https://www.st.com/resource/en/datasheet/DM00284207.pdf)、すでにMicroPythonでサポートされている自分と似たチップのデータを参考にこれらのファイルを作成、編集します。

## stm32l4a6xg.ld (リンカスクリプト)
チップのデータシートのMemory mappingを参考に記述します。

![memory_mapping.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404447/a94fc8b0-06b9-4527-55a6-3adc30d822b9.png)

今回は、同チップシリーズのSTM32L496のMemory mappingと見比べたところ、同じメモリ配置であったため、すでにMicroPythonプロジェクトに存在するstm32l496xg.ldをそのままコピーしました。

```stm32l4a6xg.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
自作ボードの回路図を見ながら、各ピンの名前と信号線の対応を記述します。

![pin_csv.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404447/3ca04f6c-ae1a-4a4e-a2fe-207d6d3918d8.png)

後ほど、MicroPython上からは以下のように使うことができます。

```python
from machine import Pin

cs = Pin("SPI2_NSS", Pin.OUT)
cs.on()

```

## mpconfigboard.h (ボードのコンフィグ)

自作ボードの回路図、他のボードのmpconfigboard.hを参考にしながら記述します。
今回はこのようになりました。

```c: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

```makefile: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用のものをそのまま流用しました。

```c:stm32l4xx_hal_conf.h
/* 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内のファイルを確認

```python
>>> import os
>>> os.listdir("/sd")
['appl.mot', 'kernel_config.txt', 'System Volume Information', 'hello.py', 'tmp', 'spi.py', 'spi_bmx160.py']
>>>
```

SD内のPythonスクリプトを実行

```python
>>> exec(open("/sd/hello.py").read())
Hello World
>>>
```

## SPI通信でセンサの情報を取得する
自作ボードにはボッシュ社の[BMX160 9軸センサ](https://www.mouser.jp/new/bosch/bosch-sensortec-bmx160/)が載っており、チップとはSPIでつながっています。MicroPythonからセンサの情報を取得するスクリプトは以下のようになります。

```python:spi_bmx160.py
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/