前記事の続きです。
せっかくの Nucleo なので、STM32CubeIDEで使いたいときもあります。
そのときにDisplayを使えるように、自分なりのテンプレートとして準備します。
動作状態を手軽に表示したいので、テキストが出れば十分ですが、
M5Stackなどで便利に使っていた Label
という概念を導入します。
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 です。
NUCLEO L452RE-Pを使っています。
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を追加します。
少しでも処理が早いほうが、と思い、PLLで80MHzに設定。
SPI2の設定はまず以下 8bit MSB First, Polarity=High, 1Edge
こちらの環境では、Prescaler = 2, 40Mbit/s で動作しました。
Libraryが必要としているようで、SPI2にDMAを追加します。
このことに気づくのに時間がかかりました。TXだけでOKです。
プロジェクトは c++ タイプにして、コードを発生させます。
ファイルを集める
以下4つは、上記 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を参考に、
Inc\AltMain.h
Inc\AltMain.cpp
を作成します。
コード編集
ST7789ピン設定
SPI2を指定。DMA使用を確認。CSなしに設定。
RST/DCピンを正しく記載。
/* 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
テキストラベルのクラスを定義
#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
#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()
作成
こちらに助けていただきました。
#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
クラスを使って、簡潔にテキストを表示、更新します。
#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しても大丈夫。
/* 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 を使った検証用ソフトウエアも作成が早くできそうです。とても勉強になりました。
追記
Label label0;
label0.Create(0, 0, & Font_16x26, WHITE, BLACK);
label0.Draw((char *)"Hello L452RE");
2行目で、表示位置、フォント、色を指定します。Font_16x26
は構造体になっていますので、アドレスで渡します。
上記参考URLで、類似事例の解説がありました。ありがたいです。
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 に変わる
}