/* 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>
#include "config.h"
// Include USB library (AVRUSB or USBTINY)
// -------------------
#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;
#define usbInit() usb_init()
#define usbPoll() usb_poll()
#endif
/* 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)
#endif
static void i2c_io_set_sda(uchar hi) {
if(hi) {
I2C_DDR &= ~I2C_SDA; // high -> input
I2C_PORT |= I2C_SDA; // with pullup
} else {
I2C_DDR |= I2C_SDA; // low -> output
I2C_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 input
I2C_PORT |= I2C_SCL; // enable pullup
// wait while pin is pulled low by client
while(!(I2C_PIN & I2C_SCL));
} else {
I2C_DDR |= I2C_SCL; // port is output
I2C_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 high
else 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 input
I2C_PORT |= I2C_SDA; // enable pullup
#ifdef ENABLE_SCL_EXPAND
I2C_DDR &= ~I2C_SCL; // port is input
I2C_PORT |= I2C_SCL; // enable pullup
#else
I2C_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 HI
i2c_io_set_scl(1); // clock back up
b = i2c_io_get_sda(); // get the ACK bit
i2c_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 activated
i2c_io_set_scl(0); // clock LOW
for(i=7;i>=0;i--) {
i2c_io_set_scl(1); // clock HI
c = 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 NAK
else i2c_io_set_sda(0); // set ACK
i2c_scl_toggle(); // clock pulse
i2c_io_set_sda(1); // leave with SDL HI
return b; // return received byte
}
#ifdef DEBUG
void i2c_scan(void) {
uchar i = 0;
for(i=0;i<127;i++) {
i2c_start(); // do start transition
if(i2c_put_u08(i << 1)) // send DEVICE address
DEBUGF("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 2
static 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();
else
i2c_repstart();
// send DEVICE address
if(!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 USBTINY
return(cmd->len?0xff:0x00);
#else
return(((cmd->flags & I2C_M_RD) && cmd->len)?0xff:0x00);
#endif
}
#ifndef USBTINY
uchar usbFunctionSetup(uchar data[8]) {
static uchar replyBuf[4];
usbMsgPtr = replyBuf;
#else
extern byte_t usb_setup ( byte_t data[8] )
{
byte_t *replyBuf = data;
#endif
DEBUGF("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 transfers
return 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 USBTINY
uchar usbFunctionRead( uchar *data, uchar len )
#else
extern 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 bytes
for(i=0;i<len;i++) {
expected--;
*data = i2c_get_u08(expected == 0);
DEBUGF("data = %x\n", *data);
data++;
}
// end transfer on last byte
if((saved_cmd & CMD_I2C_END) && !expected)
i2c_stop();
} else {
DEBUGF("not in ack state\n");
memset(data, 0, len);
}
return len;
}
/*---------------------------------------------------------------------------*/
/* usbFunctionWrite */
/*---------------------------------------------------------------------------*/
#ifndef USBTINY
uchar usbFunctionWrite( uchar *data, uchar len )
#else
extern 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 bytes
for(i=0;i<len;i++) {
expected--;
DEBUGF("data = %x\n", *data);
if(!i2c_put_u08(*data++))
err = 1;
}
// end transfer on last byte
if((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 USBTINY
return 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 DEBUG
stdout = &mystdout;
#endif
DEBUGF("i2c-tiny-usb - (c) 2006 by Till Harbaum\n");
i2c_init();
#ifdef DEBUG
i2c_scan();
#endif
/* clear usb ports */
PORT(CONFIG_USB_PORT) &= (uchar)~((1<<CONFIG_USB_DMINUS)|(1<<CONFIG_USB_DPLUS));
/* make usb data lines outputs */
DDR(CONFIG_USB_PORT) |= ((1<<CONFIG_USB_DMINUS)|(1<<CONFIG_USB_DPLUS));
/* USB Reset by device only required on Watchdog Reset */
_delay_loop_2(40000); // 10ms
/* make usb data lines inputs */
DDR(CONFIG_USB_PORT) &= ~((1<<CONFIG_USB_DMINUS)|(1<<CONFIG_USB_DPLUS));
usbInit();
sei();
for(;;) { /* main event loop */
wdt_reset();
usbPoll();
}
return 0;
}
/* ------------------------------------------------------------------------- */