目录
I2C(主)采用2个或3个GPIO模拟的方式实现,如果sdao等于sdai相等,表示SDA是双向的,否则一个GPIO作为SDA输出,一个GPIO作为SDA输入。 如果电平不想转换方向则选择3个GPIO的方式。注意,scl和sda必须在同一组内,比如scl在ACBUS0~7,那么sda也必须是这组内的,不能是ADBUS0~ADBUS7。
typedef struct
{
struct ftdi_context *ftdi;
uint8_t scl;
uint8_t sdao;
uint8_t sdai;
int freq;
uint8_t *pCommand;
int iCommand;
}mpsse_i2c_s;
freq表示I2C的频率,0表示最快速度,值越大越慢。
一般I2C可以接多个从设备,所以这里只定义了2组I2C,如果需要更多,修改数组大小即可。
typedef enum
{
I2C_PORT_0 = 0,
I2C_PORT_1,
I2C_PORT_MAX,
}mpsse_i2c_port_e;
mpsse_i2c_s i2c[I2C_PORT_MAX];
1. 初始化
void i2cInit(uint8_t port, mpsse_i2c_s init)
{
if(port >= I2C_PORT_MAX)
return;
i2c[port] = init;
i2c[port].freq += 1;
}
pCommand和iCommand这里不需要初始化。其他参数按照硬件配置即可。这里freq加1是为了后面循环时直接使用。
2. 原理
从gpio写可知,通过命令0x80或0x81写入3个字节命令,FTDI设备会执行一次IO操作,如果同样的命令写freq次,那么GPIO的操作频率就会降低。比如0x80 level dir 0x80 level dir,写了2次0x80,设备就是连续设置一样GPIO两次,频率就是写一次的一半。
注意设定I2C的GPIO必须是同一组,定义好读写命令
const uint8_t gpioWirteCommand[2] = {0x80, 0x82};
const uint8_t gpioReadCommand[2] = {0x81, 0x83};
然后根据I2C的GPIO中任意一个计算应该使用哪个命令。
#define gpioCommand(port) (i2c[port].scl / 8)
例如scl选择ACBUS0,对应IO编号为8,gpioCommand = 1, 对应的读写命令为0x83和0x82。
将命令更新到命令buffer中,注意,整个I2C的操作命令是最后才真正发送给FTDI设备。
#define i2cCommandWrite(port, n) do{\
for (int i = 0; i < (int)i2c[port].freq * n; i++)\
{\
i2c[port].pCommand[i2c[port].iCommand++] = gpioWriteCommand[gpioCommand(port)];\
i2c[port].pCommand[i2c[port].iCommand++] = (gpio.level >> (gpioCommand(port) * 8)) & 0xff;\
i2c[port].pCommand[i2c[port].iCommand++] = (gpio.dir >> (gpioCommand(port) * 8)) & 0xff;\
}\
}while(0)
定义SDA的输入输出:
#define i2cSDAOut(port) do{\
gpio.dir |= ((uint16_t)1 << i2c[port].sdao);\
}while(0)
#define i2cSDAIn(port) do{\
gpio.dir &= (uint16_t)(~((uint16_t)1 << i2c[port].sdai));\
}while(0)
定义SDA输出高低电平:
#define i2cSDAHigh(port) do{\
gpio.level |= ((uint16_t)1 << i2c[port].sdao);\
}while(0)
#define i2cSDALow(port) do{\
gpio.level &= (uint16_t)(~((uint16_t)1 << i2c[port].sdao));\
}while(0)
定义读SDA的命令:
#define i2cSDARead(port) do{\
i2c[port].pCommand[i2c[port].iCommand++] = gpioReadCommand[gpioCommand(port)];\
}while(0)
由于SCL一直为输出,不需要单独设置方向,只需要定义SCL的输出高低电平:
#define i2cSCLHigh(port) do{\
gpio.level |= ((uint16_t)1 << i2c[port].scl);\
}while(0)
#define i2cSCLLow(port) do{\
gpio.level &= (uint16_t)(~((uint16_t)1 << i2c[port].scl));\
}while(0)
接下来根据i2c协议完成各个状态函数即可。
3. i2cStart
SCL为高电平,SDA产生一个下降沿即表示I2C的Start信号
void i2cStart(uint8_t port)
{
//Set SDA output
//SCL outputs high, SDA outputs high
i2cSDAOut(port);
i2cSCLHigh(port);
i2cSDAHigh(port);
i2cCommandWrite(port, 1);
//SDA outputs low
i2cSDALow(port);
i2cCommandWrite(port, 2);
}
计算command缓存区使用情况:9 * freq
#define I2C_START_CMD_LEN (3 * 3 * i2c[port].freq)
4. i2cStop
SCL为高电平,SDA产生一个下降沿即表示I2C的Stop信号
void i2cStop(uint8_t port)
{
//SDA outpus low
i2cSDALow(port);
i2cCommandWrite(port, 1);
//SCL outputs high
i2cSCLHigh(port);
i2cCommandWrite(port, 1);
//SDA outputs high
i2cSDAHigh(port);
i2cCommandWrite(port, 1);
}
计算command缓存区使用情况:9 * freq
#define I2C_STOP_CMD_LEN (3 * 3 * i2c[port].freq )
5. i2cRecvByte
SCL产生一个上升沿,然后SDA读入1个位数据,同样的操作连续读入8个位,然后根据参数ack SDA是否输出ack信号,SDA输出低电平且SCL产生一个上升沿即为ack信号,否则SDA输出高电平。ack完成后写入0x87命令,这个命令是将FTDI设备缓冲区中的数据立刻回传到PC。
void i2cRecvByte(uint8_t port, bool ack)
{
//SCL outputs low, SDA outputs low
//Set SDA as input
i2cSCLLow(port);
i2cSDALow(port);
i2cSDAIn(port);
i2cCommandWrite(port, 1);
uint8_t loop = 8;
while (loop-- > 0)
{
//SCL output low
i2cSCLLow(port);
i2cCommandWrite(port, 1);
//SCL output high
i2cSCLHigh(port);
i2cCommandWrite(port, 1);
//Read SDA
i2cSDARead(port);
}
//SCL outputs low, SDA outputs low
//Set SDA as output
i2cSCLLow(port);
i2cSDALow(port);
i2cSDAOut(port);
i2cCommandWrite(port, 1);
if (ack == true)
{
i2cSDALow(port);
}
else
{
i2cSDAHigh(port);
}
i2cCommandWrite(port, 1);
//SCL output high
i2cSCLHigh(port);
i2cCommandWrite(port, 1);
i2c[port].pCommand[i2c[port].iCommand++] = 0x87;
//SCL output low
i2cSCLLow(port);
i2cCommandWrite(port, 1);
}
注意,读入的数据此时并没有处理,等全部完成再读入数据。
计算command缓存区使用情况:
#define I2C_RECV_CMD_LEN (5 * 3 * i2c[port].freq + 8 * (2 * 3 * i2c[port].freq + 1) + 1)
6. i2cSendByte
SCL上升沿发送SDA的一个位信息,同样的操作连续发送8个位。发送完成后同样一个SCL上升沿读入ack信息。
void i2cSendByte(uint8_t port, uint8_t dat)
{
for(uint8_t j = 0; j < 8; j++)
{
//SCL output low
i2cSCLLow(port);
i2cCommandWrite(port, 1);
//SDA output dat
if ((dat & 0x80) == 0x80)
i2cSDAHigh(port);
else
i2cSDALow(port);
dat <<= 1;
//Set SDA as output
i2cSDAOut(port);
i2cCommandWrite(port, 1);
//SCL output high
i2cSCLHigh(port);
i2cCommandWrite(port, 1);
}
//SCL outputs low, SDA outputs low
//Set SDA as input
i2cSCLLow(port);
i2cSDALow(port);
i2cSDAIn(port);
i2cCommandWrite(port, 1);
//SCL output low
//i2cSCLLow(port);
//i2cCommandWrite(port, 1);
//SCL output high
i2cSCLHigh(port);
i2cCommandWrite(port, 10);
//Read SDA
i2cSDARead(port);
//SDA outputs high
//Set SDA as output
i2cSDAHigh(port);
i2cSDAOut(port);
i2cCommandWrite(port, 1);
//SCL output low
i2cSCLLow(port);
i2cCommandWrite(port, 1);
}
计算command缓存区使用情况:
#define I2C_SEND_CMD_LEN (8 * 3 * (4 * i2c[port].freq) + 12 * 3 * i2c[port].freq + 1)
7. i2cRead
int i2cRead(uint8_t port, uint8_t slaveAddr, uint8_t addrbit, int addr, uint8_t* dat, int len)
首先判断参数合法性
if(port >= I2C_PORT_MAX)
return -1;
if(len <= 0)
return -1;
在读数据前先把USB缓存读空,可以通过变量ftdi->readbuffer_remaining获取当前缓存里面有多少有效数据,大于0的情况就把数据读出来丢弃。
if(i2c[port].ftdi->readbuffer_remaining > 0)
{
uint8_t *pDummy = (uint8_t *)malloc(i2c[port].ftdi->readbuffer_remaining);
if(pDummy)
{
ftdi_read_data(i2c[port].ftdi, pDummy, i2c[port].ftdi->readbuffer_remaining);
free(pDummy);
}
}
初始化Command缓存,按照发送的字节数计算需要多少个字节保存命令。
//2 * start + 4 * sendbyte + len * recvbyte + 1 * stop
int commandlength = (I2C_START_CMD_LEN * 2
+ I2C_SEND_CMD_LEN * 4
+ I2C_RECV_CMD_LEN * len
+ I2C_STOP_CMD_LEN);
i2c[port].pCommand = (uint8_t *)malloc(commandlength);
i2c[port].iCommand = 0;
按照I2C设备的协议配置好Command缓存
i2cStart(port);
if(addrbit > 0)
i2cSendByte(port, slaveAddr);
if(addrbit > 8)
i2cSendByte(port, (uint8_t)(addr >> 8));
if(addrbit > 0)
{
i2cSendByte(port, (uint8_t)addr);
i2cStart(port);
}
i2cSendByte(port, slaveAddr | 0x01);
int sendLen = len;
while(--sendLen)
{
i2cRecvByte(port, true);
}
i2cRecvByte(port, false);
i2cStop(port);
然后写到FTDI设备
if(i2c[port].iCommand > commandlength)
{
printf("i2cRead error: command buffer is overflow %d:%d\n",
i2c[port].iCommand, commandlength);
if(i2c[port].pCommand)
free(i2c[port].pCommand);
return -2;
}
ftdi_write_data(i2c[port].ftdi, i2c[port].pCommand, i2c[port].iCommand);
if(i2c[port].pCommand)
free(i2c[port].pCommand);
发送完这些数据后,就是等待FTDI设备执行完数据并把读入的数据更新到USB驱动层。读入的数据是8个位的GPIO数据,要计算一下一共读入了多少个字节数据,每个i2cSendByte读入包含ack的一个字节,最多4个,然后在读入len个8位数据(i2cRecvByte),所以一共读入字节数为
int rdLen = ((addrbit > 0) ? 2 : 0) +
((addrbit > 8) ? 1 : 0) + 1 +
len * 8;
uint8_t *pReadBuf = (uint8_t *)malloc(rdLen);
当ftdi_read_data时,实际是有一个超时的问题,如果读入的数据在设定的时间内没读回则返回超时错误,所以在调用前根据数据长度设置一个合适的超时时间:
int readtimeout = i2c[port].ftdi->usb_read_timeout;
i2c[port].ftdi->usb_read_timeout = 2 * i2c[port].iCommand + 2 * rdLen * i2c[port].freq;
int ret = ftdi_read_data(i2c[port].ftdi, pReadBuf, rdLen);
读入数据后如果出错就再试一次,如果再出错就返回错误退出。
if(ret < rdLen)
{//try again
int remain;
if(ret < 0)
{
remain = rdLen;
ret = ftdi_read_data(i2c[port].ftdi, pReadBuf, rdLen);
}
else
{
remain = rdLen - ret;
ret = ftdi_read_data(i2c[port].ftdi, pReadBuf + ret, remain);
}
if(ret + remain < rdLen)
{
i2c[port].ftdi->usb_read_timeout = readtimeout;
if(pReadBuf)
free(pReadBuf);
return -3;
}
}
i2c[port].ftdi->usb_read_timeout = readtimeout;
此时读入的数据中包含了ack信息和I2C读回的数据信息。先判断一下ack是不是正确。
int ackLen = ((addrbit > 0) ? 2 : 0) +
((addrbit > 8) ? 1 : 0) + 1;
for(int i = 0; i < ackLen; i++)
{
if ((pReadBuf[i] & ((uint8_t)(1 << (i2c[port].sdai % 8))))
== (uint8_t)(1 << (i2c[port].sdai % 8))) //Check ACK
{
printf("check ack fail %d, 0x%x\n", i, pReadBuf[i]);
if(pReadBuf)
free(pReadBuf);
return -4;
}
}
然后把每个位信息组合成字节数据保存在返回指针中。
int j = 0;
for(int i = ackLen; i < rdLen; i++)
{
int max = i + 8;
uint8_t tmp = 0;
for(; i < max; i++)
{
tmp <<= 1;
if ((pReadBuf[i] & ((uint8_t)(1 << (i2c[port].sdai % 8))))
== (uint8_t)(1 << (i2c[port].sdai % 8)))
{
tmp |= 0x01;
}
}
dat[j++] = tmp;
i--;
}
最后释放malloc的数据,且返回0.
if(pReadBuf)
free(pReadBuf);
printf("i2cRead OK\n");
return 0;
8. i2cWrite
int i2cWrite(uint8_t port, uint8_t slaveAddr, uint8_t addrbit, int addr, uint8_t* dat, int len)
类似i2cRead。
首先判断参数合法性
if(port >= I2C_PORT_MAX)
return -1;
if(len <= 0)
return -1;
写数据前也读空一下缓存。
if(i2c[port].ftdi->readbuffer_remaining > 0)
{
uint8_t *pDummy = (uint8_t *)malloc(i2c[port].ftdi->readbuffer_remaining);
if(pDummy)
{
ftdi_read_data(i2c[port].ftdi, pDummy, i2c[port].ftdi->readbuffer_remaining);
free(pDummy);
}
}
初始化Command缓存
//1 * start + 3 * sendbyte + len * sendbyte + 1 * stop
int commandlength = (I2C_START_CMD_LEN * 1
+ I2C_SEND_CMD_LEN * 3
+ I2C_SEND_CMD_LEN * len
+ I2C_STOP_CMD_LEN);
i2c[port].pCommand = (uint8_t *)malloc(commandlength);
i2c[port].iCommand = 0;
按照I2C设备的协议配置好Command缓存
i2cStart(port);
i2cSendByte(port, slaveAddr);
if(addrbit > 8)
i2cSendByte(port, (uint8_t)(addr >> 8));
if(addrbit > 0)
i2cSendByte(port, (uint8_t)addr);
int n = 0;
int sendLen = len;
while(sendLen--)
{
i2cSendByte(port, dat[n++]);
}
i2cStop(port);
将Command缓存一次发送出去
if(i2c[port].iCommand > commandlength)
{
printf("i2cWrite error: command buffer is overflow %d:%d\n",
i2c[port].iCommand, commandlength);
if(i2c[port].pCommand)
free(i2c[port].pCommand);
return -2;
}
int writetimeout = i2c[port].ftdi->usb_write_timeout;
i2c[port].ftdi->usb_write_timeout = i2c[port].iCommand + len * i2c[port].freq;
int ret = ftdi_write_data(i2c[port].ftdi, i2c[port].pCommand, i2c[port].iCommand);
if(i2c[port].pCommand)
free(i2c[port].pCommand);
i2c[port].ftdi->usb_write_timeout = writetimeout;
if(ret < 0)
{
printf("usb write fail %d\n", ret);
return -5;
}
i2cWrite只需要读回所有的ack信息
int rdLen = ((addrbit > 0) ? 1 : 0) +
((addrbit > 8) ? 1 : 0) + 1 + len;
同样的方式读回所有数据
int rdLen = ((addrbit > 0) ? 1 : 0) +
((addrbit > 8) ? 1 : 0) + 1 + len;
printf("need to read data:%d\n", rdLen);
uint8_t *pReadBuf = (uint8_t *)malloc(rdLen);
int readtimeout = i2c[port].ftdi->usb_read_timeout;
i2c[port].ftdi->usb_read_timeout = i2c[port].iCommand + rdLen * i2c[port].freq;
printf("usb read timeout:%d\n", i2c[port].ftdi->usb_read_timeout);
ret = ftdi_read_data(i2c[port].ftdi, pReadBuf, rdLen);
printf("read data number:%d\n", ret);
if(ret < rdLen)
{//try again
int remain;
if(ret < 0)
{
remain = rdLen;
ret = ftdi_read_data(i2c[port].ftdi, pReadBuf, rdLen);
}
else
{
remain = rdLen - ret;
ret = ftdi_read_data(i2c[port].ftdi, pReadBuf + ret, remain);
}
printf("retry read %d\n", ret + remain);
if(ret + remain < rdLen)
{
if(pReadBuf)
free(pReadBuf);
i2c[port].ftdi->usb_read_timeout = readtimeout;
return -3;
}
}
i2c[port].ftdi->usb_read_timeout = readtimeout;
判断这些读回来的数据对应SDA位的值是否符合ack正确,注意,最后一个字节并不需要判断。
for(int i = 0; i < rdLen - 1; i++)
{
if ((pReadBuf[i] & ((uint8_t)(1 << (i2c[port].sdai % 8))))
== (uint8_t)(1 << (i2c[port].sdai % 8))) //Check ACK
{
if(pReadBuf)
free(pReadBuf);
printf("ack fail:%d, 0x%x\n", i, pReadBuf[i]);
return -4;
}
}
最后释放malloc的数据,且返回0.
if(pReadBuf)
free(pReadBuf);
printf("i2cWrite OK\n");
return 0;
9. 验证
将I2C接口的EEPROM芯片接到FT4232H模块的ADBUS0和ADBUS1口。首先初始化mode为mpsse模式。
int ret;
ret = ftdi_set_bitmode(ftdi, 0, BITMODE_MPSSE);
if(ret < 0)
{
printf("Set Mode Fail: %d\n", ret);
return EXIT_FAILURE;
}
9.1 初始化i2c
mpsse_i2c_s i2cSetting;
i2cSetting.ftdi = ftdi;
i2cSetting.scl = 2;
i2cSetting.sdao = 3;
i2cSetting.sdai = 3;
i2cSetting.freq = 9;
i2cInit(0, i2cSetting);
9.2 初始化gpio
将I2C的SCL和SDA初始化为输出高电平的初始状态。
mpsse_gpio_s gpioSetting;
gpioSetting.ftdi = ftdi;
gpioSetting.dir = 0x0000; //All input
gpioSetting.dir |= ((uint16_t)GPIO_DIR_OUT << i2cSetting.scl)
| ((uint16_t)GPIO_DIR_OUT << i2cSetting.sdao); //scl output, sdao output
gpioSetting.level = 0xFFFF;
mpsseGpioInit(gpioSetting);
9.3 写10个字节到EEPROM
uint8_t wrBuf[10];
printf("i2c write data: ");
srand(time(NULL));
for(int i = 0; i < sizeof(wrBuf); i++)
{
wrBuf[i] = (uint8_t)rand();
printf("0x%2x ", wrBuf[i]);
}
printf("\n");
ret = i2cWrite(0, 0xa0, 16, 0, wrBuf, sizeof(wrBuf));
if(ret < 0)
{
printf("write eeprom fail\n");
return ret;
}
9.4 读回10字节数据
uint8_t rdBuf[10];
ret = i2cRead(0, 0xa0, 16, 0, rdBuf, sizeof(rdBuf));
if(ret < 0)
{
printf("read eeprom fail\n");
return ret;
}
printf("i2c read data: ");
for(int i = 0; i < sizeof(wrBuf); i++)
{
printf("0x%2x ", rdBuf[i]);
if(wrBuf[i] != rdBuf[i])
{
printf("\neeprom verify fail\n");
return 0;
}
}
printf("\n");
9.5 运行结果
libftdi-example$ sudo ./libftdi1-example
version:1.5.0, 1.5
Number of FTDI devices found: 1
Manufacturer: FTDI, Description: FT4232H MiniModule, Serial: FT8NZV77
Open device OK: 0
i2c write data: 0x8c 0x8d 0xc4 0xf4 0xc2 0x 4 0xd8 0x88 0x26 0xf0
need to read data:13
usb read timeout:7383
read data number:13
i2cWrite OK
read data number:84
i2cRead OK
i2c read data: 0x8c 0x8d 0xc4 0xf4 0xc2 0x 4 0xd8 0x88 0x26 0xf0