From 7669349030886c5c8427dc7cf63d9e28be0023ab Mon Sep 17 00:00:00 2001 From: Doug Szumski Date: Thu, 4 Feb 2016 19:27:25 +0000 Subject: [PATCH] parts: DS1338 real time clock which runs on the TWI bus Features: > External oscillator synced to the AVR core > Square wave output with scalable frequency > Leap year correction until 2100 (just like the real thing!) Should also work for the pin compatible DS1307 device, but that part hasn't been tested against actual hardware. --- examples/parts/ds1338_virt.c | 501 +++++++++++++++++++++++++++++++++++ examples/parts/ds1338_virt.h | 185 +++++++++++++ 2 files changed, 686 insertions(+) create mode 100644 examples/parts/ds1338_virt.c create mode 100644 examples/parts/ds1338_virt.h diff --git a/examples/parts/ds1338_virt.c b/examples/parts/ds1338_virt.c new file mode 100644 index 0000000..5381a16 --- /dev/null +++ b/examples/parts/ds1338_virt.c @@ -0,0 +1,501 @@ +/* + ds1338_virt.c + + Copyright 2014 Doug Szumski + + Based on i2c_eeprom example by: + + Copyright 2008, 2009 Michel Pollet + + This file is part of simavr. + + simavr 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 3 of the License, or + (at your option) any later version. + + simavr 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 simavr. If not, see . + */ + + +#include +#include +#include + +#include "avr_twi.h" +#include "ds1338_virt.h" +#include "sim_time.h" + + +/* + * Increment the ds1338 register address. + */ +static void +ds1338_virt_incr_addr(ds1338_virt_t * const p) +{ + if (p->reg_addr < sizeof(p->nvram)) { + p->reg_addr++; + } else { + // TODO Check if this wraps, or if it just stops incrementing + p->reg_addr = 0; + } +} + +/* + * Update the system behaviour after a control register is written to. + */ +static void +ds1338_virt_update(const ds1338_virt_t * const p) +{ + // The address of the register which was just updated + switch (p->reg_addr) + { + case DS1338_VIRT_SECONDS: + if (ds1338_get_flag(p->nvram[p->reg_addr], DS1338_VIRT_CH) == 0) { + printf("DS1338 clock ticking\n"); + } else { + printf("DS1338 clock stopped\n"); + } + break; + case DS1338_VIRT_CONTROL: + printf("DS1338 control register updated\n"); + // TODO: Check if changing the prescaler resets the clock counter + // and if so do it here? + break; + default: + // No control register updated + return; + } +} + +/* + * Calculate days in month given the year. The year should be specified + * in 4 digit format. + */ +static uint8_t +ds1338_virt_days_in_month(uint8_t month, uint16_t year) { + + uint8_t is_leap_year = 1; + if ((year & 3) == 0 && ((year % 25) != 0 || (year & 15) == 0)) + is_leap_year = 0; + + uint8_t days; + if (month == 2) + days = 28 + is_leap_year; + else + days = 31 - (month - 1) % 7 % 2; + + return days; +} + +/* + * Ticks a BCD register according to the specified constraints. + */ +static uint8_t +ds1338_virt_tick_bcd_reg(bcd_reg_t * bcd_reg) +{ + // Unpack BCD + uint8_t x = (*bcd_reg->reg & 0x0F) + + 10*((*bcd_reg->reg & bcd_reg->tens_mask) >> 4); + + // Tick + uint8_t cascade = 0; + if (++x > bcd_reg->max_val) { + x = bcd_reg->min_val; + cascade = 1; + } + + // Set the BCD part of the register + *bcd_reg->reg &= ~(0x0F | bcd_reg->tens_mask); + *bcd_reg->reg |= (x / 10 << 4) + x % 10; + + return cascade; +} + +/* + * Ticks the time registers. See table 3, p10 of the DS1338 datasheet. + */ +static void +ds1338_virt_tick_time(ds1338_virt_t *p) { + + /* + * Seconds + */ + bcd_reg_t reg = { + .reg = &p->nvram[DS1338_VIRT_SECONDS], + .min_val = 0, + .max_val = 59, + .tens_mask = 0b01110000 + }; + uint8_t cascade = ds1338_virt_tick_bcd_reg(®); + if (!cascade) + return; + + /* + * Minutes + */ + reg.reg = &p->nvram[DS1338_VIRT_MINUTES]; + cascade = ds1338_virt_tick_bcd_reg(®); + if (!cascade) + return; + + /* + * Hours + */ + reg.reg = &p->nvram[DS1338_VIRT_HOURS]; + if (ds1338_get_flag(p->nvram[DS1338_VIRT_HOURS], DS1338_VIRT_12_24_HR)) { + // 12 hour mode + reg.min_val = 1; + reg.max_val = 12; + reg.tens_mask = 0b00010000; + uint8_t pm = ds1338_get_flag(p->nvram[DS1338_VIRT_HOURS], DS1338_VIRT_AM_PM); + cascade = ds1338_virt_tick_bcd_reg(®); + if (cascade) { + if (pm) { + // Switch to AM + p->nvram[DS1338_VIRT_HOURS] &= !(1 << DS1338_VIRT_AM_PM); + } else { + // Switch to PM and catch the cascade + p->nvram[DS1338_VIRT_HOURS] |= (1 << DS1338_VIRT_AM_PM); + cascade = 0; + } + } + } else { + // 24 hour mode + reg.min_val = 0; + reg.max_val = 23; + reg.tens_mask = 0b00110000; + cascade = ds1338_virt_tick_bcd_reg(®); + } + if (!cascade) + return; + + /* + * Day + */ + reg.reg = &p->nvram[DS1338_VIRT_DAY]; + reg.min_val = 1; + reg.max_val = 7; + reg.tens_mask = 0; + ds1338_virt_tick_bcd_reg(®); + + /* + * Date + */ + reg.reg = &p->nvram[DS1338_VIRT_DATE]; + // Insert a y2.1k bug like they do in the original part + uint16_t year = 2000 + UNPACK_BCD(p->nvram[DS1338_VIRT_YEAR]); + reg.max_val = ds1338_virt_days_in_month( + UNPACK_BCD(p->nvram[DS1338_VIRT_MONTH]), + year); + reg.tens_mask = 0b00110000; + cascade = ds1338_virt_tick_bcd_reg(®); + if (!cascade) + return; + + /* + * Month + */ + reg.reg = &p->nvram[DS1338_VIRT_MONTH]; + reg.max_val = 12; + reg.tens_mask = 0b00010000; + cascade = ds1338_virt_tick_bcd_reg(®); + if (!cascade) + return; + + /* + * Year + */ + reg.reg = &p->nvram[DS1338_VIRT_YEAR]; + reg.min_val = 0; + reg.max_val = 99; + reg.tens_mask = 0b11110000; + cascade = ds1338_virt_tick_bcd_reg(®); +} + +static void +ds1338_virt_cycle_square_wave(ds1338_virt_t *p) +{ + if(!ds1338_get_flag(p->nvram[DS1338_VIRT_CONTROL], DS1338_VIRT_SQWE)) { + printf("DS1338: SQWE disabled"); + // Square wave output disabled + return; + } + + p->square_wave = !p->square_wave; + + if (p->square_wave) { + avr_raise_irq(p->irq + DS1338_SQW_IRQ_OUT, 1); + //printf ("Tick\n"); + } else { + avr_raise_irq(p->irq + DS1338_SQW_IRQ_OUT, 0); + //printf ("Tock\n"); + } +} + +/* + * This function is left in for debugging. + */ +static void +ds1338_print_time(ds1338_virt_t *p) +{ + uint8_t seconds = (p->nvram[DS1338_VIRT_SECONDS] & 0xF) + + ((p->nvram[DS1338_VIRT_SECONDS] & 0b01110000) >> 4) * 10; + uint8_t minutes = (p->nvram[DS1338_VIRT_MINUTES] & 0xF) + + (p->nvram[DS1338_VIRT_MINUTES] >> 4) * 10; + + uint8_t pm = 0; + uint8_t hours; + if (ds1338_get_flag (p->nvram[DS1338_VIRT_HOURS], DS1338_VIRT_12_24_HR)) + { + // 12hr mode + pm = ds1338_get_flag(p->nvram[DS1338_VIRT_HOURS], + DS1338_VIRT_AM_PM); + hours = (p->nvram[DS1338_VIRT_HOURS] & 0xF) + + ((p->nvram[DS1338_VIRT_HOURS] & 0b00010000) >> 4) * 10; + } else { + // 24hr mode + hours = (p->nvram[DS1338_VIRT_HOURS] & 0xF) + + ((p->nvram[DS1338_VIRT_HOURS] & 0b00110000) >> 4) * 10; + } + + uint8_t day = p->nvram[DS1338_VIRT_DAY] & 0b00000111; + uint8_t date = (p->nvram[DS1338_VIRT_DATE] & 0xF) + + (p->nvram[DS1338_VIRT_DATE] >> 4) * 10; + uint8_t month = (p->nvram[DS1338_VIRT_MONTH] & 0xF) + + (p->nvram[DS1338_VIRT_MONTH] >> 4) * 10; + uint8_t year = (p->nvram[DS1338_VIRT_YEAR] & 0xF) + + (p->nvram[DS1338_VIRT_YEAR] >> 4) * 10; + + if(p->verbose) + printf("Time: %02i:%02i:%02i Day: %i Date: %02i:%02i:%02i PM:%01x\n", + hours, minutes, seconds, day, date, month, year, pm); +} + +static avr_cycle_count_t +ds1338_virt_clock_tick(struct avr_t * avr, + avr_cycle_count_t when, + ds1338_virt_t *p) +{ + avr_cycle_count_t next_tick = when + avr_usec_to_cycles(avr, DS1338_CLK_PERIOD_US / 2); + + if (!ds1338_get_flag(p->nvram[DS1338_VIRT_SECONDS], DS1338_VIRT_CH)) { + // Oscillator is enabled. Note that this counter is allowed to wrap. + p->rtc++; + } else { + // Avoid a condition match below with the clock switched off + return next_tick; + } + + /* + * Update the time + */ + if (p->rtc == 0) { + // 1 second has passed + ds1338_virt_tick_time(p); + if (p->verbose) + ds1338_print_time(p); + } + + /* + * Deal with the square wave output + */ + uint8_t prescaler_mode = ds1338_get_flag(p->nvram[DS1338_VIRT_CONTROL], + DS1338_VIRT_RS0) + + (ds1338_get_flag(p->nvram[DS1338_VIRT_CONTROL], + DS1338_VIRT_RS1) << 1); + + switch (prescaler_mode) + { + case DS1338_VIRT_PRESCALER_DIV_32768: + if ((p->rtc + 1) % DS1338_CLK_FREQ == 0) { + ds1338_virt_cycle_square_wave(p); + } + break; + case DS1338_VIRT_PRESCALER_DIV_8: + if ((p->rtc + 1) % (DS1338_CLK_FREQ / 8) == 0) + ds1338_virt_cycle_square_wave(p); + break; + case DS1338_VIRT_PRESCALER_DIV_4: + if ((p->rtc + 1) % (DS1338_CLK_FREQ / 4) == 0) + ds1338_virt_cycle_square_wave(p); + break; + case DS1338_VIRT_PRESCALER_OFF: + ds1338_virt_cycle_square_wave(p); + break; + default: + printf("DS1338 ERROR: PRESCALER MODE INVALID\n"); + break; + } + + return next_tick; +} + +static void +ds1338_virt_clock_xtal_init(struct avr_t * avr, + ds1338_virt_t *p) +{ + p->rtc = 0; + + /* + * Set a timer for half the clock period to allow reconstruction + * of the square wave output at the maximum possible frequency. + */ + avr_cycle_timer_register_usec(avr, + DS1338_CLK_PERIOD_US / 2, + (void *) ds1338_virt_clock_tick, + p); + + printf("DS1338 clock crystal period %duS or %d cycles\n", + DS1338_CLK_PERIOD_US, + (int)avr_usec_to_cycles(avr, DS1338_CLK_PERIOD_US)); +} + +/* + * Called when a RESET signal is sent + */ +static void +ds1338_virt_in_hook(struct avr_irq_t * irq, + uint32_t value, + void * param) +{ + ds1338_virt_t * p = (ds1338_virt_t*)param; + avr_twi_msg_irq_t v; + v.u.v = value; + + /* + * If we receive a STOP, check it was meant to us, and reset the transaction + */ + if (v.u.twi.msg & TWI_COND_STOP) { + if (p->selected) { + // Wahoo, it was us! + if (p->verbose) + printf("DS1338 stop\n\n"); + } + /* We should not zero the register address here because read mode uses the last + * register address stored and write mode always overwrites it. + */ + p->selected = 0; + p->reg_selected = 0; + } + + /* + * If we receive a start, reset status, check if the slave address is + * meant to be us, and if so reply with an ACK bit + */ + if (v.u.twi.msg & TWI_COND_START) { + //printf("DS1338 start attempt: 0x%02x, mask: 0x%02x, + //twi: 0x%02x\n", p->addr_base, p->addr_mask, v.u.twi.addr); + p->selected = 0; + // Ignore the read write bit + if ((v.u.twi.addr >> 1) == (DS1338_VIRT_TWI_ADDR >> 1)) { + // it's us ! + if (p->verbose) + printf("DS1338 start\n"); + p->selected = v.u.twi.addr; + avr_raise_irq(p->irq + TWI_IRQ_INPUT, + avr_twi_irq_msg(TWI_COND_ACK, p->selected, 1)); + } + } + + /* + * If it's a data transaction, first check it is meant to be us (we + * received the correct address and are selected) + */ + if (p->selected) { + // Write transaction + if (v.u.twi.msg & TWI_COND_WRITE) { + // ACK the byte + avr_raise_irq(p->irq + TWI_IRQ_INPUT, + avr_twi_irq_msg(TWI_COND_ACK, p->selected, 1)); + // Write to the selected register (see p13. DS1388 datasheet for details) + if (p->reg_selected) { + if (p->verbose) + printf("DS1338 set register 0x%02x to 0x%02x\n", + p->reg_addr, v.u.twi.data); + p->nvram[p->reg_addr] = v.u.twi.data; + ds1338_virt_update(p); + ds1338_virt_incr_addr(p); + // No register selected so select one + } else { + if (p->verbose) + printf("DS1338 select register 0x%02x\n", v.u.twi.data); + p->reg_selected = 1; + p->reg_addr = v.u.twi.data; + } + } + // Read transaction + if (v.u.twi.msg & TWI_COND_READ) { + if (p->verbose) + printf("DS1338 READ data at 0x%02x: 0x%02x\n", + p->reg_addr, p->nvram[p->reg_addr]); + uint8_t data = p->nvram[p->reg_addr]; + ds1338_virt_incr_addr(p); + avr_raise_irq(p->irq + TWI_IRQ_INPUT, + avr_twi_irq_msg(TWI_COND_READ, p->selected, data)); + } + } +} + +static const char * _ds1338_irq_names[DS1338_IRQ_COUNT] = { + [DS1338_TWI_IRQ_INPUT] = "8>ds1338.out", + [DS1338_TWI_IRQ_OUTPUT] = "32nvram, 0x00, sizeof(p->nvram)); + // Default for day counter. Strangely it runs from 1-7. + p->nvram[DS1338_VIRT_DAY] = 1; + + p->avr = avr; + + p->irq = avr_alloc_irq(&avr->irq_pool, 0, DS1338_IRQ_COUNT, _ds1338_irq_names); + avr_irq_register_notify(p->irq + TWI_IRQ_OUTPUT, ds1338_virt_in_hook, p); + + // Start with the oscillator disabled, at least until there is some "battery backup" + p->nvram[DS1338_VIRT_SECONDS] |= (1 << DS1338_VIRT_CH); + + ds1338_virt_clock_xtal_init(avr, p); +} + +/* + * "Connect" the IRQs of the DS1338 to the TWI/i2c master of the AVR. + */ +void +ds1338_virt_attach_twi(ds1338_virt_t * p, + uint32_t i2c_irq_base) +{ + avr_connect_irq( + p->irq + TWI_IRQ_INPUT, + avr_io_getirq(p->avr, i2c_irq_base, TWI_IRQ_INPUT)); + avr_connect_irq( + avr_io_getirq(p->avr, i2c_irq_base, TWI_IRQ_OUTPUT), + p->irq + TWI_IRQ_OUTPUT); +} + +/* + * Optionally "connect" the square wave out IRQ to the AVR. + */ +void +ds1338_virt_attach_square_wave_output(ds1338_virt_t * p, + ds1338_pin_t * wiring) +{ + avr_connect_irq( + p->irq + DS1338_SQW_IRQ_OUT, + avr_io_getirq(p->avr, AVR_IOCTL_IOPORT_GETIRQ(wiring->port), wiring->pin)); +} + diff --git a/examples/parts/ds1338_virt.h b/examples/parts/ds1338_virt.h new file mode 100644 index 0000000..41044da --- /dev/null +++ b/examples/parts/ds1338_virt.h @@ -0,0 +1,185 @@ +/* + ds1338_virt.h + + Copyright 2014 Doug Szumski + + Based on i2c_eeprom example by: + + Copyright 2008, 2009 Michel Pollet + + This file is part of simavr. + + simavr 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 3 of the License, or + (at your option) any later version. + + simavr 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 simavr. If not, see . + */ + +/* + * A virtual DS1338 real time clock which runs on the TWI bus. + * + * Features: + * + * > External oscillator is synced to the AVR core + * > Square wave output with scalable frequency + * > Leap year correction until 2100 + * + * Should also work for the pin compatible DS1307 device. + */ + +#ifndef DS1338_VIRT_H_ +#define DS1338_VIRT_H_ + +#include "sim_irq.h" +#include "sim_avr.h" +#include "avr_ioport.h" + +// TWI address is fixed +#define DS1338_VIRT_TWI_ADDR 0xD0 + +/* + * Internal registers. Time is in BCD. + * See p10 of the DS1388 datasheet. + */ +#define DS1338_VIRT_SECONDS 0x00 +#define DS1338_VIRT_MINUTES 0x01 +#define DS1338_VIRT_HOURS 0x02 +#define DS1338_VIRT_DAY 0x03 +#define DS1338_VIRT_DATE 0x04 +#define DS1338_VIRT_MONTH 0x05 +#define DS1338_VIRT_YEAR 0x06 +#define DS1338_VIRT_CONTROL 0x07 + +/* + * Seconds register flag - oscillator is enabled when + * this is set to zero. Undefined on startup. + */ +#define DS1338_VIRT_CH 7 + +/* + * 12/24 hour select bit. When high clock is in 12 hour + * mode and the AM/PM bit is operational. When low the + * AM/PM bit becomes part of the tens counter for the + * 24 hour clock. + */ +#define DS1338_VIRT_12_24_HR 6 + +/* + * AM/PM flag for 12 hour mode. PM is high. + */ +#define DS1338_VIRT_AM_PM 5 + +/* + * Control register flags. See p11 of the DS1388 datasheet. + * + * +-----+-----+-----+------------+------+ + * | OUT | RS1 | RS0 | SQW_OUTPUT | SQWE | + * +-----+-----+-----+------------+------+ + * | X | 0 | 0 | 1Hz | 1 | + * | X | 0 | 1 | 4.096kHz | 1 | + * | X | 1 | 0 | 8.192kHz | 1 | + * | X | 1 | 1 | 32.768kHz | 1 | + * | 0 | X | X | 0 | 0 | + * | 1 | X | X | 1 | 0 | + * +-----+-----+-----+------------+------+ + * + * OSF : Oscillator stop flag. Set to 1 when oscillator + * is interrupted. + * + * SQWE : Square wave out, set to 1 to enable. + */ +#define DS1338_VIRT_RS0 0 +#define DS1338_VIRT_RS1 1 +#define DS1338_VIRT_SQWE 4 +#define DS1338_VIRT_OSF 5 +#define DS1338_VIRT_OUT 7 + +#define DS1338_CLK_FREQ 32768 +#define DS1338_CLK_PERIOD_US (1000000 / DS1338_CLK_FREQ) + +// Generic unpack of 8bit BCD register. Don't use on seconds or hours. +#define UNPACK_BCD(x) (((x) & 0x0F) + ((x) >> 4) * 10) + +enum { + DS1338_TWI_IRQ_OUTPUT = 0, + DS1338_TWI_IRQ_INPUT, + DS1338_SQW_IRQ_OUT, + DS1338_IRQ_COUNT +}; + +/* + * Square wave out prescaler modes; see p11 of DS1338 datasheet. + */ +enum { + DS1338_VIRT_PRESCALER_DIV_32768 = 0, + DS1338_VIRT_PRESCALER_DIV_8, + DS1338_VIRT_PRESCALER_DIV_4, + DS1338_VIRT_PRESCALER_OFF, +}; + +/* + * Describes the behaviour of the specified BCD register. + * + * The tens mask is used to avoid config bits present in + * the same register. + */ +typedef struct bcd_reg_t { + uint8_t * reg; + uint8_t min_val; + uint8_t max_val; + uint8_t tens_mask; +} bcd_reg_t; + +// TODO: This should be generic, is also used in ssd1306, and maybe elsewhere.. +typedef struct ds1338_pin_t +{ + char port; + uint8_t pin; +} ds1338_pin_t; + +/* + * DS1338 I2C clock + */ +typedef struct ds1338_virt_t { + struct avr_t * avr; + avr_irq_t * irq; // irq list + uint8_t verbose; + uint8_t selected; // selected address + uint8_t reg_selected; // register selected for write + uint8_t reg_addr; // register pointer + uint8_t nvram[64]; // battery backed up NVRAM + uint16_t rtc; // RTC counter + uint8_t square_wave; +} ds1338_virt_t; + +void +ds1338_virt_init(struct avr_t * avr, + ds1338_virt_t * p); + +/* + * Attach the ds1307 to the AVR's TWI master code, + * pass AVR_IOCTL_TWI_GETIRQ(0) for example as i2c_irq_base + */ +void +ds1338_virt_attach_twi(ds1338_virt_t * p, + uint32_t i2c_irq_base); + +void +ds1338_virt_attach_square_wave_output(ds1338_virt_t * p, + ds1338_pin_t * wiring); + +static inline int +ds1338_get_flag(uint8_t reg, uint8_t bit) +{ + return (reg & (1 << bit)) != 0; +} + +#endif /* DS1338_VIRT_H_ */ -- 2.39.5