STM32 NUCLEOボードにTOPPERS/ASP3をポーティングする

### はじめに

TOPPERS/ASP3には現在、Nucleo F401RE簡易パッケージとNucleo L552ZE-Q簡易パッケージがあります。
Nucleo F401RE簡易パッケージの`target/nucleo_f401re_gcc/target_user.txt` には `(8) 他のNUCLEOボードのサポート` という章があり、ここにポーティングの手順が書いてあります。

> 本パッケージはSTM32FCUBEに含まれているファイルを用いて，デバイス等の初期化を行っているため，他のNUCLEOも容易にサポート可能である．

これを参考にしてNUCLEO F767ZIというボードにポーティングしてみたので、その手順を書いてみました。
他のNUCLEOボードでもTOPPERS使いたい！という方の参考になれば幸いです。

ポーティングしたものは https://github.com/yurie/asp3-f767zi に公開しています。
[コミットログ](https://github.com/yurie/asp3-f767zi/commits/master)はポーティング手順に合わせて書いているので、何をしようとして変更したのかわかると思います。（自分でも、何がしたかったんだ？？と後から悩みそうだったので…）
ただし、時々リネームのタイミング等でhistoryをうまく引き継げていないファイルがあったり、途中で間違いに気づいて最終的には別のコミットで修正されている場所もあるのでご注意ください。
もし、コミットログを見て、「意図してたことと、修正内容違うで！」ということに気づかれた方がいらっしゃったらissueにコメントいただけるとありがたいです :bow:


## 参考資料
本文中で出てくるので先に書いておきます。
### Cortex-M7アーキテクチャ用 TOPPERS ASP
[ARM Cortex-M7アーキテクチャ・GCC依存部パッケージ](https://www.toppers.jp/asp-d-download.html) に　[asp_arch_arm_m7_gcc-1.9.5.tar.gz](https://www.toppers.jp/download.cgi/asp_arch_arm_m7_gcc-1.9.5.tar.gz) があります。
こちらはSTM32Cubeを使用していないものですが、各種設定値は同じなので。
他にもM4, M0向けがあるので、他のボードへのポーティング時にも参考になりそうです。

### STM32CubeF7
GitHubに公開されています。
https://github.com/STMicroelectronics/STM32CubeF7
STM32FCUBE関連はこれを使用しています。

特に、[STM32F767ZI-Nucleoのサンプルコード](https://github.com/STMicroelectronics/STM32CubeF7/tree/master/Projects/STM32F767ZI-Nucleo)は実装の参考にしました。
USARTに関しては、HALの実装例がなかったので、LLの設定を参考にしています。


# ポーティング

## 変更箇所
`arch/arm_m_gcc` 以下に不足しているものの追加 と　`target/nucleo_f401re_gcc`を複製して必要なところを書き換えるのが主な作業です。

## F401REとF767ZIの大きな違い

### USARTのポート
F401REはUSART2、F767ZIはUSART3です。
（STM32CubeIDEのGUIで設定できたので使えるかと思ったのですが、ダメっぽい）

### NUCLEO-64 vs NUCLEO-144

ピンの数が違います。だから何？という感じですが、後述のようにファイル名に影響してくる場合もあるので注意。
直接関係ないけど、144本の方もArduino Unoとピン互換です。（Arduino Uno用拡張ボードがNUCLEOのピンが余る感じで挿すことができます）

### UserLEDの数

F401REは１個ですが、F767ZIには3個あります。せっかくなので３つとも使えるようにしておきました。

### Cortex-Mx
Cortex-M4, M0+, M0, M3は対応済みでしたが、M7はありませんでした :cry:

arch以下がポーティングしたいボードに対応されているかどうかでポーティング難易度が変わってきます。


さっそく始めていきましょう。
引用文は`target_user.txt`の`(8) 他のNUCLEOボードのサポート`からの引用です。


## STM32Cubeのファイルの取得 & 変更
> ./target/nucleo_f401re_gcc/stm32fcube に以下のファイルをコピー
> \STM32Cube_FW_F4_V1.9.0\Projects\STM32F401RE-Nucleo\Templates\Inc\stm32f4xx_hal_conf.h
\STM32Cube_FW_F4_V1.9.0\Projects\STM32F401RE-Nucleo\Templates\Src\system_stm32f4xx.c
\STM32Cube_FW_F4_V1.9.0\Projects\STM32F411RE-Nucleo\Templates\Src\main.c 
\STM32Cube_FW_F4_V1.9.0\Drivers\BSP\STM32F4xx-Nucleo\stm32f4xx_nucleo.h
\STM32Cube_FW_F4_V1.9.0\Drivers\BSP\STM32F4xx-Nucleo\stm32f4xx_nucleo.c
>
> それぞれ次のように変更


F7用STM32Cubeは以下にあります。
https://github.com/STMicroelectronics/STM32CubeF7

### target用ディレクトリの作成

`./target/（ターゲット名)_gcc/stm32fcube` というディレクトリをつくります。
私の場合は、 `./target/nucleo_f767zi_gcc/stm32fcube` としました。


### stm32f4xx_hal_conf.hの修正

>
> stm32f4xx_hal_conf.h
> ・必要なHALのコメントアウトを外す

私の場合は、 [stm32f7xx_hal_conf.h](https://github.com/STMicroelectronics/STM32CubeF7/blob/master/Projects/STM32F767ZI-Nucleo/Templates/Inc/stm32f7xx_hal_conf.h) をそのまま使いました。
（後から何か追加した際に、このコメントアウトを外すのを思い出さなくてハマりそうな気がしたので…）


### system_stm32f4xx.c(system_stm32f7xx.c)の修正

> system_stm32f4xx.c
> ・SystemInitの最初にあるCPACRの初期化(FPUの初期化)をコメントアウト

TODO:system_stm32f7xx.cの場所
[target/nucleo_f767zi_gcc/stm32fcube/system_stm32f7xx.c](https://github.com/yurie/asp3-f767zi/blob/a368a1d6a523294d4a181d6bd0eecabf769cfb6d/target/nucleo_f767zi_gcc/stm32fcube/system_stm32f7xx.c#L138)のような感じで、同様の修正をしました。
ここは `stm32f4xx.c` も `stm32f7xx.c`も同じでした。

```
void SystemInit(void)
{
  /* FPU settings ------------------------------------------------------------*/
  #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
//    SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));  /* set CP10 and CP11 Full Access */
  #endif
```

### systemclock_config.cの作成

> main.c 
> ・SystemClock_Config()関数のみ残して，ファイル名をsystemclock_config.cに変更．

こちらも同様の修正をしました。
名前は同じですが、F401REとは関数の中身が違うので、F401REからコピーしてくるのはダメ :sweat_smile: 。

他にも一部変更が必要でした。(変更箇所のみ抜粋)

```
  /* Enable Power Control clock */	
//  __HAL_RCC_PWR_CLK_ENABLE();
  /* The voltage scaling allows optimizing the power consumption when the device is 	
     clocked below the maximum system frequency, to update the voltage scaling value 	
     regarding system frequency refer to product datasheet.  */	
//  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); 

RCC_OscInitStruct.HSEState = RCC_HSE_BYPASS;
RCC_OscInitStruct.HSIState = RCC_HSI_OFF;
RCC_OscInitStruct.PLL.PLLM = 8;
```


### stm32f4xx_nucleo.cの修正

> stm32f4xx_nucleo.c
> ・必要でない機能をコメントアウト

 TODO

### arch/arm_m_gccにファイルの追加

> ./arch/arm_m_gcc/stm32f4xx_stm32cube/ 以下に 
> STM32Cube_FW_F4_V1.9.0\Drivers 以下の必要なファイルをコピー．面倒な場合は全てコピーしてかまわない．

面倒だったので :sweat_smile:  `./arch/arm_m_gcc/stm32f7xx_stm32cube/` ディレクトリを作成して、[STM32CubeF7/Drivers](https://github.com/STMicroelectronics/STM32CubeF7/tree/master/Drivers)を全コピーしました。

### Makefile.targetの修正

> ./target/nucleo_f401re_gcc/ 以下のファイルを変更
>
> Makefile.target
> 22行目 : 使用するチップの型番を指定
> CDEFS := $(CDEFS) -DSTM32F401xE

[./target/nucleo_f767zi_gcc/](https://github.com/yurie/asp3-f767zi/blob/master/target/nucleo_f767zi_gcc/Makefile.target) の　`Makefile.target`を修正

```
CDEFS := $(CDEFS) -DSTM32F767ZITx -DSTM32F767xx
```
（なぜ２つ書いたのかは失念しました…。思い出したら補足します）

### リンカスクリプトの修正

> stm32f401re.ld
> メモリマップを使用するチップに合わせて変更．
> MEMORY
> {
>   FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 512K
>   SRAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 96K
> }

`stm32f401re.ld`を `stm32f767zi.ld` にリネームして上記修正

[target/nucleo_f767zi_gcc/stm32f767zi.ld](https://github.com/yurie/asp3-f767zi/blob/master/target/nucleo_f767zi_gcc/stm32f767zi.ld#L1)

```
MEMORY
{
  FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 2048K
  SRAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 512K
}
```

### target_config.hの修正

> target_config.h
> 割込みの最大数を使用するチップに合わせて変更(必ず+16すること)．
> \#define TMAX_INTNO (84 + 16)

```
#define TMAX_INTNO (109 + 16)
```
設定値はASPを参考にしました。

> クロック周波数を使用するチップに合わせて変更．
> \#define SYS_CLOCK		84000000

`SYS_CLOCK`は現在使われていないようです。`84000000`が設定されているものに`CPU_CLOCK_HZ`がありました。
それっぽかったので、これを変更。

```
#define CPU_CLOCK_HZ	216000000
```

### ~~target_config.c~~ target_kernel_impl.cの修正

> target_config.c
> target_initialize() を使用するチップに合わせて初期化処理を追加．
> usart_early_init() を使用するチップに合わせて初期化処理を追加．

`usart_early_init()`、`target_initialize() ` は `target_config.c` ではなくて、`target/nucleo_f401re_gcc/target_kernel_impl.c`にあるようです。

#### target_initialize()

[HALによる初期化をF767ZIにあわせた](https://github.com/yurie/asp3-f767zi/commit/71627ee7264786830631442ccc86d1488dba9928#diff-49e29558901bf074cf47a9bf4c7aa327d93ed92655d5b2c019ae6f93412f2665)

```
__HAL_FLASH_ART_ENABLE();
__HAL_FLASH_PREFETCH_BUFFER_ENABLE();
```

[キャッシュの有効化の設定を追加](https://github.com/yurie/asp3-f767zi/commit/5c6a3f1264a4dc32e18da38eebd74d39f0b3ebe9)

```
  /* Enable I-Cache */
  SCB_EnableICache();

  /* Enable D-Cache */
  SCB_EnableDCache();
```

LEDは３つついているので、全て使えるようにしました。

```
	/*
	 *  UserLEDの初期化
	 */
	BSP_LED_Init(LED1);
	BSP_LED_Init(LED2);
	BSP_LED_Init(LED3);
```

#### usart_early_init()

```
void
usart_early_init()
{
  usart_low_init();

  UartHandle.Instance          = USART_NAME; 
  UartHandle.Init.BaudRate     = BPS_SETTING;
  UartHandle.Init.WordLength   = UART_WORDLENGTH_8B;
  UartHandle.Init.StopBits     = UART_STOPBITS_1;
  UartHandle.Init.Parity       = UART_PARITY_NONE;
  UartHandle.Init.HwFlowCtl    = UART_HWCONTROL_NONE;
  UartHandle.Init.Mode         = UART_MODE_TX_RX;
  UartHandle.Init.OverSampling = UART_OVERSAMPLING_16;
  UartHandle.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  UartHandle.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
    
  if(HAL_UART_Init(&UartHandle) != HAL_OK) {
    Error_Handler();
  }

};
```


#### USART関連
 `usart_early_init()`内で呼ばれている`usart_low_init()`も関連して修正する必要があります。
F401REではUSART2を使っています。
F767ZIでは、USART3なので、こちらも変更する必要があります。
GPIOもF401REと異なっていたので、変更しました。

関連して`target/nucleo_f767zi_gcc/nucleo_f767zi.h`も修正します。


[target/nucleo_f767zi_gcc/nucleo_f767zi その1](https://github.com/yurie/asp3-f767zi/blob/a368a1d6a523294d4a181d6bd0eecabf769cfb6d/target/nucleo_f767zi_gcc/nucleo_f767zi.h#L77)

```
/*
 *  USART関連の定義
 */
#define USART_INTNO (USART3_IRQn + 16)
#define USART_NAME  USART3
#define USART_BASE  USART3_BASE 
```

[target/nucleo_f767zi_gcc/nucleo_f767zi その2](https://github.com/yurie/asp3-f767zi/blob/a368a1d6a523294d4a181d6bd0eecabf769cfb6d/target/nucleo_f767zi_gcc/nucleo_f767zi.h#L91)

```
/*
 *  UsartのクロックとIOの初期化
 */
Inline void
usart_low_init(void) {
  GPIO_InitTypeDef  GPIO_InitStruct;

  /* Enable Clock */
  __HAL_RCC_USART3_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
  /* UART TX GPIO pin configuration  */
  GPIO_InitStruct.Pin       = GPIO_PIN_8|GPIO_PIN_9;
  GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull      = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.Alternate = GPIO_AF7_USART3;
    
  HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
}
```
[target/nucleo_f767zi_gcc/nucleo_f767zi その３](
https://github.com/yurie/asp3-f767zi/blob/a368a1d6a523294d4a181d6bd0eecabf769cfb6d/target/nucleo_f767zi_gcc/nucleo_f767zi.h#L68)

```
#ifndef TECSGEN
#include "stm32f7xx_nucleo_144.h"
#else /* !TECSGEN */
#define USART3_BASE  0x40004800U
#define USART3_IRQn  39
#endif /* TECSGEN */
#endif /* TOPPERS_MACRO_ONLY */
```

USARTの変更は、Syslog周りはTECSで実装されているため、そちらの修正も必要になります。

[arch/arm_m_gcc/stm32f7xx_stm32cube/tUsart.c その
１](https://github.com/yurie/asp3-f767zi/blob/a368a1d6a523294d4a181d6bd0eecabf769cfb6d/arch/arm_m_gcc/stm32f7xx_stm32cube/tUsart.c#L50)

 ```
/*
 * USARTレジスタ定義
 */
#define USART_CR1(x)	(x + 0x00)
#define USART_CR2(x)	(x + 0x04)
#define USART_CR3(x)	(x + 0x08)
#define USART_BRR(x)	(x + 0x0C)
#define USART_GTPR(x)	(x + 0x10)
#define USART_ISR(x)	(x + 0x1C)
#define USART_ICR(x)	(x + 0x20)
#define USART_RDR(x)	(x + 0x24)
#define USART_TDR(x)	(x + 0x28)
```

[arch/arm_m_gcc/stm32f7xx_stm32cube/tUsart.c その２](https://github.com/yurie/asp3-f767zi/blob/a368a1d6a523294d4a181d6bd0eecabf769cfb6d/arch/arm_m_gcc/stm32f7xx_stm32cube/tUsart.c#L68)

```
/*
 *  受信バッファに文字があるか？
 */
Inline bool_t
usart_getready(CELLCB *p_cellcb)
{
	return (sil_rew_mem((void*)USART_ISR(ATTR_baseAddress)) & USART_FLAG_RXNE) != 0;
	
}

/*
 *  送信バッファに空きがあるか？
 */
Inline bool_t
usart_putready(CELLCB *p_cellcb)
{
	return (sil_rew_mem((void*)USART_ISR(ATTR_baseAddress)) & USART_FLAG_TXE) != 0;
}

/*
 *  受信した文字の取出し
 */
Inline char
usart_getchar(CELLCB *p_cellcb)
{
	return((char) sil_rew_mem((void*)USART_RDR(ATTR_baseAddress)) & 0xFF);
}

/*
 *  送信する文字の書込み
 */
Inline void
usart_putchar(CELLCB *p_cellcb, char c)
{
	sil_wrw_mem((void*)USART_TDR(ATTR_baseAddress), c);
}
```





### 型番の変更

> 全ファイル
> F401RE を，使用するチップの型番にリプレース．

これはそのまま、F401RE -> F767ZI という感じで。
[F401RE を，使用するチップの型番にリプレースに対応](https://github.com/yurie/asp3-f767zi/commit/70cb955f4f53f0a1c71bdd7ab33231b89dfb374f)
[F4xxからF7xxへの修正漏れ](https://github.com/yurie/asp3-f767zi/commit/2ef842f99f4d3ca4f064a627ec4fd94a7f1dd5a8):sweat_smile: 

## その他の修正
F767ZIの場合はこれ以外にも変更が必要でした。

### Makefile.target

「型番の変更」で察してねということでしょう。
差分は[こちら](https://github.com/yurie/asp3-f767zi/commit/28ef7ce6f534d7457756043ee05380394c4461cb#diff-cfaa40194b359eb5dc7d8dcc77ff23686b29214fd280aa785021eab3946abd7a)で。

以下、変更箇所の抜粋です。

```
BOARD = nucleo_f767zi
CHIP  = stm32f7xx_stm32cube

KERNEL_COBJS := $(KERNEL_COBJS) target_kernel_impl.o \
                system_stm32f7xx.o stm32f7xx_nucleo.o systemclock_config.o \
                stm32f7xx_hal_gpio.o stm32f7xx_hal_uart.o \
                stm32f7xx_hal_rcc.o stm32f7xx_hal_dma.o  stm32f7xx_hal_pwr_ex.o stm32f7xx_hal.o

KERNEL_COBJS := $(KERNEL_COBJS) stm32f7xx_hal_tim.o

LDSCRIPT = $(TARGETDIR)/stm32f767zi.ld
```

### 144pinボードの罠

`stm32f4xx`だから`stm32f7xx`と機械的に変換していると、はまります :sweat_smile:  。
https://github.com/yurie/asp3-f767zi/blob/a368a1d6a523294d4a181d6bd0eecabf769cfb6d/target/nucleo_f767zi_gcc/nucleo_f767zi.h#L69

```
#ifndef TECSGEN
#include "stm32f7xx_nucleo_144.h"
#else /* !TECSGEN */
```

F401REの場合は`stm32f4xx_nucleo.h`でしたが、F767ZIの場合は`stm32f7xx_nucleo_144.h`という感じで **_144** がついている
ので注意。
そもそも基板の大きさが違う（144ピン）なのですね。

### TIMxの設定値
[target_timer.c](https://github.com/yurie/asp3-f767zi/blob/a368a1d6a523294d4a181d6bd0eecabf769cfb6d/target/nucleo_f767zi_gcc/target_timer.c#L71)

```
Tim2Handle.Init.Prescaler = ((CPU_CLOCK_HZ/2)/1000000);
Tim5Handle.Init.Prescaler = ((CPU_CLOCK_HZ/2)/1000000);
```


### Cortex-M7対応
残念ながら未実装でした…。

[arch/arm_m_gcc/common/Makefile.core](https://github.com/yurie/asp3-f767zi/blob/a368a1d6a523294d4a181d6bd0eecabf769cfb6d/arch/arm_m_gcc/common/Makefile.core#L116)

```
else ifeq ($(CORE_TYPE),CORTEX_M7)
  ARM_ARCH = ARMV7M
  COPTS := $(COPTS) -mcpu=cortex-m7
  CDEFS := $(CDEFS) -DTOPPERS_CORTEX_M7
  ifeq ($(FPU_ARCH_MACRO),__TARGET_FPU_FPV5_DP)
    FPU_ARCH_OPT   = fpv5-d16
  else
    FPU_ARCH_MACRO = __TARGET_FPU_FPV4_SP
    FPU_ARCH_OPT   = fpv4-sp-d16
  endif
# CORTEX_M7のFPU_ARCH_MACROはASPのターゲット依存部を参考にした

```

[arch/arm_m_gcc/common/core_insn.h](https://github.com/yurie/asp3-f767zi/blob/a368a1d6a523294d4a181d6bd0eecabf769cfb6d/arch/arm_m_gcc/common/core_insn.h#L169)

```
#if defined(TOPPERS_CORTEX_M4) || defined(TOPPERS_CORTEX_M3) || defined(TOPPERS_CORTEX_M0) || defined(TOPPERS_CORTEX_M0PLUS) || defined(TOPPERS_CORTEX_M7)
```

[target/nucleo_f767zi_gcc/Makefile.target](https://github.com/yurie/asp3-f767zi/blob/a368a1d6a523294d4a181d6bd0eecabf769cfb6d/target/nucleo_f767zi_gcc/Makefile.target#L14)

```
#
#  FPUを使用するか
#
FPU_USAGE = FPU_LAZYSTACKING
FPU_ARCH_MACRO := __TARGET_FPU_FPV5_DP
# FPU_ABI := hard // TODO : ASPにはついていた
```

実はちょっと困っているのが、FPU_ABIってhard？softfp？どっちなの！？というところです。


[arch/arm_m_gcc/common/Makefile.core](https://github.com/yurie/asp3-f767zi/blob/a368a1d6a523294d4a181d6bd0eecabf769cfb6d/arch/arm_m_gcc/common/Makefile.core#L147)の中で

```
#
#  FPUの設定
#
ifeq ($(FPU_ABI),)
	FPU_ABI = softfp
endif
```
となっていたのと、ASP3のF401REでは`nucleo_f401re_gcc/Makefile.target`で設定していなかったので、それを踏襲しています。

# さいごに

本記事では、Nucleo F401RE簡易パッケージのtarget/nucleo_f401re_gcc/target_user.txtを参考にポーティングした内容をまとめました。
他のボードに移植してみようかなという方の参考になれば幸いです。
その際、もし、間違っているところ、不明なところなどあれば教えてください。

 [GitHubのcommit](https://github.com/yurie/asp3-f767zi/commit/02b64716b34422420538d158584b6c2a2c7bb60f)にもあるように、NUCLEO F401RE簡易パッケージを作成された [TOPPERSの本田先生](https://qiita.com/exshonda) には、最後動かなくて困っていたところを助けていただいただけでなく、途中色々ご助言いただきました。
ありがとうございました！