ILI9341って?
割と安価に手に入る320x240ドット(他にもあるかも)のLCD。XPT2046のタッチパネルが付いてたりついてなかったり。殆どの製品はSDカード(殆どの場合microではなくデカいやつ)のスロットもついてて繋いだら使える。Teensy4.0にはmicroSDのスロットがないから割と重宝する。LCDパネルはIPSのものとそうでないものがある。IPSでも書いてないことが多々あって、買って使ってみたらIPSじゃんラッキーってことも。
前提
- Arduino IDE
- Teensy4.0 or 4.1
ライブラリのインストール
ライブラリマネージャでili9341_t4と検索すれば出てくるのでそれをインストール。GitHubはhttps://github.com/vindar/ILI9341_T4 .ちなみにライセンスはLGPL2.1。
サンプル
99なんちゃらバルーンってのが動けばok。配線はサンプルのコードの上の方に書いてある通りにすればok。ピンのアサインは変えられるみたいけど、DCが10番ってのは守ったほうがいいらしい。ハードウェアのアクセラレーションが効くとか。
ただ、サンプルのコードの中に
tft.output(&Serial);
ってのがあるんだけど、これをコメントアウトしないと動作しなかった。これがないとこのライブラリのログがシリアルモニタに出てこないけど、使えないよりはマシなので、サンプル実行したんだけど画面真っ白で動かなかったらコメントアウトするってことで。
メモリ食いだけどパフォーマンス良い
ILI9341_T4はパフォーマンスと引き換えにめっちゃメモリ食う。320x240x2バイトのフレームバッファを2枚持ってて、さらに差分判定のためのバッファを2つ(サイズは任意だが4000バイトx2くらい)必要とする。それらをうまいこと使って裏でDMA使って送信することでCPU負荷を減らしてるぽく、Vsync同期取ってチラつきのない描画もできる。
SPIのクロック
配線を短くしないと高い周波数で動作しない的なことがサンプルの最初の方に書いてあるけど本当にそうだった。逆に、短くすればブレッドボードでも60MHzとかで動作した。できるだけ配線を短く、あとはどこまで上げられるかギリギリを攻める。高くすればするだけ、DMA転送にかかる時間が短くなるらしい。
SDカードとSPIの共用
これはできる(MOSI/MISO/SCKを同じとこに繋ぐ)んだけど、SDカードのアクセス時にDMA転送が終わりきってないと即ハングする(再起動もかからないハング)。30MHzでフル更新にかかる時間が41msとからしい。なので、
delay(50);
とかで対処する。
DMA割り込みがオーディオの邪魔をする
I2Sのオーディオ入出力と同時に使うと、オーディオにパツパツノイズが出る。これは割り込み優先度を下げることで解消した。
tft.begin(SPI_SPEED);
tft.setIRQPriority(254);
フォントの描画
ライブラリにフォントの描画があるが、これがILI9341_T4独自のものでよくわからんかった。Adafruit形式のフォントを使いたかったので自前で作った。ベースラインがなんとかとか、そこそこエグい感じ。
void setAdaFont(const GFXfont* font) {
currentAdaFont = font;
// 新:ベースラインオフセット を再計算
if (currentAdaFont) {
int minYO = 0;
for (uint8_t c = currentAdaFont->first; c <= currentAdaFont->last; ++c) {
auto& g = currentAdaFont->glyph[c - currentAdaFont->first];
minYO = std::min(minYO, (int)g.yOffset);
}
// minYO は負値(例えば -4), 反転して正にする
adaBaselineOffset = -minYO * textsize;
} else {
adaBaselineOffset = 0;
}
}
void drawAdaChar(uint16_t c) {
if (!currentAdaFont) return;
if (c < currentAdaFont->first || c > currentAdaFont->last) return;
// フォント情報取得
const GFXglyph* glyph = currentAdaFont->glyph + (c - currentAdaFont->first);
const uint8_t* bitmap = currentAdaFont->bitmap + glyph->bitmapOffset;
int w = glyph->width;
int h = glyph->height;
int xo = glyph->xOffset;
int yo = glyph->yOffset;
// cursor_x は文字単位座標 → ドット拡大は次行で掛ける
// cursor_y はピクセル座標(baseline) → そのまま使う
// オフセット xo, yo とドット (xx,yy) のみ textsize 倍する
int baseX = cursor_x * textsize + xo * textsize;
int baseY = cursor_y + yo * textsize;
uint8_t bits = 0;
int bit = 0, bo = 0;
for (int yy = 0; yy < h; ++yy) {
for (int xx = 0; xx < w; ++xx) {
if ((bit++ & 7) == 0) bits = bitmap[bo++];
if (bits & 0x80) {
// only the dot block is scaled
fillRect(
baseX + xx * textsize,
baseY + yy * textsize,
textsize, textsize,
textcolor
);
}
bits <<= 1;
}
}
// 次の文字位置は文字単位で進める(拡大は baseX 計算時に反映)
cursor_x += glyph->xAdvance;
}
void print(const char* text) {
const char* p = text;
while (*p) {
char c = *p++;
if (c == '\r') continue;
if (c == '\n') {
advanceLine();
continue;
}
if (currentAdaFont) {
// Adafruit GFX フォント用
if (wrap && cursor_x + measureAdaCharWidth(c) > width) {
advanceLine();
}
drawAdaChar(static_cast<uint16_t>(c));
}
}
}