RTC(DS1307)モジュールをArduino Nanoでコントロールして、時計を作ってみます。
回路図です。
表示は、I2Cシリアルインターフェース付きのLCD1602(バックライト付き)です。
![](https://blogimg.goo.ne.jp/user_image/74/31/111de327a248a014ed95ec12bed9f40a.png)
動作を簡単に説明します。
RTC(DS1307)から周期1秒の方形波を出力させ、その立ち上がりでArduino Nanoに割込みをかけます。
Arduino Nanoは、割込みがある度にRTCから時刻データを読み出し、変化した項目のみ表示を更新します。
時刻を設定するスイッチは、「SET」「UP」「DOWN」の3つです。
SETを長押し(1秒以上)すると時刻設定モードに入り、割込みを停止し、設定す津項目をアルファベット1文字で下の行の左端に表示します。
年 → Y
月 → M
日 → D
曜 → W
時 → h
分 → m
秒 → s
各項目では、「UP」DDOWN」で数値を変更します。
各項目を変更し終えたら「SET」を押すと次の項目に進みます。
![](https://blogimg.goo.ne.jp/user_image/23/24/0555b68141dd808f73941d2c7eb97e55.jpg)
「秒」の項目を設定し、「SET」を押すと、割込みが再開され、時計が動き始めます。
スケッチです。ちょっと長いです。今は使わない定義も入っています。
RTCから読みだされた時刻データ(HEX)は、REG[n] (n=0~6)に保存されます。
REG[n]のデータは、HEX→10進変換をして、Disp_REG[n]の保存し、各項目ごとに変化した項目だけ表示を更新します。現在の値(HEX)は、OLD_REG[n]に保存します。
----------------------------------------------------------------------------------------------
/* RTC(DS1307) Clock
* 2021.7.8
* JH7UBC Keiji Hata
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
//TimeKeeper Register Address
#define SECOND 0x00
#define MINUTE 0x01
#define HOUR 0x02
#define DAY 0x03 //WEEK
#define DATE 0x04
#define MONTH 0x05
#define YEAR 0x06
#define CTRL 0x07
#define RTC_address 0x68 //DS1307 I2C Address
char *week[] = {" ","MON","TUE","WED","THU","FRI","SAT","SUN"};
uint8_t REG[7]={0x00,0x00,0x00,0x01,0x01,0x01,0x21};//時刻データ初期値(HEX)
int Disp_REG[7];//表示用データ(10進)
uint8_t OLD_REG[7]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,};//Disp_REG1の前の値を格納しておくレジスタ
volatile uint8_t INT_flag=0;//割込みフラッグ
int Num = 6; //Clock data number 6:year 5:month 4:day 3:week 2:hour 1:mminute 0:second
#define SET 0 //SET BUTTON
#define UP 1 //UP BUTTON
#define DOWN 4 //DOWN BUTTON
LiquidCrystal_I2C lcd(0x27,16,2);
void setup() {
Wire.begin();
lcd.init(); //lcd初期化
lcd.backlight(); //LCD backlight ON
pinMode(SET,INPUT_PULLUP);
pinMode(UP,INPUT_PULLUP);
pinMode(DOWN,INPUT_PULLUP);
//RTCに初期データを書き込む
Time_data_Send();
//固定画面セット
lcd.setCursor(0,0);
lcd.print("20 / / ( )");
lcd.setCursor(6,1);
lcd.print(": :");
//初期時間表示
HEX2DEC();
TimeDisp();
//外部割込み設定 pin2,立ち上がり
attachInterrupt(0,IntSR,RISING);
}
//時刻データをRTCに書き込む
void Time_data_Send(){
Wire.beginTransmission(RTC_address);
Wire.write(0x00);//Register 先頭アドレス
Wire.write(REG[0]);//second
Wire.write(REG[1]);//minute
Wire.write(REG[2]);//hour
Wire.write(REG[3]);//week
Wire.write(REG[4]);//day
Wire.write(REG[5]);//month
Wire.write(REG[6]);//year
Wire.write(0x10);//1Hz output
Wire.endTransmission();
}
//RTCデータの読み込み
void TimeRead(){
Wire.beginTransmission(RTC_address);
Wire.write(0x00);//Register 先頭アドレス
Wire.endTransmission();
Wire.requestFrom(RTC_address,7);
for(int i=0;i<=7;i++){
REG[i]=Wire.read();
}
}
//REGのデータをHeXからDEC変換してDisp_REGに格納
void HEX2DEC(){
for(uint8_t i =0;i<=7;i++){
Disp_REG[i] = ((REG[i] & 0xF0)>>4)*10 +(REG[i] & 0x0F);
}
}
//Timeデータの表示
void Data_Disp(uint8_t n){
if(Disp_REG[n] != OLD_REG[n]){
if(Disp_REG[n]<10){
lcd.print("0");
}
lcd.print(Disp_REG[n]);
OLD_REG[n]=Disp_REG[n];
}
}
//year表示
void year_Disp(){
lcd.setCursor(2,0);
Data_Disp(6);
}
//month表示
void month_Disp(){
lcd.setCursor(5,0);
Data_Disp(5);
}
//day表示
void day_Disp(){
lcd.setCursor(8,0);
Data_Disp(4);
}
//week表示
void week_Disp(){
if(Disp_REG[3]!=OLD_REG[3]){
lcd.setCursor(12,0);
lcd.print(week[Disp_REG[3]]);
OLD_REG[3]=Disp_REG[3];
}
}
//hour表示
void hour_Disp(){
lcd.setCursor(4,1);
Data_Disp(2);
}
//minute表示
void minute_Disp(){
lcd.setCursor(7,1);
Data_Disp(1);
}
//second表示
void second_Disp(){
lcd.setCursor(10,1);
Data_Disp(0);
}
//時刻の表示
void TimeDisp(){
year_Disp();
month_Disp();
day_Disp();
week_Disp();
hour_Disp();
minute_Disp();
second_Disp();
}
void UP_DOWN(){
if(digitalRead(UP)==LOW){
Disp_REG[Num]++;
}
while(digitalRead(UP)==LOW);
if(digitalRead(DOWN)==LOW){
Disp_REG[Num]--;
}
while(digitalRead(DOWN)==LOW);
}
//Year set
void year_Set(){
lcd.setCursor(0,1);
lcd.print("Y");
while(digitalRead(SET)==HIGH){
UP_DOWN();
if(Disp_REG[6] > 100){
Disp_REG[6] = 100;
}
if(Disp_REG[6] < 0){
Disp_REG[6] = 0;
}
delay(10);
year_Disp();
}
while(digitalRead(SET)==LOW);
OLD_REG[6]=Disp_REG[6];
Num--;
}
//month set
void month_Set(){
lcd.setCursor(0,1);
lcd.print("M");
while(digitalRead(SET)==HIGH){
UP_DOWN();
if(Disp_REG[5] > 12){
Disp_REG[5] = 1;
}
if(Disp_REG[5] < 1){
Disp_REG[5] = 12;
}
delay(10);
month_Disp();
}
while(digitalRead(SET)==LOW);
OLD_REG[5]=Disp_REG[5];
Num--;
}
//day set
void day_Set(){
lcd.setCursor(0,1);
lcd.print("D");
while(digitalRead(SET)==HIGH){
UP_DOWN();
if(Disp_REG[4] > 31){
Disp_REG[4] = 1;
}
if(Disp_REG[4] < 1){
Disp_REG[4] = 31;
}
delay(10);
day_Disp();
}
while(digitalRead(SET)==LOW);
OLD_REG[4]=Disp_REG[4];
Num--;
}
//week set
void week_Set(){
lcd.setCursor(0,1);
lcd.print("W");
while(digitalRead(SET)==HIGH){
UP_DOWN();
if(Disp_REG[3] > 7){
Disp_REG[3] = 1;
}
if(Disp_REG[3] < 1){
Disp_REG[3] = 7;
}
delay(10);
week_Disp();
}
while(digitalRead(SET)==LOW);
OLD_REG[3]=Disp_REG[3];
Num--;
}
//hour set
void hour_Set(){
lcd.setCursor(0,1);
lcd.print("h");
while(digitalRead(SET)==HIGH){
UP_DOWN();
if(Disp_REG[2] > 23){
Disp_REG[2] = 0;
}
if(Disp_REG[2] < 0){
Disp_REG[2] = 23;
}
delay(10);
hour_Disp();
}
while(digitalRead(SET)==LOW);
OLD_REG[2]=Disp_REG[2];
Num--;
}
//minute set
void minute_Set(){
lcd.setCursor(0,1);
lcd.print("m");
while(digitalRead(SET)==HIGH){
UP_DOWN();
if(Disp_REG[1] > 59){
Disp_REG[1] = 0;
}
if(Disp_REG[1] < 0){
Disp_REG[1] = 59;
}
delay(10);
minute_Disp();
}
while(digitalRead(SET)==LOW);
OLD_REG[1]=Disp_REG[1];
Num--;
}
//second set
void second_Set(){
lcd.setCursor(0,1);
lcd.print("s");
while(digitalRead(SET)==HIGH){
UP_DOWN();
if(Disp_REG[0] > 59){
Disp_REG[0] = 0;
}
if(Disp_REG[0] < 0){
Disp_REG[0] = 59;
}
delay(10);
second_Disp();
}
while(digitalRead(SET)==LOW);
OLD_REG[0]=Disp_REG[0];
Num--;
lcd.setCursor(0,1);
lcd.print(" ");
}
void Time_set(){
while(Num > -1){
switch(Num){
case 6:
year_Set();
break;
case 5:
month_Set();
break;
case 4:
day_Set();
break;
case 3:
week_Set();
break;
case 2:
hour_Set();
break;
case 1:
minute_Set();
break;
case 0:
second_Set();
break;
}
}
//表示用データ(10進)をRTC用データ(HEX)に変換
for(int i=0;i<=7;i++){
REG[i] = ((Disp_REG[i] / 10) << 4) | (Disp_REG[i] % 10);
}
Time_data_Send();//RTCに時刻データを送信
attachInterrupt(0,IntSR,RISING);//割込み許可
}
void loop(){
//割込み(1sec)flagが立った時、時刻を更新
if(INT_flag == 1){
TimeRead();
HEX2DEC();
TimeDisp();
}
INT_flag = 0; //更新が終わったのでflagを降ろす
uint16_t t = 0;
if(digitalRead(SET)==LOW){
while(digitalRead(SET) == LOW){
t++;
delayMicroseconds(100);
}
}
if(t > 5000){ //SETボタン長押しなら
Num = 6;
detachInterrupt(0);//割込みを停止して
Time_set();//時刻セットへ
}
delay(10);
}
//割込みがあった時にフラッグを立てる
void IntSR(){
INT_flag=1;
}
----------------------------------------------------------------------------------------------
* 2021.7.8
* JH7UBC Keiji Hata
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
//TimeKeeper Register Address
#define SECOND 0x00
#define MINUTE 0x01
#define HOUR 0x02
#define DAY 0x03 //WEEK
#define DATE 0x04
#define MONTH 0x05
#define YEAR 0x06
#define CTRL 0x07
#define RTC_address 0x68 //DS1307 I2C Address
char *week[] = {" ","MON","TUE","WED","THU","FRI","SAT","SUN"};
uint8_t REG[7]={0x00,0x00,0x00,0x01,0x01,0x01,0x21};//時刻データ初期値(HEX)
int Disp_REG[7];//表示用データ(10進)
uint8_t OLD_REG[7]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,};//Disp_REG1の前の値を格納しておくレジスタ
volatile uint8_t INT_flag=0;//割込みフラッグ
int Num = 6; //Clock data number 6:year 5:month 4:day 3:week 2:hour 1:mminute 0:second
#define SET 0 //SET BUTTON
#define UP 1 //UP BUTTON
#define DOWN 4 //DOWN BUTTON
LiquidCrystal_I2C lcd(0x27,16,2);
void setup() {
Wire.begin();
lcd.init(); //lcd初期化
lcd.backlight(); //LCD backlight ON
pinMode(SET,INPUT_PULLUP);
pinMode(UP,INPUT_PULLUP);
pinMode(DOWN,INPUT_PULLUP);
//RTCに初期データを書き込む
Time_data_Send();
//固定画面セット
lcd.setCursor(0,0);
lcd.print("20 / / ( )");
lcd.setCursor(6,1);
lcd.print(": :");
//初期時間表示
HEX2DEC();
TimeDisp();
//外部割込み設定 pin2,立ち上がり
attachInterrupt(0,IntSR,RISING);
}
//時刻データをRTCに書き込む
void Time_data_Send(){
Wire.beginTransmission(RTC_address);
Wire.write(0x00);//Register 先頭アドレス
Wire.write(REG[0]);//second
Wire.write(REG[1]);//minute
Wire.write(REG[2]);//hour
Wire.write(REG[3]);//week
Wire.write(REG[4]);//day
Wire.write(REG[5]);//month
Wire.write(REG[6]);//year
Wire.write(0x10);//1Hz output
Wire.endTransmission();
}
//RTCデータの読み込み
void TimeRead(){
Wire.beginTransmission(RTC_address);
Wire.write(0x00);//Register 先頭アドレス
Wire.endTransmission();
Wire.requestFrom(RTC_address,7);
for(int i=0;i<=7;i++){
REG[i]=Wire.read();
}
}
//REGのデータをHeXからDEC変換してDisp_REGに格納
void HEX2DEC(){
for(uint8_t i =0;i<=7;i++){
Disp_REG[i] = ((REG[i] & 0xF0)>>4)*10 +(REG[i] & 0x0F);
}
}
//Timeデータの表示
void Data_Disp(uint8_t n){
if(Disp_REG[n] != OLD_REG[n]){
if(Disp_REG[n]<10){
lcd.print("0");
}
lcd.print(Disp_REG[n]);
OLD_REG[n]=Disp_REG[n];
}
}
//year表示
void year_Disp(){
lcd.setCursor(2,0);
Data_Disp(6);
}
//month表示
void month_Disp(){
lcd.setCursor(5,0);
Data_Disp(5);
}
//day表示
void day_Disp(){
lcd.setCursor(8,0);
Data_Disp(4);
}
//week表示
void week_Disp(){
if(Disp_REG[3]!=OLD_REG[3]){
lcd.setCursor(12,0);
lcd.print(week[Disp_REG[3]]);
OLD_REG[3]=Disp_REG[3];
}
}
//hour表示
void hour_Disp(){
lcd.setCursor(4,1);
Data_Disp(2);
}
//minute表示
void minute_Disp(){
lcd.setCursor(7,1);
Data_Disp(1);
}
//second表示
void second_Disp(){
lcd.setCursor(10,1);
Data_Disp(0);
}
//時刻の表示
void TimeDisp(){
year_Disp();
month_Disp();
day_Disp();
week_Disp();
hour_Disp();
minute_Disp();
second_Disp();
}
void UP_DOWN(){
if(digitalRead(UP)==LOW){
Disp_REG[Num]++;
}
while(digitalRead(UP)==LOW);
if(digitalRead(DOWN)==LOW){
Disp_REG[Num]--;
}
while(digitalRead(DOWN)==LOW);
}
//Year set
void year_Set(){
lcd.setCursor(0,1);
lcd.print("Y");
while(digitalRead(SET)==HIGH){
UP_DOWN();
if(Disp_REG[6] > 100){
Disp_REG[6] = 100;
}
if(Disp_REG[6] < 0){
Disp_REG[6] = 0;
}
delay(10);
year_Disp();
}
while(digitalRead(SET)==LOW);
OLD_REG[6]=Disp_REG[6];
Num--;
}
//month set
void month_Set(){
lcd.setCursor(0,1);
lcd.print("M");
while(digitalRead(SET)==HIGH){
UP_DOWN();
if(Disp_REG[5] > 12){
Disp_REG[5] = 1;
}
if(Disp_REG[5] < 1){
Disp_REG[5] = 12;
}
delay(10);
month_Disp();
}
while(digitalRead(SET)==LOW);
OLD_REG[5]=Disp_REG[5];
Num--;
}
//day set
void day_Set(){
lcd.setCursor(0,1);
lcd.print("D");
while(digitalRead(SET)==HIGH){
UP_DOWN();
if(Disp_REG[4] > 31){
Disp_REG[4] = 1;
}
if(Disp_REG[4] < 1){
Disp_REG[4] = 31;
}
delay(10);
day_Disp();
}
while(digitalRead(SET)==LOW);
OLD_REG[4]=Disp_REG[4];
Num--;
}
//week set
void week_Set(){
lcd.setCursor(0,1);
lcd.print("W");
while(digitalRead(SET)==HIGH){
UP_DOWN();
if(Disp_REG[3] > 7){
Disp_REG[3] = 1;
}
if(Disp_REG[3] < 1){
Disp_REG[3] = 7;
}
delay(10);
week_Disp();
}
while(digitalRead(SET)==LOW);
OLD_REG[3]=Disp_REG[3];
Num--;
}
//hour set
void hour_Set(){
lcd.setCursor(0,1);
lcd.print("h");
while(digitalRead(SET)==HIGH){
UP_DOWN();
if(Disp_REG[2] > 23){
Disp_REG[2] = 0;
}
if(Disp_REG[2] < 0){
Disp_REG[2] = 23;
}
delay(10);
hour_Disp();
}
while(digitalRead(SET)==LOW);
OLD_REG[2]=Disp_REG[2];
Num--;
}
//minute set
void minute_Set(){
lcd.setCursor(0,1);
lcd.print("m");
while(digitalRead(SET)==HIGH){
UP_DOWN();
if(Disp_REG[1] > 59){
Disp_REG[1] = 0;
}
if(Disp_REG[1] < 0){
Disp_REG[1] = 59;
}
delay(10);
minute_Disp();
}
while(digitalRead(SET)==LOW);
OLD_REG[1]=Disp_REG[1];
Num--;
}
//second set
void second_Set(){
lcd.setCursor(0,1);
lcd.print("s");
while(digitalRead(SET)==HIGH){
UP_DOWN();
if(Disp_REG[0] > 59){
Disp_REG[0] = 0;
}
if(Disp_REG[0] < 0){
Disp_REG[0] = 59;
}
delay(10);
second_Disp();
}
while(digitalRead(SET)==LOW);
OLD_REG[0]=Disp_REG[0];
Num--;
lcd.setCursor(0,1);
lcd.print(" ");
}
void Time_set(){
while(Num > -1){
switch(Num){
case 6:
year_Set();
break;
case 5:
month_Set();
break;
case 4:
day_Set();
break;
case 3:
week_Set();
break;
case 2:
hour_Set();
break;
case 1:
minute_Set();
break;
case 0:
second_Set();
break;
}
}
//表示用データ(10進)をRTC用データ(HEX)に変換
for(int i=0;i<=7;i++){
REG[i] = ((Disp_REG[i] / 10) << 4) | (Disp_REG[i] % 10);
}
Time_data_Send();//RTCに時刻データを送信
attachInterrupt(0,IntSR,RISING);//割込み許可
}
void loop(){
//割込み(1sec)flagが立った時、時刻を更新
if(INT_flag == 1){
TimeRead();
HEX2DEC();
TimeDisp();
}
INT_flag = 0; //更新が終わったのでflagを降ろす
uint16_t t = 0;
if(digitalRead(SET)==LOW){
while(digitalRead(SET) == LOW){
t++;
delayMicroseconds(100);
}
}
if(t > 5000){ //SETボタン長押しなら
Num = 6;
detachInterrupt(0);//割込みを停止して
Time_set();//時刻セットへ
}
delay(10);
}
//割込みがあった時にフラッグを立てる
void IntSR(){
INT_flag=1;
}
----------------------------------------------------------------------------------------------
2021/7/8 追記
スケッチの一部を修正しました。
-----------------------------------------------------------------------------------------------
久しぶりにArduino Nanoを使いました。
Arduino Nano互換機のUSBシリアル変換デバイスCH340のドライバは、Windows 10では、接続すると自動的にインストールされ、使える状態になりました。
しかし、スケッチを書き込めないトラブル。これは、プロセッサをATmega328P(Old Bootloader)を選択して解決しました。
![](https://blogimg.goo.ne.jp/user_image/7d/63/2fcf4430c8ce9b7d192b09c1c02423fe.png)
※コメント投稿者のブログIDはブログ作成者のみに通知されます