Mercurial > ~darius > hgwebdir.cgi > avr
diff ds1307.c @ 60:50fca9562310
Add support for reading/writing to a DS1307 over TWI/IIC.
Note that iic_read() appears to have a bug where it stops 1 byte earlier than
expected, work around it for now by adding 1 in the gettod() function.
tempctrl.c now prints the TOD as read from the RTC for each line.
author | darius@Inchoate |
---|---|
date | Thu, 30 Oct 2008 11:53:32 +1030 |
parents | |
children | 0910ab6f0095 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ds1307.c Thu Oct 30 11:53:32 2008 +1030 @@ -0,0 +1,486 @@ +/* + * Interface to a DS1307 + * + * Copyright (c) 2008 + * Daniel O'Connor <darius@dons.net.au>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <inttypes.h> +#include <avr/io.h> +#include <avr/pgmspace.h> +#include <util/twi.h> +#include <util/delay.h> + +#include "ds1307.h" + +//#define TWDEBUG + +/* + * ds1307_init + * + * Setup TWI interface + * + */ +int +ds1307_init(void) { + TWSR = 0; /* TWI Prescaler = 1 */ + +#if F_CPU < 3600000UL + TWBR = 10; /* Smallest valid TWBR */ +#else + TWBR = (F_CPU / 100000UL - 16) / 2; +#endif + + TWCR = _BV(TWEN); + + return(0); +} + +/* + * iic_read + * + * Read len bytes of data from address adr in slave sla into + * data. Presume that the slave auto-increments the address on + * successive reads. + * + * Returns the number of bytes read, or the following on failure. + * IIC_STFAIL Could generate START condition (broken bus or busy). + * IIC_FAILARB Failed bus arbitration. + * IIC_SLNAK Slave NAK'd. + * IIC_NOREPLY No reply (no such slave?) + * IIC_UNKNOWN Unexpected return from TWI reg. + */ +int +iic_read(uint8_t *data, uint8_t len, uint8_t adr, uint8_t sla) { + uint8_t twst, twcr, cnt; + + /* Generate START */ + TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN); + + /* Spin waiting for START to be generated */ + while ((TWCR & _BV(TWINT)) == 0) + ; + switch (twst = TW_STATUS) { + case TW_REP_START: /* OK but shouldn't happen */ + case TW_START: + break; + + case TW_MT_ARB_LOST: + return IIC_FAILARB; + break; + + default: + /* Not in start condition, bail */ + return IIC_UNKNOWN; + } +#ifdef TWDEBUG + printf_P(PSTR("Sent START\r\n")); +#endif + /* Send SLA+W */ + TWDR = sla | TW_WRITE; + TWCR = _BV(TWINT) | _BV(TWEN); + + /* Spin waiting for a response to be generated */ + while ((TWCR & _BV(TWINT)) == 0) + ; + +#ifdef TWDEBUG + printf_P(PSTR("Sent SLA+W\r\n")); +#endif + switch (twst = TW_STATUS) { + case TW_MT_SLA_ACK: + break; + + case TW_MT_SLA_NACK: + /* Send STOP */ + TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); + return IIC_SLNAK; + + case TW_MT_ARB_LOST: + return IIC_FAILARB; + break; + + default: + /* Send STOP */ + TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); + return IIC_UNKNOWN; + } + /* Send address */ + TWDR = adr; + TWCR = _BV(TWINT) | _BV(TWEN); + + /* Spin waiting for a response to be generated */ + while ((TWCR & _BV(TWINT)) == 0) + ; +#ifdef TWDEBUG + printf_P(PSTR("Sent address\r\n")); +#endif + switch ((twst = TW_STATUS)) { + case TW_MT_DATA_ACK: + break; + + case TW_MT_DATA_NACK: + /* Send STOP */ + TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); + return IIC_SLNAK; + + case TW_MT_ARB_LOST: + return IIC_FAILARB; + + default: + /* Send STOP */ + TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); + return IIC_UNKNOWN; + } + + /* Master receive cycle */ + TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN); + while ((TWCR & _BV(TWINT)) == 0) /* wait for transmission */ + ; + +#ifdef TWDEBUG + printf_P(PSTR("Sent START\r\n")); +#endif + switch ((twst = TW_STATUS)) { + case TW_REP_START: /* OK but shouldn't happen */ + case TW_START: + break; + + case TW_MT_ARB_LOST: + return IIC_FAILARB; + + default: + /* Send STOP */ + TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); + return IIC_UNKNOWN; + } + + /* send SLA+R */ + TWDR = sla | TW_READ; + TWCR = _BV(TWINT) | _BV(TWEN); /* clear interrupt to start transmission */ + + /* Spin waiting for a response to be generated */ + while ((TWCR & _BV(TWINT)) == 0) + ; +#ifdef TWDEBUG + printf_P(PSTR("Sent SLA+R\r\n")); +#endif + switch ((twst = TW_STATUS)) { + case TW_MR_SLA_ACK: + break; + + case TW_MR_SLA_NACK: + /* Send STOP */ + TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); + return IIC_SLNAK; + + case TW_MR_ARB_LOST: + return IIC_FAILARB; + + default: + /* Send STOP */ + TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); + return IIC_UNKNOWN; + } + + cnt = 0; + for (twcr = _BV(TWINT) | _BV(TWEN) | _BV(TWEA); + len > 0; len--) { + /* Send NAK on last byte */ + if (len == 1) + twcr = _BV(TWINT) | _BV(TWEN); + TWCR = twcr; /* clear int to start transmission */ + /* Spin waiting for a response to be generated */ + while ((TWCR & _BV(TWINT)) == 0) + ; +#ifdef TWDEBUG + printf_P(PSTR("Data request done\r\n")); +#endif + switch ((twst = TW_STATUS)) { + case TW_MR_DATA_NACK: + /* Send STOP */ + TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); + return cnt; + + case TW_MR_DATA_ACK: + *data++ = TWDR; + cnt++; + break; + + default: + return IIC_UNKNOWN; + } + } + + /* Send STOP */ + TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); + return cnt; +} + +/* + * iic_write + * + * Write len bytes of data from address adr in slave sla into + * data. Presume that the slave auto-increments the address on + * successive writes. + * + * Returns the number of bytes read, or the following on failure. + * IIC_STFAIL Could generate START condition (broken bus or busy). + * IIC_FAILARB Failed bus arbitration. + * IIC_SLNAK Slave NAK'd. + * IIC_NOREPLY No reply (no such slave?) + * IIC_UNKNOWN Unexpected return from TWI reg. + */ +int +iic_write(uint8_t *data, uint8_t len, uint8_t adr, uint8_t sla) { + uint8_t twst, cnt; + + /* Generate START */ + TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN); + + /* Spin waiting for START to be generated */ + while ((TWCR & _BV(TWINT)) == 0) + ; + switch (twst = TW_STATUS) { + case TW_REP_START: /* OK but shouldn't happen */ + case TW_START: + break; + + case TW_MT_ARB_LOST: + return IIC_FAILARB; + break; + + default: + /* Not in start condition, bail */ + return IIC_UNKNOWN; + } +#ifdef TWDEBUG + printf_P(PSTR("Sent START\r\n")); +#endif + + /* Send SLA+W */ + TWDR = sla | TW_WRITE; + TWCR = _BV(TWINT) | _BV(TWEN); + + /* Spin waiting for a response to be generated */ + while ((TWCR & _BV(TWINT)) == 0) + ; + +#ifdef TWDEBUG + printf_P(PSTR("Sent SLA+W\r\n")); +#endif + switch (twst = TW_STATUS) { + case TW_MT_SLA_ACK: + break; + + case TW_MT_SLA_NACK: + /* Send STOP */ + TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); + return IIC_SLNAK; + + case TW_MT_ARB_LOST: + return IIC_FAILARB; + break; + + default: + /* Send STOP */ + TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); + return IIC_UNKNOWN; + } + /* Send address */ + TWDR = adr; + TWCR = _BV(TWINT) | _BV(TWEN); + + /* Spin waiting for a response to be generated */ + while ((TWCR & _BV(TWINT)) == 0) + ; +#ifdef TWDEBUG + printf_P(PSTR("Sent address\r\n")); +#endif + switch ((twst = TW_STATUS)) { + case TW_MT_DATA_ACK: + break; + + case TW_MT_DATA_NACK: + /* Send STOP */ + TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); + return IIC_SLNAK; + + case TW_MT_ARB_LOST: + return IIC_FAILARB; + + default: + /* Send STOP */ + TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); + return IIC_UNKNOWN; + } + + cnt = 0; + for (; len > 0; len--) { + TWDR = *data++; + TWCR = _BV(TWINT) | _BV(TWEN); + + /* Spin waiting for a response to be generated */ + while ((TWCR & _BV(TWINT)) == 0) + ; +#ifdef TWDEBUG + printf_P(PSTR("Data sent\r\n")); +#endif + switch ((twst = TW_STATUS)) { + case TW_MT_DATA_NACK: + /* Send STOP */ + TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); + return cnt; + + case TW_MT_DATA_ACK: + cnt++; + break; + + default: + return IIC_UNKNOWN; + } + } + + /* Send STOP */ + TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); + return cnt; +} + +/* + * ds1307_gettod + * + * Read time of day from DS1307 into time + * + * Note that we canonify to 24hr mode. + * + */ +int +ds1307_gettod(ds1307raw_t *time) { + int len; + + len = iic_read((uint8_t *)time, sizeof(ds1307raw_t) + 1, 0, DS1307_ADR); + if (len < 0) { + printf_P(PSTR("iic_read failed - %d\r\n"), len); + return(0); + } + +#if 1 + if (len != sizeof(ds1307raw_t)) { + printf_P(PSTR("Couldn't read from RTC, got %d bytes (vs %d)\r\n"), len, sizeof(ds1307raw_t)); + return(0); + } +#endif + +#ifdef TWDEBUG + int i; + + for (i = 0; i < len; i++) + printf_P(PSTR("0x%02x: 0x%02x\r\n"), i, *(((uint8_t *)time) + i)); +#endif + + return(1); +} + +/* + * ds1307_settod + * + * Set the DS1307 with the supplied time, format like so + * sc 2008/10/29 23:45:30 + * + */ +int +ds1307_settod(char *date) { + ds1307raw_t rtime; + int i, year, month, day, hour, min, sec; + + if ((i = sscanf_P(date, PSTR("%d/%d/%d %d:%d:%d"), &year, &month, &day, &hour, &min, &sec)) != 6) { + printf_P(PSTR("Couldn't parse date, got %d\r\n"), i); + return(0); + } + + if (year > 1900) + year -= 1900; + + rtime.split.year10 = year / 10; + rtime.split.year = year % 10; + rtime.split.month10 = month / 10; + rtime.split.month = month % 10; + rtime.split.day10 = day / 10; + rtime.split.day = day % 10; + rtime.split.pmam = ((hour / 10) & 0x02) >> 1; + rtime.split.hour10 = (hour / 10) & 0x01; + rtime.split.hour = hour % 10; + rtime.split.min10 = min / 10; + rtime.split.min = min % 10; + rtime.split.sec10 = sec / 10; + rtime.split.sec = sec % 10; + + rtime.split.ch = 0; // Enable clock + rtime.split.s1224 = 0; // 24 hour mode + rtime.split.dow = 0; // XXX: unused + rtime.split.out = 0; // No clock out + +#if TWDEBUG + for (i = 0; i < sizeof(ds1307raw_t); i++) + printf_P(PSTR("0x%02x: 0x%02x\r\n"), i, *(((uint8_t *)rtime) + i)); +#endif + if ((i = iic_write((uint8_t *)&rtime, sizeof(ds1307raw_t), 0, DS1307_ADR)) != sizeof(ds1307raw_t)) + printf_P(PSTR("Unable to write to RTC, only sent %d (vs %d)\r\n"), i, sizeof(ds1307raw_t)); + + return(1); +} + +/* + * ds1307_printtime + * + * Print the time in rtime with trailer + * + */ +void +ds1307_printtime(char *leader, char *trailer) { + ds1307raw_t rtime; + uint8_t hour; + + if (ds1307_gettod(&rtime) != 1) + return; + + // Handle 12/24 hour time + hour = rtime.split.hour10 * 10 + rtime.split.hour; + if (rtime.split.s1224) { + if (rtime.split.pmam) + hour += 12; + } else + hour += (rtime.split.pmam << 1) * 10; + + printf_P(PSTR("%S%04d/%02d/%d %02d:%02d:%02d%S"), leader, + 1900 + rtime.split.year10 * 10 + rtime.split.year, + rtime.split.month10 * 10 + rtime.split.month, + rtime.split.day10 * 10 + rtime.split.day, + hour, + rtime.split.min10 * 10 + rtime.split.min, + rtime.split.sec10 * 10 + rtime.split.sec, + trailer); +}