Mercurial > ~darius > hgwebdir.cgi > avr
changeset 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 | c72cf25881fe |
children | 0910ab6f0095 |
files | Makefile ds1307.c ds1307.h tempctrl.c testavr.c |
diffstat | 5 files changed, 608 insertions(+), 9 deletions(-) [+] |
line wrap: on
line diff
--- a/Makefile Thu Oct 30 11:44:52 2008 +1030 +++ b/Makefile Thu Oct 30 11:53:32 2008 +1030 @@ -3,7 +3,7 @@ # PROG= testavr -SRCS= 1wire.c cons.c tempctrl.c testavr.c +SRCS= 1wire.c cons.c tempctrl.c testavr.c ds1307.c .if defined(WITHUSB) SRCS+= usb.c CFLAGS+=-DWITHUSB
--- /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); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ds1307.h Thu Oct 30 11:53:32 2008 +1030 @@ -0,0 +1,93 @@ +/* + * DS1307 headers + * + * 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. + */ + +#define IIC_STFAIL -1 +#define IIC_FAILARB -2 +#define IIC_SLNAK -3 +#define IIC_NOREPLY -4 +#define IIC_UNKNOWN -99 + +#define DS1307_ADR 0xd0 // DS1307's TWI address + +typedef struct { + uint8_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t min; + uint8_t sec; + + uint8_t ctrl; + uint8_t ram[56]; +} ds1307_t; + +typedef union { + uint8_t raw[8]; + struct { + uint8_t sec : 4; + uint8_t sec10 : 3; // Seconds (0-59) + uint8_t ch : 1; // Clock enable (1 = off) + + uint8_t min : 4; + uint8_t min10 : 3; // Minutes (0-59) + uint8_t pad0 : 1; + + uint8_t hour : 4; + uint8_t hour10 : 1; // Hours + uint8_t pmam : 1; // AM/PM => 1 = PM or extra bit for hour10 + uint8_t s1224 : 1; // 1 = 12 hour mode + uint8_t pad1 : 1; + + uint8_t dow : 3; // Day of the week (1-7) + uint8_t pad2 : 5; + + uint8_t day : 4; + uint8_t day10 : 2; // Day of the month (1-31) + uint8_t pad3 : 2; + + uint8_t month : 4; + uint8_t month10 : 1; + uint8_t pad4 : 3; // Month (1-12) + + uint8_t year : 4; + uint8_t year10 : 4; // Year (0-99) + + uint8_t rs : 2; // Rate select + uint8_t pad6 : 2; + uint8_t sqwe : 1; // Square wave enable + uint8_t pad5 : 2; + uint8_t out : 1; // Output control enable + } split; +} ds1307raw_t; + +int ds1307_init(void); +int iic_read(uint8_t *data, uint8_t len, uint8_t adr, uint8_t sla); +int iic_write(uint8_t *data, uint8_t len, uint8_t adr, uint8_t sla); +int ds1307_gettod(ds1307raw_t *time); +int ds1307_settod(char *date); +void ds1307_printtime(char *leader, char *trailer);
--- a/tempctrl.c Thu Oct 30 11:44:52 2008 +1030 +++ b/tempctrl.c Thu Oct 30 11:53:32 2008 +1030 @@ -39,6 +39,7 @@ #include "cons.h" #include "1wire.h" #include "tempctrl.h" +#include "ds1307.h" typedef struct { int32_t sec; @@ -335,7 +336,11 @@ if (nextstate != '-') currstate = nextstate; +#if 0 printf_P(PSTR("Time: %10ld, "), t); +#else + ds1307_printtime(PSTR(""), PSTR(": ")); +#endif printtemp(PSTR("Target"), settings.target_temp, PSTR(", ")); printtemp(PSTR("Fermenter"), fermenter_temp, PSTR(", ")); printtemp(PSTR("Fridge"), fridge_temp, PSTR(", ")); @@ -552,12 +557,12 @@ } #ifndef WITHUSB if (!strcasecmp_P(cmd, PSTR("beep"))) { - if (buf[8] == '1') + if (buf[8] == '1' || buf[8] == 'y') settings.dobeep = 1; - else if (buf[8] == '0') + else if (buf[8] == '0' || buf[8] == 'n') settings.dobeep = 0; else - printf_P(PSTR("Expected a 0 or 1\r\n")); + printf_P(PSTR("Expected a y/1 or n/0\r\n")); return; } #endif @@ -630,4 +635,3 @@ break; } } -
--- a/testavr.c Thu Oct 30 11:44:52 2008 +1030 +++ b/testavr.c Thu Oct 30 11:53:32 2008 +1030 @@ -42,6 +42,7 @@ #include "usb.h" #endif #include "tempctrl.h" +#include "ds1307.h" /* * Mirror of the MCUCSR register, taken early during startup. @@ -114,9 +115,15 @@ /* Set up the one wire stuff */ OWInit(); + /* Setup IIC */ + ds1307_init(); + /* Init UART */ cons_init(); + /* Init temperature control stuff */ + tempctrl_init(); + printf_P(PSTR("\r\n\r\n===============\r\n" "Inited!\r\n\r\n")); @@ -135,8 +142,6 @@ if ((mcucsr & _BV(JTRF)) == _BV(JTRF)) printf_P(PSTR("JTAG reset\r\n")); - tempctrl_init(); - /* Ready to go! */ sei(); @@ -204,10 +209,12 @@ "we ROMID adr val Write data into a DS2502 PROM (adr & val in hex)\r\n" "rr ROMID Read DS2502 PROM\r\n" "zz Reset MCU\r\n" + "gc Get time of day\r\n" + "sc time Set time of day (time is YYYY/MM/DD HH:MM:SS)\r\n" #ifdef WITHUSB "us Generate USB data\r\n" #endif - "tc ... Temperature control related (tc help for more)\r\n")); + "tc ... Temperature control related (tc help for more)\r\n" return; } else if (!strncasecmp_P((char *)cmd.buf, PSTR("zz"), 2)) { @@ -549,9 +556,18 @@ } else if (!strncasecmp_P((char *)cmd.buf, PSTR("us"), 2)) { usb_gendata(); #endif + } else if (!strncasecmp_P((char *)cmd.buf, PSTR("gc"), 2)) { + ds1307_printtime(PSTR(""), PSTR("\r\n")); + } else if (!strncasecmp_P((char *)cmd.buf, PSTR("sc"), 2)) { + if (cmd.len < 17) { + printf_P(PSTR("Command not long enough\r\n")); + } else { + if (ds1307_settod(cmd.buf + 3) != 1) + printf_P(PSTR("Unable to set time of day\r\n")); + } } else { printf_P(PSTR("Unknown command, help for a list\r\n")); } } - +