当前位置: 首页 > article >正文

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以下。 


http://www.kler.cn/a/406513.html

相关文章:

  • GitLab使用操作v1.0
  • VSCode【下载】【安装】【汉化】【配置C++环境】【运行调试】(Windows环境)
  • ubuntu20.04的arduino+MU编辑器安装教程
  • PVE的优化与温度监控(二)—无法识别移动硬盘S.M.A.R.T信息的思考并解决
  • python之开发笔记
  • 【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
  • Mesh路由组网
  • 端到端的专线管理与运维:实时掌握专线的运行状态
  • python pytorch 加载MNIST训练集,解释
  • 谁的年龄最小(结构体专题)
  • udp_socket
  • 初级数据结构——栈与队列的互相实现
  • 【倍数问题——同余系】
  • PDF电子发票信息转excel信息汇总
  • Elasticsearch 分词器
  • “人工智能+高职”:VR虚拟仿真实训室的发展前景
  • 安装多个nodejs版本(nvm)
  • 2024年11月最新版Adobe PhotoShop(26.0)中文版下载
  • 高性能网络SIG月度动态: 推进SMC支持基于eBPF透明替换和内存水位限制等多项功能支持
  • 在线pdf转word免费工具
  • AI科技赋能,探索人力资源管理软件的高效应用
  • C++11异步操作——std::future
  • 即时通讯app入侵了 怎么办?
  • 浦语提示词工程实践(LangGPT版,服务器上部署internlm2-chat-1_8b,踩坑很多才完成的详细教程,)
  • IAR与鸿轩科技共同推进汽车未来
  • 实验07---7-03 n个数存入数组,输出下标奇数的元素