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:
parent
4dea8b6665
commit
93743fa12e
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
340
src/platform/avr32/i2c.c
Normal 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
7
src/platform/avr32/i2c.h
Normal 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 );
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user