/* rtc.c */ /* binwatch - Tiny binary wristwatch. {{{ * * Copyright (C) 2010 Nicolas Schodet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * Contact : * Web: http://ni.fr.eu.org/ * Email: * }}} */ #include "common.h" #include "rtc.h" #include "modules/twi/twi.h" #define RTC_STOP 0x20 #define RTC_GO 0 #define RTC_ADDR 0xa2 #define RTC_RESET_YEAR 2008 #define RTC_RESET_MONTH 5 #define RTC_RESET_WEEKDAY 6 #define RTC_RESET_DAY 10 #define RTC_RESET_HOUR 15 #define RTC_RESET_MINUTE 0 #define RTC_RESET_SECOND 0 #define RTC_RESET_GO 0 /** RTC registers. */ enum { RTC_REG_CONTROL1, RTC_REG_CONTROL2, RTC_REG_SECOND, RTC_REG_MINUTE, RTC_REG_HOUR, RTC_REG_DAY, RTC_REG_WEEKDAY, RTC_REG_MONTH_CENTURY, RTC_REG_YEAR, RTC_REG_ALARM_MINUTE, RTC_REG_ALARM_HOUR, RTC_REG_ALARM_DAY, RTC_REG_ALARM_WEEKDAY, RTC_REG_CLKOUT, RTC_REG_TIMER_CONTROL, RTC_REG_TIMER, }; /** Convert from binary to BCD, overflow will return 0x99. */ static uint8_t rtc_bin2bcd (uint8_t bin) { if (bin > 99) return 0x99; else { uint8_t u = bin % 10; uint8_t d = bin / 10; return d << 4 | u; } } /** Convert from BCD to binary. */ static uint8_t rtc_bcd2bin (uint8_t bcd) { uint8_t d = bcd >> 4; uint8_t u = bcd & 0xf; return d * 10 + u; } /** Prepare time buffer from natural time. */ static void rtc_prepare_time (uint8_t *buffer, uint8_t hour, uint8_t minute, uint8_t second) { assert (hour < 24); assert (minute < 60); assert (second < 60); buffer[0] = rtc_bin2bcd (second); buffer[1] = rtc_bin2bcd (minute); buffer[2] = rtc_bin2bcd (hour); } /** Prepare date buffer from natural date. */ static void rtc_prepare_date (uint8_t *buffer, uint16_t year, uint8_t month, uint8_t weekday, uint8_t day) { /* 1900 could be coded, but the RTC will be wrong concerning the number of * days in February. */ assert (year > 1900 && year < 2100); assert (month > 0 && month <= 12); assert (weekday < 7); assert (day > 0 && day <= 31); buffer[0] = rtc_bin2bcd (day); buffer[1] = weekday; buffer[2] = (year >= 2000 ? 0x80 : 0) | rtc_bin2bcd (month); buffer[3] = rtc_bin2bcd (year % 100); } /** Reset RTC, return non zero on success. */ static uint8_t rtc_reset (uint16_t year, uint8_t month, uint8_t weekday, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second, uint8_t go) { uint8_t buffer[] = { RTC_REG_CONTROL1, RTC_STOP, 0, 0, 0, 0, 0, 0, 0, 0, 0x80, 0x80, 0x80, 0x80, 0, 0, 0, go ? RTC_GO : RTC_STOP, 0 }; rtc_prepare_time (buffer + 1 + RTC_REG_SECOND, hour, minute, second); rtc_prepare_date (buffer + 1 + RTC_REG_DAY, year, month, weekday, day); twi_master_send (RTC_ADDR, buffer, sizeof (buffer)); return twi_master_wait () == sizeof (buffer); } /** Send register address for future read, return non zero on success. */ static uint8_t rtc_send_addr (uint8_t reg) { twi_master_send (RTC_ADDR, ®, 1); twi_master_wait (); return 1; } uint8_t rtc_init (void) { uint8_t buffer; twi_init (0); if (!rtc_send_addr (RTC_REG_SECOND)) return 0; twi_master_recv (RTC_ADDR, &buffer, 1); if (twi_master_wait () != 1) return 0; if (buffer & 0x80) { /* Low Voltage, reset. */ if (!rtc_reset (RTC_RESET_YEAR, RTC_RESET_MONTH, RTC_RESET_WEEKDAY, RTC_RESET_DAY, RTC_RESET_HOUR, RTC_RESET_MINUTE, RTC_RESET_SECOND, RTC_RESET_GO)) return 0; return RTC_LOW_VOLTAGE; } else return 1; } void rtc_get_time (uint8_t *hour, uint8_t *minute, uint8_t *second) { uint8_t buffer[3]; rtc_send_addr (RTC_REG_SECOND); twi_master_recv (RTC_ADDR, buffer, sizeof (buffer)); twi_master_wait (); *second = rtc_bcd2bin (buffer[0] & 0x7f); *minute = rtc_bcd2bin (buffer[1] & 0x7f); *hour = rtc_bcd2bin (buffer[2] & 0x3f); } void rtc_get_date (uint16_t *year, uint8_t *month, uint8_t *weekday, uint8_t *day) { uint8_t buffer[4]; rtc_send_addr (RTC_REG_DAY); twi_master_recv (RTC_ADDR, buffer, sizeof (buffer)); twi_master_wait (); *day = rtc_bcd2bin (buffer[0] & 0x3f); *weekday = buffer[1] & 0x7; *month = rtc_bcd2bin (buffer[2] & 0x1f); *year = ((buffer[2] & 0x80) ? 2000 : 1900) + rtc_bcd2bin (buffer[3]); } void rtc_stop (void) { uint8_t buffer[] = { RTC_REG_CONTROL1, RTC_STOP }; twi_master_send (RTC_ADDR, buffer, sizeof (buffer)); twi_master_wait (); } void rtc_go (void) { uint8_t buffer[] = { RTC_REG_CONTROL1, RTC_GO }; twi_master_send (RTC_ADDR, buffer, sizeof (buffer)); twi_master_wait (); } void rtc_set_time (uint8_t hour, uint8_t minute, uint8_t second) { uint8_t buffer[4]; buffer[0] = RTC_REG_SECOND; rtc_prepare_time (buffer + 1, hour, minute, second); twi_master_send (RTC_ADDR, buffer, sizeof (buffer)); twi_master_wait (); } void rtc_set_date (uint16_t year, uint8_t month, uint8_t weekday, uint8_t day) { uint8_t buffer[5]; buffer[0] = RTC_REG_DAY; rtc_prepare_date (buffer + 1, year, month, weekday, day); twi_master_send (RTC_ADDR, buffer, sizeof (buffer)); twi_master_wait (); }