[TH] Arduino : RTC DS1302

จากบทความการเขียนโปรแกรมภาษาไพธอนบน Micropython เพื่อใช้งาน RTC เบอร์ DS1302 ในคราวนี้เปลี่ยนภาษาการเขียนโปรแกรมเป็น C++ สำหรับ Arduino โดยใช้ไมโครคอนโทรลเลอร์ Cortex-M0 เบอร์ STM32F030F4P6 / esp8266 และ Arduino Mega เป็นตัวทำงานแทน ESP32 ดังภาพที่ 1, 2 และ 6 โดยรายงานการทำงานออกทางพอร์ต RS232 เพื่อแสดงวันและเวลาตามตัวอย่างผลลัพธ์ในภาพที่ 4

ภาพที่ 1 การทดลอง DS1302 กับ STM32F30F4_6
ภาพที่ 2 การใช้งานโมดูล ds1302 ด้วย esp8266

โปรแกรม

โค้ดโปรแกรมสำหรับการอ่านและเขียนวันที่/เวลากับโมดูล RTC ของไอซี ds1302 เป็นดังนี้ โดยเชื่อมต่อกับไมโครคอนโทรลเลอร์ผ่านทางขา PA2, PA3 และ PA4 เข้ากับขา SCLK, IO และ CE ตามลำดับ ดังภาพที่ 3

ภาพที่ 3 แสดงขาที่เชื่อมต่อกับโมดูล DS1302
// ds1302
// (C) 2021, by JarutEx (https://ww.jarutex.com)
#include <miniSerial.h>

#define PIN_CE PA4
#define PIN_SCLK PA2
#define PIN_IO PA3
#define REG_SECOND  0x80
#define REG_MINUTE  0x82
#define REG_HOUR    0x84
#define REG_DAY     0x86
#define REG_MONTH   0x88
#define REG_WEEKDAY 0x8A
#define REG_YEAR    0x8C
#define REG_WP      0x8E
#define REG_CTRL    0x90
#define REG_RAM     0xC0
uint8_t rtc[] = {0, 0, 0, 0, 0, 0, 0}; // y/m/d/dw/h/mi/s

inline void disCe() {
  digitalWrite( PIN_CE, LOW );
}

inline void enaCe() {
  digitalWrite( PIN_CE, HIGH );
}

inline void pulse() {
  digitalWrite( PIN_SCLK, HIGH );
  digitalWrite( PIN_SCLK, LOW );
}

uint8_t dec2bcd(uint8_t n) {
  return ((int)(n / 10) * 16 + (n % 10));
}

uint8_t bcd2dec( uint8_t n ) {
  return ((int)(n / 16 * 10) + (n % 16));
}

void write( uint8_t data ) {
  uint8_t dataBit;
  pinMode( PIN_IO, OUTPUT );
  for (int i = 0; i < 8; i++) {
    dataBit = (data & 0x01);
    data >>= 1;
    digitalWrite( PIN_IO, dataBit );
    pulse();
  }
}

uint8_t read() {
  uint8_t byteData = 0x00;
  uint8_t bitData;
  pinMode( PIN_IO, INPUT );
  for (int i = 0; i < 8; i++) {
    bitData = digitalRead( PIN_IO ) & 0x01;
    bitData <<= i;
    byteData |= bitData;
    pulse();
  }
  return byteData;
}

void set(uint8_t reg, uint8_t value) {
  enaCe();
  write(reg);
  write(value);
  disCe();
}

uint8_t get(uint8_t reg) {
  uint8_t value = 0;
  enaCe();
  write(reg + 1);
  value = read();
  disCe();
  return value;
}

void now() {
  rtc[0] = bcd2dec( get(REG_YEAR) );
  rtc[1] = bcd2dec( get(REG_MONTH) );
  rtc[2] = bcd2dec( get(REG_DAY) );
  rtc[3] = bcd2dec( get(REG_WEEKDAY) );
  rtc[4] = bcd2dec( get(REG_HOUR) );
  rtc[5] = bcd2dec( get(REG_MINUTE) );
  rtc[6] = bcd2dec( get(REG_SECOND) );
}

void adjust(uint8_t day, uint8_t month, uint16_t year,
            uint8_t dow, uint8_t hour, uint8_t minute, uint8_t second) {
  // convert
  year -= 2000;
  year &= 0xff;
  year = dec2bcd((uint8_t)year);
  month = dec2bcd(month);
  day = dec2bcd(day);
  dow = dec2bcd(dow);
  minute = dec2bcd(minute);
  hour = hour & 0x1F;
  hour = dec2bcd(hour);
  second = dec2bcd(second);
  // adjust
  set(REG_YEAR, year);
  set(REG_MONTH, month);
  set(REG_DAY, day);
  set(REG_WEEKDAY, dow);
  set(REG_HOUR, hour);
  set(REG_MINUTE, minute);
  set(REG_SECOND, second);
}

void show() {
  Serial.print( rtc[3] );
  Serial.print(" ");
  Serial.print( rtc[0]+2000 );
  Serial.print("-");
  Serial.print( rtc[1] );
  Serial.print("-");
  Serial.print( rtc[2] );
  Serial.print(" ");
  Serial.print( rtc[4] );
  Serial.print(":");
  Serial.print( rtc[5] );
  Serial.print(":");
  Serial.print( rtc[6] );
  Serial.println("");
}

void setup(void)
{
  Serial.begin(9600);
  // Serial.begin(19200, PA2, PA3);
  // Setting
  pinMode( PIN_IO, OUTPUT );
  pinMode( PIN_SCLK, OUTPUT );
  pinMode( PIN_CE, OUTPUT );
  disCe();
  digitalWrite( PIN_SCLK, LOW );

  adjust( 8, 7, 2021, 4, 13, 53, 0);
}

void loop()
{
  Serial.run();
  now();
  show();
  delay(1000);
}

ผลการทำงานเป็นดังภาพที่ 4

ภาพที่ 4 ตัวอย่างผลลัพธ์การทำงานจากโปรแกรม Serial Monitor

ตัวอย่างสำหรับ esp8266

กรณีที่ใช้กับ esp8266 ทางทีมงานเราเลือกขาหมายเลข D1, D2 และ D4 หรือ GPIO5, GPIO4 และ GPIO2 เชื่อมต่อกับขา SCLK, IO และ CE เพื่อใช้งาน DS1302 ซึ่งโค้ดโปรแกรมเป็นดังนี้

// ds1302/esp8266
// (C) 2021, by JarutEx (https://ww.jarutex.com)

#define PIN_CE 2 // D4
#define PIN_SCLK 5 // D1
#define PIN_IO 4 // D2

#define REG_SECOND  0x80
#define REG_MINUTE  0x82
#define REG_HOUR    0x84
#define REG_DAY     0x86
#define REG_MONTH   0x88
#define REG_WEEKDAY 0x8A
#define REG_YEAR    0x8C
#define REG_WP      0x8E
#define REG_CTRL    0x90
#define REG_RAM     0xC0
uint8_t rtc[] = {0, 0, 0, 0, 0, 0, 0}; // y/m/d/dw/h/mi/s

inline void disCe() {
  digitalWrite( PIN_CE, LOW );
}

inline void enaCe() {
  digitalWrite( PIN_CE, HIGH );
}

inline void pulse() {
  digitalWrite( PIN_SCLK, HIGH );
  digitalWrite( PIN_SCLK, LOW );
}

uint8_t dec2bcd(uint8_t n) {
  return ((int)(n / 10) * 16 + (n % 10));
}

uint8_t bcd2dec( uint8_t n ) {
  return ((int)(n / 16 * 10) + (n % 16));
}

void write( uint8_t data ) {
  uint8_t dataBit;
  pinMode( PIN_IO, OUTPUT );
  for (int i = 0; i < 8; i++) {
    dataBit = (data & 0x01);
    data >>= 1;
    digitalWrite( PIN_IO, dataBit );
    pulse();
  }
}

uint8_t read() {
  uint8_t byteData = 0x00;
  uint8_t bitData;
  pinMode( PIN_IO, INPUT );
  for (int i = 0; i < 8; i++) {
    bitData = digitalRead( PIN_IO ) & 0x01;
    bitData <<= i;
    byteData |= bitData;
    pulse();
  }
  return byteData;
}

void set(uint8_t reg, uint8_t value) {
  enaCe();
  write(reg);
  write(value);
  disCe();
}

uint8_t get(uint8_t reg) {
  uint8_t value = 0;
  enaCe();
  write(reg + 1);
  value = read();
  disCe();
  return value;
}

void now() {
  rtc[0] = bcd2dec( get(REG_YEAR) );
  rtc[1] = bcd2dec( get(REG_MONTH) );
  rtc[2] = bcd2dec( get(REG_DAY) );
  rtc[3] = bcd2dec( get(REG_WEEKDAY) );
  rtc[4] = bcd2dec( get(REG_HOUR) );
  rtc[5] = bcd2dec( get(REG_MINUTE) );
  rtc[6] = bcd2dec( get(REG_SECOND) );
}

void adjust(uint8_t day, uint8_t month, uint16_t year,
            uint8_t dow, uint8_t hour, uint8_t minute, uint8_t second) {
  // convert
  year -= 2000;
  year &= 0xff;
  year = dec2bcd((uint8_t)year);
  month = dec2bcd(month);
  day = dec2bcd(day);
  dow = dec2bcd(dow);
  minute = dec2bcd(minute);
  hour = hour & 0x1F;
  hour = dec2bcd(hour);
  second = dec2bcd(second);
  // adjust
  set(REG_YEAR, year);
  set(REG_MONTH, month);
  set(REG_DAY, day);
  set(REG_WEEKDAY, dow);
  set(REG_HOUR, hour);
  set(REG_MINUTE, minute);
  set(REG_SECOND, second);
}

void show() {
  Serial.print( rtc[3] );
  Serial.print(" ");
  Serial.print( rtc[0]+2000 );
  Serial.print("-");
  Serial.print( rtc[1] );
  Serial.print("-");
  Serial.print( rtc[2] );
  Serial.print(" ");
  Serial.print( rtc[4] );
  Serial.print(":");
  Serial.print( rtc[5] );
  Serial.print(":");
  Serial.print( rtc[6] );
  Serial.println("");
}

void setup(void)
{
  Serial.begin(9600);

  pinMode( PIN_IO, OUTPUT );
  pinMode( PIN_SCLK, OUTPUT );
  pinMode( PIN_CE, OUTPUT );
  disCe();
  digitalWrite( PIN_SCLK, LOW );

  adjust( 8, 7, 2021, 4, 13, 53, 0);
}

void loop()
{
  now();
  show();
  delay(1000);
}

โดยตั้งค่าของ Arduino IDE ดังภาพที่ 5

ภาพที่ 5 การตั้งค่าบอร์ดของ Arduino IDE เพื่อใช้กับ esp8266

ตัวอย่างสำหรับ Arduino Mega

เมื่อลองเปลี่ยนค่าของขาสำหรับ SCLK, IO และ CE เป็น D2, D3 และ D4 ของบอร์ด Arduino Mega ดังภาพที่ 6 และตั้งค่าตามภาพที่ 7 จะได้โค้ดของโปรแกรมเป็นดังนี้

// ds1302/Arduino Mega
// (C) 2021, by JarutEx (https://ww.jarutex.com)

#define PIN_CE 4
#define PIN_SCLK 2
#define PIN_IO 3

#define REG_SECOND  0x80
#define REG_MINUTE  0x82
#define REG_HOUR    0x84
#define REG_DAY     0x86
#define REG_MONTH   0x88
#define REG_WEEKDAY 0x8A
#define REG_YEAR    0x8C
#define REG_WP      0x8E
#define REG_CTRL    0x90
#define REG_RAM     0xC0
uint8_t rtc[] = {0, 0, 0, 0, 0, 0, 0}; // y/m/d/dw/h/mi/s

inline void disCe() {
  digitalWrite( PIN_CE, LOW );
}

inline void enaCe() {
  digitalWrite( PIN_CE, HIGH );
}

inline void pulse() {
  digitalWrite( PIN_SCLK, HIGH );
  digitalWrite( PIN_SCLK, LOW );
}

uint8_t dec2bcd(uint8_t n) {
  return ((int)(n / 10) * 16 + (n % 10));
}

uint8_t bcd2dec( uint8_t n ) {
  return ((int)(n / 16 * 10) + (n % 16));
}

void write( uint8_t data ) {
  uint8_t dataBit;
  pinMode( PIN_IO, OUTPUT );
  for (int i = 0; i < 8; i++) {
    dataBit = (data & 0x01);
    data >>= 1;
    digitalWrite( PIN_IO, dataBit );
    pulse();
  }
}

uint8_t read() {
  uint8_t byteData = 0x00;
  uint8_t bitData;
  pinMode( PIN_IO, INPUT );
  for (int i = 0; i < 8; i++) {
    bitData = digitalRead( PIN_IO ) & 0x01;
    bitData <<= i;
    byteData |= bitData;
    pulse();
  }
  return byteData;
}

void set(uint8_t reg, uint8_t value) {
  enaCe();
  write(reg);
  write(value);
  disCe();
}

uint8_t get(uint8_t reg) {
  uint8_t value = 0;
  enaCe();
  write(reg + 1);
  value = read();
  disCe();
  return value;
}

void now() {
  rtc[0] = bcd2dec( get(REG_YEAR) );
  rtc[1] = bcd2dec( get(REG_MONTH) );
  rtc[2] = bcd2dec( get(REG_DAY) );
  rtc[3] = bcd2dec( get(REG_WEEKDAY) );
  rtc[4] = bcd2dec( get(REG_HOUR) );
  rtc[5] = bcd2dec( get(REG_MINUTE) );
  rtc[6] = bcd2dec( get(REG_SECOND) );
}

void adjust(uint8_t day, uint8_t month, uint16_t year,
            uint8_t dow, uint8_t hour, uint8_t minute, uint8_t second) {
  // convert
  year -= 2000;
  year &= 0xff;
  year = dec2bcd((uint8_t)year);
  month = dec2bcd(month);
  day = dec2bcd(day);
  dow = dec2bcd(dow);
  minute = dec2bcd(minute);
  hour = hour & 0x1F;
  hour = dec2bcd(hour);
  second = dec2bcd(second);
  // adjust
  set(REG_YEAR, year);
  set(REG_MONTH, month);
  set(REG_DAY, day);
  set(REG_WEEKDAY, dow);
  set(REG_HOUR, hour);
  set(REG_MINUTE, minute);
  set(REG_SECOND, second);
}

void show() {
  Serial.print( rtc[3] );
  Serial.print(" ");
  Serial.print( rtc[0]+2000 );
  Serial.print("-");
  Serial.print( rtc[1] );
  Serial.print("-");
  Serial.print( rtc[2] );
  Serial.print(" ");
  Serial.print( rtc[4] );
  Serial.print(":");
  Serial.print( rtc[5] );
  Serial.print(":");
  Serial.print( rtc[6] );
  Serial.println("");
}

void setup(void)
{
  Serial.begin(9600);

  pinMode( PIN_IO, OUTPUT );
  pinMode( PIN_SCLK, OUTPUT );
  pinMode( PIN_CE, OUTPUT );
  disCe();
  digitalWrite( PIN_SCLK, LOW );

  adjust( 8, 7, 2021, 4, 13, 53, 0);
}

void loop()
{
  now();
  show();
  delay(1000);
}
ภาพที่ 6 การใช้งานโมดูล DS1302 กับบอร์ด Arduino Mega

ภาพที่ 7 การตั้งค่าบอร์ดสำหรับ Arduino Mega

สรุป

จากบทความนี้จะพบว่าเมื่อเข้าใจหลักของการทำงาน และสามารถเขียนด้วยภาษาใดภาษาหนึ่งได้ การแปลงโค้ดโปรแกรมข้ามภาษาเป็นสิ่งที่กระทำได้ไม่ยุ่งยากนัก และเป็นการฝึกปรือการเขียนโค้ดที่ดี นอกจากนี้จะพบว่า การย้ายโค้ดเพื่อใช้กับไมโครคอนโทรลเลอร์ที่แตกต่างกันด้วยการใช้เฟรมเวิร์ก Arduino นั้นจะต้องระวังเรื่องของสถาปัตยกรรมและหมายเลขขาให้ดีก่อนการเขียนโปรแกรม สุดท้าย พวกเราหวังว่าตัวอย่างโปรแกรมการใช้งานโมูล RTC ของไอซี DS1302 คงเป็นประโยชน์บ้างไม่มากก็น้อย และขอให้สนุกกับการเขียนโปรแกรมครับ

ท่านใดอยากพูดคุยสามารถคอมเมนท์ไว้ได้เลยครับ

(C) 2020-2021, โดย อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ
ปรับปรุงเมื่อ 2021-07-08, 2021-07-09, 2021-10-18