Create a gist now

Instantly share code, notes, and snippets.

// 2010 Kenneth Finnegan
// kennethfinnegan.blogspot.com
//
// 4 digit 7 segment clock
// Runs on a ATTiny 2313, with time keeping handled by a DS1307
// Digits are output as BCD, which is decoded by an external 7447
// PA0:1 - Time set buttons
// PB0:3 - Digit anodes
// PB5 - SDA
// PB7 - SCK
// PD2:5 - BCD output to 7447
// PD6 - PM LED
#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#ifndef __ATtiny2313__
#define __ATtiny2313__
#endif
#include "USI_I2C.c"
// CONSTANTS
#define XTAL 8000000L // Crystal frequency in Hz
#define TIMER_FREQ 300 // multiplex frequency in Hz
#define DS1307 0x68
// GLOBALS
static volatile uint8_t digits[4];
static volatile uint8_t ampmflag;
static volatile uint8_t currdig=0;
int count;
uint8_t time[3];
// Output Compare 1 overflow interrupt
// Handles display multiplexing
SIGNAL (SIG_OUTPUT_COMPARE1A)
{
currdig = (currdig + 1) % 4;
// Turn off last digit
PORTB |= 0xF;
// Setup 7447 decoder with next digit
PORTD = 0x3 | (digits[currdig] << 2) | (ampmflag<<PD6);
// Turn on new digit
PORTB &= ~(1<<currdig);
}
uint8_t BCD2DEC(uint8_t bcd) {
return (bcd & 0xF) + ((bcd >> 4) * 10);
}
uint8_t DEC2BCD(uint8_t dec) {
return (dec % 10) | ((dec / 10) << 4);
}
void downloadTime(void) {
uint8_t buf[4];
buf[0] = (DS1307 << 1) | 0;
buf[1] = 0x00;
I2C_xfer(buf, 2);
buf[0] |= 1;
I2C_xfer(buf, 4);
time[0] = buf[1];
time[1] = buf[2];
time[2] = buf[3];
}
void uploadTime(void) {
uint8_t buf[5];
buf[0] = (DS1307 << 1) | 0;
buf[1] = 0x00;
buf[2] = time[0];
buf[3] = time[1];
buf[4] = time[2];
I2C_xfer(buf, 5);
}
void updateDisplay(void) {
if (PINA & 1) {
// Normal hour and minute display
digits[0] = time[1] & 0xF;
digits[1] = time[1] >> 4;
uint8_t hour = BCD2DEC(time[2]);
ampmflag = (hour < 12);
hour = hour%12;
if (hour == 0)
hour = 12;
digits[2] = hour%10;
digits[3] = (hour/10?1:0xF); // Blank on 0
} else {
// Display seconds count
digits[0] = time[0] & 0xF;
digits[1] = time[0] >> 4;
digits[2] = 0xF;
digits[3] = 0xF;
ampmflag = time[0] & 1;
}
}
int main(void)
{
DDRA = 0x04;
PORTA = 0x07;
DDRB = 0xFF
PORTB = 0x00;
DDRD = 0x7C;
PORTD = 0x03;
I2C_init();
// use CLK/1024 prescale value, clear timer/counter on compareA match
TCCR1B = (1<<CS10) | (1<<CS12) | (1<<WGM12);
// preset timer1 high/low byte
OCR1A = ((XTAL/1024/TIMER_FREQ) - 1 );
// enable Output Compare 1 overflow interrupt
TIMSK = (1<<OCIE1A);
// Enable interrupts
sei();
while (1) {
downloadTime();
updateDisplay();
if (!(PIND & (1<<0))) {
time[2] = DEC2BCD((BCD2DEC(time[2])+1)%24);
uploadTime();
}
if (!(PIND & (1<<1))) {
time[0] = 0x00;
time[1] = DEC2BCD((BCD2DEC(time[1])+1)%60);
uploadTime();
}
_delay_ms(250);
}
}
// I2C library using USI hardware support
// 2010 Kenneth Finnegan
// Heavily based on Atmel application note AVR310
#include <util/delay.h>
#include <avr/io.h>
#include <inttypes.h>
//#define PARAM_VERIFY
//#define SIGNAL_VERIFY
//#define NOISE_TESTING
#ifdef I2C_FAST_MODE
#define I2C_T1 2 // >1.3us
#define I2C_T2 1 // >0.6us
#else
#define I2C_T1 5 // >4.7us
#define I2C_T2 5 // >4.0us
#endif
// Bit & byte definitions
#define I2C_READ_BIT 0 // R/W bit position in device address byte
#define I2C_ADDR_BITS 1 // LSB position of device address
#define I2C_NACK_BIT 0 // Bit position of (N)ACK bit
#define TRUE 1
#define FALSE 0
#define I2C_READ 1
#define I2C_WRITE 0
// ERRORS
#define I2C_ERR_NO_DATA 0x01
#define I2C_ERR_DATA_OUT_OF_BND 0x02
#define I2C_ERR_UE_START 0x03
#define I2C_ERR_UE_STOP 0x04
#define I2C_ERR_UE_DATA_COL 0x05
#define I2C_ERR_NO_ACK_ON_DATA 0x06
#define I2C_ERR_NO_ACK_ON_ADDR 0x07
#define I2C_ERR_MISSING_START_CON 0x08
#define I2C_ERR_MISSING_STOP_CON 0x09
// Device port definitions
#if defined(__ATtiny2313__)
#define I2C_DDR DDRB
#define I2C_PORT PORTB
#define I2C_PIN PINB
#define I2C_SDA PB5
#define I2C_SCL PB7
#endif
// Globals
union I2C_state {
uint8_t errorState;
struct {
uint8_t addressByte :1;
uint8_t dataDirection :1;
uint8_t unused :6;
};
} I2C_state;
// Function Definitions
void I2C_init(void);
uint8_t I2C_xfer(uint8_t *buffer, uint8_t length);
// Private function definitions
uint8_t I2C_byte_xfer(uint8_t reg);
uint8_t I2C_gen_start(void);
uint8_t I2C_gen_stop(void);
void I2C_delay(uint8_t length) {
do {
_delay_us(1);
} while (--length);
}
void I2C_init(void) {
// Set IO pins as output with pullup resistors
I2C_PORT |= (1<<I2C_SDA) | (1<<I2C_SCL);
I2C_DDR |= (1<<I2C_SDA) | (1<<I2C_SCL);
// Preload release of SDA
USIDR = 0xFF;
// Set USI in two wire mode, clock source USITC software strobe
USICR = (1<<USIWM1) | (1<<USICS1) | (1<<USICLK);
// Clear flags and counter
USISR = (1<<USISIF) | (1<<USIOIF) | (1<<USIPF) | (1<<USIDC) |
(0x0<<USICNT0);
}
// Main I2C transfer function. First byte of the buffer should be:
// (I2C_DEV_ADDR << I2C_ADDR_BITS) | READ/WRITE
uint8_t I2C_xfer(uint8_t *buffer, uint8_t buflen) {
// USISR for 8 bit xfer
uint8_t SR_8bit = (1<<USISIF)|(1<<USIOIF)|(1<<USIPF)|(1<<USIDC)|
(0x0<<USICNT0);
// USISR for 1 bit xfer
uint8_t SR_1bit = (1<<USISIF)|(1<<USIOIF)|(1<<USIPF)|(1<<USIDC)|
(0xE<<USICNT0);
I2C_state.errorState = 0;
I2C_state.addressByte = TRUE;
#ifdef PARAM_VERIFY
if (buffer > (uint8_t*)RAMEND) {
I2C_state.errorState = I2C_ERR_DATA_OUT_OF_BND;
return FALSE;
}
if (buflen <= 1) {
I2C_state.errorState = I2C_ERR_NO_DATA;
return FALSE;
}
#endif
// TODO NOISE_TESTING
if ((buffer[0] & (1<<I2C_READ_BIT)) == I2C_READ) {
I2C_state.dataDirection = I2C_READ;
} else {
I2C_state.dataDirection = I2C_WRITE;
}
I2C_gen_start();
do {
if (I2C_state.addressByte ||
(I2C_state.dataDirection == I2C_WRITE)) {
// Transmit a byte on the I2C bus
// SCL low
I2C_PORT &= ~(1<<I2C_SCL);
// Load data register
USIDR = *(buffer++);
I2C_byte_xfer(SR_8bit);
// Receive N/ACK from slave
I2C_DDR &= ~(1<<I2C_SDA);
if (I2C_byte_xfer(SR_1bit) & (1<<I2C_NACK_BIT)) {
I2C_state.errorState = I2C_state.addressByte ?
I2C_ERR_NO_ACK_ON_ADDR :
I2C_ERR_NO_ACK_ON_DATA;
return FALSE;
}
// Only one address byte
I2C_state.addressByte = FALSE;
} else {
// Receive a byte on the I2C bus
// Set SDA as input
I2C_DDR &= ~(1<<I2C_SDA);
// Pull byte off the bus
*(buffer++) = I2C_byte_xfer(SR_8bit);
// Transmit ACK or NACK data
USIDR = (buflen == 1) ? 0xFF : 0x00;
I2C_byte_xfer(SR_1bit);
}
} while (--buflen);
I2C_gen_stop();
return TRUE;
}
// Run the shift register until the counter overflows
// Function resets SDA line to output on return
uint8_t I2C_byte_xfer(uint8_t reg) {
// Preset counter
USISR = reg;
// Setup control register for clock strobe
reg = (1<<USIWM1) | (1<<USICS1) | (1<<USICLK) | (1<<USITC);
do {
I2C_delay(I2C_T1);
// Positive edge
USICR = reg;
// Wait for clock stretching
while (!(I2C_PIN & (1<<I2C_SCL))) ;
I2C_delay(I2C_T2);
// Negative edge
USICR = reg;
} while (!(USISR & (1<<USIOIF)));
I2C_delay(I2C_T1);
// Save shift register
reg = USIDR;
// Release SDA
USIDR = 0xFF;
// Set SDA as output
I2C_DDR |= (1<<I2C_SDA);
return reg;
}
uint8_t I2C_gen_start(void) {
// Release SCL
I2C_PORT |= (1<<I2C_SCL);
while(!(I2C_PORT & (1<<I2C_SCL))) ;
#ifdef I2C_FAST_MODE
I2C_delay(I2C_T2);
#else
I2C_delay(I2C_T1);
#endif
// Generate start condition
I2C_PORT &= ~(1<<I2C_SDA);
I2C_delay(I2C_T2);
I2C_PORT &= ~(1<<I2C_SCL);
I2C_PORT |= (1<<I2C_SDA);
#ifdef SIGNAL_VERIFY
// Check that the start condition was detected
if (!(USISR & (1<<USISIF))) {
I2C_state.errorState = I2C_ERR_MISSING_START_CON;
return FALSE;
}
#endif
return TRUE;
}
uint8_t I2C_gen_stop(void) {
// Pull SDA low
I2C_PORT &= ~(1<<I2C_SDA);
// Release SCL
I2C_PORT |= (1<<I2C_SCL);
// Wait for SCL to release
while(!(I2C_PIN & (1<<I2C_SCL))) ;
I2C_delay(I2C_T2);
I2C_PORT |= (1<<I2C_SDA);
I2C_delay(I2C_T1);
#ifdef SIGNAL_VERIFY
if (!(USISR & (1<<USIPF))) {
I2C_state.errorState = I2C_ERR_MISSING_STOP_CON;
return FALSE;
}
#endif
return TRUE;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment