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の読み込みをしているみたいです。