ESP32-ATM0130B3,ST7789とのSPI通信処理1

秋月から購入した240×240のLDCディスプレイをESP32(Arduinoベースではない)で動かそうと思いいろいろやっていたら1週間が過ぎてしまった。

初めに

グラフィックLCD TFTカラー ATM0130B3: オプトエレクトロニクス 秋月電子通商-電子部品・ネット通販
電子部品,通販,販売,半導体,IC,LED,マイコン,電子工作グラフィックLCD TFTカラー ATM0130B3秋月電子通商 電子部品通信販売

これは、ST7789を4-line serial interface Ⅰという結線方式で操作しているらしい。
4-line serial interface Ⅰでは、CSX, DCX, SDA, WRX、もしくはCS, MOSI, SCLK, WRXの3線SPI+制御用の1線でつなぐ方式とのこと。
ちょっと変わった制御方式で、ST7789へのデータ書き込みはいいのだが、ST7789からのデータ読み込みにはちょっとコツが必要だった。これについては後で。

ST7789とのSPI送信について

Arduinoベースの場合

ArduinoのAdafruit_GFX_Libraryとその派生ライブラリを使ってみたところ、特に問題なく実装ができた。
ただsetSPISpeedでSPIの周波数を変えてみても表示スピードに変化がなかった。
個人的にはSPI周波数を上げることで、処理速度は向上するのだろうと漠然と思っていたため。

なぜかなとロジアナで見たところ、ESP32ベースは固定というかソフトウェア制御のようだった。

これがSPIバスの状態。チャンネル3がCS。SCLKは一定で発生し、データが送信されているようなのだが、その周波数は大体1.2MHzだった。
個別にSPIをGPIO制御で行ったときの速度が同じだったのでたぶんそうなのだろうと思う。

秋月のサンプルソースベースのESP APIを使った場合

秋月のサンプルソースベースで、SPI部分のみをESP32 APIのspi_~ベースの関数群に置き換えたものを使用した場合、関数で設定した周波数となっていた。

上のは5MHz周波数。

ただ、コマンドとパラメータの書き込みを別関数かつspi_device_transmitを使ったため、転送データの間はかなり時間経過があるような状態になってしまった。

左がコマンド、右がそれに対するデータになるのだが、その間43μ秒の時間が空いてしまった。

本来であれば、コマンドとデータをDMAを使ったSPI転送をすることで、送信データの作成と転送を別々に実行することで高速化を図ることができるのだろうと思う。

送信側については、ここまで。

ST7789からのSPI受信について

ESP32 APIベースのものについては、以下githubにサンプルコードがあった。

esp-idf/examples/peripherals/spi_master/lcd/main/spi_master_example_main.c at 73db142403c6e5b763a0e1c07312200e9b622673 · espressif/esp-idf
Espressif IoT Development Framework. Official development framework for Espressif SoCs. - espressif/esp-idf

上記のコードをベースに作成していったのだが、一向にデータが受信できる気配が感じられなかった。

ここで問題となったのが4-line serial interface Ⅰという接続方法。
ST7789V2の仕様書の8.4.5を見ると、Interface-І形式は、データの送信がなく、直接データの受信が始まるといったものだった。しかも8bit以外のデータではダミークロックサイクルもあるという。

ダミークロックサイクルまでは、spi_~の呼び出しで何とかできるのだが、送信データなしの受信だけというのは、spi_~の関数では実現できないようだった。

結果、Interface-І形式ではハードウェアSPIが使えず、ソフトウェアでSPI通信をするしかデータ受信ができなかった。

一応、以下のような実装で、ST7789V2からのデータ読み込みは可能になった。

#include <stdint.h>
#include <driver/gpio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <rom/ets_sys.h>

const gpio_num_t DSP_SCK = GPIO_NUM_18;  // Serial Clock
const gpio_num_t DSP_MOSI = GPIO_NUM_23; // Serial Data In
const gpio_num_t DSP_CS = GPIO_NUM_5;    // Chip Select
const gpio_num_t DSP_Reset = GPIO_NUM_17;
const gpio_num_t DSP_DC = GPIO_NUM_16;

const uint32_t delay = 0;

void write(uint8_t data, int dc)
{
    // ピン初期化
    gpio_config_t io_conf = {
        1ULL << DSP_MOSI,
        GPIO_MODE_OUTPUT,
        GPIO_PULLUP_DISABLE,
        GPIO_PULLDOWN_DISABLE,
        GPIO_INTR_DISABLE
    };
    gpio_config(&io_conf);
    gpio_set_level(DSP_DC, dc);

    for (int8_t i = 7; i >= 0; i--) {
        gpio_set_level(DSP_MOSI, data & (1 << i)? 1 : 0);
        gpio_set_level(DSP_SCK, 1);
        gpio_set_level(DSP_SCK, 0);
    }
}

uint32_t read(uint8_t bits, uint8_t dummy)
{
    // ピン初期化
    gpio_config_t io_conf = {
        1ULL << DSP_MOSI,
        GPIO_MODE_INPUT,
        GPIO_PULLUP_ENABLE,
        GPIO_PULLDOWN_DISABLE,
        GPIO_INTR_DISABLE
    };
    gpio_config(&io_conf);
    gpio_set_level(DSP_DC, 1);
    for (int8_t i = 0; i < dummy; i++) {  //any dummy clocks
        gpio_set_level(DSP_SCK, 1);
        gpio_set_level(DSP_SCK, 0);
    }

    uint32_t response = 0;
    for (int8_t i = 0; i < bits; i++) {
        response <<= 1;
        response |= (gpio_get_level(DSP_MOSI) == 0 ? 0 : 1);
        gpio_set_level(DSP_SCK, 1);
        gpio_set_level(DSP_SCK, 0);
    }
    return response;
}

uint32_t readwrite8(uint8_t cmd, uint8_t bits, uint8_t dummy)
{
    gpio_set_level(DSP_CS, 0);
    write(cmd, 0);
    auto data = read(bits, dummy);
    gpio_set_level(DSP_CS, 1);
    return data;
}

extern "C" void app_main()
{
    // ピン初期化
    gpio_config_t io_conf = {
        1ULL << DSP_Reset | 1ULL << DSP_SCK | 1ULL << DSP_CS | 1ULL << DSP_DC | 1ULL << DSP_MOSI,
        GPIO_MODE_OUTPUT,
        GPIO_PULLUP_DISABLE,
        GPIO_PULLDOWN_DISABLE,
        GPIO_INTR_DISABLE
    };
    gpio_config(&io_conf);
    gpio_set_level(DSP_CS, 1);

    gpio_set_level(DSP_Reset, 1);
    vTaskDelay(20/portTICK_PERIOD_MS);
    gpio_set_level(DSP_Reset, 0);
    vTaskDelay(20/portTICK_PERIOD_MS);
    gpio_set_level(DSP_Reset, 1);
    vTaskDelay(20/portTICK_PERIOD_MS);

    auto data = readwrite8(0x04, 24, 1);
    printf("%x\n", data);

    data = readwrite8(0x04, 24, 1);

    printf("%x\n", data);
}

これがST7789から来たデータ。ちなみに周波数は1~1.2MHzぐらいだった。

またMOSIはINPUT/OUTPUTの設定でやったらデータが受け取れなかった。
送信時、受信時で、INPUT、OUTPUTの設定をきちんとしないといけないようだ。

最後に

本当はSPIでDMA転送で、表示の高速化をやってみたかったのだけど、 ST7789からのデータの読み込みができないことが気になってしまい、まずはそちらから解決していってみた。

ただ、ST7789からデータを読み込むというケースは実際にないのかもしれない。
これに関してググってみても、あまり情報がなかったから、誰も気にしていなかったのか?

コメント

タイトルとURLをコピーしました