Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Nixie Tube based thermometer
// Kenneth Finnegan, 2011
// kennethfinnegan.blogspot.com
//
// Nixie Thermometer
// Reads DS1631 I2C thermometer and displays on
// three 74141 decoded Nixie tubes connected
// directly to the ATTiny2313 I/O ports
//
// Pinout on ATTiny2313
// Digit 1: PB[0-3]
// Digit 2: PD[2-5]
// Decimal point: PD1
// Digit 3: PA[0-1],PD6,PB4
// Mode select jumper: PD0
// I2C to DS1361: PB[5,7]
#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 DS1631 0x48
// GLOBALS
uint8_t temp[2];
uint8_t convertC2F;
// Sends op code to DS1631 to start continuous temp conversions
void startConvert(void) {
uint8_t buf[2];
buf[0] = (DS1631 << 1) | 0;
buf[1] = 0x51;
I2C_xfer(buf, 2);
}
// Download most recent 12 bit temperature conversion from DS1631
// First 8 bits whole degrees, second byte 4 bits of fractional degrees
// Frame bits: 8.4
void downloadTemp(void) {
uint8_t buf[3];
buf[0] = (DS1631 << 1) | 0;
buf[1] = 0xAA;
I2C_xfer(buf, 2);
buf[0] |= 1;
I2C_xfer(buf, 3);
temp[0] = buf[1];
temp[1] = buf[2];
}
// Convert 8.4 in celcius to appropriate BCD for 74141 decoders
void updateDisplay(void) {
uint8_t digits[3];
if (convertC2F == 0) { // Display C temps
digits[0] = temp[0] / 10;
digits[1] = temp[0] % 10;
digits[2] = temp[1] / 25;
PORTD |= 0x02; // Set decimal point
} else {
// Handle as single 32 bit int for temperature conversion
// 24.8 Due to overflow and carry issues with 8/16 bit words
uint32_t i = (temp[0] << 8) | temp[1];
// Convert from C to F
i = (9 * i) / 5 + (32 << 8);
if ((i >> 8) > 100) {
digits[0] = (i >> 8) / 100;
digits[1] = ((i >> 8) / 10) % 10;
digits[2] = (i >> 8) % 10;
PORTD &= ~(0x02); // Clear decimal point
} else {
digits[0] = (i >> 8) / 10;
digits[1] = (i >> 8) % 10;
digits[2] = (i & 0xFF) / 25;
// Check for overflow for byte to decimal conversion (250-255)
digits[2] = digits[2] > 9 ? 9 : digits[2];
PORTD |= 0x02; // Set decimal point
}
}
// Update output PORTs
// Digit 0: PB[0-3]
// Digit 1: PD[2-5]
// Decimal point: PD1
// Digit 2: PA[0-1],PD6,PB4
PORTA = (PORTA & 0xFC) | (digits[2] & 0x03);
PORTB = (PORTB & 0xE0) | digits[0] | ((digits[2] & 0x08) << 1);
PORTD = (PORTD & 0x83) | (digits[1] << 2) | ((digits[2] & 0x4) << 4);
}
int main(void)
{
DDRA = 0x03;
PORTA = 0x07;
DDRB = 0xFF;
PORTB = 0x00;
DDRD = 0xFE;
PORTD = 0x01;
I2C_init();
sei();
startConvert();
uint8_t i=0;
while (1) {
// Check the C/F config jumper
convertC2F = PIND & 0x01;
// Update displayed temperature
downloadTemp();
updateDisplay();
// Slow it all down to make the display seem less nervous
_delay_ms(1000);
}
}
// 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