ST7789 を使う 続 STM32CubeIDE編

前記事の続きです。

せっかくの Nucleo なので、STM32CubeIDEで使いたいときもあります。
そのときにDisplayを使えるように、自分なりのテンプレートとして準備します。

動作状態を手軽に表示したいので、テキストが出れば十分ですが、
M5Stackなどで便利に使っていた `Label` という概念を導入します。

```cpp:main.cpp（例）
	HAL_Delay(1000);
	label0.Draw((char *)"Hello L452RE");
	HAL_Delay(1000);
	label0.Draw((char *)"Update text");
```

この場合、2回目 `Update text` を書くとき、前のテキストを消しています。そうすることで重ねて表示されるのを防いでいます。簡単なようですが、このような動作をするためには仕掛けが必要です。ここでは自作の class を準備しました。

C(++)言語はかなり苦手意識がありますが、Google Gemini に相談しまくってなんとか形になりました。

使うディスプレイは前回と同じ、CSがない 1.3 240x240 です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/97659c08-e3fe-01b2-eaa4-81680a67bdce.png)

NUCLEO L452RE-Pを使っています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/e3ef745b-25da-18ee-27c0-4a8c02b110c3.png)

|Display|SCL|SDA|RES|DC|BLK|
|:-:|:-:|:-:|:-:|:-:|:-:|
|Nucleo|PB13(SCK)|PB15(MOSI)|PB6|PA8|3V3|

ON/OFF制御しないのでBacklightは電源直結。Nucleoの内側のピンを使っていますが、L452RE-Pの場合、SCK/MOSIは`SPI2`に割り当てられています。

# Thanks to

https://github.com/Floyd-Fish/ST7789-STM32
https://github.com/Korzhak/STM32CppProjectExample
https://qiita.com/tlab/items/f3e12a4559e883a5fb78
https://www.tohoho-web.com/ex/c-lang.html#pointer

# .ioc設定

NUCLEOデフォルト設定に、PB6/PA8/PB15/PB13を追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/d5c8df1e-a055-61db-778c-b650049c637d.png)

少しでも処理が早いほうが、と思い、PLLで80MHzに設定。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/d5cf1eb6-f07e-8022-fc83-b3338cd5ad4e.png)

SPI2の設定はまず以下 8bit MSB First, Polarity=High, 1Edge
こちらの環境では、Prescaler = 2, 40Mbit/s で動作しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/696dc70d-6049-107f-1621-f9d8af654506.png)

Libraryが必要としているようで、SPI2にDMAを追加します。
このことに気づくのに時間がかかりました。TXだけでOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/f283e7cc-249c-3d15-98a5-79452e7f4528.png)

プロジェクトは c++ タイプにして、コードを発生させます。

# ファイルを集める

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/2501b70b-9736-d392-012f-61f4e2d53dee.png)

以下4つは、上記 [https://github.com/Floyd-Fish/ST7789-STM32](https://github.com/Floyd-Fish/ST7789-STM32)
 からもらってきて、`Inc` および `Src` フォルダーに配置します。コピーしたら右クリックして `Refresh` を選ぶと、ツリーにも反映されます。

```
Inc\fonts.h
Inc\st7789.h
Src\fonts.c
Src\st7789.c
```

c++ で Class を扱うので、どうせなら、と Adruino風にします。[https://github.com/Korzhak/STM32CppProjectExample](https://github.com/Korzhak/STM32CppProjectExample)を参考に、

```
Inc\AltMain.h
Inc\AltMain.cpp
```

を作成します。

# コード編集

## ST7789ピン設定

SPI2を指定。DMA使用を確認。CSなしに設定。
RST/DCピンを正しく記載。

```st7789.h（抜粋）
/* choose a Hardware SPI port to use. */
//#define ST7789_SPI_PORT hspi1
#define ST7789_SPI_PORT hspi2
extern SPI_HandleTypeDef ST7789_SPI_PORT;

/* choose whether use DMA or not */
#define USE_DMA

/* If u need CS control, comment below*/
#define CFG_NO_CS

/* Pin connection*/
#define ST7789_RST_PORT GPIOB
#define ST7789_RST_PIN  GPIO_PIN_6
#define ST7789_DC_PORT  GPIOA
#define ST7789_DC_PIN   GPIO_PIN_8

#ifndef CFG_NO_CS
#define ST7789_CS_PORT  ST7789_CS_GPIO_Port
#define ST7789_CS_PIN   ST7789_CS_Pin
#endif
```

## テキストラベルのクラスを定義

```Label.h
#ifndef LABEL_H
#define LABEL_H

#include <altMain.h>
#include "ST7789.h"
#include "fonts.h"

class Label {
public:
    void Create(uint16_t x, uint16_t y, FontDef* font,  uint16_t fg, uint16_t bg);
    void Draw(const char* s);

private:
    uint16_t x0, y0;
    FontDef* font0;
    uint16_t fg0, bg0; // foreground color, background color
    char prev_s[20];
};

#endif // LABEL_H
```

```Label.cpp
#include "Label.h"
#include <string.h>
#include "ST7789.h"
#include "fonts.h"

void Label::Create(uint16_t x, uint16_t y, FontDef* font, uint16_t fg, uint16_t bg) {
    x0 = x;
    y0 = y;
    font0 = font;
    fg0 = fg;
    bg0 = bg;
    strcpy(prev_s, "");
}

void Label::Draw(const char* s) {
    ST7789_WriteString(x0, y0, prev_s, *font0, bg0, bg0);
    ST7789_WriteString(x0, y0, s, *font0, fg0, bg0);
    strcpy(prev_s, s);
}
```

ここは個人的にかなり苦労しました。

文字を書くだけなら、たとえば `ST7789_WriteString(0, 0, "Hello!", Font_16x26, WHITE, BLACK);` と書いてしまえば終わりなのですが、

クラス内で扱うので、いろいろ悩まされました。Google Geminiさんに教えてもらってやっとたどり着きました。

`Create()` するときにフォントを指定し、それを描画のときに使うので、privateな変数でフォントを覚えておく必要があります。フォントは `font.h` で構造体で定義されています。

試行錯誤した結果、構造体のアドレスをポインター変数で覚えておく、という方法となりました。 `*` なのか `&` なのか、まだまだ私は修業が必要なようですね...

## `setup()` および `loop()` 作成

[こちら](https://github.com/Korzhak/STM32CppProjectExample)に助けていただきました。


```altMain.h
#ifndef INC_ALTMAIN_HPP_
#define INC_ALTMAIN_HPP_


#ifdef __cplusplus
extern "C"
{
#endif
// START C-BLOCK IN C++ CODE

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "st7789.h"
#include "fonts.h"

/* Functions -----------------------------------------------------------------*/
void setup();
void loop();
// YOUR FUNCTIONS


// END C-BLOCK IN C++ CODE
#ifdef __cplusplus
}
#endif


#endif /* INC_ALTMAIN_HPP_ */
```

ここで、自分で定義した `Label`クラスを使って、簡潔にテキストを表示、更新します。


```altMain.cpp
#include <altMain.h>
#include "Label.h"

Label label0;
Label label1, label2, label3, label4;

void setup() {

	ST7789_Init();
	ST7789_Fill_Color(BLACK);

	label0.Create(0, 0, & Font_16x26, WHITE, BLACK);

	label1.Create(0, 30, & Font_11x18, YELLOW, BLACK);
	label2.Create(0, 50, & Font_11x18, CYAN, BLACK);
	label3.Create(0, 70, & Font_11x18, MAGENTA, BLACK);
	label4.Create(0, 90, & Font_11x18, GREEN, BLACK);

	label1.Draw((char *)"  CH1 YELLOW");
	label2.Draw((char *)"  CH2 CYAN");
	label3.Draw((char *)"  CH3 MAGENTA");
	label4.Draw((char *)"  CH4 GREEN");
}

void loop() {
	// YOUR LOOP CODE
	HAL_Delay(1000);
	label0.Draw((char *)"Hello L452RE");
	HAL_Delay(1000);
	label0.Draw((char *)"Update text");
}
```

# `main.c`

こちらは `#include "altMain.h"` と `setup()` および `loop()` を追加して放置です。`.ioc`を編集して再generateしても大丈夫。

```c:main.c（抜粋）

/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "altMain.h"
/* USER CODE END Includes */

// （中略）

  /* USER CODE BEGIN 2 */
  setup();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  loop();
  }
  /* USER CODE END 3 */
}

```

# おわりに

長くなりました。

Nucleoで各種テストをするとき、とくにそれが数時間とか、長時間になるときは、
Nucleoに接続した表示機があれば、状況を簡単に把握することができます。

ST7789の表示機であれば、昔ながらのキャラクターディスプレイや、よくある SSD1306 OLEDユニットよりも多くの情報が一度に確認できますね。

手軽に使うために、STM32CubeIDEで使う方法を模索しました。また、テキストで情報を簡単に表示させるため、C++のクラスの扱いを調べ、自前の簡単なライブラリーみたいなものを作ってみました。

作成するまでは苦労しましたが、やはり一度 Class をしっかり作れば、移植も簡単で、Class を使った検証用ソフトウエアも作成が早くできそうです。とても勉強になりました。

# 追記

```c:main.c（抜粋）
Label label0;
label0.Create(0, 0, & Font_16x26, WHITE, BLACK);
label0.Draw((char *)"Hello L452RE");
```

2行目で、表示位置、フォント、色を指定します。`Font_16x26` は構造体になっていますので、アドレスで渡します。

上記参考URLで、類似事例の解説がありました。ありがたいです。

```c:
main() {
    int a1 = 123;        // int変数 a1 を確保し、その値として 123 を格納
    int *a2 = &a1;       // ポインタ変数 a2 を確保し、その値として a1 のポインタを代入
    printf("%d\n", *a2); // a2 ポインタの中身(ポインタが示すメモリの中身)は 123
    *a2 = 321;           // a2 ポインタの中身(ポインタが示すメモリの中身)を 321 に変更
    printf("%d\n", a1);  // a1 の値が 321 に変わる
}
```