C言語(SDCC)を使用して、今回はZ80+SIO+PIO+PCA9564PWでのI2Cデバイスを使用してRTC(DS3231)、温度・湿度のLCD表示プログラムを作成したいと思います。

回路図は、Z80+SIO+PIOにPCA9564PWの回路を追加しました。

PCA9564PWのI/Oアドレスは、0x8c~0x8fに設定しました。

必要なソフトを作成していきます。

まずは、mycrt0.asmですが、これは、TMPZ84C015と同一です。


    .module crt0
    .globl  _main


    ;; Ordering of segments for the linker.

    .area   _CODE


    .area   _DATA


    .area   _CODE
init::

    call    _main
    ret


    .area   _DATA

アセンブルして、mycrt0.relを得ます。

$ asz80 -l -s -o mycrt0.asm

時間稼ぎのルーチンsleep.asmを作成します。
今回は、クロックとして8.000MHzに調整しています。

;;  uint8_t sleep(uint16_t)
;;  Z80 CPU
;;  8.000MHz clock sleep(0x0001) = 1mS


_sleep::

    ld  hl, #0x0002
    add hl, sp
    ld  e,  (hl)
    inc hl
    ld  d,  (hl)
loop1:
    ld  b,  #163
loop2:  nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    djnz    loop2

    dec de
    ld  a,d
    or  e
    jr  nz, loop1
    
    ld  hl, #0x0000

    ret

アセンブルして、sleep.relを得ます。

$ asz80 -l -s -o sleep.asm

PCA9564PW をイニシャライズするi2c_init.asmを作成します。

;;     Z80+SIO+PIO void i2c_init(void)
;;
;;     PCA9564 Initialize
;;
I2CSTA  .equ    0x8c    ;PCA9564 setting adress
I2CTO   .equ    0x8c
I2CDAT  .equ    0x8d
I2CADR  .equ    0x8e
I2CCON  .equ    0x8f

_i2c_init::
        LD      A,0xff                  ;;Timout Register
        out    (I2CTO),a
        ld      a,0x02                  ;;Own Address
        out    (I2CADR),a
        ld      a,0x44                  ;;Enable Serial io
        out    (I2CCON),a

        ld  b,  #82                 ;;500uS wait
loop:   nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        djnz    loop

        ld      a,0xc4                  ;;Slave recciver mode
        out    (I2CCON),a              ;; AA=1 ENSIO=1 SI=0
        ret 

アセンブルしてi2c_init.relを得ます。

$ asz80 -l -s -o i2c_init.asm

i2c読み込みルーチンi2c_read.asmを作成します。

;;     Z80+SIO+PIO uint8_t  i2c_read(uint8_t,uint8_t,uint16_t *)
;;
;;     i2c read
;;     i2c address,length,buff address
;;      
;;
I2CSTA  .equ    0x8c    ;PCA9564 setting adress
I2CTO   .equ    0x8c
I2CDAT  .equ    0x8d
I2CADR  .equ    0x8e
I2CCON  .equ    0x8f

_i2c_read::
        ld  hl, #0x0002
        add hl, sp

        ld      a,0xe4                  ;;Master Receiver Mode
        out     (I2CCON),a              ;; AA=1 ENSIO=1 STA=1
i2c_r01:  
        in      a,(I2CCON)              ;; SI=1 ?
        bit     3,a     
        jr      z,i2c_r01
        in      a,(I2CSTA)              ;;Poll from transmission finished
        cp      0x08
        jr      nz,i2c_r_err
        in      a,(I2CDAT)
        ld      a,(hl)                  ;set slave addres set a reg
        sla     a                       ;carry flag set at write mode
        set     0,a                     ;slave address + read
        out     (I2CDAT),a              ;sleve address set
        ld      a,0xc4                  ;;Slave recciver mode
        out     (I2CCON),a              ;; AA=1 ENSIO=1 SI=0

i2c_r02:
        in      a,(I2CCON)              ;; SI=1 ?
        bit     3,a
        jr      z,i2c_r02
        
        in      a,(I2CSTA)              ;;Poll from transmission finished
        cp      0x40
        jr      nz,i2c_r_err
 
        inc     hl                      ;set length >> b reg
        ld      b,(HL)

        inc     hl                      ;set buff  >> de reg
        ld      e,(hl)
        inc     hl
        ld      d,(hl)
        push    de
data_read:
        ld      a,b
        cp      0x01
        jr      z,data_read_end 
        ld      a,0xc4                  ;;Slave recciver mode
        out    (I2CCON),a               ;; AA=1 ENSIO=1 SI=0
i2c_r03:
        in      a,(I2CCON)              ;; SI=1 ?
        bit     3,a
        jr      z,i2c_r03

        in      a,(I2CSTA)              ;;Poll from transmission finished
        cp      0x50
        jr      nz,i2c_r_err
               
        in      a,(I2CDAT)              ;;read data Areg is I2CDAT
        ld      (de),a   
        inc     de
        dec     b
        jr      data_read
data_read_end:
        ld      a,0x44                  ;;Slave recciver mode
        out    (I2CCON),a               ;; AA=1 ENSIO=1 SI=0
i2c_r04:
        in      a,(I2CCON)              ;; SI=1 ?
        bit     3,a
        jr      z,i2c_r04
        in      a,(I2CSTA)              ;;Poll from transmission finished
        cp      0x58
        jr      nz,i2c_r_err
        in      a,(I2CDAT)              ;;read data Areg is I2CDAT
        ld      (de),a           
        ld      l,0x01
        jr      i2c_stop               
                 
i2c_r_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
    pop hl
        ret


i2c_stop_err:
        pop     hl
        ld      l,0x00  ;return 0x00
        ret

アセンブルして、i2c_read.relを得ます。

$ asz80 -l -s -o i2c_read.asm

i2c読み込みルーチンi2c_write.asmを作成します。

;;     Z80+SIO+PIO uint8_t i2c_write(uint8_t,uint8_t,uint16_t *)
;;
;;     i2c write
;;     i2c address,length,buff address
;;
I2CSTA  .equ    0x8c    ;PCA9564 setting adress
I2CTO   .equ    0x8c
I2CDAT  .equ    0x8d
I2CADR  .equ    0x8e
I2CCON  .equ    0x8f

_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.relを得ます。

$ asz80 -l -s -o i2c_write.asm

アドレスサーチi2c_addr.asmを作成します。

;;     Z80+SIO+PIO uint8_t i2c_addr(uint8_t)
;;
;;     i2c address search
;;     address is return is ack=1  nack=0
;;
I2CSTA  .equ    0x8c    ;PCA9564 setting adress
I2CTO   .equ    0x8c
I2CDAT  .equ    0x8d
I2CADR  .equ    0x8e
I2CCON  .equ    0x8f

_i2c_addr::
        ld  hl, #0x0002
        add hl, sp

        ld      a,0xe4                  ;;Master Receiver Mode
        out     (I2CCON),a              ;; AA=1 ENSIO=1 STA=1
i2c_a01:  
        in      a,(I2CCON)              ;; SI=1 ?
        bit     3,a     
        jr      z,i2c_a01
        in      a,(I2CSTA)              ;;Poll from transmission finished
        cp      0x08
        jp      nz,i2c_r_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_a02:
        in      a,(I2CCON)              ;; SI=1 ?
        bit     3,a
        jr      z,i2c_a02

        in      a,(I2CSTA)              ;;Poll from transmission finished
    ld  l,a
    
        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_r_err
        ret


i2c_r_err:
        xor     a           ;return nack 0x00
        ld      l,a
        ret

アセンブルして、i2c_addr.relを得ます。

$ asz80 -l -s -o i2c_addr.asm

printf実行のためにputchar.asmを作成します。

;;   Z80+SIO+PIO vido putchar(uint8_t)
;;
SIOAD   .equ    0x84        ;SIO A-CH DATA REG
SIOAC   .equ    0x85        ;SIO A-CH CONTL REG

_putchar::
    ld  hl, #0002
    add hl, sp
putchar01:
    in  a,  (SIOAC);
    bit 2,  a
    jr  z,  putchar01
    ld  a,  (hl)
    out (SIOAD),    a
    ld  hl, #0000
    ret

アセンブルして、putchar.relを得ます。

$ asz80 -l -s -o putchar.asm

c言語でアドレスサーチのブロブラムを作成、i2cアドレスをサーチします。

//  I2C Address Search (PCA9564)

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


extern  void    i2c_init(void);
extern  uint8_t i2c_addr(uint8_t);


int main(){

    uint8_t i, j, addr;
    i2c_init();
    printf("I2C Address Search (PCA9564)\n");
    printf("Addr 0 1 2 3 4 5 6 7 8 9 a b c d e f\n");
    for (i = 0; i <= 0x7; i++) {
        printf("  %2x ", i*0x10);
        for (j = 0; j <= 0xf; j++) {
            addr = i * 0x10 + j;
            switch(i2c_addr(addr)){
                case 0x18:                  //addr+W >> ACK
                    printf("%2x",addr);
                    break;
                case 0x20:                  //addr+w >> NACK
                    printf("- ");
                    break;
                case 0x68:                  //Master
                    printf("M ");
                    break;
                default:
                    printf("er");
             }
        }
        printf("\n");
    };
    return(0);
}  

コンパイルして、hexファイルを作成ます。

$ sdcc -mz80 --out-fmt-ihx --code-loc 0xa000 --no-std-crt0 -o i2c_addr_main.ihx mycrt0.rel i2c_addr_main.c i2c_init.rel i2c_addr.rel putchar.rel

モニターlコマンドで読み込み実行してみます。(LCD等はi2cに接続しておきます。)

>c a000 y:
I2C Address Search (PCA9564)
Addr 0 1 2 3 4 5 6 7 8 9 a b c d e f
   0 - M - - - - - - - - - - - - - -
  10 - - - - - - - - - - - - - - - -
  20 - - - - - - - 27- - - - - - - -
  30 - - - - - - - - - - - - - - - -
  40 - - - - - - - - - - - - - - - -
  50 - - - - - - - 57- - - - - - - -
  60 - - - - - - - - 68- - - - - - -
  70 - - - - - - - - - - - - - - - -

>

上記のようにI2Cアドレスが確認出来たら準備OKです。

RTC(DS3231)、温度・湿度のLCD表示プログラムを作成します。
今回1部、分の値が00~39分までになっていたので修正しました。

//  I2C(PCA9564) rtc(DS3231) temp,hum(AM2321) LCD(i2c) Display

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

extern  void    i2c_init(void);
extern  uint8_t i2c_read(uint8_t,uint8_t,uint16_t *);
extern  uint8_t i2c_write(uint8_t,uint8_t,uint16_t *);
extern  void sleep(uint16_t);

void i2c_lcd_init(void);
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(){

    uint8_t tmp_addr=0x5c;
    uint8_t rtc_addr=0x68;
    
    uint8_t len=8;
    uint8_t out_buff[8];
    uint16_t *buff = out_buff;
    uint8_t data_set=0x00;
    
    uint8_t mesg1[16];
    uint8_t mesg2[16];
    
    uint8_t tenmetu=0x01;
    uint8_t data_addr = 0x00;
    uint16_t temp,hum;  
       
    int8_t i;
    
    i2c_init();
 
    i2c_lcd_init();
    
    while(1){                                           // main loop
        data_set = 0x00;                                //rtc read
        i2c_write(rtc_addr,1,&data_set);
        i2c_read(rtc_addr,len,buff);
        mesg1[0] = 0x32;    // 2
        mesg1[1] = 0x30;    // 0
        mesg1[2] =(out_buff[6] >>4) | 0x30;             //years
        mesg1[3] =(out_buff[6] & 0x0f) | 0x30;
        mesg1[4] = 0x2f;  // 
        mesg1[5] =((out_buff[5] & 0x1f) >>4) | 0x30;    //months
        mesg1[6] =(out_buff[5] & 0x0f) | 0x30; 
        mesg1[7] = 0x2f;  //            
        mesg1[8] =((out_buff[4] & 0x3f) >>4) | 0x30;    //days
        mesg1[9] =(out_buff[4] & 0x0f) | 0x30; 
        mesg1[10] = 0x20;  // 
        mesg1[11] =((out_buff[2] & 0x3f) >>4) | 0x30;   //hours
        mesg1[12] =(out_buff[2] & 0x0f) | 0x30; 
        mesg1[13] = 0x3a;  //            
        mesg1[14] =((out_buff[1] & 0x7f) >>4) | 0x30;   //minutes 2022/4/10 Fix
        mesg1[15] =(out_buff[1] & 0x0f) | 0x30;         // 0x3f >> 0x7f

       data_set = 0x80;
        lcd8bitwrite(data_set);                         //x80 (1line top address)
        for (i=0; i<16 ;i++){
            data_lcd8bitwrite(mesg1[i]);
//          printf("%c\n",mesg1[i]);
        }
 

        data_addr = 0x00;                               //AM2321 Wake up
        i2c_write(tmp_addr,1,&data_addr);
        sleep(1);
        out_buff[0] = 0x03;                             //4 bytes temperature and humidity
        out_buff[1] = 0x00;
        out_buff[2] = 0x04;
        i2c_write(tmp_addr,3,buff);
        sleep(2);
        i2c_read(tmp_addr,len,buff);                    // get temperature and humidity


        hum=((uint8_t)out_buff[2] << 8) | (uint8_t)out_buff[3];                 //humidity
        temp=((((uint8_t)out_buff[4]) & 0x7f) << 8) | (uint8_t)out_buff[5];     //temperature 
//      printf("temp=%1x,%1x,%1x\n",temp/100,(temp%100)/10,(temp%100) %10);
    
        if ((out_buff[4] & 0x80) == 0x80){              //temperature Upper 7-bit polarity
            mesg2[0]= 0x2d;                             // -
        }else{
            mesg2[0]= 0x2b;                             // +   
        }       
    
        mesg2[1] = ((temp/100) | 0x30) & 0x3f;          //temperature Tenth digit
        mesg2[2] = (((temp%100) /10) | 0x30 ) & 0x3f;   //temperature 1st digit
        mesg2[3] = 0x2e;                                // . 
        mesg2[4] = (((temp%100) %10) | 0x30 ) & 0x3f;   //temperature 1st decimal place
        mesg2[5] = 0xdf;                                // do
        mesg2[6] = 0x43;                                // C
        mesg2[7] = 0x20;                                // space
        mesg2[8] = ((hum/100) | 0x30 ) & 0x3f;          //humidity Tenth digit  
        mesg2[9] = (((hum%100) /10) | 0x30 ) & 0x3f;    //humidity 1st digit
        mesg2[10] = 0x2e;                               // .
        mesg2[11] = (((hum%100) %10) | 0x30 ) & 0x3f;   //humidity 1st decimal place
        mesg2[12] = 0x25;                               // %
        mesg2[13] = 0x52;                               // R          
        mesg2[14] = 0x48;                               // H  
   
        if(tenmetu == 0x01){                            // Blinking
            mesg2[15] = 0x20;
        }else{
            mesg2[15] = 0xff;
        }
        tenmetu = tenmetu ^ 0x01;
        
        data_set = 0xc0;
        lcd8bitwrite(data_set);                             //x80 + 0x40(2 line top address)
        for (i=0; i<16;i++){
            data_lcd8bitwrite(mesg2[i]);
//          printf("%2x\n",mesg2[i]);           
        }
        
        sleep(500);                                         //500mS wait
    }
    return(0);
}   

void i2c_lcd_init(void){
        //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);
}
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);

}

コンパイルします。

$ sdcc -mz80 --out-fmt-ihx --code-loc 0xa000 --no-std-crt0 -o i2c_lcd_rtc_tmp_main.ihx mycrt0.rel i2c_lcd_rtc_tmp_main.c i2c_init.rel i2c_read.rel i2c_write.rel  putchar.rel sleep.rel

LCD上に前回記事と同じように表示されます(無限ルーチンになっているのでモニタには帰ってきません)

おすすめの記事