TeensyのILI9341_T4ライブラリを使う

## 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。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107818/2599c4b9-1dfa-4c52-a7de-4fe3915447d7.png)

## サンプル ##
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107818/bd616f6c-f9ee-46b9-9ccb-58d515f4938b.png)

99なんちゃらバルーンってのが動けばok。配線はサンプルのコードの上の方に書いてある通りにすればok。ピンのアサインは変えられるみたいけど、DCが10番ってのは守ったほうがいいらしい。ハードウェアのアクセラレーションが効くとか。

ただ、サンプルのコードの中に

```cpp
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とからしい。なので、 
```cpp
delay(50);
```
とかで対処する。

## DMA割り込みがオーディオの邪魔をする ##
I2Sのオーディオ入出力と同時に使うと、オーディオにパツパツノイズが出る。これは割り込み優先度を下げることで解消した。
```cpp
tft.begin(SPI_SPEED);
tft.setIRQPriority(254);
```

## フォントの描画 ##
ライブラリにフォントの描画があるが、これがILI9341_T4独自のものでよくわからんかった。Adafruit形式のフォントを使いたかったので自前で作った。ベースラインがなんとかとか、そこそこエグい感じ。
```cpp
    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));
            }
        }
    }
    
```