C言語(SDCC)を使用して、TMPZ84C015でのI2C書き込みとキャラクターLCD表示のプログラムを作成したいと思います。
回路図は、以前作成している「Z80(TMPZ84C015)に萌えたい。I2C回路編」の回路をそのまま使用します。
回路図上で使用しているPCA9564Dはなかなか手に入りくいので、PCA9564PWを使用しました。
外形がTSSOP(0.65mmピッチ)となり半田付けがちょっと大変でした。

C言語のソフトでは、アセンブラとのリンクが必要となりましたので、i2c_write.asmを新たに作成しました。
i2c_writeは引数として、i2cアドレス、書き込み数、データが格納されたポインタアドレスを渡します。
;; uint8_t i2c_write(uint8_t,uint8_t,uint16_t *)
;;
;; i2c write
;; i2c address,length,buff address
;;
I2CSTA .equ 0x80 ;PCA9564 setting adress
I2CTO .equ 0x80
I2CDAT .equ 0x81
I2CADR .equ 0x82
I2CCON .equ 0x83
_i2c_write::
ld hl, #0x0002
add hl, sp
ld a,0xe4 ;;Master Transmitter Mode
out (I2CCON),a ;; AA=1 ENSIO=1 STA=1
i2c_w01:
in a,(I2CCON) ;; SI=1 ?
bit 3,a
jr z,i2c_w01
in a,(I2CSTA) ;;Poll from transmission finished
cp 0x08
jp nz,i2c_w_err
in a,(I2CDAT)
ld a,(hl) ;set slave addres set a reg
sla a ;carry flag set at write mode
;slave address + write
out (I2CDAT),a ;sleve address set
ld a,0xc4 ;;Slave recciver mode
out (I2CCON),a ;; AA=1 ENSIO=1 SI=0
i2c_w02:
in a,(I2CCON) ;; SI=1 ?
bit 3,a
jr z,i2c_w02
in a,(I2CSTA) ;;Poll from transmission finished
cp 0x18
jr nz,i2c_w_err
inc hl ;set length >> b reg
ld b,(HL)
inc hl ;set buff >> de reg
ld e,(hl)
inc hl
ld d,(hl)
data_write:
ld a,(de) ;; write data Areg is I2CDAT
out (I2CDAT),a
ld a,0xc4 ;;Slave recciver mode
out (I2CCON),a ;; AA=1 ENSIO=1 SI=0
i2c_w03:
in a,(I2CCON) ;; SI=1 ?
bit 3,a
jr z,i2c_w03
in a,(I2CSTA) ;;Poll from transmission finished
cp 0x28
jr nz,i2c_w_err
inc de
djnz data_write
ld l,0x01
jr i2c_stop
i2c_w_err:
ld l,0x00
i2c_stop:
ld a,0xd4 ;Generate STOP command
out (I2CCON),a ;AA=1 ENSIO=1 STA=0 STO=1
i2c_stop_loop:
in a,(I2CCON)
bit 4,a ;STO=0 ?
jr nz,i2c_stop_loop
in a,(I2CSTA)
cp 0xf8 ;reset or STOP command
jr nz,i2c_stop_err
ret
i2c_stop_err:
ld l,0x00 ;return 0x00
ret
上記をi2c_write.asmとして保存します。アセンブルします。
$ asz80 -l -s -o i2c_write.asm
i2cでの書き込みプログラムが作成できたので、さっそくC言語でのキャラクターLCDの表示を行っていきたいと思います。
もとになったプログラムは、「RP2040(RaspberryPi Pico)に萌えたい。例題PIO I2C その2」で使用したキャラクターLCDのC言語プログラムを参考にしました。
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経由の書き込みは先ほど作成した。i2c_write((uint8_t,uint8_t,uint16_t *) を使用します。
i2c_write は、i2c_addrを与えることにより、ポインター変数経由での書き込みが可能です。
今回は、アドレス演算子&を使い、一つだけの書き込みとしました。
i2c_write(i2c_lcd_addr,1,&command);
なおi2cアドレスは、定数書き込みにしています。
const uint8_t i2c_lcd_addr = 0x27;
コマンドデータの4ビットの書き込みは下記のようになりました
void lcd4bitwrite(uint8_t command){
command <<= 4;
command |= (uint8_t)0b00001000;
command &= (uint8_t)0b11111000;
i2c_write(i2c_lcd_addr,1,&command);
command |= (uint8_t)0b00001100;
command &= (uint8_t)0b11111100;
i2c_write(i2c_lcd_addr,1,&command);
command |= (uint8_t)0b00001000;
command &= (uint8_t)0b11111000;
i2c_write(i2c_lcd_addr,1,&command);
}
同じような手順で、表示データの4ビットの書き込みは下記のようになります。
コマンドデータの書き込みと統合するとプログラムは短くなりますが、あえてわかりやすいように(ちょっと言い訳)分けてみました
void data_lcd4bitwrite(uint8_t command){
command <<= 4;
command |= (uint8_t)0b00001001;
command &= (uint8_t)0b11111001;
i2c_write(i2c_lcd_addr,1,&command);
command |= (uint8_t)0b00001101;
command &= (uint8_t)0b11111101;
i2c_write(i2c_lcd_addr,1,&command);
command |= (uint8_t)0b00001001;
command &= (uint8_t)0b11111001;
i2c_write(i2c_lcd_addr,1,&command);
}
8ビット単位の書き込みは、上位4ビットを書き込み続いて下位4ビットを書き込みしています。
コマンド書き込みと表示データ書き込みは次のようになります。
void lcd8bitwrite(uint8_t command){
uint8_t command2;
command2 = command;
command >>= 4;
lcd4bitwrite(command);
lcd4bitwrite(command2);
}
void data_lcd8bitwrite(uint8_t command){
uint8_t command2;
command2 = command;
command >>= 4;
data_lcd4bitwrite(command);
data_lcd4bitwrite(command2);
}
プログラム全体
実際にプログラムは下記のようになります。
#include <stdio.h>
#include <stdint.h>
extern void i2c_init(void);
extern uint8_t i2c_write(uint8_t,uint8_t,uint16_t *);
extern uint8_t sleep(uint16_t);
void lcd4bitwrite(uint8_t command);
void lcd8bitwrite(uint8_t command);
void data_lcd4bitwrite(uint8_t command);
void data_lcd8bitwrite(uint8_t command);
const uint8_t i2c_lcd_addr = 0x27;
int main() {
i2c_init();
uint8_t message1[]=" **TMPZ84C015** ";
uint8_t message2[]="Hello I2C World ";
//Display initialization
uint8_t data_set;
data_set=0x03;
lcd4bitwrite(data_set);
sleep(5);
lcd4bitwrite(data_set);
lcd4bitwrite(data_set);
data_set=0x02;
lcd4bitwrite(data_set); //4bit mode
data_set=0x28;
lcd8bitwrite(data_set); //function set 4bit bus,2 line ,1 line=8
data_set=0x0c;
lcd8bitwrite(data_set); //disp on,under cursor off,block
data_set=0x01;
lcd8bitwrite(data_set); //disp clr
sleep(2);
data_set=0x06;
lcd8bitwrite(data_set); //disp address incrmant on,disp shift off
data_set=0x02;
lcd8bitwrite(data_set); //cursor home set
sleep(2);
for (int i=0;message1[i] != '\0';i++){
data_lcd8bitwrite(message1[i]);
// printf("%c\n",message1[i]);
}
data_set = 0xc0;
lcd8bitwrite(data_set); //x80 + 0x40(2 line top address)
for (int i=0;message2[i] != '\0';i++){
data_lcd8bitwrite(message2[i]);
// printf("%c\n",message2[i]);
}
return(0);
}
void lcd4bitwrite(uint8_t command){
command <<= 4;
command |= (uint8_t)0b00001000;
command &= (uint8_t)0b11111000;
i2c_write(i2c_lcd_addr,1,&command);
command |= (uint8_t)0b00001100;
command &= (uint8_t)0b11111100;
i2c_write(i2c_lcd_addr,1,&command);
command |= (uint8_t)0b00001000;
command &= (uint8_t)0b11111000;
i2c_write(i2c_lcd_addr,1,&command);
}
void lcd8bitwrite(uint8_t command){
uint8_t command2;
command2 = command;
command >>= 4;
lcd4bitwrite(command);
lcd4bitwrite(command2);
}
void data_lcd4bitwrite(uint8_t command){
command <<= 4;
command |= (uint8_t)0b00001001;
command &= (uint8_t)0b11111001;
i2c_write(i2c_lcd_addr,1,&command);
command |= (uint8_t)0b00001101;
command &= (uint8_t)0b11111101;
i2c_write(i2c_lcd_addr,1,&command);
command |= (uint8_t)0b00001001;
command &= (uint8_t)0b11111001;
i2c_write(i2c_lcd_addr,1,&command);
}
void data_lcd8bitwrite(uint8_t command){
uint8_t command2;
command2 = command;
command >>= 4;
data_lcd4bitwrite(command);
data_lcd4bitwrite(command2);
}
以上のCプログラムを 「i2c_lcd_main.c] として保存します。
コンパイルします。
$ sdcc -mz80 --out-fmt-ihx --code-loc 0xa000 --no-std-crt0 -o i2c_lcd_main.ihx mycrt0.rel i2c_lcd_main.c i2c_init.rel i2c_write.rel sleep.rel
これで、HEXファイルの i2c_lcd_main.ihx が作成されたので、モニターコマンドlでhexファイルをダウンローロードします。
>l
20A00000DDE5DD210000DD3921DCFF39F9CD44A3211100394D443E2002696023362A696017
20A020002323362A6960232323365421040009364D21050009365021060009365A21070070
20A04000093638210800093634210900093643210A00093630210B00093631210C0009369F
中略
20A3C000C92E00C9210200395E235606F800000000000000000010F51B7AB320EE21000010
01A3E000C9B3
00000001FF
OK
>
>c a000 y:
>
LCDの表示は
**TMPZ84C015**
Hello I2C World
と表示されます。