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

と表示されます。

おすすめの記事