1
0
mirror of https://github.com/elua/elua.git synced 2025-01-08 20:56:17 +08:00

First working hack at single channel bit-bang I2C for AVR32.

To get round the inadequacies of the AVR32 "TWI" hardware, this implements
I2C as a bit-banging interface, currently handling a single channel only.
Tested on EVK1100 and Mizar32. "Should work" on EVK1101 if the 2-line change
is made to evk1101_conf.h
This commit is contained in:
Martin Guy 2011-06-28 04:20:11 +02:00
parent 4dea8b6665
commit 93743fa12e
7 changed files with 397 additions and 2 deletions

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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.

340
src/platform/avr32/i2c.c Normal file
View File

@ -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 <martinwguy@gmail.com>, 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)
{
}

7
src/platform/avr32/i2c.h Normal file
View File

@ -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 );

View File

@ -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