メインコンテンツまでスキップ

reComputer J4012 Classic で SPI ディスプレイを使用する

はじめに

この Wiki では、Seeed reComputer J4012 Classic 上で SPI ディスプレイを接続して駆動する方法を紹介します。40 ピンヘッダを介して SPI ディスプレイを使用するための基本的なワークフローを扱い、ハードウェア配線、SPI インターフェース設定、デバイスノードの確認、依存関係のインストール、C++ デモのコンパイル、および簡単な表示テストの実行を含みます。

本ガイドでは、ST7789 SPI LCD ディスプレイを例として使用します。ST7735 や ILI9341 など、他の SPI ディスプレイモジュールでも全体的なワークフローは類似していますが、配線の詳細、表示解像度、初期化シーケンス、ドライバパラメータなどが異なる場合があります。

本ガイドの方法は、他の SPI ディスプレイアプリケーションの参考としても利用できます。

ハードウェアの準備

必要なハードウェア

項目説明
reComputer J4012 ClassicJetson ベースのエッジ AI コンピュータ
SPI ディスプレイモジュール本ガイドでは ST7789 SPI LCD ディスプレイを例として使用
ジャンパワイヤ(デュポン線)ディスプレイを 40 ピンヘッダに接続するために使用
HDMI ディスプレイまたは SSH 端末デバイスの設定およびテストに使用

必要なソフトウェア

ソフトウェア説明
JetPack / UbuntureComputer J4012 Classic 上で動作するオペレーティングシステム
g++C++ デモをコンパイルするために使用
spidevLinux の SPI ユーザ空間インターフェース
sysfs GPIODC や RES などの GPIO ピンを制御するために使用

ハードウェア接続

40 ピンヘッダ

reComputer J4012 Classic には 40 ピン拡張ヘッダが用意されています。このヘッダを通して SPI 信号や GPIO ピンを使用し、小型ディスプレイモジュールを接続できます。

40-pin header pinout

図 1. reComputer J4012 Classic の 40 ピンヘッダのピン配置

SPI ディスプレイ配線例: ST7789

本ガイドでは、ST7789 SPI ディスプレイをサンプルのディスプレイモジュールとして使用します。以下の表に従って、ディスプレイを 40 ピンヘッダに接続します。

ST7789 ピンJ4012 Classic 40 ピン機能説明
GNDPin 6GNDグラウンド
VCCPin 13.3Vディスプレイの電源入力
SCLPin 23SPI SCLKSPI クロック信号
SDAPin 19SPI MOSIJ4012 Classic からディスプレイへの SPI データ
RESPin 31GPIO / PQ.06ハードウェアリセット信号
DCPin 29GPIO / PQ.05データ / コマンド選択
CSPin 24SPI CSSPI チップセレクト
BLKPin 173.3Vバックライト電源、常時オン

ST7789 SPI display wiring

図 2. reComputer J4012 Classic と ST7789 SPI ディスプレイ間の配線

SPI インターフェースを有効化する

ディスプレイデモを実行する前に、40 ピンヘッダ上の SPI インターフェースを有効にする必要があります。

Jetson-IO 設定ツールを開きます:

sudo /opt/nvidia/jetson-io/jetson-io.py

40 ピンヘッダ設定メニューを選択します。

Jetson-IO main menu

図 3. Jetson-IO メインメニュー

Configure header pins manually

図 4. "Configure header pins manually" を選択

Enable spi1 function

図 5. 40 ピンヘッダ上で spi1 機能を有効化

設定を保存し、デバイスを再起動します:

sudo reboot

デバイスが再起動したら、spidev カーネルモジュールをロードします:

sudo modprobe spidev

この手順により、/dev/spidev* を確認またはアクセスする前に、Linux ユーザ空間 SPI ドライバが利用可能であることを確実にします。

SPI デバイスを確認する

デバイスが再起動したら、SPI デバイスノードが生成されているか確認します:

ls /dev/spidev*

SPI が正しく有効化されていれば、次のような出力が表示される場合があります:

/dev/spidev0.0
/dev/spidev0.1

Check SPI device node

図 6. SPI デバイスノードが正常に生成された状態

本ガイドでは、ST7789 ディスプレイは Pin 19、Pin 23、および Pin 24 に接続された SPI 信号を使用します。サンプルコードではデフォルトで /dev/spidev0.0 を使用します。システムで生成される SPI デバイスノードが異なる場合は、コード内の SPI デバイスパスを変更してください。

依存関係をインストールする

パッケージリストを更新します:

sudo apt update

C++ コンパイラをインストールします:

sudo apt install -y g++

SPI デバイスノードが存在するか確認します:

ls /dev/spidev*

GPIO ピンをエクスポートする

ディスプレイデモを実行する前に、DCRES で使用する GPIO ピンをエクスポートします。デモはこれら 2 本のピンを sysfs GPIO インターフェース経由で制御します。

本ガイドでは:

信号40 ピンGPIO 名GPIO 番号
DCPin 29PQ.05453
RESPin 31PQ.06454

/sys/class/gpio/export を通して GPIO をエクスポートする際は、GPIO 名ではなく GPIO 番号を使用します。本ガイドでは、GPIO 453PQ.05 に、GPIO 454PQ.06 に対応します:

sudo sh -c 'echo 453 > /sys/class/gpio/export'
sudo sh -c 'echo 454 > /sys/class/gpio/export'

エクスポート後、対応する GPIO ノードは PQ.05PQ.06 として表示されるはずです。GPIO ノードが存在するか確認します:

ls /sys/class/gpio/PQ.05
ls /sys/class/gpio/PQ.06

ST7789 ディスプレイデモを実行する

このセクションでは、C++ デモを使用して、ST7789 SPI ディスプレイが reComputer J4012 Classic 上で正しく動作することを確認します。

このデモは次の処理を行います:

  1. SPI デバイス /dev/spidev0.0 をオープンします。

  2. SPI モード、1 ワードあたりのビット数、および SPI 速度を設定します。

  3. sysfs GPIO を通して DCRES ピンを制御します。

  4. ST7789 ディスプレイコントローラを初期化します。

  5. 画面全体をさまざまな RGB565 カラーで連続的に塗りつぶします。

このデモで使用する配線は次のとおりです。

信号40 ピンGPIO / デバイス
SPI SCLKPin 23SPI クロック
SPI MOSIPin 19SPI MOSI
SPI CSPin 24SPI CS
RESPin 31/sys/class/gpio/PQ.06
DCPin 29/sys/class/gpio/PQ.05
BLKPin 173.3V

st7789_spi.cpp という名前のファイルを作成します:

nano st7789_spi.cpp

次の C++ デモコードを追加します:

#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#include <cstdint>
#include <cstring>

int spi_fd = -1;
int dc_fd = -1;
int res_fd = -1;

// GPIO paths for reComputer J4012 Classic
// DC -> Pin 29 -> PQ.05
// RES -> Pin 31 -> PQ.06
const char* DC_DIR_PATH = "/sys/class/gpio/PQ.05/direction";
const char* DC_VAL_PATH = "/sys/class/gpio/PQ.05/value";
const char* RES_DIR_PATH = "/sys/class/gpio/PQ.06/direction";
const char* RES_VAL_PATH = "/sys/class/gpio/PQ.06/value";

bool write_file(const char* path, const char* value)
{
int fd = open(path, O_WRONLY);
if (fd < 0) {
std::cerr << "open failed: " << path << std::endl;
return false;
}

write(fd, value, strlen(value));
close(fd);
return true;
}

bool init_gpios()
{
if (!write_file(DC_DIR_PATH, "out")) return false;
if (!write_file(RES_DIR_PATH, "out")) return false;

dc_fd = open(DC_VAL_PATH, O_WRONLY);
res_fd = open(RES_VAL_PATH, O_WRONLY);

if (dc_fd < 0 || res_fd < 0) {
std::cerr << "open gpio value failed" << std::endl;
return false;
}

return true;
}

void gpio_write(int fd, int value)
{
lseek(fd, 0, SEEK_SET);
write(fd, value ? "1" : "0", 1);
}

void WriteCommand(uint8_t cmd)
{
gpio_write(dc_fd, 0);
write(spi_fd, &cmd, 1);
}

void WriteData(uint8_t data)
{
gpio_write(dc_fd, 1);
write(spi_fd, &data, 1);
}

void WriteDataBuf(const uint8_t* data, size_t len)
{
gpio_write(dc_fd, 1);

const size_t CHUNK_SIZE = 4096;
size_t bytes_sent = 0;

while (bytes_sent < len) {
size_t current_chunk = (len - bytes_sent > CHUNK_SIZE) ? CHUNK_SIZE : (len - bytes_sent);

struct spi_ioc_transfer tr;
std::memset(&tr, 0, sizeof(tr));

tr.tx_buf = (unsigned long)(data + bytes_sent);
tr.rx_buf = 0;
tr.len = current_chunk;
tr.speed_hz = 24000000;
tr.bits_per_word = 8;

if (ioctl(spi_fd, SPI_IOC_MESSAGE(1), &tr) < 0) {
std::cerr << "SPI chunk transfer failed at " << bytes_sent << std::endl;
return;
}

bytes_sent += current_chunk;
}
}

void ST7789_Reset()
{
gpio_write(res_fd, 0);
usleep(200000);

gpio_write(res_fd, 1);
usleep(200000);
}

void ST7789_Init()
{
ST7789_Reset();

WriteCommand(0x11); // Sleep Out
usleep(120000);

WriteCommand(0x3A); // Pixel Format Set
WriteData(0x05); // RGB565

WriteCommand(0x36); // Memory Access Control
WriteData(0x08); // Default direction

WriteCommand(0x21); // Display Inversion ON

WriteCommand(0x29); // Display ON
usleep(20000);
}

void ST7789_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
WriteCommand(0x2A); // Column Address Set
WriteData(x0 >> 8);
WriteData(x0 & 0xFF);
WriteData(x1 >> 8);
WriteData(x1 & 0xFF);

WriteCommand(0x2B); // Row Address Set
WriteData(y0 >> 8);
WriteData(y0 & 0xFF);
WriteData(y1 >> 8);
WriteData(y1 & 0xFF);

WriteCommand(0x2C); // Memory Write
}

void ST7789_FillColor(uint16_t color)
{
const int width = 240;
const int height = 320;

ST7789_SetWindow(0, 0, width - 1, height - 1);

uint8_t screen_buf[height * width * 2];

uint8_t high = color >> 8;
uint8_t low = color & 0xFF;

for (int i = 0; i < height * width * 2; i += 2) {
screen_buf[i] = high;
screen_buf[i + 1] = low;
}

WriteDataBuf(screen_buf, sizeof(screen_buf));
}

bool init_spi()
{
spi_fd = open("/dev/spidev0.0", O_RDWR);
if (spi_fd < 0) {
std::cerr << "open /dev/spidev0.0 failed" << std::endl;
return false;
}

uint8_t mode = SPI_MODE_0;
uint8_t bits = 8;
uint32_t speed = 24000000;

ioctl(spi_fd, SPI_IOC_WR_MODE, &mode);
ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);

return true;
}

uint16_t Rainbow_HSV_To_RGB565(int h)
{
float r = 0, g = 0, b = 0;

int sector = h / 60;
float fractional = (h % 60) / 60.0f;

float x = 1.0f - fractional;
float y = fractional;

switch (sector) {
case 0: r = 1.0f; g = y; b = 0.0f; break;
case 1: r = x; g = 1.0f; b = 0.0f; break;
case 2: r = 0.0f; g = 1.0f; b = y; break;
case 3: r = 0.0f; g = x; b = 1.0f; break;
case 4: r = y; g = 0.0f; b = 1.0f; break;
case 5: r = 1.0f; g = 0.0f; b = x; break;
default: r = 0.0f; g = 0.0f; b = 0.0f; break;
}

uint8_t R8 = static_cast<uint8_t>(r * 255);
uint8_t G8 = static_cast<uint8_t>(g * 255);
uint8_t B8 = static_cast<uint8_t>(b * 255);

return ((R8 >> 3) << 11) | ((G8 >> 2) << 5) | (B8 >> 3);
}

int main()
{
uint16_t color = 0x001F;
int i = 0;

std::cout << "Init SPI..." << std::endl;
if (!init_spi()) return -1;

std::cout << "Init GPIO..." << std::endl;
if (!init_gpios()) return -1;

std::cout << "Init ST7789..." << std::endl;
ST7789_Init();

std::cout << "Fill 240x320 Color..." << std::endl;

while (1) {
color = Rainbow_HSV_To_RGB565(i++ % 360);
ST7789_FillColor(color);
usleep(10000);
}

close(spi_fd);
close(dc_fd);
close(res_fd);

return 0;
}

デモをコンパイルします:

g++ st7789_spi.cpp -o st7789_spi

デモを実行します:

sudo ./st7789_spi

配線と SPI 設定が正しければ、ST7789 ディスプレイはさまざまな色で連続的にリフレッシュされます。

ST7789 display demo result

図 7. ST7789 ディスプレイ デモ結果

コードの説明

SPI 初期化

このデモは /dev/spidev0.0 を開き、SPI インターフェースを次のように設定します。

パラメータ
SPI デバイス/dev/spidev0.0
SPI モードSPI_MODE_0
1 ワード当たりのビット数8
SPI 速度24000000 Hz

システムで生成される SPI デバイスノードが異なる場合は、デモコード内の次の行を変更してください:

spi_fd = open("/dev/spidev0.0", O_RDWR);

例えば、デバイスノードが /dev/spidev1.0 の場合は、次のように変更します:

spi_fd = open("/dev/spidev1.0", O_RDWR);

GPIO 制御

このデモは sysfs GPIO を使用して DCRES ピンを制御します。

信号40 ピン ピン番号sysfs GPIO パス
DCPin 29/sys/class/gpio/PQ.05
RESPin 31/sys/class/gpio/PQ.06

DC ピンはコマンドモードとデータモードを切り替えるために使用されます。RES ピンは ST7789 ディスプレイをリセットするために使用されます。

ST7789 初期化

このデモは次のコマンドで ST7789 ディスプレイを初期化します。

コマンド説明
0x11スリープ解除
0x3Aピクセルフォーマット設定
0x05RGB565 フォーマット
0x36メモリアクセス制御
0x21ディスプレイ反転 On
0x29ディスプレイ On

画面塗りつぶしテスト

このデモは RGB565 フォーマットを使用してフルスクリーンを塗りつぶします。デモで使用しているディスプレイ解像度は次のとおりです:

const int width = 240;
const int height = 320;

使用している ST7789 ディスプレイの解像度が異なる場合は、実際の画面サイズに合わせてこれらの値を変更してください。

SPI データ転送

フルスクリーンバッファは、1 回の小さな SPI 転送よりも大きくなります。そのため、このデモでは表示データをチャンクに分けて送信します:

const size_t CHUNK_SIZE = 4096;

これにより転送サイズの制限を回避し、フルスクリーンリフレッシュをより安定させることができます。

トラブルシューティング

/dev/spidev* デバイスが見つからない

考えられる原因:

  1. SPI インターフェースが有効になっていない。

  2. Jetson-IO の設定が保存されていない。

  3. SPI を有効にした後にデバイスを再起動していない。

  4. デバイスツリーが SPI デバイスノードを公開していない。

  5. システムイメージに想定される SPI 設定が含まれていない。

推奨される確認事項:

ls /dev/spidev*

SPI デバイスが見つからない場合は、Jetson-IO を再度実行し、SPI が有効になっているか確認してください。

SPI または GPIO へアクセスすると Permission Denied になる

C++ デモを実行したときにパーミッションエラーが表示される場合は、sudo を付けて実行してみてください:

sudo ./st7789_spi

SPI デバイスおよび GPIO ノードのパーミッションを確認することもできます:

ls -l /dev/spidev*
ls -l /sys/class/gpio/PQ.05/value
ls -l /sys/class/gpio/PQ.06/value

GPIO パスが存在しない

/sys/class/gpio/PQ.05 または /sys/class/gpio/PQ.06 が存在しない場合、使用しているシステムイメージでは GPIO の命名またはエクスポート方法が異なる可能性があります。

利用可能な GPIO ノードを確認してください:

ls /sys/class/gpio/

その後、実際のシステムに合わせてデモコード内の GPIO パスを変更してください。

SPI はオープンできるがディスプレイに何も表示されない

考えられる原因:

  1. ディスプレイの配線が正しくない。

  2. SPI デバイスパスが正しくない。

  3. CS、DC、または RES ピンが正しく接続されていない。

  4. ディスプレイコントローラが ST7789 ではない。

  5. ディスプレイに異なる初期化シーケンスが必要。

  6. バックライトピンに電源が供給されていない。

推奨される確認事項:

  1. VCC が Pin 1 に接続されていることを確認します。

  2. GND が Pin 6 に接続されていることを確認します。

  3. BLK が Pin 17 に接続され、バックライトが点灯していることを確認します。

  4. SCLSDACS が Pin 23、Pin 19、Pin 24 に接続されていることを確認します。

  5. RESDC が Pin 31 と Pin 29 に接続されていることを確認します。

  6. SPI 速度を下げて再度テストします。

  7. ディスプレイモジュールのデータシートからディスプレイコントローラの型番を確認します。

バックライトは点灯するが画像が表示されない

バックライトは点灯しているが画像が表示されない場合、電源配線は正しいものの、SPI 通信またはディスプレイ初期化が正しくない可能性があります。

次の点を確認してください:

  1. 正しい SPI デバイスを使用しているかどうか。

  2. DC と RES ピンが正しく設定されているかどうか。

  3. ディスプレイドライバが ST7789 コントローラに対応しているかどうか。

  4. 画面解像度が正しいかどうか。

  5. ディスプレイモジュールに行または列のオフセット設定が必要かどうか。

画像の色がおかしい

考えられる原因:

  1. RGB と BGR のカラー順序が一致していない。

  2. ディスプレイ反転設定が異なる。

  3. MADCTL パラメータが使用しているパネルに適していない。

  4. ディスプレイモジュールが、わずかに異なる ST7789 初期化シーケンスを使用している。

推奨される対処方法:

  1. MADCTL の値を変更してみてください。

  2. ディスプレイ反転を有効または無効にしてみてください。

  3. ST7789 ディスプレイモジュールのデータシートを確認してください。

  4. 使用しているモジュールが RGB または BGR のどちらのカラー順序を使用しているか確認してください。

画像の向きが正しくない

画像が回転している、または反転している場合は、初期化関数内の MADCTL コマンドパラメータを変更してください:

WriteCommand(0x36);
WriteData(0x08);

正しい値はディスプレイモジュールの向きによって異なります。

ディスプレイのリフレッシュが遅い

考えられる原因:

  1. SPI クロック速度が低すぎる。

  2. プログラムが毎回フルスクリーンをリフレッシュしている。

  3. ディスプレイモジュールのリフレッシュ性能に制限がある。

  4. C++ デモは検証用に単純なフルスクリーン塗りつぶし方式を使用している。

推奨される対処方法:

  1. SPI クロック速度を徐々に上げてください。

  2. 不要なフルスクリーンリフレッシュを避けてください。

  3. アプリケーションが対応している場合は、変更された領域のみをリフレッシュしてください。

  4. 現在のカラーフィルテストは、基本的なハードウェア検証デモとしてのみ使用してください。

まとめ

この wiki では、Seeed reComputer J4012 Classic に 40 ピンヘッダを介して SPI ディスプレイを接続する方法を紹介しました。一般的な SPI ディスプレイのワークフローには、ディスプレイの配線、SPI インターフェースの有効化、SPI デバイスノードの確認、依存関係のインストール、C++ デモのコンパイル、およびディスプレイテストの実行が含まれます。

このガイドでは、例として ST7789 SPI LCD をディスプレイモジュールに使用しました。他の SPI ディスプレイでも全体的な手順は同様ですが、ディスプレイドライバ、解像度、初期化シーケンス、および配線の詳細は、実際のディスプレイモジュールに合わせて調整する必要があります。

リソース

技術サポート & 製品ディスカッション

弊社製品をお選びいただきありがとうございます。弊社は、製品をできるだけスムーズにご利用いただけるよう、さまざまなサポートを提供しています。お好みやニーズに応じて選択できる、複数のコミュニケーションチャネルをご用意しています。

Loading Comments...