今回は、実際にキャラクター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に表示させることができました。