FPGAでILI9341搭載2.8インチLCDを制御（後編）

## はじめに

こちらは **「FPGAでILI9341搭載2.8インチLCDを制御（前編）」** の後編となります。  
前編をまだご覧になっていない方は、こちらから先にお読みください。
→[前編の記事はこちら](https://qiita.com/BruCandy/items/da79328ff94775c80c61)

すべてのコードはこちらのリポジトリで公開しています。  
→[GitHub - fpga-spi-display](https://github.com/BruCandy/fpga-spi-display)

---

## 今回の目標
- ペテルセングラフの描画  
- 制限付きで画像の描画（135×135px / 3色）

---

## ペテルセングラフの描画

### ペテルセングラフとは？
ペテルセングラフは、10個の頂点と15本の辺を持つ対称性の高いグラフで、  グラフ理論の分野では非常に有名な構造のひとつです。  
大学の授業でも登場し印象に残っていたため、今回はこの図形を描画対象として選びました。

### 水平・垂直線の描画方法
水平線や垂直線は、前回紹介した正方形の描画方法とほぼ同じ要領で描くことができます。

- 水平線を描く場合は、Y座標を固定し、X座標の範囲を指定して描画します。
- 垂直線を描く場合は、X座標を固定し、Y座標の範囲を指定して描画します。

今回は、太さを1ピクセルにしたいため、どちらの線も描画時に XまたはYの片方を1ピクセル幅に固定します。

データの送信手順は、正方形の描画と同様で、以下のコマンドを送信した後に、描画する座標、色のデータを送ります。

| 内容         | コマンド |
|--------------|----------|
| SET_COLUMN   | 2A       |
| SET_PAGE     | 2B       |
| WRITE_RAM    | 2C       |

つまり、X座標が100～120、Y座標が100に水平線を書きたい場合は、  `SET_COLUMN` コマンド送信後に `00 64（100）`・`00 78（120）` を送信し、  `SET_PAGE` 送信後には `00 64 (100)`・`00 64 (100)`（Y=100で固定）を指定します。  
その後、`WRITE_RAM` コマンドを送信し、必要なピクセル数分（この例では21ピクセル）色データを連続して送信すれば水平線が描画されます。
一方で垂直線の場合はX座標を固定します。


### 斜線の描画方法
水平・垂直線は簡単に描画できますが、斜線を描くには少し工夫が必要です。  
そこで、前回の記事でも紹介した[GitHubのリポジトリ](https://github.com/rdagger/micropython-ili9341)にある`ili9341.py`でも使用していた「Bresenhamのアルゴリズム」を採用しました。

Bresenhamのアルゴリズムとは、始点と終点を指定してできるだけ滑らかに見えるピクセルの列で直線を描画するための方法です。整数のみで実装可能であるため、限られた環境下でも使用することができます。




### 実装結果
座標指定に注意しつつ、水平・垂直線、そして斜線を描くことで、以下のようにペテルセングラフを描画することができました。
なお、BresenhamのアルゴリズムをVerilogで実装する際には、多くの警告が発生しましたが、動作には支障なく、期待通りの描画が行えました。



<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3671248/e7b76a84-4793-4afa-94d6-8226a64c39fa.jpeg" width="400">


## 画像の描画（制限付き）

### 描画における制限
前回の記事でも述べたように、以下の制限があります。

- 画像サイズ：135×135 pxまで（GowinのpROM IP制限）
- 色数：3色（白・黒・中間色）

もともとは BGR565 形式で送信を試みましたが、表示画像の縦横比がおかしくなりました。そこで、BGR888 形式に切り替えたところ、縦横比に問題が起こらず表示できました。しかし、色が正確に表示されないのであらかじめ3色に減色してからディスプレイに表示します。
また、今回はGowinのpROMを使用するため、その制限により135×135 pxまでの画像しか表示できないです。

---

### 画像の準備（Pythonスクリプト）

Pythonで以下の処理を行いました。
- 画像サイズを 135×135 にリサイズ
- 白・黒・中間色の3色に減色
- `.jpg`→`.hex`→`.mi` 形式の順に変換してpROM用に保存

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3671248/1fe114b6-4966-4848-9458-1d5357bbaf45.png" width="500">

使用した変換コードも[リポジトリ内](https://github.com/BruCandy/FPGA_SPI_Display/tree/main/draw_picture/data)にあります。

画像の変換後にどのような画像がディスプレイに表示されるか把握したい場合は`hex2img.py`を使ってください。
各ピクセルはBGRの平均で3色に分けているため、明暗がはっきりした画像がきれいに描画されます。例えば、鉛筆のみで描かれた絵はうまく表示できないです。

---

### IP Core（pROM）の準備

Gowin EDA に内蔵されている pROM IP Core を使用しているため、GitHubに公開しているすべてのコードをそのままGowin EDAでTang Nano 9Kに書き込んでも動作しません。
そのため、以下の手順で画像をROMに組み込む必要があります。


#### 手順
1. IP Core Generator を開く  
2. `Hard Module` → `Memory` → `Block Memory` → `pROM` を選択  
3. IP Customization にて以下を設定  
    - Address Depth : 18225（135×135）
    - Data Width : 24（BGR888）
    - Memory Initialization File : `.mi`形式の画像ファイル
4. OK を押して生成

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3671248/27888edb-7893-4aa1-a369-e5b0afe1424b.jpeg" width="400">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3671248/642e9863-fec9-4e49-9cab-6708f7ae401c.jpeg" width="400">

### 画像の表示方法
pROMの準備が完了したら、Verilog内でpROMのインスタンスを宣言し、アドレスをインクリメントしながら1ピクセルずつ画像データを読み出して表示します。

今回の画像は、1ピクセルあたり24bit（BGR888） の形式で .mi ファイルに保存されており、各行に1ピクセル分の色情報が格納されています。

.mi形式の一部を以下に示します。各行はアドレスとBGR888の色情報を示しており、pROMからこの情報を読み出して1ピクセルずつ描画します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3671248/7f5a48a0-27b8-4501-a8de-1182c29a9de0.png" width="400">



そのため、表示の際には WRITE_RAM コマンドを送信した後、pROMから取得したデータをそのまま送ることで画像を描画できます。

ただし、今回の場合は、すべての色情報をそのまま扱おうとすると、表示される色が不安定になったり、正しく表示されないといった問題が発生しました。
そこで、処理を簡略化し安定した表示を実現するために、画像をあらかじめ白・黒・中間色の3色に減色してから表示する方式に変更しました。
この方法により、輪郭やコントラストが明瞭な画像であれば、比較的きれいに描画できるようになりました。




### 実装結果

画像描画の実装結果です。右下の画像は鉛筆で描いた絵であるため、輪郭がはっきりと表示されていません。
しかし、そのほかの画像は輪郭がはっきり描画されており、制限下では及第点かと思います。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3671248/f8f22846-db23-4ca8-b068-ca10e58d15c4.png" width="600">

---

## 今後の課題
- ペテルセングラフ実装時に発生する警告の解消
- 色の問題の解決
- 画像サイズの制限突破（SDカード利用を検討中）

また、GitHubにあるコードの整理も進めていく予定です。

## おわりに

ここまでご覧いただきありがとうございました。  
本記事は筆者の初投稿記事であり、内容に誤りがあるかもしれません。ご指摘いただければ助かります。

今回は、**Tang Nano 9K** と **ILI9341搭載 2.8インチLCD** によるSPI通信を通じて、
- ペテルセングラフの描画
- 制限付き画像の描画

に挑戦しました。

前回の記事の冒頭でも述べた通り、制限はあるものの図形や画像の描画に成功した一例として、この記事がこれからFPGAやディスプレイ制御に取り組む方々の参考になれば幸いです。

---

## 参考リンク
- [前編の記事](https://qiita.com/BruCandy/items/da79328ff94775c80c61)
- [ILI9341 データシート（Adafruit版）](https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf)
- [github.com/rdagger/micropython-ili9341](https://github.com/rdagger/micropython-ili9341)
- [ラズベリーパイ Pico にILI9341(MSP2807)を接続する](https://kousaku-prog.com/ili9341msp2807)
- [wiki/ブレセンハムのアルゴリズム](https://ja.wikipedia.org/wiki/%E3%83%96%E3%83%AC%E3%82%BC%E3%83%B3%E3%83%8F%E3%83%A0%E3%81%AE%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0)