/*
* Simple I2C driver:
* 1. Assume you have multiple masters and slave devices using the bus.
* [dev] Two cases need to check:
* a. Must check bus busy before any transfer because another master may take bus
* b. Must check each transfer status to make sure this master not lost arbitration
* (2 master may start same time, and which master send more 0 will take bus)
* --> re-init the transfer if bus not busy
*
* 2. It should have 3 basic functions – Detect, Read and Write.
* [dev] For simple: probe (detect device exist), read/write data by loop RW 1byte API
*
* 3. Be able to handle any error conditions.
* [dev] Timeout, bus busy, error lost arbitration and fail transfer
*
* 4. Write some test cases.
* [dev] interract with user input, and pseudo read/write/taking bus
*/
/* Include */
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
/* Define */
#define I2C_POLL_TIMEOUT 10 /* 10ms. FIXME: change it in real HW */
#define I2C_WAIT_TIMEOUT 10000 /* 10s. Wait time to take bus in multi-master */
#define WAIT_BUS_TIMEOUT_ERR -1 /* Error return when waiting bus timeout */
#define I2C_LOST_ARBIT_ERR 5 /* Error transfer because of arbitration lost in multi master */
#define I2C_LOST_NAK_ERR 4 /* NAK */
/* Define for test case */
#define I2C_DEFAULT_BUS_NUM 0 /* Port 0 */
#define I2C_DEFAULT_SPEED 100 /* 100KHz */
#define VERBOSE 1
#define i2c_err(...) printf("ERR: " __VA_ARGS__)
#define i2c_warn(...) printf("WARN: " __VA_ARGS__)
#if VERBOSE >= 1
#define i2c_verbose(...) printf(__VA_ARGS__)
#else
#define i2c_verbose(...)
#endif
/* Define for test case some pseudo return */
int pseudo_return_bus_busy();
int pseudo_return_status();
int pseudo_return_done();
int pseudo_bus_busy = 0; /* 0: idle, 1: busy */
int pseudo_err_status = 0; /* I2C_LOST_ARBIT_ERR, >0: other err */
int pseudo_transer_done = 1; /* 1: done, 0: not done */
/* API I2C */
/* I2C initialization: HW early setup */
void i2c_init(unsigned int bus, unsigned int speed)
{
i2c_verbose("I2C HW init\n");
/* Configure I2C, base address base on bus number */
/* Configure I2C mode, speed, timeout, timing */
}
/* I2C start: Setup before each transfer */
void i2c_start(char slave_add, unsigned int address_len)
{
i2c_verbose("I2C Transfer setup\n");
/* Configure slave address */
/* TODO: some specific config may come along with this slave add from input */
}
/* I2C check bus busy. Return 1: busy, 0: idle */
int i2c_bus_busy()
{
/* Check bus busy or not from HW reg */
/* FIXME: this pseudo is using for manual test case only */
return pseudo_return_bus_busy();
}
/* I2C get err status after each transfer */
int i2c_get_status()
{
/* Get status register */
/* FIXME: this pseudo is using for manual test case only */
return pseudo_return_status();
}
/* I2C check bus busy. Return 1: done, 0: timeout */
int i2c_poll_done()
{
int time_out = I2C_POLL_TIMEOUT;
while (time_out--) {
/* FIXME: this pseudo is using for manual test case only */
if (pseudo_return_done() == 1)
return 1;
/* FIXME: add delay here, maybe 1ms in real HW */
}
return 0;
}
/* TODO: I2C Master write buffer */
/* TODO: I2C Master read buffer */
/*
* I2C Master write 1 byte:
* Input:
* slave_add: Target device I2C address.
* address: address to access for transfer.
* address_len: number of byte address (1 or 2 byte mode)
* data: 1 byte data write.
*
* Return: 0 - write OK, -1: timeout, > 0: Write fail with status from I2C reg
*/
int i2c_master_write_byte(char slave_add, unsigned int address,
unsigned int address_len, char data)
{
int ret, time_out = I2C_WAIT_TIMEOUT;
i2c_verbose("I2C write slave 0x%x addr 0x%x (%d) data 0x%x\n",
slave_add, address, address_len, data);
/* Start config for a transaction write */
i2c_start(slave_add, address_len);
/* Because of multi-master, must wait until bus ready */
while (i2c_bus_busy() == 1 && time_out) {
time_out--;
/* FIXME: add delay here, maybe 1ms in real HW */
}
/* If wait time over timeout, must stop */
if (time_out <= 0) {
i2c_err("Waiting bus timeout !!\n");
return WAIT_BUS_TIMEOUT_ERR;
}
/* Write data input to register buffer for sending out */
/* Trigger write */
/* Polling status */
ret = i2c_poll_done();
if (ret == 0) { /* not done */
return i2c_get_status();
}
return 0;
}
/*
* I2C Master read 1 byte
* Input:
* slave_add: Target device I2C address.
* address: address to access for transfer.
* address_len: number of byte address (1 or 2 byte mode)
* data: pointer to location stored data read out.
*
* Return: 0 - Read OK, -1: timeout, > 0: Read fail with status from I2C reg
*/
int i2c_master_read_byte(char slave_add, unsigned int address,
unsigned int address_len, char *data)
{
int ret, time_out = I2C_WAIT_TIMEOUT;
i2c_verbose("I2C read slave 0x%x addr 0x%x (%d)\n",
slave_add, address, address_len);
/* Start config for a transaction write */
i2c_start(slave_add, address_len);
/* Because of multi-master, must wait until bus ready */
while (i2c_bus_busy() == 1 && time_out) {
time_out--;
/* FIXME: add delay here, maybe 1ms in real HW */
}
/* If wait time over timeout, must stop */
if (time_out <= 0) {
i2c_err("Waiting bus timeout !!\n");
return WAIT_BUS_TIMEOUT_ERR;
}
/* Trigger read data */
/* Polling status */
ret = i2c_poll_done();
if (ret == 0) { /* not done */
return i2c_get_status();
}
/* Copy data from reg to buffer input */
return 0;
}
/*
* I2C probe/detect: send read command at first address, if received ACK means
* slave device address is exist.
*
* Return 0: device exist, != 0: no exist.
*/
/* FIXME: some special I2C device may not response this probe command */
int i2c_detect(char slave_add)
{
char data;
return i2c_master_read_byte(slave_add, 0, 0, &data);
}
/*
* I2C Write:
* Return: 0 - Write OK, -1: timeout, > 0: Write fail with status from I2C reg.
*/
int i2c_master_write(char slave_add, unsigned int address,
unsigned int address_len, char *data, unsigned int len)
{
int i, ret;
for (i = 0; i < len; i++) {
ret = i2c_master_write_byte(slave_add, address, address_len, data[i]);
/* If lost arbitration must re-init this transfer */
if (ret == I2C_LOST_ARBIT_ERR) {
i2c_verbose("Lost arbitration -> retry\n");
i = i - 1;
/* FIXME: Create pseudo to remove arbitration lost in next times, just for test case */
pseudo_transer_done = 1;
pseudo_err_status = 0;
continue;
/* If wait bus timeout, must stop */
} else if (ret == WAIT_BUS_TIMEOUT_ERR) {
return ret;
/* TODO: If other error may stop or diff. process */
} else if (ret > 0) {
i2c_err("Read fail, error code 0x%x !!\n", ret);
return ret;
}
}
return 0;
}
/*
* I2C read:
* Return: 0 - Read OK, -1: timeout, > 0: Read fail with status from I2C reg.
*/
int i2c_master_read(char slave_add, unsigned int address,
unsigned int address_len, char *data, unsigned int len)
{
int i, ret;
for (i = 0; i < len; i++) {
ret = i2c_master_read_byte(slave_add, address, address_len, &data[i]);
/* If lost arbitration must re-init this transfer */
if (ret == I2C_LOST_ARBIT_ERR) {
i = i - 1;
continue;
/* If wait bus timeout, must stop */
} else if (ret == WAIT_BUS_TIMEOUT_ERR) {
return ret;
/* TODO: If other error may stop or diff. process */
} else if (ret > 0) {
i2c_err("Read fail, error code 0x%x !!\n", ret);
return ret;
}
}
return 0;
}
/* API Test case: create some pseudo data return */
int pseudo_return_bus_busy()
{
return pseudo_bus_busy;
}
int pseudo_return_status()
{
return pseudo_err_status;
}
int pseudo_return_done()
{
return pseudo_transer_done;
}
/* Main func interract with user */
int main(void)
{
int bus = I2C_DEFAULT_BUS_NUM, speed = I2C_DEFAULT_SPEED;
int sel, slv_add;
char data_wr[4] = { 0x00, 0x01, 0x02, 0x03 };
char data_rd[4] = { 0 };
/* Config I2C: HW setup register */
i2c_init(bus, speed);
/* User select option */
while (1) {
printf("Selection:\n");
printf("1. Probe all device form 0x50 to 0x55\n");
printf("2. Write input slave (format: hex number without '0x')\n");
printf("3. Read input slave (format: hex number without '0x')\n");
printf("4. Write input slave but bus not ready\n");
printf("5. Write input slave but lost abritration & retry\n");
printf("\nSelect: ");
/* User input */
scanf("%d", &sel);
/* Process */
switch (sel) {
case 1:
for (slv_add = 0x50; slv_add < 0x55; slv_add++) {
/* Create pseudo available for even slv addr, not available for odd slv addr */
pseudo_transer_done = (slv_add % 2) ? 0 : 1;
pseudo_err_status = I2C_LOST_NAK_ERR;
if (i2c_detect(slv_add)) {
printf("0x%x: Not available\n\n", slv_add);
} else {
printf("0x%x: Available\n\n", slv_add);
}
}
/* Restore pseudo */
pseudo_transer_done = 1;
pseudo_err_status = 0;
break;
case 2:
printf("\nSlave address: ");
scanf("%x", &slv_add);
/* Create pseudo available for even slv addr, not available for odd slv addr */
pseudo_transer_done = (slv_add % 2) ? 0 : 1;
pseudo_err_status = I2C_LOST_NAK_ERR;
if (i2c_detect(slv_add)) {
printf("0x%x: Not available\n\n", slv_add);
} else {
if (i2c_master_write(slv_add, 0, 1, &data_wr[0], 4) == 0)
printf("\nWRITE PASS\n\n");
}
/* Restore pseudo */
pseudo_transer_done = 1;
pseudo_err_status = 0;
break;
case 3:
printf("\nSlave address: ");
scanf("%x", &slv_add);
/* Create pseudo available for even slv addr, not available for odd slv addr */
pseudo_transer_done = (slv_add % 2) ? 0 : 1;
pseudo_err_status = I2C_LOST_NAK_ERR;
if (i2c_detect(slv_add)) {
printf("0x%x: Not available\n\n", slv_add);
} else {
if (i2c_master_read(slv_add, 0, 1, &data_rd[0], 4) == 0)
printf("\nREAD PASS\n\n");
}
/* Restore pseudo */
pseudo_transer_done = 1;
pseudo_err_status = 0;
break;
case 4:
printf("\nSlave address: ");
scanf("%x", &slv_add);
if (i2c_detect(slv_add)) {
printf("0x%x: Not available\n\n", slv_add);
} else {
/* Create bus busy always */
pseudo_bus_busy = 1;
if (i2c_master_write(slv_add, 0, 1, &data_wr[0], 4) == 0)
printf("\nWRITE PASS\n");
printf("\n");
}
/* Restore pseudo */
pseudo_bus_busy = 0;
break;
case 5:
printf("\nSlave address: ");
scanf("%x", &slv_add);
if (i2c_detect(slv_add)) {
printf("0x%x: Not available\n", slv_add);
} else {
/* Create bus busy and lost arbitration */
pseudo_transer_done = 0;
pseudo_err_status = I2C_LOST_ARBIT_ERR;
if (i2c_master_write(slv_add, 0, 1, &data_wr[0], 4) == 0)
printf("\nWRITE PASS\n");
printf("\n");
}
/* Restore pseudo */
pseudo_transer_done = 1;
pseudo_err_status = 0;
break;
default:
printf("Not available selection %d !!\n", sel);
break;
}
}
return 0;
}
To embed this project on your website, copy the following code and paste it into your website's HTML: