RaspberryPi Pico と trykernelで dualcoreマイコン開発　その2 I2C接続のローコストLCDを操作

## キャラクタ液晶を操作したい！

・長く使える汎用システムを作る上で、表示は欲しくなりますね。
・以前秋月で購入したI2C接続の16文字×2行のLCDがありましたので、pico-sdkを使って操作しました。
・ちなみにコア１のtry-kernelを使っても操作できましたので、機会があれば公開します。
・LCDは特にリアルタイム性が必要ないので、最終的にはコア0で操作を考えています。
・LCDは3.3V系なので、picoで直接操作できるのもよい点です
https://akizukidenshi.com/catalog/g/g105693/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3657924/7ba84cb8-b779-46cd-80ca-0f471ce8acba.png)

## LCD仕様
・型番：ACM1602
・I2C接続
・スレーブアドレス　0x50(7bitモード)
・速度　100kbpsまでと表記ありました。念のため50kbpsで使用
・文字は1文字ずつ送信。(よくある連続送信モードがない？)
・文字コードはACSII文字に準拠しているので使いやすい。


## pico-sdk のサンプル
・参考にしたpico-sdkのサンプルは下記です
https://github.com/raspberrypi/pico-examples/tree/master/i2c/lcd_1602_i2c
・ただしPCF8574というIOエキスパンダを使った例になっており、今回使うACM1602とは少し違います。
・ACM1602はPICマイコンが中にはいっているそうです。解析された方のHPを貼っておきます。
https://www.ne.jp/asahi/air/variable/picmel/applications/ACM1602NI/index.html

## pico-sdk サンプルのポイント
■制御ピンの変更
　picoにはI2C0と1の2つがあります。また各I2Cに割り当てられるピンも複数あります。
  例えばI2C0はGP4,5(def)　GP8,9　GP12,13　GP16,17　GP20,21　と5ペアもあります。
　サンプルではGP4,5のペアで定義されており、変更する場合はpoco.hの情報を変更します。
 ~~~
 // ..sdk/1.5.1/src/boards/include/boards/pico.h
 
#ifndef PICO_DEFAULT_I2C
#define PICO_DEFAULT_I2C 0
#endif
#ifndef PICO_DEFAULT_I2C_SDA_PIN
#define PICO_DEFAULT_I2C_SDA_PIN 8
#endif
#ifndef PICO_DEFAULT_I2C_SCL_PIN
#define PICO_DEFAULT_I2C_SCL_PIN 9
#endif
~~~

■I2Cの設定
~~~c
i2c_init(i2c_default, 50 * 1000);  //I2C初期化（50kHz）
gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C));
~~~
・i2c_initで通信速度を定義します。
・gpio_set_functionでSDA,SCLのピン設定を行います
・gpio_pull_upを使えば、内蔵のプルアップ(24kΩくらいだそう)を有効にできます。
・bi_decl() 
マクロはバイナリファイルに情報を埋め込むためのものです。
埋め込まれた情報は、picotool というツールを使用して読み出すことができます。
picotoolは、バイナリから情報を抽出したり、Pico自体の情報を読み取ることができます。
・bi_2pins_with_func() 
2つの GPIO ピンのペアを特定の機能に割り当てるために使用されます。

■I2C書き込みのAPI
・今回読み出しは使用しないので、割愛します。
・書き込みのAPIは3つありました。
~~~c
int i2c_write_blocking(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop) {
    return i2c_write_blocking_internal(i2c, addr, src, len, nostop, NULL, NULL);
}

int i2c_write_blocking_until(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop,
                             absolute_time_t until) {
    timeout_state_t ts;
    return i2c_write_blocking_internal(i2c, addr, src, len, nostop, init_single_timeout_until(&ts, until), &ts);
}

int i2c_write_timeout_per_char_us(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop,
                                  uint timeout_per_char_us) {
    timeout_state_t ts;
    return i2c_write_blocking_internal(i2c, addr, src, len, nostop,
                                       init_per_iteration_timeout_us(&ts, timeout_per_char_us), &ts);
}
~~~

・単純な書き込み処理で、タイムアウトを気にする必要がない場合は「i2c_write_blocking」を使用します。

・特定の時間内に書き込みを完了する必要がある場合は「i2c_write_blocking_until」を使用します。

・文字ごとのタイムアウトを設定する必要がある場合は「i2c_write_timeout_per_char_us」を使用します。
<br>
<br>

・今回はタイムアウトを実装したいので、i2c_write_timeout_per_char_usを使いました。

・ちなみに「i2c_write_blocking_until」 関数の until 引数は、ミリ秒 (ms) ではなく、absolute_time_t 型の絶対時間です。

・absolute_time_t は、Raspberry Pi Pico SDK で定義されている時間型で、特定の時点からの経過時間を表すのではなく、絶対的な時刻を表します。つまり、特定の日時や時刻を示す値ですがちょっと使いにくいので今回は候補から外しました。

## 実際のコード例
・以下のコードでコマンドと文字送信を行っています。
・1文字、1コマンド送る毎に5msディレイを入れています。

~~~c
/* ======================================================================
		ACM1602用　LCDへの文字送信
        引数：uint8_t data：送信する文字
        文字列を送信する場合は、lcdPrintを使う
   ====================================================================== */
static void i2cLcdWriteData(uint8_t data)
{
    uint8_t t_data[2] = { LCD_DATA, 0x00 };

    t_data[1] = data;
    i2c_write_timeout_per_char_us(i2c_default, LCD_DEV_ADD, t_data, sizeof(t_data), false, I2C_TIMEOUT);
    sleep_ms(5);
        
}
/* ======================================================================
		ACM1602用　LCDへの文字列送信
        引数：char str：送信する文字
        lcdSetCursorで表示場所を決めない場合、現在のカーソル位置から描画
   ====================================================================== */
void lcdPrint(char* str)
{
    while (*str) {
        i2cLcdWriteData((uint8_t)*str++);
    }
}
/* ======================================================================
		ACM1602用　LCDへのコマンド送信
        引数：char t_command：送信するコマンド
   ====================================================================== */
void i2clcd_writeCommand(char t_command){

    uint8_t t_data[2] = { LCD_CMD, 0x00 };

    t_data[1]=t_command;
    i2c_write_timeout_per_char_us(i2c_default, LCD_DEV_ADD, t_data, sizeof(t_data), false, I2C_TIMEOUT);
    sleep_ms(5);
}
~~~
<br>
・2025/04/23追記<br>
i2c_write_timeout_per_char_us を使うとI2Cデバイスが接続されていない場合、<br>
無限ループに入るようです。(実機で確認。フォーラムにも同じ質問あり)<br>
i2c_write_timeout_us を使うと正常にタイムアウトで抜けてくれることを確認しました。<br>
i2c_write_timeout_per_char_us ⇒ i2c_write_timeout_us をおすすめします。
<br>
<br>
・初期化の流れは以下のように行っています。

![flash.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3657924/771b947f-2e43-4420-b706-f14d9d4e70c2.png)

・サンプル表示例
![a.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3657924/f30094ed-8b24-4e06-9be2-c4fd91242ee8.jpeg)




今日はここまで。