0,0 → 1,578 |
/* 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) |
#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 */ |
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; |
} |
|
/* ------------------------------------------------------------------------- */ |