本小节内容基于单片机平台的firmware开发,也可用于非保护模式运行的操作系统内的开发。
1. GPIO模拟
//function: set_scl_high
//description:
static void set_scl_high ( void )
{
uint16_t volatile timeout = SCL_TIMEOUT;
SET_SCL;
while ( !GET_SCL )
{
if ( timeout-- == 0 )
{
err_handler();
}
}
}
//function: send_stop
//description:
static void send_stop ( void )
{
CLEAR_SCL;
CLEAR_SDA;
set_scl_high();
SET_SDA;
}
//function: send_byte
//description:
static void send_byte ( uint8_t abyte )
{
uint8_t i;
for ( i = 0; i < 8; i++ )
{
if ( abyte & 0x80 )
{
SET_SDA; //send each bit, MSB first
}
else
{
CLEAR_SDA;
}
set_scl_high(); //do clock cycle
CLEAR_SCL;
abyte <<= 1; //shift over to get next bit
}
SET_SDA; //listen for ACK
set_scl_high();
if ( GET_SDA ) //error if no ACK
{
if ( !is_transaction )
{
SendStopBus();
err_handler();
}
}
CLEAR_SCL;
}
//function: send_addr
//description:
static void send_addr ( uint8_t addr, uint8_t read )
{
volatile uint8_t x = 0; //delay variable
//generate START condition
set_scl_high();
x++; //short delay to keep setup times in spec
CLEAR_SDA;
x++;
CLEAR_SCL;
x++;
send_byte( addr | read ); //send address byte with read/write bit
}
// function: get_byte
// description:
static uint8_t get_byte ( uint8_t lastbyte )
{
uint8_t abyte = 0;
uint8_t i;
for ( i = 0; i < 8; i++ ) //get each bit, MSB first
{
set_scl_high();
abyte <<= 1; //shift result over to make room for next bit
if ( GET_SDA )
{
abyte++; //same as 'abyte |= 1', only faster
}
CLEAR_SCL;
}
if ( lastbyte )
{
SET_SDA; //do not ACK last byte read
}
else
{
CLEAR_SDA;
}
set_scl_high();
CLEAR_SCL;
SET_SDA;
return( abyte );
}
上述函数为基本操作模块函数,下面的函数为可对外提供的API接口。
// Function: i2c_read_byte
// Description:
uint8_t i2c_read_byte ( uint8_t device_id, uint8_t addr )
{
uint8_t abyte = 0;
send_addr( device_id, WRITE );
send_byte( addr );
send_addr( device_id, READ );
abyte = get_byte( LAST_BYTE ); //get single byte, same as last byte, no ACK
send_stop();
return( abyte );
}
// Function: i2c_write_byte
// Description:
void i2c_write_byte ( uint8_t deviceID, uint8_t offset, uint8_t value )
{
send_addr( deviceID, WRITE );
send_byte( offset );
send_byte( value );
send_stop();
}
// Function: i2c_read_block
// Description:
bool_t i2c_read_block ( uint8_t deviceID, uint8_t addr, uint8_t *p_data, uint16_t nbytes )
{
uint16_t i;
if ( nbytes == 0 )
{
return( true );
}
send_addr( deviceID, WRITE );
send_byte( addr );
send_addr( deviceID, READ );
nbytes--; //get all except last byte
for ( i = 0; i < nbytes; i++ )
{
p_data[i] = get_byte( NOT_LAST_BYTE );
}
p_data[i] = get_byte( LAST_BYTE ); //get last byte, no ACK
send_stop();
return true;
}
// Function: i2c_write_block
// Description:
bool_t i2c_write_block ( uint8_t device_id, uint8_t addr, uint8_t *p_data, uint16_t nbytes )
{
uint16_t i;
if ( nbytes == 0 )
{
return( true );
}
send_addr( device_id, WRITE );
send_byte( addr );
for ( i = 0; i < nbytes; i++ )
{
send_byte( p_data[i] );
}
send_stop();
return true;
}
上述API已经包含了常用的字节读写和block读写操作,满足日常i2c的应用。
2. 硬件实现
i2c功能早已经很成熟,各芯片厂商都有很成熟的IP解决方案,一般只需要配置几个状态位即可实现数据传输功能,本节提到的是AT89C51上的twi方案,可能相对复杂,但是可让软件开发人员看到更多状态,对i2c协议也能有更深的理解。
上图是AT89C51 twi模块的master trasmitter模式下的状态转移图,以下代码是通过配置寄存器实现状态转换的,大家也可以去下载官方twi的demo code进行学习。具体寄存器的说明请参见该芯片说明文档。
void twi_init( void )
{
SSCON = TWI_RATIO_960; //clock config
SSCON |= TWI_SSIE;
}
void twi_start( void )
{
uint32_t volatile Timeout = I2C_BUS_TIMEOUT;
SSCON = (SSCON & ~(TWI_STO|TWI_SI)) | TWI_STA;
while(( SSCON & TWI_SI ) == 0)
{
if (Timeout-- == 0)
{
return;
}
}
}
void twi_stop( void )
{
unint32_t volatile Timeout = I2C_BUS_TIMEOUT;
SSCON = (SSCON & ~(TWI_STA|TWI_SI)) | TWI_STO;
while( SSCON & TWI_STO )
{
if (Timeout-- == 0)
{
return;
}
}
}
uint8_t twi_send_byte( uint8_t val, uint8_t expected )
{
uint32_t volatile Timeout = I2C_BUS_TIMEOUT;
SSDAT = val;
SSCON &= ~(TWI_STA|TWI_SI|TWI_STO);
while(( SSCON & TWI_SI ) == 0)
{
if (Timeout-- == 0)
{
return 1;
}
}
expected ^= SSCS;
return expected;
}
#define twi_send_MTslave( val ) twi_send_byte( val, 0x18 )
#define twi_send_data( val ) twi_send_byte( val, 0x28 )
uint8_t twi_write( uint8_t slave_addr, uint8_t offset, uint16_t count, uint8_t *devdata )
{
uint16_t i;
slave_addr &= 0xfe;
twi_start();
if( twi_send_MTslave( slave_addr) ){
twi_stop();
return I2C_NOACK; /* NoACK */
}
if ( twi_send_data( offset )){
twi_stop();
return I2C_NOACK; /* NoACK */
}
for( i=0; i<count; i++ ){
if (twi_send_data( devdata[i] )){
twi_stop();
return I2C_NOACK; /* NoACK */
}
}
twi_stop();
return I2C_NOERROR;
}
uint8_t twi_write_byte( uint8_t slave_addr, uint8_t reg_addr, uint8_t byte )
{
uint8_t count = 0;
while(twi_write(slave_addr, reg_addr, 1, &byte) != 0 )
{
if (++count == 3)
{
return -1;
}
}
return 0;
}
uint8_t twi_write_block( uint8_t slave_addr, uint8_t reg_addr, uint8_t *p_data, uint16_t nbytes )
{
uint8_t count = 0;
while(twi_write(slave_addr, reg_addr, nbytes, p_data) != 0 )
{
if (++count == 3)
{
return -1;
}
}
return 0;
}
上述函数实现了基于AT89C51的twi模块实现的i2c写功能,读功能类似,在此处不再赘述。