diff --git a/src/platform/avr32/EVK1100/evk1100_conf.h b/src/platform/avr32/EVK1100/evk1100_conf.h index 11999228..f661f341 100644 --- a/src/platform/avr32/EVK1100/evk1100_conf.h +++ b/src/platform/avr32/EVK1100/evk1100_conf.h @@ -87,6 +87,7 @@ _ROM( AUXLIB_UART, luaopen_uart, uart_map )\ _ROM( AUXLIB_PIO, luaopen_pio, pio_map )\ _ROM( AUXLIB_PWM, luaopen_pwm, pwm_map )\ + _ROM( AUXLIB_I2C, luaopen_i2c, i2c_map )\ _ROM( AUXLIB_SPI, luaopen_spi, spi_map )\ _ROM( AUXLIB_TMR, luaopen_tmr, tmr_map )\ _ROM( AUXLIB_TERM, luaopen_term, term_map )\ @@ -116,6 +117,7 @@ #define NUM_TIMER 3 #endif #define NUM_PWM 7 +#define NUM_I2C 1 #define NUM_ADC 8 #define NUM_CAN 0 diff --git a/src/platform/avr32/MIZAR32/mizar32_conf.h b/src/platform/avr32/MIZAR32/mizar32_conf.h index 44f04cd4..12b5ca7b 100644 --- a/src/platform/avr32/MIZAR32/mizar32_conf.h +++ b/src/platform/avr32/MIZAR32/mizar32_conf.h @@ -113,6 +113,7 @@ _ROM( AUXLIB_UART, luaopen_uart, uart_map )\ _ROM( AUXLIB_PIO, luaopen_pio, pio_map )\ _ROM( AUXLIB_PWM, luaopen_pwm, pwm_map )\ + _ROM( AUXLIB_I2C, luaopen_i2c, i2c_map )\ _ROM( AUXLIB_SPI, luaopen_spi, spi_map )\ _ROM( AUXLIB_TMR, luaopen_tmr, tmr_map )\ NETLINE\ @@ -144,6 +145,7 @@ #define NUM_TIMER 3 #endif #define NUM_PWM 7 // PWM7 is on GPIO50 +#define NUM_I2C 1 #define NUM_ADC 8 // Though ADC3 pin is the Ethernet IRQ #define NUM_CAN 0 diff --git a/src/platform/avr32/conf.lua b/src/platform/avr32/conf.lua index 126cf2a8..e3cf207b 100644 --- a/src/platform/avr32/conf.lua +++ b/src/platform/avr32/conf.lua @@ -1,6 +1,6 @@ -- Configuration file for the AVR32 microcontrollers -specific_files = "crt0.s trampoline.s platform.c exception.s intc.c pm.c flashc.c pm_conf_clocks.c usart.c gpio.c tc.c spi.c platform_int.c adc.c pwm.c ethernet.c" +specific_files = "crt0.s trampoline.s platform.c exception.s intc.c pm.c flashc.c pm_conf_clocks.c usart.c gpio.c tc.c spi.c platform_int.c adc.c pwm.c i2c.c ethernet.c" addm( "FORAVR32" ) -- See board.h for possible BOARD values. diff --git a/src/platform/avr32/conf.py b/src/platform/avr32/conf.py index 13f5f79d..6a74a0e0 100644 --- a/src/platform/avr32/conf.py +++ b/src/platform/avr32/conf.py @@ -1,6 +1,6 @@ # Configuration file for the AVR32 microcontrollers -specific_files = "crt0.s trampoline.s platform.c exception.s intc.c pm.c flashc.c pm_conf_clocks.c usart.c gpio.c tc.c spi.c platform_int.c adc.c pwm.c ethernet.c" +specific_files = "crt0.s trampoline.s platform.c exception.s intc.c pm.c flashc.c pm_conf_clocks.c usart.c gpio.c tc.c spi.c platform_int.c adc.c pwm.c i2c.c ethernet.c" comp.Append(CPPDEFINES = 'FORAVR32') # See board.h for possible BOARD values. diff --git a/src/platform/avr32/i2c.c b/src/platform/avr32/i2c.c new file mode 100644 index 00000000..0d8b4ef6 --- /dev/null +++ b/src/platform/avr32/i2c.c @@ -0,0 +1,340 @@ +// This file implements I2C protocol on AVR32 devices in eLua. +// +// For reasons outlined below, it does not use the Atmel TWI hardware +// but implements it by bit-banging two GPIO lines. +// +// Martin Guy , June 2011 + +// AVR32 has two families of "two wire" interfaces, both of them inadequate: +// +// AT32UC3A[01]* and AT32UC3B* have a single "TWI" "I2C-compatibile" interface +// that can be switched between master and slave modes; +// AT32UC3A[34]* and AT32UC3C* devices have separate "TWIM" and "TWIS" +// interfaces, three of each in the C series. +// Their registers and functionality are completely different, +// +// All currently supported eLua AVR32 targets +// (EVK1100 Mizar32 == AT32UC3A0* and EVK1101 == AT32UC3B*) +// have one "TWI" interface. +// +// 1) "TWI" hardware limitations +// +// The only repeated start sequence that TWI can generate as master is +// START/ADDRESS(W)/WRITE 1, 2 or 3 bytes/START/ADDRESS(R)/READ n BYTES/STOP. +// +// It cannot emit START/ADDRESS(R/W)/STOP, which is necessary to probe +// for the presence of a device on the bus. Their SDK writes a single 0 bytes +// to do this, which has different effects on different hardware. +// This makes I2C unimplementable using this variant of the hardware. +// +// Worse, you have to have all the data in hand before you start any transfer +// because if you fail to rewrite the "Transmit Holding Register" with the next +// byte while the current byte is being sent, the hardware automatically +// generates a STOP without asking you. +// This makes eLua's current Lua and C I2C interfaces unimplementable. +// +// 2) "TWIM" and "TWIS" hardware limitations +// +// This seems to be able to generate a wider range of repeated start signals +// but the amount of data you can send or receive in one packet is limited to +// 255 bytes. +// +// 3) Bit-banging them as GPIO pins +// +// Given the two sets of incompatible hardware and the fact that neither is +// capable either of speaking I2C or of implementing the current eLua I2C +// interface, we just bit-bang the IO pins. +// +// Some of the chips (e.g. AVR32UC3[01]*) have a GPIO open-collector mode, +// which sounds promising, but others (AVR32UC3A[34], AVR32UC3[BC]) do not +// so we obtain a pseudo-open-collector mode by switching the GPIO pins +// between output (always low) and input (of high impedance). +// +// The disadvantage of bit-banging GPIO pins instead of using the TWI hardware +// is that in TWI mode, the pins "are open-drain outputs with slew-rate +// limitation and inputs with spike-filtering" (AT32UC3A Datasheet, para 8.3). +// In GPIO mode, a "glitch filter" is available to reject pulses of 1 CPU cycle +// but this only affects the interrupt function, not the value read from the +// PVR register. (para 22.4.8) + +// This first hack only support a single I2C channel and is only tested on the +// currently supported hardware (AVR32UC3A0*), all of which has a single TWI +// interface. +// To extend it to have multiple I2C channels you need to turn the variables +// started, delay, sda_regs, scl_regs +// and the constants +// {SDA,SCL}_{PIN,PORT,PINMASK} +// into arrays[NUM_I2C] and index them with id. + +#include "platform_conf.h" +#include "compiler.h" +#include "gpio.h" +#include "i2c.h" + +// Which port/pins are used for I2C? + +#if defined(ELUA_CPU_AT32UC3A0128) || \ + defined(ELUA_CPU_AT32UC3A0256) || \ + defined(ELUA_CPU_AT32UC3A0512) || \ + defined(ELUA_CPU_AT32UC3A1128) || \ + defined(ELUA_CPU_AT32UC3A1256) || \ + defined(ELUA_CPU_AT32UC3A1512) + +// One master-slave TWI interface +# define SDA_PIN AVR32_PIN_PA29 +# define SCL_PIN AVR32_PIN_PA30 + +#elif defined(ELUA_CPU_AT32UC3A364) || defined(ELUA_CPU_AT32UC3A364S) || \ + defined(ELUA_CPU_AT32UC3A3128) || defined(ELUA_CPU_AT32UC3A3128S) || \ + defined(ELUA_CPU_AT32UC3A3256) || defined(ELUA_CPU_AT32UC3A3256S) || \ + defined(ELUA_CPU_AT32UC3A464) || defined(ELUA_CPU_AT32UC3A464S) || \ + defined(ELUA_CPU_AT32UC3A4128) || defined(ELUA_CPU_AT32UC3A4128S) || \ + defined(ELUA_CPU_AT32UC3A4256) || defined(ELUA_CPU_AT32UC3A4256S) + +// The first of the two TWIM/TWIS interfaces, in pin configuration A +# define SDA_PIN AVR32_PIN_PA25 +# define SCL_PIN AVR32_PIN_PA26 + +#elif defined(ELUA_CPU_AT32UC3B064) || \ + defined(ELUA_CPU_AT32UC3B0128) || \ + defined(ELUA_CPU_AT32UC3B0256) || \ + defined(ELUA_CPU_AT32UC3B0512) || \ + defined(ELUA_CPU_AT32UC3B164) || \ + defined(ELUA_CPU_AT32UC3B1128) || \ + defined(ELUA_CPU_AT32UC3B1256) || \ + defined(ELUA_CPU_AT32UC3B1512) + +// One master-slave TWI interface +# define SDA_PIN AVR32_PIN_PA10 +# define SCL_PIN AVR32_PIN_PA09 + +#elif defined(ELUA_CPU_AT32UC3C064C) || \ + defined(ELUA_CPU_AT32UC3C0128C) || \ + defined(ELUA_CPU_AT32UC3C0256C) || \ + defined(ELUA_CPU_AT32UC3C0512C) || \ + defined(ELUA_CPU_AT32UC3C164C) || \ + defined(ELUA_CPU_AT32UC3C1128C) || \ + defined(ELUA_CPU_AT32UC3C1256C) || \ + defined(ELUA_CPU_AT32UC3C1512C) || \ + defined(ELUA_CPU_AT32UC3C264C) || \ + defined(ELUA_CPU_AT32UC3C2128C) || \ + defined(ELUA_CPU_AT32UC3C2256C) || \ + defined(ELUA_CPU_AT32UC3C2512C) + +// One master-slave TWI interface +# define SDA_PIN AVR32_PIN_PC02 +# define SCL_PIN AVR32_PIN_PC03 + +#else +# error "I2C pin assignment is unknown for this CPU" +#endif + +// Split these into port and pinmask +#define SDA_PORT ( SDA_PIN >> 5 ) +#define SCL_PORT ( SCL_PIN >> 5 ) +#define SDA_PINMASK ( 1 << ( SDA_PIN & 31 ) ) +#define SCL_PINMASK ( 1 << ( SCL_PIN & 31 ) ) + +// The set of GPIO registers we will be using for each bus line. +// In practice, these two will always have the same value. Ho hum. +static volatile avr32_gpio_port_t *sda_regs = + &AVR32_GPIO.port[ SDA_PORT ]; +static volatile avr32_gpio_port_t *scl_regs = + &AVR32_GPIO.port[ SCL_PORT ]; + +// Half an I2C bus clock cycle, as a number of HSB(==CPU) clock cycles; +// Be default, the slow mode setting +static u32 delay = REQ_CPU_FREQ / 100000 / 2; + +// Local functions used by the bit-banger +static void I2CDELAY(void); // Pause for half an I2C bus clock cycle +static int READSCL(void); // Set SCL as input and return current level of line +static int READSDA(void); // Set SDA as input and return current level of line +static void CLRSCL(void); // Actively drive SCL signal low +static void CLRSDA(void); // Actively drive SDA signal low +static void ARBITRATION_LOST(void); // Bus control was lost + +// ************************ +// The bitbanger itself, taken from http://en.wikipedia.org/wiki/I2C + +// We don't use GPIO open-drain mode, which is not available on all hardware +// models. Instead, we use two modes to simulate open-drain: +// output of 0 and input. +u32 i2c_setup( u32 speed ) +{ + // First, set both pins as high-impedance inputs to avoid startup glitches + sda_regs->oderc = SDA_PINMASK; + scl_regs->oderc = SCL_PINMASK; + + // When they are outputs, they will always output 0. + sda_regs->ovrc = SDA_PINMASK; + scl_regs->ovrc = SCL_PINMASK; + + // Let the GPIO hardware control these pins + sda_regs->gpers = SDA_PINMASK; + scl_regs->gpers = SCL_PINMASK; + + // Figure out how many clock cycles correspond to half a clock waveform. + delay = REQ_CPU_FREQ / speed / 2; + + return speed; +} + +// Are we between a start bit and a stop bit? +static int started = 0; + +void i2c_start_cond(void) +{ + if (started) { + // if started, do a restart cond + // set SDA to 1 + READSDA(); + I2CDELAY(); + // Clock stretching + while (READSCL() == 0) + ; // You can add a timeout to this loop to + // recover from SCL being stuck low. + } + if (READSDA() == 0) + ARBITRATION_LOST(); + // SCL is high, set SDA from 1 to 0 + CLRSDA(); + I2CDELAY(); + CLRSCL(); + started = true; +} + +void i2c_stop_cond(void) +{ + /* set SDA to 0 */ + CLRSDA(); + I2CDELAY(); + /* Clock stretching */ + while (READSCL() == 0) + ; /* You should add timeout to this loop */ + /* SCL is high, set SDA from 0 to 1 */ + if (READSDA() == 0) + ARBITRATION_LOST(); + I2CDELAY(); + started = false; +} + +/* Write a bit to I2C bus */ +static void i2c_write_bit(int bit) +{ + if (bit) + READSDA(); + else + CLRSDA(); + I2CDELAY(); + /* Clock stretching */ + while (READSCL() == 0) + ; /* You should add timeout to this loop */ + /* SCL is high, now data is valid */ + /* If SDA is high, check that nobody else is driving SDA */ + if (bit && READSDA() == 0) + ARBITRATION_LOST(); + I2CDELAY(); + CLRSCL(); +} + +/* Read a bit from I2C bus */ +static int i2c_read_bit(void) +{ + int bit; + + /* Let the slave drive data */ + READSDA(); + I2CDELAY(); + /* Clock stretching */ + while (READSCL() == 0) + ; /* You should add timeout to this loop */ + /* SCL is high, now data is valid */ + bit = READSDA(); + I2CDELAY(); + CLRSCL(); + return bit; +} + +/* Write a byte to I2C bus. Return 0 if ack by the slave */ +int i2c_write_byte(unsigned char byte) +{ + unsigned bit; + int nack; + + for (bit = 0; bit < 8; bit++) { + i2c_write_bit((byte & 0x80) != 0); + byte <<= 1; + } + nack = i2c_read_bit(); + + return nack; +} + +/* Read a byte from I2C bus */ +unsigned char i2c_read_byte(int nack) +{ + unsigned char byte = 0; + unsigned bit; + + for (bit = 0; bit < 8; bit++) + byte = (byte << 1) | i2c_read_bit(); + i2c_write_bit(nack); + + return byte; +} + +//******************* +// Low-level functions used by the bit-banger + +// Pause for half an I2C bus clock cycle +static void I2CDELAY() +{ + // Code stolen from sdramc.c::sdramc_ck_delay() + + // Use the CPU cycle counter (CPU and HSB clocks are the same). + u32 delay_start_cycle = Get_system_register(AVR32_COUNT); + u32 delay_end_cycle = delay_start_cycle + delay; + + // To be safer, the end of wait is based on an inequality test, so CPU cycle + // counter wrap around is checked. + if (delay_start_cycle > delay_end_cycle) + { + while ((unsigned long)Get_system_register(AVR32_COUNT) > delay_end_cycle); + } + while ((unsigned long)Get_system_register(AVR32_COUNT) < delay_end_cycle); +} + +// Set SCL as input and return current level of line +static int READSCL() +{ + scl_regs->oderc = SCL_PINMASK; + return ( scl_regs->pvr & SCL_PINMASK ) ? 1 : 0; +} + +// Set SDA as input and return current level of line +static int READSDA() +{ + sda_regs->oderc = SDA_PINMASK; + return ( sda_regs->pvr & SDA_PINMASK ) ? 1 : 0; +} + +// Actively drive SCL signal low +static void CLRSCL(void) +{ + scl_regs->oders = SCL_PINMASK; +} + +// Actively drive SDA signal low +static void CLRSDA(void) +{ + sda_regs->oders = SDA_PINMASK; +} + +// Bus control was lost. +// Not currently used. With a higher-level I2C interface, this can do a +// longjmp back to the eLua C interface routine which can retry the transfer +// when the bus is free. +static void ARBITRATION_LOST(void) +{ +} diff --git a/src/platform/avr32/i2c.h b/src/platform/avr32/i2c.h new file mode 100644 index 00000000..7d94304d --- /dev/null +++ b/src/platform/avr32/i2c.h @@ -0,0 +1,7 @@ +// Declarations for the low-level AVR32 I2C driver for eLua + +u32 i2c_setup( u32 speed ); // speed is in Hz +void i2c_start_cond( void ); +void i2c_stop_cond( void ); +int i2c_write_byte( unsigned char byte ); // returns 0 if acked by slave +unsigned char i2c_read_byte( int nack ); diff --git a/src/platform/avr32/platform.c b/src/platform/avr32/platform.c index 2f1778a4..73fde8e0 100644 --- a/src/platform/avr32/platform.c +++ b/src/platform/avr32/platform.c @@ -40,6 +40,7 @@ #include "spi.h" #include "adc.h" #include "pwm.h" +#include "i2c.h" // UIP sys tick data // NOTE: when using virtual timers, SYSTICKHZ and VTMR_FREQ_HZ should have the @@ -961,6 +962,49 @@ u32 platform_pwm_op( unsigned id, int op, u32 data) return 0; } +// **************************************************************************** +// I2C support + +u32 platform_i2c_setup( unsigned id, u32 speed ) +{ + return i2c_setup(speed); +} + +void platform_i2c_send_start( unsigned id ) +{ + i2c_start_cond(); +} + +void platform_i2c_send_stop( unsigned id ) +{ + i2c_stop_cond(); +} + +int platform_i2c_send_address( unsigned id, u16 address, int direction ) +{ + // Convert enum codes to R/w bit value. + // If TX == 0 and RX == 1, this test will be removed by the compiler + if ( ! ( PLATFORM_I2C_DIRECTION_TRANSMITTER == 0 && + PLATFORM_I2C_DIRECTION_RECEIVER == 1 ) ) { + direction = ( direction == PLATFORM_I2C_DIRECTION_TRANSMITTER ) ? 0 : 1; + } + + // Low-level returns nack (0=acked); we return ack (1=acked). + return ! i2c_write_byte( (address << 1) | direction ); +} + +int platform_i2c_send_byte( unsigned id, u8 data ) +{ + // Low-level returns nack (0=acked); we return ack (1=acked). + return ! i2c_write_byte( data ); +} + +int platform_i2c_recv_byte( unsigned id, int ack ) +{ + return i2c_read_byte( !ack ); +} + + // **************************************************************************** // Network support