Skip to content

Instantly share code, notes, and snippets.

@PhirePhly
Created December 19, 2010 06:24
Show Gist options
  • Save PhirePhly/747140 to your computer and use it in GitHub Desktop.
Save PhirePhly/747140 to your computer and use it in GitHub Desktop.
// Kenneth Finnegan, 2010
// kennethfinnegan.blogspot.com
//
// 4 digit 7 segment clock
// Runs on a ATTiny 2313, with time keeping handled by a DS1307
#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 // AVR clock frequency in Hz
#define TIMER_FREQ 300 // multiplex frequency in Hz
#define DS1307 0x68 // I2C address
// GLOBALS
static volatile uint8_t currdig=0;
static volatile uint8_t newdigit;
uint16_t count, bounce[2]; // tracks rough passage of time
uint8_t time[3];
uint16_t digits[9];
// These are functions instead of macros to save program space
uint8_t BCD2DEC(uint8_t bcd) {
return (bcd & 0xF) + ((bcd >> 4) * 10);
}
uint8_t DEC2BCD(uint8_t dec) {
return (dec % 10) | ((dec / 10) << 4);
}
// Download the current time from DS1307 RTC
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];
}
// Upload updated time to DS1307 RTC
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);
}
// Update the cached display bitfields, so they don't need to be
// calculated when needed
void updateDisplay(void) {
uint8_t table[] = {0x3F,0x18,0x76,0x7C,0x59,0x6D,
0x6F,0x38,0x7F,0x79,0x7B,0x73};
uint8_t hour = BCD2DEC(time[2]), ampm=0;
if (hour > 11) ampm = 1;
hour = (hour % 12) ? (hour % 12) : 12;
digits[0] = 0;
digits[1] = (hour/10) ? table[hour/10] : 0;
digits[2] = table[hour%10] | (1 << 8);
digits[3] = table[time[1] >> 4];
digits[4] = table[time[1] & 0x0F] | (1 << 8);
digits[5] = table[time[0] >> 4];
digits[6] = table[time[0] & 0x0F];
digits[7] = table[10 + ampm];
digits[8] = 0;
}
// Shift out a variable number of bits shift-register style
void shiftOut(uint16_t bits, uint8_t cnt) {
for ( ; cnt; cnt--) {
PORTB = (PORTB & 0xFC)| ((bits>>(cnt-1))<<1);
PINB = 1;
PINB = 1;
}
}
// Output Compare 1 overflow interrupt
// Flags for new digit for display multiplexing
SIGNAL (SIG_OUTPUT_COMPARE1A) {
newdigit = 1;
}
int main(void)
{
DDRA = 0x04;
PORTA = 0x07;
DDRB = 0xFF;
PORTB = 0x00;
DDRD = 0x20;
PORTD = 0x03;
I2C_init();
// Timer 0 Fast PWM for charge pump
TCCR0A = 0x23;
TCCR0B = 0x01;
OCR0B = 210;
// Timer 1 for multiplex flag
// use CLK/1024 prescale value, clear timer/counter on compareA match
TCCR1B = (0<<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) {
// Flag set by timer int
if (newdigit) {
newdigit = 0;
currdig = (currdig + 1) % 9;
if (currdig == 8) currdig = 1;
shiftOut(1<<currdig, 9);
shiftOut(digits[currdig], 11);
// Update time from DS1307 every 1/3 second
count++;
if (count > 100) {
count = 0;
downloadTime();
updateDisplay();
}
// Decrement debounce timers
if (bounce[0]) bounce[0] = bounce[0] - 1;
if (bounce[1]) bounce[1] = bounce[1] - 1;
}
// Check for button press, which is disabled if bounce[n] isn't
// back down to zero yet. Buttons go to ground with internal
// pull-up resistors
if (!(PIND & (1<<0)) && bounce[0] == 0) {
time[2] = DEC2BCD((BCD2DEC(time[2])+1)%24);
uploadTime();
bounce[0] = 0x1FF;
}
if (!(PIND & (1<<1)) && bounce[1] == 0) {
time[0] = 0x00;
time[1] = DEC2BCD((BCD2DEC(time[1])+1)%60);
uploadTime();
bounce[1] = 0x1FF;
}
// Brightness adjust by changing PWM for night-mode
OCR0B = 100 + 110 * (PINA & 1);
}
}
// I2C library using USI hardware support
// Kenneth Finnegan, 2010
// kennethfinnegan.blogspot.com
// 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