「JLX12864G 液晶を SPI コマンドで動かしてみる」https://qiita.com/nanbuwks/items/09b3271fc078f2964a8d
では、ドット操作で文字列描画を行ってました。フォントデータはソース中に埋め込んで使いましたが、もうちょっと高度なことを行いたいので LovyanGFX が使えないかと調べてみました。
環境
- Arduino IDE 1.8.19
- esp32 by Espressif Systems Version 2.0.14
- LovyanGFX Version 1.1.12
- ESP32 オジリナルボード
cf., https://github.com/nanbuwks/ozone12864
ST7567 コントローラへの対応
ドキュメントには ST77xx しか対応が書かれてませんが、
https://github.com/lovyan03/LovyanGFX/releases
によると、
1.1.6 時点で ST7565 に対応しているらしいです。
ST7567 と ST75465 は互換性があるらしく、また@lavyan03 さんによると、ST75465 搭載の AQM1248A は動く状態になっていたらしいので、JLX12864G も動くのではないかな?
サンプルスケッチ
LovyanGFX は多くのボードやパネルが自動認識されますが、さすがにオリジナルボードと対応外のLCDだとアレでしょう。手動設定で動かします。
「ファイル」-「スケッチ例」-「カスタムライブラリのスケッチ例」-「LovyanGFX-HowToUse」-「2_user_setting」を開きます。
インスタンスを用意
「接続するパネルの型にあったインスタンスを用意します」
のところには ST7565 の雛形はありませんでしたが、試しに
lgfx::Panel_ST7565 _panel_instance;
と書いてみたらOKでした。
パネルに合わせて書き換え
タッチスクリーンを無効化
こちらコメントアウトします。
// タッチスクリーンの型にあったインスタンスを用意します。(必要なければ削除)
//lgfx::Touch_CST816S _touch_instance;
lgfx::Touch_FT5x06 _touch_instance; // FT5206, FT5306, FT5406, FT6206, FT6236, FT6336, FT6436
//lgfx::Touch_GSL1680E_800x480 _touch_instance; // GSL_1680E, 1688E, 2681B, 2682B
//lgfx::Touch_GSL1680F_800x480 _touch_instance;
//lgfx::Touch_GSL1680F_480x272 _touch_instance;
//lgfx::Touch_GSLx680_320x320 _touch_instance;
//lgfx::Touch_GT911 _touch_instance;
//lgfx::Touch_STMPE610 _touch_instance;
//lgfx::Touch_TT21xxx _touch_instance; // TT21100
//lgfx::Touch_XPT2046 _touch_instance;
SPI の設定
sclk
と mosi
の設定を行い、更に cfg.pin_dc
を追加しました。4wire の SPI 設定に合わせて cfg.spi_32ire
を false
にします。
SPIクロックは、当初40000000としていたら全然動作せず、@lovyan03 さんのアドバイスを受けてクロックを下げました。
// SPIバスの設定
cfg.spi_host = VSPI_HOST; // 使用するSPIを選択 ESP32-S2,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST
// ※ ESP-IDFバージョンアップに伴い、VSPI_HOST , HSPI_HOSTの記述は非推奨になるため、エラーが出る場合は代わりにSPI2_HOST , SPI3_HOSTを使用してください。
cfg.spi_mode = 1; // SPI通信モードを設定 (0 ~ 3)
cfg.freq_write = 10000000; // 送信時のSPIクロック (最大80MHz, 80MHzを整数で割った値に丸められます)
cfg.freq_read = 10000000; // 受信時のSPIクロック
cfg.spi_3wire = false; // 受信をMOSIピンで行う場合はtrueを設定
cfg.use_lock = true; // トランザクションロックを使用する場合はtrueを設定
cfg.dma_channel = SPI_DMA_CH_AUTO; // 使用するDMAチャンネルを設定 (0=DMA不使用 / 1=1ch / 2=ch / SPI_DMA_CH_AUTO=自動設定)
// ※ ESP-IDFバージョンアップに伴い、DMAチャンネルはSPI_DMA_CH_AUTO(自動設定)が推奨になりました。1ch,2chの指定は非推奨になります。
cfg.pin_sclk = 18; // SPIのSCLKピン番号を設定
cfg.pin_mosi = 23; // SPIのMOSIピン番号を設定
cfg.pin_miso = 19; // SPIのMISOピン番号を設定 (-1 = disable)
cfg.pin_dc = 17; // SPIのD/Cピン番号を設定 (-1 = disable)
// SDカードと共通のSPIバスを使う場合、MISOは省略せず必ず設定してください。
//*/
パネル制御
cs と rst はSPI設定ではなくパネル制御に書くようです。
{ // 表示パネル制御の設定を行います。
auto cfg = _panel_instance.config(); // 表示パネル設定用の構造体を取得します。
cfg.pin_cs = 5; // CSが接続されているピン番号 (-1 = disable)
cfg.pin_rst = 16; // RSTが接続されているピン番号 (-1 = disable)
.
.
.
cfg.readable = false; // データ読出しが可能な場合 trueに設定
cfg.invert = true; // パネルの明暗が反転してしまう場合 trueに設定
バックライト設定
{ // バックライト制御の設定を行います。(必要なければ削除)
auto cfg = _light_instance.config(); // バックライト設定用の構造体を取得します。
cfg.pin_bl = 15; // バックライトが接続されているピン番号
cfg.invert = false; // バックライトの輝度を反転させる場合 true
テスト動作
最初は全然表示しませんでしたが、先に書いたように SPI クロックを 10MHz に下げたら何やら画面が出てきました。
文字がカスレているのはモノクロ液晶なのにカラーでデータを与えているからです。
文字色を以下のように指定するとくっきりします。
display.setTextColor(TFT_BLACK);
背景が黒いのは
/src/lgfx/v1/panel/Panel_SSD1306.hpp
で以下のように初期化設定がされていますが、
//コントラスト設定
0x23, //Vo voltage regulator internal resistor ratio set
0x81, //Electronic volume mode set
0x1C, //Electronic volume register set
この 0x1C
を 0x08
に調整するとうまくいきました。
ここのコントラスト値はLCDのパネル種類ごとに異なるので、それにあわせるのが良いぽいですね。
なので以下の初期化コマンドを display.init();
のあとに呼び出すようにしてみました。
void Init_LCD()
{
pinMode(LCD_CS, OUTPUT);
digitalWrite(LCD_CS,HIGH);
pinMode(LCD_RS, OUTPUT);
digitalWrite(LCD_RS,HIGH);
pinMode(LCD_RSET, OUTPUT);
digitalWrite(LCD_RSET,LOW);
delay(500);
digitalWrite(LCD_RSET,HIGH);
digitalWrite(LCD_CS,LOW);
digitalWrite(LCD_RS,LOW);
SPI.transfer(0xAE); // display off
SPI.transfer(0xA0); // ADC normal ( Column address is left to right )
SPI.transfer(0xC8); // line scan is reverce ( bottom to top )
SPI.transfer(0xA3); // LCD bias = 1/7
SPI.transfer(0x2C); // Voltage supply up converter on
delay(50);
SPI.transfer(0x2E); // Voltage supply voltage regurator on
delay(50);
SPI.transfer(0x2F); // foltage supply voltage follower on
SPI.transfer(0x23); // contrast rough (0x20-0x27)
SPI.transfer(0x81); // contrast trim command
SPI.transfer(0x08); // contrast trim data (0x00-0x3F)
SPI.transfer(0xA4); // all dot displays: normal
SPI.transfer(0x40); // display start line set 0 (0x40-0x7F)
SPI.transfer(0xA7); // display normal/reverse: reverce
SPI.transfer(0xAF); // display on
digitalWrite(LCD_CS,HIGH);
}
もともと、
SPI.transfer(0xA7); // display normal/reverse: reverce
は 0xA6 で normal で設定するのですが、 LovyanGFX のからみで reverce にする必要がありました。
実行結果
きれいに表示できるようになりました!
スケッチ
全体はこのようになっています。
#include <LovyanGFX.hpp>
#include <SPI.h>
#define LCD_CS 5
#define LCD_RS 17
#define LCD_RSET 16
#define SPI_CLK 18
#define TFT_BACKLIGHT_PIN 15
class LGFX : public lgfx::LGFX_Device
{
lgfx::Panel_ST7565 _panel_instance;
//lgfx::Panel_ST7735 _panel_instance;
//lgfx::Panel_SSD1306 _panel_instance;
lgfx::Bus_SPI _bus_instance; // SPIバスのインスタンス
lgfx::Light_PWM _light_instance;
public:
LGFX(void)
{
{ // バス制御の設定を行います。
auto cfg = _bus_instance.config(); // バス設定用の構造体を取得します。
// SPIバスの設定
cfg.spi_host = VSPI_HOST; // 使用するSPIを選択 ESP32-S2,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST
// ※ ESP-IDFバージョンアップに伴い、VSPI_HOST , HSPI_HOSTの記述は非推奨になるため、エラーが出る場合は代わりにSPI2_HOST , SPI3_HOSTを使用してください。
cfg.spi_mode = 1; // SPI通信モードを設定 (0 ~ 3)
cfg.freq_write = 10000000; // 送信時のSPIクロック (最大80MHz, 80MHzを整数で割った値に丸められます)
cfg.freq_read = 10000000; // 受信時のSPIクロック
cfg.spi_3wire = false; // 受信をMOSIピンで行う場合はtrueを設定
cfg.use_lock = true; // トランザクションロックを使用する場合はtrueを設定
cfg.dma_channel = SPI_DMA_CH_AUTO; // 使用するDMAチャンネルを設定 (0=DMA不使用 / 1=1ch / 2=ch / SPI_DMA_CH_AUTO=自動設定)
// ※ ESP-IDFバージョンアップに伴い、DMAチャンネルはSPI_DMA_CH_AUTO(自動設定)が推奨になりました。1ch,2chの指定は非推奨になります。
cfg.pin_sclk = 18; // SPIのSCLKピン番号を設定
cfg.pin_mosi = 23; // SPIのMOSIピン番号を設定
cfg.pin_miso = 19; // SPIのMISOピン番号を設定 (-1 = disable)
cfg.pin_dc = 17; // SPIのD/Cピン番号を設定 (-1 = disable)
_bus_instance.config(cfg); // 設定値をバスに反映します。
_panel_instance.setBus(&_bus_instance); // バスをパネルにセットします。
}
{ // 表示パネル制御の設定を行います。
auto cfg = _panel_instance.config(); // 表示パネル設定用の構造体を取得します。
cfg.pin_cs = 5; // CSが接続されているピン番号 (-1 = disable)
cfg.pin_rst = 16; // RSTが接続されているピン番号 (-1 = disable)
cfg.pin_busy = -1; // BUSYが接続されているピン番号 (-1 = disable)
// ※ 以下の設定値はパネル毎に一般的な初期値が設定されていますので、不明な項目はコメントアウトして試してみてください。
cfg.panel_width = 128; // 実際に表示可能な幅
cfg.panel_height = 64; // 実際に表示可能な高さ
cfg.offset_x = 0; // パネルのX方向オフセット量
cfg.offset_y = 0; // パネルのY方向オフセット量
cfg.offset_rotation = 0; // 回転方向の値のオフセット 0~7 (4~7は上下反転)
cfg.dummy_read_pixel = 8; // ピクセル読出し前のダミーリードのビット数
cfg.dummy_read_bits = 1; // ピクセル以外のデータ読出し前のダミーリードのビット数
cfg.readable = false; // データ読出しが可能な場合 trueに設定
cfg.invert = true; // パネルの明暗が反転してしまう場合 trueに設定
cfg.rgb_order = false; // パネルの赤と青が入れ替わってしまう場合 trueに設定
cfg.dlen_16bit = true; // 16bitパラレルやSPIでデータ長を16bit単位で送信するパネルの場合 trueに設定
cfg.bus_shared = true; // SDカードとバスを共有している場合 trueに設定(drawJpgFile等でバス制御を行います)
// 以下はST7735やILI9163のようにピクセル数が可変のドライバで表示がずれる場合にのみ設定してください。
// cfg.memory_width = 240; // ドライバICがサポートしている最大の幅
// cfg.memory_height = 320; // ドライバICがサポートしている最大の高さ
_panel_instance.config(cfg);
}
{ // バックライト制御の設定を行います。(必要なければ削除)
auto cfg = _light_instance.config(); // バックライト設定用の構造体を取得します。
cfg.pin_bl = 15; // バックライトが接続されているピン番号
cfg.invert = false; // バックライトの輝度を反転させる場合 true
cfg.freq = 44100; // バックライトのPWM周波数
cfg.pwm_channel = 7; // 使用するPWMのチャンネル番号
_light_instance.config(cfg);
_panel_instance.setLight(&_light_instance); // バックライトをパネルにセットします。
}
setPanel(&_panel_instance); // 使用するパネルをセットします。
}
};
LGFX display;
SPISettings spiSettings = SPISettings(SPI_CLK, SPI_MSBFIRST, SPI_MODE1);
void Init_LCD()
{
pinMode(LCD_CS, OUTPUT);
digitalWrite(LCD_CS,HIGH);
pinMode(LCD_RS, OUTPUT);
digitalWrite(LCD_RS,HIGH);
pinMode(LCD_RSET, OUTPUT);
digitalWrite(LCD_RSET,LOW);
delay(500);
digitalWrite(LCD_RSET,HIGH);
digitalWrite(LCD_CS,LOW);
digitalWrite(LCD_RS,LOW);
SPI.transfer(0xAE); // display off
SPI.transfer(0xA0); // ADC normal ( Column address is left to right )
SPI.transfer(0xC8); // line scan is reverce ( bottom to top )
SPI.transfer(0xA3); // LCD bias = 1/7
SPI.transfer(0x2C); // Voltage supply up converter on
delay(50);
SPI.transfer(0x2E); // Voltage supply voltage regurator on
delay(50);
SPI.transfer(0x2F); // foltage supply voltage follower on
SPI.transfer(0x23); // contrast rough (0x20-0x27)
SPI.transfer(0x81); // contrast trim command
SPI.transfer(0x08); // contrast trim data (0x00-0x3F)
SPI.transfer(0xA4); // all dot displays: normal
SPI.transfer(0x40); // display start line set 0 (0x40-0x7F)
SPI.transfer(0xA7); // display normal/reverse: normal
SPI.transfer(0xAF); // display on
digitalWrite(LCD_CS,HIGH);
}
void setup(void)
{
display.init();
SPI.begin();
Init_LCD();
display.setTextSize((std::max(display.width(), display.height()) + 255) >> 8);
display.fillScreen(TFT_WHITE);
}
uint32_t count = ~0;
void loop(void)
{
display.startWrite();
display.setRotation(++count & 7);
display.setColorDepth((count & 8) ? 16 : 24);
display.setTextColor(TFT_BLACK);
display.drawNumber(display.getRotation(), 16, 0);
display.drawString("R", 30, 16);
display.drawString("G", 40, 16);
display.drawString("B", 50, 16);
display.drawRect(30,30,display.width()-60,display.height()-60,count*7);
display.drawFastHLine(0, 0, 10);
display.endWrite();
}