ftdi_sio应用学习笔记 4 - I2C
目录
1. 查找设备
2. 打开设备
3. 写数据
4. 读数据
5. 设置频率
6 验证
6.1 遍历设备
6.2 开关设备
6.3 读写测试
I2C设备最多有6个(FT232H),其他为2个。和之前的设备一样,定义个I2C结构体记录找到的设备。
#define FTDI_DEVICE_MAX_INTEFACE_I2C 2
#define FTDI_DEVICE_MAX_I2C 6
struct ftdi_i2c_info {
struct ftdi_i2c_info *next;
int i2c_num[FTDI_DEVICE_MAX_INTEFACE_I2C][FTDI_DEVICE_MAX_I2C];
int pid;
int vid;
char serial_number[64];
};
FTDI设备和I2C设备对应的关系,可以在/sys/bus/usb下找到ttyUSBn(串口的那个文件夹内),在这个文件夹内可以看到i2c设备的信息,例如:
:/sys/bus/usb/devices/2-1/2-1:1.0/ttyUSB0$ ls
driver i2c-1 latency_timer power subsystem uevent
event_char i2c-2 port_number spi_master tty
可以看到该设备(FT4232H)的接口0有2个i2c设备。
1. 查找设备
和串口类似,先找到ttyUSB字符串,然后在这个文件夹内找"i2c-"字符串。
DIR *i2c_dir;
struct dirent *i2c_entry;
int i2c_index = 0;
sprintf(name_path, "/sys/bus/usb/devices/%s:1.%d/%s", entry->d_name, interface, tty_entry->d_name);
i2c_dir = opendir(name_path);
while ((i2c_entry = readdir(i2c_dir)) != NULL) {
if (strstr(i2c_entry->d_name, "i2c-") != NULL) {
printf("Found:%s\n", i2c_entry->d_name);
sscanf(i2c_entry->d_name, "i2c-%d", &dev_list->i2c_num[interface][i2c_index]);
i2c_index++;
}
}
closedir(i2c_dir);
2. 打开设备
分2种情况,通过pid或通过串口号打开
int ftdi_sio_i2c::open_i2c(int pid, int n, int num)
int ftdi_sio_i2c::open_i2c(char *serial_number, int interface, int num)
参数:
pid - FTDI设备的PID号
n - 需要打开的同PID号的第n个设备
num - 该设备的第num个i2c设备
返回i2c设备的设备句柄。
找到设备的方式和之前的方式一样。
char i2c_path[PATH_MAX];
int fd;
sprintf(i2c_path, "/dev/i2c-%d", dev_list->i2c_num[interface][num]);
printf("open:%s\n", i2c_path);
if ((fd = open(i2c_path, O_RDWR)) < 0) {
perror("Failed to open the i2c bus\n");
}
3. 写数据
int ftdi_sio_i2c::write_bytes(int fd, char slave_addr, char reg_addr_width,
int reg_addr, unsigned char *pdat, int len)
参数:
fd - open设备时返回的设备句柄
slave_addr - 从设备的地址
reg_addr_width - 从设备内部寄存器地址宽度,有效参数为0/8/16
reg_addr - 从设备内部寄存器地址
pdat - 写入从设备的数据
len - 写入数据长度
写数据需要将地址和数据一起打包到i2c_msg类型的数据中,一个信息就可以写入设备。
if(reg_addr_width == 16) {
outbuf[offset++] = (unsigned char)(reg_addr >> 8);
outbuf[offset++] = (unsigned char)reg_addr;
} else if(reg_addr_width == 8)
outbuf[offset++] = (unsigned char)reg_addr;
memcpy(outbuf + offset, pdat, len);
messages[0].addr = slave_addr;
messages[0].flags = 0;
messages[0].len = total;
messages[0].buf = outbuf;
packets.nmsgs = 1;
packets.msgs = messages;
if(ioctl(fd, I2C_RDWR, &packets) < 0) {
perror("i2cWrite ioctl fail");
free(outbuf);
return -1;
}
4. 读数据
int ftdi_sio_i2c::read_bytes(int fd, char slave_addr, char reg_addr_width,
int reg_addr, unsigned char *pdat, int len)
参数意义与写数据一样的。
当需要写寄存器地址时,需要2个msg写入设备,第一个msg是写地址,第二个msg是读数据。
messages[0].addr = slave_addr;
messages[0].flags = 0;
messages[0].len = offset;
messages[0].buf = outbuf;
/* The data will get returned in this structure */
messages[1].addr = slave_addr;
messages[1].flags = I2C_M_RD/* | I2C_M_NOSTART*/;
messages[1].len = len;
messages[1].buf = pdat;
/* Send the request to the kernel and get the result back */
packets.msgs = messages;
packets.nmsgs = 2;
如果没有寄存器的地址,只需要1个msg写入设备。
messages[0].addr = slave_addr;
messages[0].flags = I2C_M_RD/* | I2C_M_NOSTART*/;
messages[0].len = len;
messages[0].buf = pdat;
/* Send the request to the kernel and get the result back */
packets.msgs = messages;
packets.nmsgs = 1;
5. 设置频率(失败)
一般的I2C设备并不能支持直接修改i2c的频率,这里在内核驱动中添加频率的属性参数。由于之前是一个设备共用一个i2c_clk的参数,所以只在ttyUSB设备里面增加i2c_clk属性。
static ssize_t ftdi_mpsse_show_i2c_clk(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct usb_serial_port *port = to_usb_serial_port(dev);
struct ftdi_private *priv = usb_get_serial_port_data(port);
return sprintf(buf, "%d\n", priv->i2c_clk - 1);
}
static ssize_t ftdi_mpsse_set_i2c_clk(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct usb_serial_port *port = to_usb_serial_port(dev);
struct ftdi_private *priv = usb_get_serial_port_data(port);
priv->i2c_clk = simple_strtoul(buf, NULL, 10) + 1;
return count;
}
static DEVICE_ATTR(i2c_clk, S_IWUSR | S_IRUSR, ftdi_mpsse_show_i2c_clk, ftdi_mpsse_set_i2c_clk);
注意,i2c_clk的值要减一,即i2c_clk的值为0时最快,但是在驱动中的值是1。
在初始化中添加初始化这个属性:
device_create_file(&port->dev, &dev_attr_i2c_clk);
在释放设备中删掉这个属性:
device_remove_file(&port->dev, &dev_attr_i2c_clk);
这样就可以在ttyUSBn的文件夹中找到这个属性(i2c_clk):
:/sys/bus/usb/devices/1-2/1-2:1.0/ttyUSB0$ ls
driver i2c-1 i2c_clk port_number spi_master tty
event_char i2c-2 latency_timer power subsystem uevent
在/sys/class/tty/里面也可以看到这个属性
:/sys/class/tty/ttyUSB0/device$ ls
driver i2c-1 i2c_clk port_number spi_master tty
event_char i2c-2 latency_timer power subsystem uevent
只要写这个文件就可以改变设备的i2c频率,和打开设备一样,提供2个函数设置频率,由于整个设备都是一个频率,所以这里不区分interface(如果需要区分interface或者每个i2c独立设置频率,则需要修改ftdi_sio_i2c.c里面频率部分)
int ftdi_sio_i2c::set_freq(int pid, int n, int freq)
int ftdi_sio_i2c::set_freq(char *serial_number, int freq)
这里有一个问题,如果改动过频率,读写就会提示错误,ACK错误,不知道原因,所以这个设置频率的方式有问题。
6 验证
使用FT4232H模块验证。
6.1 遍历设备
ftdi_sio_i2c i2c;
i2c.find_devices();
i2c.free_devices();
打印结果:
$ sudo ./ftdi_sio_app
serial number:FT9PQ9R2
Found:i2c-1
Found:i2c-2
Found:i2c-3
Found:i2c-4
6.2 开关设备
打开FT4232H的第一个I2C。
fd = i2c.open_i2c((char *)"FT9PQ9R2", 0, 0);
i2c.close_i2c(fd);
打印结果:
$ sudo ./ftdi_sio_app
serial number:FT9PQ9R2
Found:i2c-1
Found:i2c-2
Found:i2c-3
Found:i2c-4
open:/dev/i2c-1
6.3 读写测试
将FT4232H的AD4和AD5分别接到EEPROM的SCL和SDA脚上。定义EEPROM的地址和数据长度
#define EEPROM_ADDR_WIDTH 16
#define I2C_LEN 16
写入数据随机产生,然后再写入EEPROM
printf("i2c write data:\n");
srand(time(NULL));
for(int i = 0; i < (int)sizeof(wr_buf); i++) {
wr_buf[i] = (unsigned char)rand();
}
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f");
for(int i = 0; i < I2C_LEN; i++) {
if((i % 16) == 0) {
printf("\n%2x: ", i);
}
printf(" %2x ", wr_buf[i]);
}
printf("\n");
ret = i2c->write_bytes(fd, 0x50, EEPROM_ADDR_WIDTH, 0, wr_buf, sizeof(wr_buf));
if(ret < 0) {
printf("write eeprom fail\n");
return;
}
再从EEPROM读出这笔数据,并比较判断
for(int i = 0; i < I2C_LEN; i++) {
rd_buf[i] = 0;
}
ret = i2c->read_bytes(fd, 0x50, EEPROM_ADDR_WIDTH, 0, rd_buf, sizeof(rd_buf));
if(ret < 0) {
printf("read eeprom fail\n");
return;
}
printf("Read value from register\n");
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f");
for(int i = 0; i < I2C_LEN; i++) {
if((i % 16) == 0) {
printf("\n%2x: ", i);
}
printf(" %2x ", rd_buf[i]);
}
printf("\n");
测试速度可以看到速度大约是400KHz以下。