C言語(SDCC)を使用して、TMPZ84C015でのI2C読み込みとRTC(DS3231)の読み書きプログラムを作成したいと思います。

回路図は、以前作成している「Z80(TMPZ84C015)に萌えたい。I2C回路編」の回路をそのまま使用します。

今回使用するリアルタイムクロック(RTC)はDS3231とEEPROM(AT24C32)のモジュールになっているHiletgoのRTCモジュールを使用します。
このモジュールはバッテリー用のソケットもついていますが、LIR2032(2次電池、充電可能)専用になっています。
今回は1次電池のCR2032を使用するので、充電用の抵抗(200Ω)を外しました。

C言語でのRTCの読み込みが必要なりますので、新たに、i2c_read.asmを作成しました。

i2c_readは引数として、i2cアドレス、読み込み数、データが格納するためのポインターアドレスを渡します。


;;     uint8_t  i2c_read(uint8_t,uint8_t,uint16_t *)
;;
;;     i2c read
;;     i2c address,length,buff address
;;      
;;
I2CSTA  .equ    0x80    ;PCA9564 setting adress
I2CTO   .equ    0x80
I2CDAT  .equ    0x81
I2CADR  .equ    0x82
I2CCON  .equ    0x83

_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.asmとして保存します。アセンブルします。

$ asz80 -l -s -o i2c_read.asm

i2cでの読み込みプログラムが作成できたので、さっそくC言語でのRTCの表示を行っていきたいと思います。
と、思ったんですが、RTCの設定ができていませんので、まずは、RTCの設定から行っていきます。

まずは、i2cアドレスを確認します。
DS3231 : 0x68
AT24C32 : 0x57

RTCの設定レジスタは、BCD(二進化十進数、Binary-coded decimal)となっていますので、設定をコード変換しなくてはなりません。
方法としては、10で割って、商を2桁目、余りを1桁目にして |(論理和)を取り、必要なビットになりように&(論理積)を施しました。
例えば、月では、

	out_buff[6] = (((months/10) << 4) | months%10) & 0x1f;

書き込みの際は、先頭に書き込みアドレスを指定し、そのあと書き込みデータが続きます。
例として、

2022年2月22日、13時50分00秒を設定する時には

	years = 22;
	months = 2;
	days = 22;
	hours = 13;
	minutes = 50;
	seconds = 00;
	
	out_buff[0] = 0x00;						//address
	out_buff[1] = (((seconds/10) << 4) | seconds%10) & 0x7f;
	out_buff[2] = (((minutes/10) << 4) | minutes%10) & 0x7f;
	out_buff[3] = (((hours/10) << 4) | hours%10) & 0x3f;  		//24h
	out_buff[4] = 0x00;
	out_buff[5] = (((days/10) << 4) | days%10) & 0x3f;
	out_buff[6] = (((months/10) << 4) | months%10) & 0x1f;
	out_buff[7] = (((years/10) << 4) | years%10);

Day(曜日)については設定方法がよくわからなかったので、取り合えず、0x00にしました。

配列をポインターに変換して、i2cアドレス、長さ、ポインタアドレスを指定してi2c_writeで書き込みします。

    uint8_t addr=0x68;
    uint8_t len=8;
    uint8_t out_buff[8];

    uint16_t *buff = out_buff;

    i2c_write(addr,len,buff);

続いてRTCからの読み込みは、最初にRTCの読み込みアドレスを書き込みしてから、順次読み込みしていきます。

buffの先頭は0x00(out_buff[0]=0x00)なので、1バイトだけ書き込みします。
続いて、i2c_readすると、buffは読み込みデータで上書きされます。

	i2c_write(addr,1,buff);
	i2c_read(addr,len,buff);

読み込みされたデータは、BCDコードなので、コード変換します。

int16_t bcd_to_dec(uint8_t bcd){
	int16_t dec10=(bcd >>4) & 0x0f;
	return (dec10 * 10 +(bcd  & 0x0f));
}    

必要なビットを &(論理積)してから、bcd_to_decをコールし、printfで表示します。

	years = bcd_to_dec (out_buff[6]);
	months = bcd_to_dec(out_buff[5] & 0x1f);
	days = bcd_to_dec(out_buff[4] & 0x3f);
	hours = bcd_to_dec(out_buff[2] & 0x3f);
	minutes = bcd_to_dec(out_buff[1] & 0x7f);
	seconds = bcd_to_dec(out_buff[0] & 0x7f);


	printf(" 20%2d/%2d/%2d  %2d:%2d:%2d\n",years,months,days,hours,minutes,seconds);
	

全体的なコードは次のようになりました。

//  I2C(PCA9564) rtc(DS3231) write

#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);

 int16_t bcd_to_dec(uint8_t);



int main(){

    uint8_t addr=0x68;
    uint8_t len=8;
    uint8_t out_buff[8];

    int16_t years,months,days,hours,minutes,seconds;
  
    uint16_t *buff = out_buff;
    i2c_init();
    i2c_read(addr,len,buff);
        
    years = 22;
    months = 2;
    days = 22;
    hours = 13;
    minutes = 50;
    seconds = 00;
    
    out_buff[0] = 0x00;                                         //address
    out_buff[1] = (((seconds/10) << 4) | seconds%10) & 0x7f;
    out_buff[2] = (((minutes/10) << 4) | minutes%10) & 0x7f;
    out_buff[3] = (((hours/10) << 4) | hours%10) & 0x3f;        //24h
    out_buff[4] = 0x00;
    out_buff[5] = (((days/10) << 4) | days%10) & 0x3f;
    out_buff[6] = (((months/10) << 4) | months%10) & 0x1f;
    out_buff[7] = (((years/10) << 4) | years%10);

//  for(uint8_t i=0;i<7;i++){
//      printf("data=%2x\n",out_buff[i]);
//  }
//  printf("\n");
        
    i2c_write(addr,len,buff);
    sleep(100);
    i2c_write(addr,1,buff);
    i2c_read(addr,len,buff);

//  for(uint8_t i=0;i<7;i++){
//      printf("data%d=%2x\n",i,out_buff[i]);
//  }
    years = bcd_to_dec (out_buff[6]);
    months = bcd_to_dec(out_buff[5] & 0x1f);
    days = bcd_to_dec(out_buff[4] & 0x3f);
    hours = bcd_to_dec(out_buff[2] & 0x3f);
    minutes = bcd_to_dec(out_buff[1] & 0x7f);
    seconds = bcd_to_dec(out_buff[0] & 0x7f);


    printf(" 20%2d/%2d/%2d  %2d:%2d:%2d\n",years,months,days,hours,minutes,seconds);
    
    return(0);
}   

int16_t bcd_to_dec(uint8_t bcd){
    int16_t dec10=(bcd >>4) & 0x0f;
    return (dec10 * 10 +(bcd  & 0x0f));
}    
        

上記をi2c_rtc_write.main.cとして保存し、コンパイルします。

$ sdcc -mz80 --out-fmt-ihx --code-loc 0xa000 --no-std-crt0 -o i2c_rtc_write_main.ihx mycrt0.rel i2c_rtc_write_main.c i2c_init.rel i2c_read.rel i2c_write.rel sleep.rel putchar.rel
Conflicting flags in area    _CODE
Conflicting flags in area    _DATA
Conflicting flags in area    _CODE
Conflicting flags in area    _DATA
Conflicting flags in area    _CODE
Conflicting flags in area    _DATA
Conflicting flags in area    _CODE
Conflicting flags in area    _DATA
Conflicting flags in area    _CODE
Conflicting flags in area    _DATA
Conflicting flags in area    _CODE
Conflicting flags in area    _DATA

モニターのlコマンドで読み込み実行してみました。

>c a000 y:
 2022/ 2/22  13:50: 0

次に読み込み専用のi2c_rtc_read_main.cを作成します。

//  I2C(PCA9564) rtc(DS3231) read 

#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 *);

int16_t bcd_to_dec(uint8_t);

int main(){

    uint8_t addr=0x68;
    uint8_t len=7;
    uint8_t out_buff[7];

    int16_t years,months,days,hours,minutes,seconds;
  
    uint16_t *buff = out_buff;
    i2c_init();
    uint8_t data_addr = 0x00;
    i2c_write(addr,1,&data_addr);
    i2c_read(addr,len,buff);

//  for(uint8_t i=0;i<7;i++){
//      printf("data%d=%2x\n",i,out_buff[i]);
//  }

    years = bcd_to_dec (out_buff[6]);
    months = bcd_to_dec(out_buff[5]  & 0x1f);
    days = bcd_to_dec(out_buff[4] & 0x3f );
    hours = bcd_to_dec(out_buff[2] & 0x3f);
    minutes = bcd_to_dec(out_buff[1] & 0x7f);
    seconds = bcd_to_dec(out_buff[0] & 0x7f);

    printf(" 20%2d/%2d/%2d %2d:%2d:%2d\n",years,months,days,hours,minutes,seconds);
    
    return(0);
}   

int16_t bcd_to_dec(uint8_t bcd){
    int16_t dec10=(bcd >>4) & 0x0f;
    return (dec10 * 10 +(bcd  & 0x0f));
}    

i2c_rtc_read_main.c として保存し、コンパイルします。

$ sdcc -mz80 --out-fmt-ihx --code-loc 0xa000 --no-std-crt0 -o i2c_rtc_read_main.ihx mycrt0.rel i2c_rtc_read_main.c i2c_init.rel i2c_read.rel i2c_write.rel  putchar.rel
Conflicting flags in area    _CODE
Conflicting flags in area    _DATA
Conflicting flags in area    _CODE
Conflicting flags in area    _DATA
Conflicting flags in area    _CODE
Conflicting flags in area    _DATA
Conflicting flags in area    _CODE
Conflicting flags in area    _DATA
Conflicting flags in area    _CODE
Conflicting flags in area    _DATA

モニターのlコマンドで読み込み実行してみました。

>c a000 y:
 2022/ 2/22 13:57:26

>c a000 y:
 2022/ 2/22 13:58: 1

>c a000 y:
 2022/ 2/22 13:58:14

>

ちゃんとRTCの読み込みをしているみたいです。

おすすめの記事