I2C设备驱动(二)

时间:2021-12-16 16:54:12

本小节内容基于单片机平台的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协议也能有更深的理解。
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写功能,读功能类似,在此处不再赘述。