今回は、実際にキャラクターLCDをPicoに接続し表示をさせたいと思います。
使用するLCDは、前回と同様に「KKHMF DC 5V 1602 LCD ディスプレイモジュール 16×2キャラクタ LCDブルーブラックライト」と「EasyWordMall 1602 LCD ブラック IIC/I2C/TWI/SPI シリアル インタフェース ボード モジュール」(ともにamazonです)を使用します。

LCD ディスプレイモジュール とI2C シリアル インタフェース ボードモジュールとは次のような接続になっています。
LCDの制御のために、RS/RW/EN/LEDの各端子のビット位置を設定します。ちなみに LCDへのデータ転送は、4BitモードになっていますのでD7~D4までの4Bitで設定します。

    ;; lcd 1602 port
    ;;
    ;;      D0 > RS
    ;;      D1 > R/W
    ;;      D2 > E
    ;;      D3 > LED on/off
    ;;      D4 > DB4
    ;;      D5 > DB5
    ;;      D6 > DB6
    ;;      D7 > DB7  

LCDへの書き込みは4ビットごとに書き込みします。
書き込みには、コマンドデータと表示用データの書き込みの2種類があります。
モードの切り替えは RS端子で行います。
コマンドデータ >> RS=0
表示用データ  >> RS=1

書き込みと読み出しのモード切替はRW端子で行います。
読み出し >> RW=1
書き込み >> RW=0
今回の場合は、読み出しは行わないので、RW端子は常に 0のままです。

実際の書き込みタイミングは、ENを正論理でパルスを送ります。
つまり、 EN >> 0>1>0のタイミングの1発だけのパルスを生成します。

バックライトは常にONにしています
パックライトON >> LED=1
バックライトOFF>> LED=0

I2C経由の書き込みには、pico_i2c.cの中にある、pio_i2c_write_blocking(PIO pio,uint sm,uint8_t addr,uint8_t *txbuf,uint len)を使用しました。
pio_i2c_write_blocking は、pio、smおよびaddrを与えることにより、ポインター変数経由で複数ブロックの書き込みが可能です。
今回は、アドレス演算子&を使い、一つだけのブロックとしました。
pio_i2c_write_blocking(pio,sm,addr,&command,1) ;

コマンドデータの4ビットの書き込みは下記のようになりました

int lcd4bitwrite(PIO pio,uint sm,uint8_t addr,uint8_t command){
    int err;
    command <<= 4;
    command |= (uint8_t)0b00001000;
    command &= (uint8_t)0b11111000;
    err=pio_i2c_write_blocking(pio,sm,addr,&command,1);
    command |= (uint8_t)0b00001100;
    command &= (uint8_t)0b11111100;
    err=pio_i2c_write_blocking(pio,sm,addr,&command,1); 
    command |= (uint8_t)0b00001000;
    command &= (uint8_t)0b11111000;
    err=pio_i2c_write_blocking(pio,sm,addr,&command,1);     
    return err;
}

同じような手順で、表示データの4ビットの書き込みは下記のようになります。
コマンドデータの書き込みと統合するとプログラムは短くなりますが、あえてわかりやすいように(ちょっと言い訳)分けてみました

int data_lcd4bitwrite(PIO pio,uint sm,uint8_t addr,uint8_t command){
    int err;
    command <<= 4;
    command |= (uint8_t)0b00001001;
    command &= (uint8_t)0b11111001;
    err=pio_i2c_write_blocking(pio,sm,addr,&command,1);
    command |= (uint8_t)0b00001101;
    command &= (uint8_t)0b11111101;
    err=pio_i2c_write_blocking(pio,sm,addr,&command,1); 
    command |= (uint8_t)0b00001001;
    command &= (uint8_t)0b11111001;
    err=pio_i2c_write_blocking(pio,sm,addr,&command,1);     
    return err;
}

8ビット単位の書き込みは、上位4ビットを書き込み続いて下位4ビットを書き込みしています。
コマンド書き込みと表示データ書き込みは次のようになります。

int lcd8bitwrite(PIO pio,uint sm,uint8_t addr,uint8_t command){
    int err;
    uint8_t command2;
    
    command2 = command;
    command >>= 4;
    err=lcd4bitwrite(pio,sm,addr,command);
    err=lcd4bitwrite(pio,sm,addr,command2);
    return err;
}
int data_lcd8bitwrite(PIO pio,uint sm,uint8_t addr,uint8_t command){
    int err;
    uint8_t command2;
    
    command2 = command;
    command >>= 4;
    err=data_lcd4bitwrite(pio,sm,addr,command);
    err=data_lcd4bitwrite(pio,sm,addr,command2);
    return err;
}

プログラム全体

実際にプログラムは下記のようになります。

#include <stdio.h>
#include <stdint.h>

#include "pico/stdlib.h"
#include "pio_i2c.h"

#define PIN_SDA 2
#define PIN_SCL 3

    
int lcd4bitwrite(PIO pio,uint sm,uint8_t addr,uint8_t command);
int lcd8bitwrite(PIO pio,uint sm,uint8_t addr,uint8_t command);
int data_lcd4bitwrite(PIO pio,uint sm,uint8_t addr,uint8_t command);
int data_lcd8bitwrite(PIO pio,uint sm,uint8_t addr,uint8_t command);

int main() {
    
	uint8_t addr=0x27;
	uint8_t command = 0x00;
	
    stdio_init_all();
    setup_default_uart();
    PIO pio = pio0;
    uint sm = 0;
    uint offset = pio_add_program(pio, &i2c_program);
    i2c_program_init(pio, sm, offset, PIN_SDA, PIN_SCL);
 
    uint8_t message1[]="RaspberryPi Pico";
    uint8_t message2[]="Hello Pico World";
    
    //Display initialization
	uint8_t data_set;

	data_set=0x03;

    lcd4bitwrite(pio,sm,addr,data_set);
    sleep_ms(5);
    lcd4bitwrite(pio,sm,addr,data_set);
    lcd4bitwrite(pio,sm,addr,data_set);
	data_set=0x02;
    lcd4bitwrite(pio,sm,addr,data_set);    			//4bit mode
	data_set=0x28;
    lcd8bitwrite(pio,sm,addr,data_set);     		//function set 4bit bus,2 line ,1 line=8
	data_set=0x0c;
    lcd8bitwrite(pio,sm,addr,data_set);     		//disp on,under cursor off,block
	data_set=0x01;
    lcd8bitwrite(pio,sm,addr,data_set);     		//disp clr
    sleep_ms(2);
	data_set=0x06;
    lcd8bitwrite(pio,sm,addr,data_set);     		//disp address incrmant on,disp shift off
	data_set=0x02;
    lcd8bitwrite(pio,sm,addr,data_set);     //cursor home set
    sleep_ms(2);
    
	for (int i=0;message1[i] != '\0';i++){
        data_lcd8bitwrite(pio,sm,addr,message1[i]);
        printf("%c\n",message1[i]);
    }
	data_set = 0xc0;
    lcd8bitwrite(pio,sm,addr,data_set);     //x80 + 0x40(2 line top address)
	for (int i=0;message2[i] != '\0';i++){
        data_lcd8bitwrite(pio,sm,addr,message2[i]);
        printf("%c\n",message2[i]);       
    }
}


int lcd4bitwrite(PIO pio,uint sm,uint8_t addr,uint8_t command){
    int err;
    command <<= 4;
    command |= (uint8_t)0b00001000;
    command &= (uint8_t)0b11111000;
    err=pio_i2c_write_blocking(pio,sm,addr,&command,1);
    command |= (uint8_t)0b00001100;
    command &= (uint8_t)0b11111100;
    err=pio_i2c_write_blocking(pio,sm,addr,&command,1); 
    command |= (uint8_t)0b00001000;
    command &= (uint8_t)0b11111000;
    err=pio_i2c_write_blocking(pio,sm,addr,&command,1);     
    return err;
}
int lcd8bitwrite(PIO pio,uint sm,uint8_t addr,uint8_t command){
    int err;
    uint8_t command2;
    
    command2 = command;
    command >>= 4;
    err=lcd4bitwrite(pio,sm,addr,command);
    err=lcd4bitwrite(pio,sm,addr,command2);
    return err;
}
int data_lcd4bitwrite(PIO pio,uint sm,uint8_t addr,uint8_t command){
    int err;
    command <<= 4;
    command |= (uint8_t)0b00001001;
    command &= (uint8_t)0b11111001;
    err=pio_i2c_write_blocking(pio,sm,addr,&command,1);
    command |= (uint8_t)0b00001101;
    command &= (uint8_t)0b11111101;
    err=pio_i2c_write_blocking(pio,sm,addr,&command,1); 
    command |= (uint8_t)0b00001001;
    command &= (uint8_t)0b11111001;
    err=pio_i2c_write_blocking(pio,sm,addr,&command,1);     
    return err;
}
int data_lcd8bitwrite(PIO pio,uint sm,uint8_t addr,uint8_t command){
    int err;
    uint8_t command2;
    
    command2 = command;
    command >>= 4;
    err=data_lcd4bitwrite(pio,sm,addr,command);
    err=data_lcd4bitwrite(pio,sm,addr,command2);
    return err;
}

以上のCプログラムを 「pio_i2c_lcd.c] として新たに作成したi2c_lcdに保存します。

$ cd ${PICO_SDK_PATH}/../pico-examples
$ cd pio
$ mkdir i2c_lcd
$ cd i2c_lcd

コンパイルしますが、CMAKEファイルの作成が必要です。
CMakelists.txt にはi2c_bus_scan.cのmake方法が記述されいますが、今回はちょっとずるをして、 i2c_bus_scan.c の代わりに pio_i2c_lcd.c の記述の上書きして作成しました。
CMakelists.txtに i2c_bus_scan.c のmake方法が記されているので取り合えず新たに作成したi2c_ledにコピーします。

$ cd ${PICO_SDK_PATH}/../pico-examples
$ cd pio
$ cd i2c
$ cp CMakeLists.txt ../i2c_lcd/CMakeLists.txt

また、コンパイルに必要なpio_i2c.h、pio_i2c.c、i2c.pioもコピーします。

$ cp i2c.pio ../i2c_lcd/i2c.pio
$ cp pio_i2c.h ../i2c_lcd/pio_i2c.h
$ cp pio_i2c.c ../i2c_lcd/pio_i2c.c

vimなどで CMakelists.txt を書き換えます。

$ vim CMakeLists.txt

以下のように書き替えました。

add_executable(pio_i2c_lcd)

pico_generate_pio_header(pio_i2c_lcd ${CMAKE_CURRENT_LIST_DIR}/i2c.pio)

target_sources(pio_i2c_lcd PRIVATE
        pio_i2c_lcd.c
        pio_i2c.c
        pio_i2c.h
        )

target_link_libraries(pio_i2c_lcd PRIVATE pico_stdlib hardware_pio)
pico_add_extra_outputs(pio_i2c_lcd)

# add url via pico_set_program_url
example_auto_set_url(pio_i2c_lcd)

ビルド会場へ移動します。

$ cd ${PICO_SDK_PATH}/../pico-examples
$ cd build/pio

ここで新しくフォルダーを作成します。
$ mkdir i2c_lcd
$ cd i2c_lcd

ここでcmakeで先ほどのソースフォルダーを指定します。

$ cmake ../../../pio/i2c_lcd

しかし、cmakeはエラーを返してしまいます。

cmakeはデフォルトでは、armのgccを選んでいるためと思われます。
そこで、pico-sdkをどこかで指定していると思われるので、ちょっと探してみます。

pico-examples 直下のCMakeLists.txtにその答えがありました。

cmake_minimum_required(VERSION 3.12)

# Pull in SDK (must be before project)
include(pico_sdk_import.cmake)

project(pico_examples C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

set(PICO_EXAMPLES_PATH ${PROJECT_SOURCE_DIR})

# Initialize the SDK
pico_sdk_init()

include(example_auto_set_url.cmake)
# Add blink example
add_subdirectory(blink)

# Add hello world example
add_subdirectory(hello_world)

# Hardware-specific examples in subdirectories:
add_subdirectory(adc)
add_subdirectory(clocks)
add_subdirectory(cmake)
add_subdirectory(divider)
add_subdirectory(dma)
add_subdirectory(flash)
add_subdirectory(gpio)
add_subdirectory(i2c)
add_subdirectory(interp)
add_subdirectory(multicore)
add_subdirectory(picoboard)
add_subdirectory(pio)
add_subdirectory(pwm)
add_subdirectory(reset)
add_subdirectory(rtc)
add_subdirectory(spi)
add_subdirectory(system)
add_subdirectory(timer)
add_subdirectory(uart)
add_subdirectory(usb)
add_subdirectory(watchd

このファイルの前半部分がその指定になっているみたいです。
そこで、ソースファイルのCMakeLists.txtに追加してみました。
また、インクルードファイルとして、 pico_sdk_import.cmake および example_auto_set_url.cmake が必要と思われますのでこのファイルも、ソースのフォルダーのなかに入れておきます。

ソースのCMakeLists.txt は次のようになりました

cmake_minimum_required(VERSION 3.12)

# Pull in SDK (must be before project)
include(pico_sdk_import.cmake)

project(pico_examples C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

set(PICO_EXAMPLES_PATH ${PROJECT_SOURCE_DIR})

# Initialize the SDK
pico_sdk_init()

include(example_auto_set_url.cmake)

add_executable(pio_i2c_lcd)

pico_generate_pio_header(pio_i2c_lcd ${CMAKE_CURRENT_LIST_DIR}/i2c.pio)

target_sources(pio_i2c_lcd PRIVATE
        pio_i2c_lcd.c
        pio_i2c.c
        pio_i2c.h
        )

target_link_libraries(pio_i2c_lcd PRIVATE pico_stdlib hardware_pio)
pico_add_extra_outputs(pio_i2c_lcd)

# add url via pico_set_program_url
example_auto_set_url(pio_i2c_lcd)

フォルダー位置は次のようになっています。
pico-examples
 |-pio
 | |−i2c_lcd >> ソースプログラム pio_i2c_lcd.c
 |           i2c.pio、pio_i2c.h、pio_i2c.c
 |           CMakeLists.txt
 |            pico_sdk_import.cmake
 |            example_auto_set_url.cmake
 |
 |−build
   |−pio
     |−i2c_lcd >> ここでcmakeおよびmakeを実行すると、実行ファイルが
               生成される

再度、 ビルド会場へ移動 します。


$ cd ${PICO_SDK_PATH}/../pico-examples
$ cd build/pio

ここで先ほど作成した i2c_lcd はcmakeに失敗しているので、フォルダーごと削除して再度 i2c_lcd を作成します。
削除コマンド rm -rf i2c_lcdは非常に危険なコマンドで一度削除したものは戻ってきません。特に削除コマンドを発行する時には注意してください。

$ cd ${PICO_SDK_PATH}/../pico-examples
$ cd build/pio
$ rm -rf i2c_lcd
$ mkdir i2c_lcd
$ cd i2c_lcd

ここでcmakeで先ほどのソースフォルダーを指定します。

$ cmake ../../../pio/i2c_lcd

Makefileが作成されましたので、makeを実行します。

$ make

これで無事、pio_i2c_lcd.elfが作成されました。

OpenOCD経由で、ターゲットにオブジェクトをダウンロードします。

まずは、OpenOCD(SWD経由)で書き込みデバックするので、OpenOCD用picoをターゲット用picoに接続し、RaspberryPiにUSBで接続しておきます。
端末を1つ開き、OpenOCDを起動します。

$ cd ${PICO_SDK_PATH}/../openocd
$ src/openocd -f interface/picoprobe.cfg -f target/rp2040.cfg -s tcl

「Info : Listening on port 3333 for gdb connections」が最後に表示されたら端末を最小化しておきます。

gdbのための端末を開きます。ソースを編集することに実行ファイルを読み込むので、gdbは終了せず、端末は開いたままにします。

$ gdb-multiarch  pio_i2c_lcd.elf

gdbで最初にOpenOCDに接続します。

(gdb) target remote localhost:3333

実行ファイルを読み込みます。

(gdb) load

picoをリセットし、実行します。

(gdb) monitor reset init
(gdb) c

実行すると、下記のように表示されます。

実行を停止します。
Ctrlキーを押しながらcキーを押します。

終了するには、detachしたのち、quitします。

(gdb) detach
(gdb) quit

これで何とか、LCDに表示させることができました。

おすすめの記事