view ds1307.c @ 19:b5e4591b6570

- Satisfy new compiler - Put strings in PROGMEM - Add various time functions
author Daniel O'Connor <darius@dons.net.au>
date Sat, 31 Jan 2015 23:27:10 +1030
parents 119688bb743f
children
line wrap: on
line source

/*
 * 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

/* Helper code to wait for the TWCR to do something */
#define WAITFORTWINT()	do {			\
	uint8_t i; \
	for (i = 0; i < 255 && (TWCR & _BV(TWINT)) == 0; i++) \
	    _delay_ms(1); \
	if (i == 255) \
	    return(IIC_NORESP); \
    } while (0)
    
	
/* 
 * ds1307_init
 *
 * Setup TWI interface
 *
 */
int
ds1307_init(void) {
#ifdef PRR
    PRR &= ~_BV(PRTWI);		/* Power TWI on - note that the
				 * datasheet says this is already 0 at
				 * power on.. */
#endif
    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.
 *
 * Heaviy cribbed from twitest.c by Joerg Wunsch
 */
int8_t
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 */
    WAITFORTWINT();

    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 */
    WAITFORTWINT();

#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 */
    WAITFORTWINT();
    
#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);

    /* wait for transmission */
    WAITFORTWINT();
    
#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 */
    WAITFORTWINT();
    
#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 */
	WAITFORTWINT();
	
#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);
		//printf_P(PSTR("NACK on byte %d\r\n"), cnt);
		return cnt;
		
	    case TW_MR_DATA_ACK:
		*data++ = TWDR;
		//printf_P(PSTR("ACK on byte %d for 0x%02x\r\n"), cnt, *(data - 1));
		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.
 *
 * Heaviy cribbed from twitest.c by Joerg Wunsch
 */
int8_t
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 */
    WAITFORTWINT();

    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 */
    WAITFORTWINT();

#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 */
    WAITFORTWINT();
    
#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 */
	WAITFORTWINT();
	
#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.
 *
 */
int8_t
ds1307_gettod(ds1307raw_t *time) {
    int8_t 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("Only 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
 *
 */
int8_t
ds1307_settod(char *date) {
    ds1307raw_t rtime;
    uint16_t year;
    uint8_t i,  month, day, hour, min, sec;

    if ((i = sscanf_P(date, PSTR("%hu/%hhd/%hhd %hhd:%hhd:%hhd"), &year, &month, &day, &hour, &min, &sec)) != 6) {
	printf_P(PSTR("Can't parse date\r\n"));
	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

#ifdef 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("Can't write to RTC, sent %d (vs %d)\r\n"), i, sizeof(ds1307raw_t));
    
    return(1);
}

/* 
 * ds1307_printnow
 *
 * Print the current time of day
 *
 */
void
ds1307_printnow(const char *leader, const char *trailer) {
    ds1307raw_t rtime;
    ds1307_t time;

    if (ds1307_gettod(&rtime) != 1)
	return;
    
    ds1307_cook(&rtime, &time);
    ds1307_printtime(&time, leader, trailer);
}

/* 
 * ds1307_printtime
 *
 * Print time with header & trailer
 *
 */
void
ds1307_printtime(ds1307_t *time, const char *leader, const char *trailer) {
    printf_P(PSTR("%S%04d/%02d/%02d %02d:%02d:%02d%S"), leader,
	     1900 + time->year, time->month, time->day,
	     time->hour, time->min, time->sec, trailer);
}

/*
 * ds1307_cook
 *
 * Convert a ds1307raw_t to ds1307_t
 */
void
ds1307_cook(ds1307raw_t *raw, ds1307_t *cooked) {
    cooked->year = raw->split.year + raw->split.year10 * 10;
    cooked->month = raw->split.month + raw->split.month10 * 10;
    cooked->day = raw->split.day + raw->split.day10 * 10;

    /* Handle 12/24 hour time */
    cooked->hour = raw->split.hour10 * 10 + raw->split.hour;
    if (raw->split.s1224) {
	if (raw->split.pmam)
	    cooked->hour += 12;
    } else
	cooked->hour += (raw->split.pmam << 1) * 10;

    cooked->min = raw->split.min + raw->split.min10 * 10;
    cooked->sec = raw->split.sec + raw->split.sec10 * 10;
}

/* 
 * ds1307_totimet
 *
 * Convert a ds1307_t to time_t
 *
 */
#define SECONDS_PER_DAY 86400
static uint8_t dayspermonth[] = { 31, 27, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
static uint8_t dayspermonth_leap[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

static int
isleapyear(int year) {
    return ((!(year % 4) && (year % 100)) || !(year % 400)) ? 1 : 0;
}

time_t
ds1307_totimet(ds1307_t *time) {
    time_t	t = 0;
    int16_t	i;

    for (i = time->year + 1900; i > 1970; i--)
	t += (isleapyear(i) ? 366 : 365) * SECONDS_PER_DAY;
    for (i = time->month; i > 1; i--)
	t += (isleapyear(i) ? dayspermonth_leap[i] : dayspermonth[i]) * SECONDS_PER_DAY;
    t += (time->day - 1) * SECONDS_PER_DAY;
    t += time->hour * 60 * 60;
    t += time->min * 60;
    t += time->sec;
    t += 65535; // XXX: this is magical :(
    return(t);
}

/* 
 * ds1307_time
 *
 * Return TOD as a time_t
 *
 */
time_t
ds1307_time(void) {
    ds1307raw_t	r;
    ds1307_t	c;
    
    if (ds1307_gettod(&r) == 0)
	return -1;
    
    ds1307_cook(&r, &c);
    return ds1307_totimet(&c);
}