ftdi_sio应用学习笔记 4 - I2C

embedded/2024/11/24 0:27:50/

目录

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.ppmy.cn/embedded/139980.html

相关文章

构建nginx1.26.1轻量级Docker镜像添加第三方模块nginx_upstream_check_module

文章目录 1.构建自定义nginx镜像原因2.准备构建文件3.构建镜像4.验证第三方模块是否加载成功 1.构建自定义nginx镜像原因 docker hub仓库里的nginx官方镜像太大了&#xff0c;足足188MB不能重新引入nginx内部模块 并且也 不能静态方式 添加nginx的第三方模块。因为此过程需要涉…

el-table表头前几列固定,后面几列根据接口返回的值不同展示不同

在使用 Element UI 的 el-table 组件时&#xff0c;如果想要实现表头的前几列固定&#xff0c;而后面的列根据接口返回的数据动态展示&#xff0c;可以通过以下步骤来实现&#xff1a; 1. 固定表头前几列 在 el-table-column 中使用 fixed 属性来固定表头的前几列。例如&…

深度学习之目标检测的技巧汇总

1 Data Augmentation 介绍一篇发表在Big Data上的数据增强相关的文献综述。 Introduction 数据增强与过拟合 验证是否过拟合的方法&#xff1a;画出loss曲线&#xff0c;如果训练集loss持续减小但是验证集loss增大&#xff0c;就说明是过拟合了。 数据增强目的 通过数据增强…

【MySQL】避免执行SQl文件后自动转化表名为小写字母

在云端的MySQL数据库中有一部分表名为大写&#xff0c;导出sql文件其中表名也是大写&#xff0c;但是本地新建一个数据库后执行sql文件后对应的表名全部变成了小写。 解决方案&#xff1a; 因为MySQL在默认情况下会将表名转换为小写。可以通过修改MySQL配置文件中的lower_case…

机器学习周志华学习笔记-第3章<线性模型>

机器学习周志华学习笔记-第3章<线性模型> 3线性模型 意义&#xff1a;线性模型是机器学习中的基础模型&#xff0c;它通过属性的线性组合来进行预测。这种模型形式简单&#xff0c;易于理解和建模&#xff0c;并且具有良好的可解释性。原理&#xff1a;线性模型的基本形…

力扣 LeetCode 617. 合并二叉树(Day9:二叉树)

解题思路&#xff1a; 前序遍历 中左右&#xff0c;先有中间节点&#xff0c;才有左右节点 可以在原tree1的基础上操作&#xff0c;也可以重新定义一个新的树 class Solution {public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {if (root1 null) return root2…

elementui表格鼠标滑过不改变背景色——el-table背景色

elementui表格鼠标滑过不改变背景色 在Element UI中&#xff0c;要实现表格鼠标滑过时不改变背景色&#xff0c;可以通过CSS覆盖默认的样式。 以下是实现这一功能的CSS代码示例&#xff1a; /* 覆盖表格行鼠标滑过的背景色 */ .el-table .el-table__body tr:hover > td {…

华为IPD流程管理体系L1至L5最佳实践-解读

该文档主要介绍了华为IPD流程管理体系&#xff0c;包括流程体系架构、流程框架实施方法、各业务流程框架示例以及相关案例等内容&#xff0c;旨在帮助企业建立高效、规范的流程管理体系&#xff0c;实现业务的持续优化和发展。具体内容如下&#xff1a; 1. 华为流程体系概述 -…