view ds1307.c @ 8:119688bb743f

Don't spin forever waiting for the TWI hardware to do something. I _think_ this will help the case where I find the micro is hanging but I haven't seen an error from it yet.
author darius@dons.net.au
date Fri, 01 May 2009 15:21:31 +0930
parents 3da232f97e81
children b5e4591b6570
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_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/%02d %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);
}