ftdi_sio应用学习笔记 4 - I2C

目录

[1. 查找设备](#1. 查找设备)

[2. 打开设备](#2. 打开设备)

[3. 写数据](#3. 写数据)

[4. 读数据](#4. 读数据)

[5. 设置频率](#5. 设置频率)

[6 验证](#6 验证)

[6.1 遍历设备](#6.1 遍历设备)

[6.2 开关设备](#6.2 开关设备)

[6.3 读写测试](#6.3 读写测试)


I2C设备最多有6个(FT232H),其他为2个。和之前的设备一样,定义个I2C结构体记录找到的设备。

cpp 复制代码
#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设备的信息,例如:

cpp 复制代码
:/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-"字符串。

cpp 复制代码
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或通过串口号打开

cpp 复制代码
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设备的设备句柄。

找到设备的方式和之前的方式一样。

cpp 复制代码
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. 写数据

cpp 复制代码
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类型的数据中,一个信息就可以写入设备。

cpp 复制代码
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. 读数据

cpp 复制代码
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是读数据。

cpp 复制代码
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写入设备。

cpp 复制代码
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属性。

cpp 复制代码
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。

在初始化中添加初始化这个属性:

cpp 复制代码
device_create_file(&port->dev, &dev_attr_i2c_clk);

在释放设备中删掉这个属性:

cpp 复制代码
device_remove_file(&port->dev, &dev_attr_i2c_clk);

这样就可以在ttyUSBn的文件夹中找到这个属性(i2c_clk):

bash 复制代码
:/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/里面也可以看到这个属性

cpp 复制代码
:/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里面频率部分)

cpp 复制代码
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 遍历设备

cpp 复制代码
ftdi_sio_i2c i2c;
i2c.find_devices();

i2c.free_devices();

打印结果:

bash 复制代码
$ sudo ./ftdi_sio_app 
serial number:FT9PQ9R2
Found:i2c-1
Found:i2c-2
Found:i2c-3
Found:i2c-4

6.2 开关设备

打开FT4232H的第一个I2C。

cpp 复制代码
fd = i2c.open_i2c((char *)"FT9PQ9R2", 0, 0);

i2c.close_i2c(fd);

打印结果:

bash 复制代码
$ 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的地址和数据长度

cpp 复制代码
#define EEPROM_ADDR_WIDTH       16
#define I2C_LEN                 16

写入数据随机产生,然后再写入EEPROM

cpp 复制代码
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读出这笔数据,并比较判断

cpp 复制代码
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以下。

相关推荐
麻衣带我去上学1 小时前
Spring源码学习(一):Spring初始化入口
java·学习·spring
maknul2 小时前
【学习笔记】AD智能PDF导出(装配文件)
笔记·学习·pdf
坊钰2 小时前
【Java 数据结构】时间和空间复杂度
java·开发语言·数据结构·学习·算法
knoci4 小时前
【Go】-go中的锁机制
后端·学习·golang
快乐飒男4 小时前
Linux基础05
linux·笔记·学习
山山而川粤4 小时前
大连环保公益管理系统|Java|SSM|Vue| 前后端分离
java·开发语言·后端·学习·mysql
田梓燊5 小时前
湘潭大学软件工程算法设计与分析考试复习笔记(六)
笔记·算法·软件工程
网安墨雨5 小时前
网络安全笔记
网络·笔记·web安全
南东山人5 小时前
关于内核编程的一些笔记
linux·笔记