/* Name: main.c* Project: i2c-tiny-usb* Author: Till Harbaum* Tabsize: 4* Copyright: (c) 2005 by Till Harbaum <till@harbaum.org>* License: GPL* This Revision: $Id: main.c,v 1.9 2007/06/07 13:53:47 harbaum Exp $** $Log: main.c,v $* Revision 1.9 2007/06/07 13:53:47 harbaum* Version number fixes** Revision 1.8 2007/05/19 12:30:11 harbaum* Updated USB stacks** Revision 1.7 2007/04/22 10:34:05 harbaum* *** empty log message ***** Revision 1.6 2007/01/05 19:30:58 harbaum* i2c clock bug fix** Revision 1.5 2007/01/03 18:35:07 harbaum* usbtiny fixes and pcb layouts** Revision 1.4 2006/12/03 21:28:59 harbaum* *** empty log message ***** Revision 1.3 2006/11/22 19:12:45 harbaum* Added usbtiny support** Revision 1.2 2006/11/14 19:15:13 harbaum* *** empty log message *****/#include <stdio.h>#include <ctype.h>#include <string.h>#include <avr/io.h>#include <avr/interrupt.h>#include <avr/pgmspace.h>#include <avr/wdt.h>#include <util/delay.h>#ifndef USBTINY// use avrusb library#include "usbdrv.h"#include "oddebug.h"#else// use usbtiny library#include "usb.h"#include "usbtiny.h"typedef byte_t uchar;#if! defined (__AVR_ATtiny45__)#define USBDDR DDRC#define USB_CFG_IOPORT PORTC#else#define USBDDR DDRB#define USB_CFG_IOPORT PORTB#endif#define USB_CFG_DMINUS_BIT USBTINY_DMINUS#define USB_CFG_DPLUS_BIT USBTINY_DPLUS#define usbInit() usb_init()#define usbPoll() usb_poll()#endif#define ENABLE_SCL_EXPAND/* commands from USB, must e.g. match command ids in kernel driver */#define CMD_ECHO 0#define CMD_GET_FUNC 1#define CMD_SET_DELAY 2#define CMD_GET_STATUS 3#define CMD_I2C_IO 4#define CMD_I2C_BEGIN 1 // flag fo I2C_IO#define CMD_I2C_END 2 // flag fo I2C_IO/* linux kernel flags */#define I2C_M_TEN 0x10 /* we have a ten bit chip address */#define I2C_M_RD 0x01#define I2C_M_NOSTART 0x4000#define I2C_M_REV_DIR_ADDR 0x2000#define I2C_M_IGNORE_NAK 0x1000#define I2C_M_NO_RD_ACK 0x0800/* To determine what functionality is present */#define I2C_FUNC_I2C 0x00000001#define I2C_FUNC_10BIT_ADDR 0x00000002#define I2C_FUNC_PROTOCOL_MANGLING 0x00000004 /* I2C_M_{REV_DIR_ADDR,NOSTART,..} */#define I2C_FUNC_SMBUS_HWPEC_CALC 0x00000008 /* SMBus 2.0 */#define I2C_FUNC_SMBUS_READ_WORD_DATA_PEC 0x00000800 /* SMBus 2.0 */#define I2C_FUNC_SMBUS_WRITE_WORD_DATA_PEC 0x00001000 /* SMBus 2.0 */#define I2C_FUNC_SMBUS_PROC_CALL_PEC 0x00002000 /* SMBus 2.0 */#define I2C_FUNC_SMBUS_BLOCK_PROC_CALL_PEC 0x00004000 /* SMBus 2.0 */#define I2C_FUNC_SMBUS_BLOCK_PROC_CALL 0x00008000 /* SMBus 2.0 */#define I2C_FUNC_SMBUS_QUICK 0x00010000#define I2C_FUNC_SMBUS_READ_BYTE 0x00020000#define I2C_FUNC_SMBUS_WRITE_BYTE 0x00040000#define I2C_FUNC_SMBUS_READ_BYTE_DATA 0x00080000#define I2C_FUNC_SMBUS_WRITE_BYTE_DATA 0x00100000#define I2C_FUNC_SMBUS_READ_WORD_DATA 0x00200000#define I2C_FUNC_SMBUS_WRITE_WORD_DATA 0x00400000#define I2C_FUNC_SMBUS_PROC_CALL 0x00800000#define I2C_FUNC_SMBUS_READ_BLOCK_DATA 0x01000000#define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA 0x02000000#define I2C_FUNC_SMBUS_READ_I2C_BLOCK 0x04000000 /* I2C-like block xfer */#define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK 0x08000000 /* w/ 1-byte reg. addr. */#define I2C_FUNC_SMBUS_READ_I2C_BLOCK_2 0x10000000 /* I2C-like block xfer */#define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK_2 0x20000000 /* w/ 2-byte reg. addr. */#define I2C_FUNC_SMBUS_READ_BLOCK_DATA_PEC 0x40000000 /* SMBus 2.0 */#define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA_PEC 0x80000000 /* SMBus 2.0 */#define I2C_FUNC_SMBUS_BYTE I2C_FUNC_SMBUS_READ_BYTE | \I2C_FUNC_SMBUS_WRITE_BYTE#define I2C_FUNC_SMBUS_BYTE_DATA I2C_FUNC_SMBUS_READ_BYTE_DATA | \I2C_FUNC_SMBUS_WRITE_BYTE_DATA#define I2C_FUNC_SMBUS_WORD_DATA I2C_FUNC_SMBUS_READ_WORD_DATA | \I2C_FUNC_SMBUS_WRITE_WORD_DATA#define I2C_FUNC_SMBUS_BLOCK_DATA I2C_FUNC_SMBUS_READ_BLOCK_DATA | \I2C_FUNC_SMBUS_WRITE_BLOCK_DATA#define I2C_FUNC_SMBUS_I2C_BLOCK I2C_FUNC_SMBUS_READ_I2C_BLOCK | \I2C_FUNC_SMBUS_WRITE_I2C_BLOCK#define I2C_FUNC_SMBUS_EMUL I2C_FUNC_SMBUS_QUICK | \I2C_FUNC_SMBUS_BYTE | \I2C_FUNC_SMBUS_BYTE_DATA | \I2C_FUNC_SMBUS_WORD_DATA | \I2C_FUNC_SMBUS_PROC_CALL | \I2C_FUNC_SMBUS_WRITE_BLOCK_DATA | \I2C_FUNC_SMBUS_WRITE_BLOCK_DATA_PEC | \I2C_FUNC_SMBUS_I2C_BLOCK/* the currently support capability is quite limited */const unsigned long func PROGMEM = I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;#ifdef DEBUG#define DEBUGF(format, args...) printf_P(PSTR(format), ##args)/* ------------------------------------------------------------------------- */static int uart_putchar(char c, FILE *stream) {if (c == '\n')uart_putchar('\r', stream);loop_until_bit_is_set(UCSRA, UDRE);UDR = c;return 0;}static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL,_FDEV_SETUP_WRITE);#else#define DEBUGF(format, args...)#endif/* ------------------------------------------------------------------------- */#define DEFAULT_DELAY 10 // default 10us (100khz)static unsigned short clock_delay = DEFAULT_DELAY;static unsigned short clock_delay2 = DEFAULT_DELAY/2;static unsigned short expected;static unsigned char saved_cmd;#if! defined (__AVR_ATtiny45__)#define I2C_PORT PORTC#define I2C_PIN PINC#define I2C_DDR DDRC#define I2C_SDA _BV(4)#define I2C_SCL _BV(5)#else#define I2C_PORT PORTB#define I2C_PIN PINB#define I2C_DDR DDRB#define I2C_SDA _BV(1)#define I2C_SCL _BV(5)#endifstatic void i2c_io_set_sda(uchar hi) {if(hi) {I2C_DDR &= ~I2C_SDA; // high -> inputI2C_PORT |= I2C_SDA; // with pullup} else {I2C_DDR |= I2C_SDA; // low -> outputI2C_PORT &= ~I2C_SDA; // drive low}}static uchar i2c_io_get_sda(void) {return(I2C_PIN & I2C_SDA);}static void i2c_io_set_scl(uchar hi) {#ifdef ENABLE_SCL_EXPAND_delay_loop_2(clock_delay2);if(hi) {I2C_DDR &= ~I2C_SCL; // port is inputI2C_PORT |= I2C_SCL; // enable pullup// wait while pin is pulled low by clientwhile(!(I2C_PIN & I2C_SCL));} else {I2C_DDR |= I2C_SCL; // port is outputI2C_PORT &= ~I2C_SCL; // drive it low}_delay_loop_2(clock_delay);#else_delay_loop_2(clock_delay2);if(hi) I2C_PORT |= I2C_SCL; // port is highelse I2C_PORT &= ~I2C_SCL; // port is low_delay_loop_2(clock_delay);#endif}static void i2c_init(void) {/* init the sda/scl pins */I2C_DDR &= ~I2C_SDA; // port is inputI2C_PORT |= I2C_SDA; // enable pullup#ifdef ENABLE_SCL_EXPANDI2C_DDR &= ~I2C_SCL; // port is inputI2C_PORT |= I2C_SCL; // enable pullup#elseI2C_DDR |= I2C_SCL; // port is output#endif/* no bytes to be expected */expected = 0;}/* clock HI, delay, then LO */static void i2c_scl_toggle(void) {i2c_io_set_scl(1);i2c_io_set_scl(0);}/* i2c start condition */static void i2c_start(void) {i2c_io_set_sda(0);i2c_io_set_scl(0);}/* i2c repeated start condition */static void i2c_repstart(void){/* scl, sda may not be high */i2c_io_set_sda(1);i2c_io_set_scl(1);i2c_io_set_sda(0);i2c_io_set_scl(0);}/* i2c stop condition */void i2c_stop(void) {i2c_io_set_sda(0);i2c_io_set_scl(1);i2c_io_set_sda(1);}uchar i2c_put_u08(uchar b) {char i;for (i=7;i>=0;i--) {if ( b & (1<<i) ) i2c_io_set_sda(1);else i2c_io_set_sda(0);i2c_scl_toggle(); // clock HI, delay, then LO}i2c_io_set_sda(1); // leave SDL HIi2c_io_set_scl(1); // clock back upb = i2c_io_get_sda(); // get the ACK biti2c_io_set_scl(0); // not really ??return(b == 0); // return ACK value}uchar i2c_get_u08(uchar last) {char i;uchar c,b = 0;i2c_io_set_sda(1); // make sure pullups are activatedi2c_io_set_scl(0); // clock LOWfor(i=7;i>=0;i--) {i2c_io_set_scl(1); // clock HIc = i2c_io_get_sda();b <<= 1;if(c) b |= 1;i2c_io_set_scl(0); // clock LO}if(last) i2c_io_set_sda(1); // set NAKelse i2c_io_set_sda(0); // set ACKi2c_scl_toggle(); // clock pulsei2c_io_set_sda(1); // leave with SDL HIreturn b; // return received byte}#ifdef DEBUGvoid i2c_scan(void) {uchar i = 0;for(i=0;i<127;i++) {i2c_start(); // do start transitionif(i2c_put_u08(i << 1)) // send DEVICE addressDEBUGF("I2C device at address 0x%x\n", i);i2c_stop();}}#endif/* ------------------------------------------------------------------------- */struct i2c_cmd {unsigned char type;unsigned char cmd;unsigned short flags;unsigned short addr;unsigned short len;};#define STATUS_IDLE 0#define STATUS_ADDRESS_ACK 1#define STATUS_ADDRESS_NAK 2static uchar status = STATUS_IDLE;static uchar i2c_do(struct i2c_cmd *cmd) {uchar addr;DEBUGF("i2c %s at 0x%02x, len = %d\n",(cmd->flags&I2C_M_RD)?"rd":"wr", cmd->addr, cmd->len);/* normal 7bit address */addr = ( cmd->addr << 1 );if (cmd->flags & I2C_M_RD )addr |= 1;if(cmd->cmd & CMD_I2C_BEGIN)i2c_start();elsei2c_repstart();// send DEVICE addressif(!i2c_put_u08(addr)) {DEBUGF("I2C read: address error @ %x\n", addr);status = STATUS_ADDRESS_NAK;expected = 0;i2c_stop();} else {status = STATUS_ADDRESS_ACK;expected = cmd->len;saved_cmd = cmd->cmd;/* check if transfer is already done (or failed) */if((cmd->cmd & CMD_I2C_END) && !expected)i2c_stop();}/* more data to be expected? */#ifndef USBTINYreturn(cmd->len?0xff:0x00);#elsereturn(((cmd->flags & I2C_M_RD) && cmd->len)?0xff:0x00);#endif}#ifndef USBTINYuchar usbFunctionSetup(uchar data[8]) {static uchar replyBuf[4];usbMsgPtr = replyBuf;#elseextern byte_t usb_setup ( byte_t data[8] ){byte_t *replyBuf = data;#endifDEBUGF("Setup %x %x %x %x\n", data[0], data[1], data[2], data[3]);switch(data[1]) {case CMD_ECHO: // echo (for transfer reliability testing)replyBuf[0] = data[2];replyBuf[1] = data[3];return 2;break;case CMD_GET_FUNC:memcpy_P(replyBuf, &func, sizeof(func));return sizeof(func);break;case CMD_SET_DELAY:/* The delay function used delays 4 system ticks per cycle. *//* This gives 1/3us at 12Mhz per cycle. The delay function is *//* called twice per clock edge and thus four times per full cycle. *//* Thus it is called one time per edge with the full delay *//* value and one time with the half one. Resulting in *//* 2 * n * 1/3 + 2 * 1/2 n * 1/3 = n us. */clock_delay = *(unsigned short*)(data+2);if(!clock_delay) clock_delay = 1;clock_delay2 = clock_delay/2;if(!clock_delay2) clock_delay2 = 1;DEBUGF("request for delay %dus\n", clock_delay);break;case CMD_I2C_IO:case CMD_I2C_IO + CMD_I2C_BEGIN:case CMD_I2C_IO + CMD_I2C_END:case CMD_I2C_IO + CMD_I2C_BEGIN + CMD_I2C_END:// these are only allowed as class transfersreturn i2c_do((struct i2c_cmd*)data);break;case CMD_GET_STATUS:replyBuf[0] = status;return 1;break;default:// must not happen ...break;}return 0; // reply len}/*---------------------------------------------------------------------------*//* usbFunctionRead *//*---------------------------------------------------------------------------*/#ifndef USBTINYuchar usbFunctionRead( uchar *data, uchar len )#elseextern byte_t usb_in ( byte_t* data, byte_t len )#endif{uchar i;DEBUGF("read %d bytes, %d exp\n", len, expected);if(status == STATUS_ADDRESS_ACK) {if(len > expected) {DEBUGF("exceeds!\n");len = expected;}// consume bytesfor(i=0;i<len;i++) {expected--;*data = i2c_get_u08(expected == 0);DEBUGF("data = %x\n", *data);data++;}// end transfer on last byteif((saved_cmd & CMD_I2C_END) && !expected)i2c_stop();} else {DEBUGF("not in ack state\n");memset(data, 0, len);}return len;}/*---------------------------------------------------------------------------*//* usbFunctionWrite *//*---------------------------------------------------------------------------*/#ifndef USBTINYuchar usbFunctionWrite( uchar *data, uchar len )#elseextern void usb_out ( byte_t* data, byte_t len )#endif{uchar i, err=0;DEBUGF("write %d bytes, %d exp\n", len, expected);if(status == STATUS_ADDRESS_ACK) {if(len > expected) {DEBUGF("exceeds!\n");len = expected;}// consume bytesfor(i=0;i<len;i++) {expected--;DEBUGF("data = %x\n", *data);if(!i2c_put_u08(*data++))err = 1;}// end transfer on last byteif((saved_cmd & CMD_I2C_END) && !expected)i2c_stop();if(err) {DEBUGF("write failed\n");//TODO: set status}} else {DEBUGF("not in ack state\n");memset(data, 0, len);}#ifndef USBTINYreturn len;#endif}/* ------------------------------------------------------------------------- */int main(void) {wdt_enable(WDTO_1S);#if DEBUG_LEVEL > 0/* let debug routines init the uart if they want to */odDebugInit();#else#ifdef DEBUG/* quick'n dirty uart init */UCSRB |= _BV(TXEN);UBRRL = F_CPU / (19200 * 16L) - 1;#endif#endif#ifdef DEBUGstdout = &mystdout;#endifDEBUGF("i2c-tiny-usb - (c) 2006 by Till Harbaum\n");i2c_init();#ifdef DEBUGi2c_scan();#endif/* clear usb ports */USB_CFG_IOPORT &= (uchar)~((1<<USB_CFG_DMINUS_BIT)|(1<<USB_CFG_DPLUS_BIT));/* make usb data lines outputs */USBDDR |= ((1<<USB_CFG_DMINUS_BIT)|(1<<USB_CFG_DPLUS_BIT));/* USB Reset by device only required on Watchdog Reset */_delay_loop_2(40000); // 10ms/* make usb data lines inputs */USBDDR &= ~((1<<USB_CFG_DMINUS_BIT)|(1<<USB_CFG_DPLUS_BIT));usbInit();sei();for(;;) { /* main event loop */wdt_reset();usbPoll();}return 0;}/* ------------------------------------------------------------------------- */