下記の内容は2026年2月時点でのライブラリや公開ページによるものであり、バージョンの更新やライブラリの変更については手持ちのAIに聞いてください。
特にESP32へのフォント/dataアップロード周りはIDE1.xと2.xでプラグイン周りが変わってるので間違えると沼ります。
手持ちのarduino uno3 CH06 tft7735
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SoftwareSerial.h>
// ---- TFT pins (UNO) ----
#define TFT_CS 10
#define TFT_DC 9
#define TFT_RST 8
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);
// ---- HC-06 SoftwareSerial pins ----
// HC-06 TXD -> D2 (RX)
// HC-06 RXD <- D3 (TX) ※UNO→HC-06は分圧推奨
SoftwareSerial bt(2, 3); // RX, TX ※HC-06 TX->D2, HC-06 RX<-D3(分圧)
#define BT_RX 2
#define BT_TX 3
// ---- text layout ----
const uint8_t TEXT_SIZE = 2; // 1が無難。大きくしたいなら2
const uint16_t FG = ST77XX_WHITE;
const uint16_t BG = ST77XX_BLACK;
int16_t cursorX = 0;
int16_t cursorY = 0;
int16_t lineH;
void newLine() {
cursorX = 0;
cursorY += lineH;
if (cursorY + lineH > tft.height()) {
tft.fillScreen(BG);
cursorY = 0;
}
tft.setCursor(cursorX, cursorY);
}
void setup() {
Serial.begin(115200);
bt.begin(9600);
tft.initR(INITR_MINI160x80_PLUGIN);// ここはあなたの個体に合わせて
tft.setRotation(0);
uint8_t madctl = 0x08; // まず0x00から
madctl ^= 0x40; // MX をトグル(左右反転)
tft.sendCommand(0x36, &madctl, 1); // MADCTL
tft.fillScreen(ST77XX_BLACK);
tft.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
tft.setTextSize(1);
tft.setCursor(0,0);
tft.println("BT ready");
}
static char line[128];
static uint8_t idx = 0;
void loop() {
while (bt.available()) {
char c = bt.read();
if (c == '\r') continue;
if (c == '\n') {
line[idx] = 0;
// 行を描画(ここで初めてTFTを触る)
tft.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
tft.println(line);
idx = 0;
return;
}
if (idx < sizeof(line)-1) {
line[idx++] = c;
} else {
// 行が長すぎたら強制フラッシュ
line[idx] = 0;
tft.println(line);
idx = 0;
}
}
}
esp32とtft7789(graphictestの改変)
最初に入れておく
https://github.com/wilmsn/Arduino-ST7789-Library
/***************************************************
This is a library for the ST7789 IPS SPI display.
Written by Ananev Ilya.
****************************************************/
#include <Adafruit_GFX.h> // Core graphics library by Adafruit
#include <Arduino_ST7789.h> // Hardware-specific library for ST7789 (with or without CS pin)
#include <SPI.h>
#include "BluetoothSerial.h"
BluetoothSerial BT;
static char line[256];
static uint16_t idx = 0;
#define TFT_DC 19
#define TFT_RST 5
#define TFT_CS 10 // only for displays with CS pin
#define TFT_MOSI 23 // for hardware SPI data pin (all of available pins)
#define TFT_SCLK 18 // for hardware SPI sclk pin (all of available pins)
Arduino_ST7789 tft = Arduino_ST7789(TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK); //for display without CS pin
float p = 3.1415926;
void setup(void) {
Serial.begin(9600);
Serial.print("Hello! ST7789 TFT Test");
tft.init(240, 240); // initialize a ST7789 chip, 240x240 pixels
Serial.println("Initialized");
uint16_t time = millis();
tft.fillScreen(BLACK);
time = millis() - time;
Serial.println(time, DEC);
delay(500);
tft.setTextWrap(true);
tft.setTextColor(WHITE, BLACK);
tft.setTextSize(2);
tft.setCursor(0, 0);
tft.println("BT ready");
// 追加:Bluetooth SPP
BT.begin("ESP32-ST7789");
}
void loop() {
while (BT.available()) {
char c = (char)BT.read();
if (c == '\r') continue;
if (c == '\f') { // クリア
tft.fillScreen(BLACK);
tft.setCursor(0,0);
idx = 0;
continue;
}
if (c == '\n') { // 1行確定
line[idx] = 0;
tft.println(line);
idx = 0;
// 末尾に行ったらまとめて消す(ここだけ)
if (tft.getCursorY() > tft.height() - 16) {
tft.fillScreen(BLACK);
tft.setCursor(0,0);
}
continue;
}
if (idx < sizeof(line)-1) line[idx++] = c;
}
}
参考先
esp32 file system uploader (IDE2.X)
pluginに
https://github.com/earlephilhower/arduino-littlefs-upload/releases


ctrl shit P

skechbook/プロジェクト/data
ここにフォントfntを配置
misaki.hをideにインストールしてなかったのでインストール
https://github.com/mgo-tec/ESP32_SPIFFS_MisakiFNT
https://github.com/mgo-tec/ESP32_SPIFFS_UTF8toSJIS
(zipをそのままライブラリに)
フォントアップロード確認
#include "FS.h"
#include "LittleFS.h"
void dump(const char* path){
File f = LittleFS.open("/MSKG13.FNT","r");
Serial.printf("%s open=%s size=%d\n", path, f?"OK":"NG", f?(int)f.size():-1);
if(f){
for(int i=0;i<16 && f.available(); i++){
Serial.printf("%02X ", (uint8_t)f.read());
}
Serial.println();
f.close();
}
}
void setup(){
Serial.begin(115200);
delay(300);
if(!LittleFS.begin(true)){
Serial.println("LittleFS mount failed");
return;
}
Serial.println("LittleFS mounted");
dump("/Utf8Sjis.tbl");
dump("/mgotec48.FNT");
dump("/MSKG13.FNT");
}
void loop(){}
size=0でなければ完了、あとシリアルモニタとか別のIDEウインドウがあるとアップロード失敗する(10敗)
必ずスケッチフォルダ/dataにフォントは格納する。
以下を参照
日本語フォント導入
#include <Arduino.h>
#include <SPI.h>
#include <FS.h>
#include <LittleFS.h>
#include <Arduino_ST7789.h>
#include "BluetoothSerial.h"
// mgo-tec(あなたがLittleFS化した版を使う想定)
#include "ESP32_LittleFS_MisakiFNT.h"
#include "ESP32_LittleFS_UTF8toSJIS.h"
// ====== ファイル名(LittleFS直下) ======
static const char* UTF8SJIS_FILE = "/Utf8Sjis.tbl";
static const char* MISAKI_HALF_FILE = "/mgotec48.FNT";
static const char* MISAKI_ZEN_FILE = "/MSKG13.FNT";
// ====== ST7789配線(あなたの現状に合わせた) ======
#define TFT_DC 19
#define TFT_RST 5
#define TFT_MOSI 23
#define TFT_SCLK 18
Arduino_ST7789 tft(TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK);
// ====== BT ======
BluetoothSerial BT;
// ====== Misaki ======
ESP32_LittleFS_MisakiFNT MFR;
// ====== 画面/文字設定 ======
static const int TFT_W = 240;
static const int TFT_H = 240;
static const uint16_t FG = WHITE;
static const uint16_t BG = BLACK;
static const int SCALE = 2; // 1:8x8, 2:16x16
static const int GLYPH_W = 8 * SCALE;
static const int GLYPH_H = 8 * SCALE;
static const int LINE_CHARS = TFT_W / GLYPH_W; // SCALE=2なら15
static int curX = 0;
static int curY = 0;
// フォント描画バッファ(グローバル固定。ここが肝)
static uint8_t font_buf[LINE_CHARS][8];
// BT受信用(1行バッファ)
static char lineBuf[256];
static uint16_t idx = 0;
// ====== UTF-8 文字数で最大maxCharsまでのバイト長を返す ======
static size_t utf8_prefix_bytes(const char* s, size_t maxChars) {
size_t bytes = 0;
size_t chars = 0;
while (s[bytes] && chars < maxChars) {
uint8_t b = (uint8_t)s[bytes];
size_t adv = 1;
if ((b & 0x80) == 0x00) adv = 1; // 0xxxxxxx
else if ((b & 0xE0) == 0xC0) adv = 2; // 110xxxxx
else if ((b & 0xF0) == 0xE0) adv = 3; // 1110xxxx
else if ((b & 0xF8) == 0xF0) adv = 4; // 11110xxx(絵文字等)
else adv = 1; // 壊れたUTF-8は1扱い
// 途中で切れないようにする
for (size_t i = 1; i < adv; i++) {
if (!s[bytes + i]) return bytes;
}
bytes += adv;
chars++;
}
return bytes;
}
// ====== 8x8ビットマップをSCALE倍して描画 ======
static inline void drawGlyph8x8Scaled(int x, int y, const uint8_t g8[8]) {
for (int row = 0; row < 8; row++) {
uint8_t bits = g8[row];
for (int col = 0; col < 8; col++) {
bool on = (bits >> (7 - col)) & 1;
uint16_t c = on ? FG : BG;
for (int dy = 0; dy < SCALE; dy++) {
for (int dx = 0; dx < SCALE; dx++) {
tft.drawPixel(x + col * SCALE + dx, y + row * SCALE + dy, c);
}
}
}
}
}
static inline void clearScreen() {
tft.fillScreen(BG);
curX = 0;
curY = 0;
}
static inline void newLine() {
curX = 0;
curY += GLYPH_H;
if (curY + GLYPH_H > TFT_H) {
clearScreen();
}
}
// ====== 1行描画:UTF-8文字列 → 美咲フォント → ST7789 ======
static void drawUTF8Line(const char* utf8) {
// ここで「最大15文字」に切ってからライブラリに渡す(破壊対策)
char tmp[256];
strncpy(tmp, utf8, sizeof(tmp) - 1);
tmp[sizeof(tmp) - 1] = 0;
size_t n = utf8_prefix_bytes(tmp, LINE_CHARS);
tmp[n] = 0;
// ライブラリ変換(LINE_CHARSぶんをfont_bufへ)
// ※ ここがあなたのライブラリ関数名に合ってる前提
MFR.StrDirect_MisakiFNT_readALL(tmp, font_buf);
// 描画
for (int i = 0; i < LINE_CHARS; i++) {
if (curX + GLYPH_W > TFT_W) break;
drawGlyph8x8Scaled(curX, curY, font_buf[i]);
curX += GLYPH_W;
}
newLine();
}
// ====== ファイル存在/サイズ確認 ======
static void dumpFile(const char* path) {
File f = LittleFS.open(path, "r");
Serial.printf("%s open=%s size=%d\n", path, f ? "OK" : "NG", f ? (int)f.size() : -1);
if (f) f.close();
}
void setup() {
Serial.begin(115200);
delay(300);
// TFT init
tft.init(TFT_W, TFT_H);
clearScreen();
// LittleFS(ここは1回だけ)
bool ok = LittleFS.begin(false);
if (!ok) {
Serial.println("LittleFS mount NG -> format");
LittleFS.format();
ok = LittleFS.begin(false);
}
Serial.printf("LittleFS.begin = %s\n", ok ? "OK" : "NG");
if (!ok) {
Serial.println("STOP");
while (1) delay(1000);
}
dumpFile(UTF8SJIS_FILE);
dumpFile(MISAKI_HALF_FILE);
dumpFile(MISAKI_ZEN_FILE);
// Misaki init(あなたがLittleFS化したライブラリならOK)
// ここでライブラリがLittleFS.begin()を呼ばないようにしてある前提
MFR.LittleFS_Misaki_Init3F(UTF8SJIS_FILE, MISAKI_HALF_FILE, MISAKI_ZEN_FILE);
// BT
BT.begin("ESP32-ST7789-JP");
BT.println("BT ready");
BT.println("Send lines with \\n. \\f=clear, !=draw test, ?=tx test.");
drawUTF8Line("BT ready");
}
void loop() {
while (BT.available()) {
char c = (char)BT.read();
if (c == '\r') continue;
// command: clear
if (c == '\f') {
clearScreen();
idx = 0;
BT.println("[CLR]");
continue;
}
// command: tx test
if (c == '?') {
BT.println("TEST_TX: こんにちは / Hello / 123");
continue;
}
// command: draw test
if (c == '!') {
const char* s = "TEST_DRAW: こんにちは / Hello / 123";
drawUTF8Line(s);
BT.println(s);
continue;
}
// line end
if (c == '\n') {
lineBuf[idx] = 0;
drawUTF8Line(lineBuf);
idx = 0;
continue;
}
// buffer
if (idx < sizeof(lineBuf) - 1) {
lineBuf[idx++] = c;
} else {
// overflow: 強制確定して流す(落ちない方を優先)
lineBuf[sizeof(lineBuf) - 1] = 0;
drawUTF8Line(lineBuf);
idx = 0;
}
}
}
ここまでやってなんだけどrasberrypi Picoとかのほうがライブラリ回り簡単でいい気がする。
→ESP32でwebページ型で試す。
GPT5.2log(トラブル履歴)
了解。ここまでの往復を「やったこと → 出た問題 → 切り分け → 修正」の流れで、ログっぽく残すね(時系列)。
最初の目的
スマホ→Bluetoothでテキストを飛ばして、ESP32 + ST7789(240x240) に表示。最終的にAR的な“文字だけ表示”用。
第1段階:UNO + HC-06 + ST7735(仮の最小系)
やったこと
・AndroidのBluetooth terminal からHC-06へ送信、UNOがSoftwareSerialで受けて、Adafruit_ST7735で表示。
出た問題
・鏡文字(左右反転)が起きたり戻ったり。
・画面ノイズや半分死ぬ/残像っぽい。
・Serialで送れる文字列に上限がある。
・日本語表示はそのままだと無理(ASCII想定、フォント/エンコーディング問題)。
切り分け/対処
・MADCTL(0x36)でMX/MYなどをトグルして鏡文字は解消できた。
・ノイズは配線/初期化/リフレッシュ絡みの疑い、最終的にはESP32へ移行。
第2段階:ESP32 + ST7789へ移植
やったこと
・ESP32はBluetooth内蔵なのでHC-06を捨ててBluetoothSerialへ。
・ST7789はCSなし基板で、Adafruit_ST7789のコンストラクタがCS必須寄りで詰まり、別ライブラリ(Arduino_ST7789系)へ。
出た問題
・最初は何も映らない(配線/ドライバ/初期化パラメータ不一致)。
・一度映ってから、受信文字送信はできるがノイズが出る/位置ズレ疑い。
切り分け/対処
・動いたgraphicstest構成を土台にして、BT受信→printlnで表示する方向に寄せた。
第3段階:日本語表示(美咲フォント + 変換テーブル)
狙い
・UTF-8の日本語を受けて、Shift_JIS変換テーブル(Utf8Sjis.tbl)経由で美咲フォント(FNT)を引き、8x8ビットマップとして描画。
やったこと
・mgo-tec系の「SPIFFS + 美咲フォント」手法をESP32環境に持ち込み。
・最初はヘッダが無い、関数名が違う、LittleFSが宣言されてない等でコンパイルが崩れる。
出た問題(コンパイル)
・ESP32_SPIFFS_MisakiFNT.h: No such file(ライブラリ導入不足)
・UTF8_to_SJIS_1Char などメンバ不一致(記事の版と手元ライブラリのAPI差)
・LittleFS was not declared(FS種別の混線)
対処
・ライブラリ/ヘッダを正しく入れ、API差は「手元の実装に合わせる」方針へ。
・ファイルシステムは一旦「LittleFSで統一」に寄せる判断。
第4段階:ファイルシステム地獄(SPIFFS/LittleFS/SDの混線)
観測ログ(典型)
・E ... Corrupted dir pair / mount failed (-84)(LittleFSが壊れてる)
・SPIFFS: mount failed -10025(SPIFFSも呼ばれて失敗してる=誰かがSPIFFS.beginしてる)
・なのに LittleFS mounted が出る(戻り値チェック無しの嘘ログ)
切り分けで分かったこと
・スケッチ側だけじゃなく、ライブラリ側が勝手に SPIFFS.begin(true) を呼んでいた。
・ログの card initialized. はSDじゃなくSPIFFS初期化ログとして書かれてて紛らわしい。
・一時期 Detected size(4096k) smaller than ... header(8192k) も出て、Flash size/Partition前提がズレたバイナリを書いて起動すら落ちた(この時は土台が崩壊)。
修正
Flash size/Partitionを実フラッシュ(4MB)に合わせる(8MBヘッダ問題を消す)
「成功してるのにmountedと表示」問題を潰すため、LittleFS.begin() の戻り値を必ずチェックしてログを正直にする
最重要:ライブラリ内のSPIFFS依存を排除
・#include をやめて LittleFS.h に変更
・SPIFFS.begin/open/exists... を全部 LittleFS... に置換
結果
・起動ログが LittleFS initialized. になり、Utf8Sjis.tbl / mgotec48.FNT / MSKG13.FNT が正常に open & size取得できるようになった。
第5段階:実行時クラッシュ(heap_caps_free outside heap)
観測ログ
・ファイル読み込み成功の後、数秒〜十数秒で
assert failed: heap_caps_free ... free() target pointer is outside heap areas
原因推定
・ほぼメモリ破壊(バッファオーバーラン/二重free/壊れたポインタ)
・状況的に「受信した1行が長すぎる」→「フォント変換/描画バッファが固定長想定」→踏み抜いて後でfreeで爆発、が一番整合。
修正
・BT受信1行を、表示可能文字数に制限してから描画処理へ渡す。
・240x240で16x16相当表示(SCALE=2)なら1行15文字なので、まず「15文字まで」にクリップ。
・UTF-8はバイト数が可変なので、UTF-8文字境界で切る(途中バイトで切って文字化け/破壊しない)。
結果
・「15文字までなら成功」で安定動作を確認。
最終状態(現時点の到達点)
・ESP32 + ST7789(240x240) で、BluetoothSerial経由のテキスト受信→表示が動作。
・美咲フォント一式(Utf8Sjis.tbl / mgotec48.FNT / MSKG13.FNT)は LittleFS から読み出せる。
・ライブラリはSPIFFS依存を全撤去してLittleFSに統一。
・クラッシュは「入力行が長すぎて内部の固定長バッファを踏む」だったので、1行15文字制限で回避して安定。
補足として残しておくと良い“地雷”
・「LittleFS begin(true) なのに Corrupted dir pair が出る」みたいな矛盾は、“別コンポーネントが先に触ってログを吐いてる/二重初期化/戻り値無視ログ”で起きる。まずログを正直にする(戻り値チェック)と切り分けが速い。
・SPIFFS/LittleFSを混ぜると、どっちの領域をどのpartitionで使ってるか不明になって壊れやすい。今回は「ライブラリ側が勝手にSPIFFS.begin」してたのが根っこ。
・Flash size/partitionがズレると、起動時にassertで死ぬ(4MB実機に8MBヘッダを書かない)。

