当前位置:   article > 正文

Linux FT260驱动内核学习笔记

Linux FT260驱动内核学习笔记

目录

1. 安装ft260驱动

2. 编译ft260源码

3. 通过sysfs配置ft260设备

3.1 多功能GPIO配置

3.2 控制GPIO

3.3 配置i2c总线频率

4. UART

5. 使用i2c-tools交互I2C设备

5.1 安装i2c-tools

5.2 探测I2C设备

5.3 读取所有寄存器数据

5.4 读取和写入

5.5 16位地址的读写

6. 通过libi2c交互I2C设备(C语言)

6.1 安装libi2c

6.2 加载i2c内核模块

6.3 C语言使用范例

6.3.1 头文件

6.3.2 找到FT260的总线编号

6.3.3 打开设备

6.3.4 设置I2C设备地址

6.3.5 从设备读数据

6.3.6 写数据到从设备

6.3.6 设置频率

7 通过libgpiod控制GPIO(C语言)

7.1 安装libgpiod

7.2 找到GPIO

7.3 打开和关闭GPIO CHIP

7.4 获取GPIO句柄和释放

7.5 设置输出或输入

7.6 输出高低

7.6 读入


系统采用Ubuntu 22,X86 64。

1. 安装ft260驱动

新版本的Linux内核是自带hid-ft260.ko的。

sudo modprobe hid-ft260

然后执行lsmod查看:

  1. $ lsmod
  2. Module Size Used by
  3. hid_ft260 45056 0
  4. usbhid 77824 1 hid_ft260
  5. hid 180224 2 usbhid,hid_ft260

2. 编译ft260源码

下载ft260驱动源代码

git clone https://github.com/MichaelZaidman/hid-ft260.git

进入hid-ft260,编译

make

编辑一下makefile文件,增加install部分:

  1. install:
  2. rmmod hid-ft260 || true
  3. insmod hid-ft260.ko || true
  4. mkdir -p /usr/lib/modules/$(shell uname -r)/kernel/drivers/hid/ || true
  5. cp -f ./hid-ft260.ko /usr/lib/modules/$(shell uname -r)/kernel/drivers/hid/ || true
  6. depmod -a

3. 通过sysfs配置ft260设备

可以在shell里面先执行(每次拔插后都要运行这个脚本),这个脚本在hid-ft260的源文件夹里面。

  1. $ . ./setenv.sh
  2. sysfs_i2c_11
  3. sysfs_ttyFT0

返回2个设备,sysfs_i2c_xx表示i2c的接口,sysfs_ttyFTx表示uart的接口。这个接口类型由硬件跳线DCNF0和DCNF1决定,当前设置是0b11的配置。

注意,不管哪种配置,返回的都是2个接口,因为0b00和0b11是一样的,0b01和0b10是只有一个接口,要么是串口,要么是i2c.

查看2个接口的信息:

  1. $ echo $sysfs_i2c_11
  2. /sys/bus/hid/drivers/ft260/0003:0403:6030.0007
  3. $ echo $sysfs_ttyFT0
  4. /sys/bus/hid/drivers/ft260/0003:0403:6030.0008/tty

查看接口的所有属性:

  1. $ ls $sysfs_i2c_11
  2. chip_mode driver gpioa_func hid_over_i2c_en i2c_reset power_saving_en subsystem uart_mode
  3. clock gpio gpiochip0 i2c-11 modalias pwren_status suspend_status uevent
  4. clock_ctl gpio2_func gpiog_func i2c_enable power report_descriptor uart_dcd_ri

以chip_mode为例,查看该属性

  1. ls -l $sysfs_i2c_11/chip_mode
  2. -r--r--r-- 1 root root 4096 4月 28 15:37 /sys/bus/hid/drivers/ft260/0003:0403:6030.0007/chip_mode

这个属性只读,然后输出内容:

  1. $ cat $sysfs_i2c_11/chip_mode
  2. 3

对应DCNF0和DCNF1的设置0b11。

3.1 多功能GPIO配置

FT260的IO都是多功能,但是大部分是2个功能复用,当默认功能禁止时,自动变为GPIO,例如pin10可以是RXD和GPIOC,RXD是默认功能,当UART功能关闭时,这个管脚自动设置为GPIOC。FT260有3个特殊的多功能GPIO,他们是GPIO 2(pin 14), GPIOA (pin 7), and GPIOG (pin 27),它们可以通过eFuse、EEPROM或USB命令配置。

接口的所有属性中gpio2_func、gpioa_func、gpiog_func分别对应这3个GPIO的功能配置。默认功能是:

3个GPIO的功能如下:

GPIO2的功能设定值含义如下:

0 - GPIO2,1 - SUSPOUT_N, 2 - PWREN, 4 - TX_LED

GPIOA的功能设定值含义如下:

0 - GPIOA,3 - TX_ACTIVE, 4 - TX_LED

GPIOG的功能设定值含义如下:

0 - GPIOG,2 - PWREN,5 - RX_LED, 6 - BCD_DET

读取对应gpio的func结果如下:

  1. $ . ./setenv.sh
  2. sysfs_i2c_11
  3. sysfs_ttyFT0
  4. $ cat $sysfs_i2c_11/gpio2_func
  5. 1
  6. $ cat $sysfs_i2c_11/gpioa_func
  7. 3
  8. $ cat $sysfs_i2c_11/gpiog_func
  9. 6

 配置其他参数,例如将pin 14配置为GPIO2

sudo bash -c "echo 0 > $sysfs_i2c_11/gpio2_func"

 运行结果如下:

  1. $ sudo bash -c "echo 0 > $sysfs_i2c_11/gpio2_func"
  2. $ cat $sysfs_i2c_11/gpio2_func
  3. 0

其他的GPIO可以通过DCNF0、DCNF1 配置UART和I2C关闭来使能GPIO。

3.2 控制GPIO

正常使用sysfs操作gpio是通过echo命令将GPIO引脚导出到用户空间:

sudo bash -c "echo <GPIO_NUMBER> > $sysfs_i2c_11/gpio/export"

注意,gpio编号不是2,a,g,但是这样无效。要先控制GPIO,需要先将对应的GPIO配置为GPIO模式,默认是没有gpio的。

可以先列一下/sys/class/gpio/

  1. $ ls /sys/class/gpio
  2. export gpiochip512 unexport

gpiochip512, 偏移值是512,GPIO2的编号是514,GPIOA的编号为512+6=518, GPIOG的编号为512+12=525

  1. sudo bash -c 'echo 514 > /sys/class/gpio/export'
  2. sudo bash -c 'echo 518 > /sys/class/gpio/export'
  3. sudo bash -c 'echo 524 > /sys/class/gpio/export'

设置为输出

  1. sudo bash -c 'echo out > /sys/class/gpio/gpio514/direction'
  2. sudo bash -c 'echo out > /sys/class/gpio/gpio518/direction'
  3. sudo bash -c 'echo out > /sys/class/gpio/gpio524/direction'

输出高电平:

  1. sudo bash -c 'echo 1 > /sys/class/gpio/gpio514/value'
  2. sudo bash -c 'echo 1 > /sys/class/gpio/gpio518/value'
  3. sudo bash -c 'echo 1 > /sys/class/gpio/gpio524/value'

3.3 配置i2c总线频率

sudo bash -c 'echo <clk> > $sysfs_i2c_11/clock'

其中<clk>表示设置的频率,单位kHz,例如设置为400KHz

sudo bash -c 'echo 400 > $sysfs_i2c_11/clock'

不过这样写无效,没有提示错误。但是量频率一直是100KHz。从github的issue里面也有人问这个问题,需要在sysfs下找出USB总线上的ft260设备。

  1. $ ls /sys/bus/usb/devices
  2. 1-0:1.0 1-1:1.0 2-1 2-1:1.0 2-1.3 2-1.3:1.1 3-4 3-4:1.1 usb1 usb3
  3. 1-1 2-0:1.0 2-1.1 2-1.1:1.0 2-1.3:1.0 3-0:1.0 3-4:1.0 4-0:1.0 usb2 usb4

然后通过lsusb看一下ft260在哪个bus上

Bus 003 Device 026: ID 0403:6030 Future Technology Devices International, Ltd FT260

结合lsusb和ls /sys/bus/usb/devices的结果,bus3上有2个设备,3-0和3-4,一般3-0是hub本身,所以3-4应该是FT260

  1. $ cat /sys/bus/usb/devices/3-4/idProduct
  2. 6030
  3. $ cat /sys/bus/usb/devices/3-4/idVendor
  4. 0403

找到对应文件clock

  1. $ cat /sys/bus/usb/devices/3-4:1.0/0003:0403:6030.0023/clock
  2. 100

操作这个文件即可

  1. $ sudo bash -c 'echo 400 > /sys/bus/usb/devices/3-4:1.0/0003:0403:6030.0023/clock'
  2. $ cat /sys/bus/usb/devices/3-4:1.0/0003:0403:6030.0023/clock
  3. 400

4. UART

对于UART功能,操作比较简单,和普通的串口使用一样,只是设备名变为ttyFT0了。例如使用cutecom就可以使用。

5. 使用i2c-tools交互I2C设备

5.1 安装i2c-tools

sudo apt-get install i2c-tools

5.2 探测I2C设备

如之前的信息,本例中i2c设备是i2c_11,所以通过i2cdetect探测设备

  1. $ sudo i2cdetect -y 11
  2. Warning: Can't use SMBus Quick Write command, will skip some addresses
  3. 0 1 2 3 4 5 6 7 8 9 a b c d e f
  4. 00:
  5. 10:
  6. 20:
  7. 30: -- -- -- -- -- -- -- --
  8. 40:
  9. 50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  10. 60:
  11. 70:

这里的-y选项用于关闭交互模式,这样在运行时不会显示警告信息。数字11代表I2C总线的编号,根据你的系统配置,这个编号可能会有所不同。

输出结果是遍历所有的I2C地址,因为总线上只有一个AT24C02的设备,所以可以看到输出结果只有0x50这个设备。

5.3 读取所有寄存器数据

假设I2C总线上接的设备是AT24C02(UMFT260EV1A板子上默认自带),EEPROM,设备地址为0x50。

  1. $ sudo i2cdump -y 11 0x50
  2. No size specified (using byte-data access)
  3. 0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
  4. 00: 45 31 03 04 30 60 00 00 a0 32 46 54 44 49 2c 0a E1??0`..?2FTDI,?
  5. 10: 36 0c 00 00 60 20 cf be 00 00 00 00 00 00 00 00 6?..` ??........
  6. 20: 40 00 00 00 00 00 00 00 00 00 00 00 0a 03 46 00 @...........??F.
  7. 30: 54 00 44 00 49 00 0c 03 46 00 54 00 32 00 36 00 T.D.I.??F.T.2.6.
  8. 40: 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0...............
  9. 50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
  10. 60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
  11. 70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
  12. 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
  13. 90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
  14. a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
  15. b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
  16. c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
  17. d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
  18. e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
  19. f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 b8 14 ..............??

5.4 读取和写入

  • 读取寄存器:
sudo i2cget -y <bus> <device-address> <register-address> [w]

<device-address>替换为你要操作的设备的地址,<register-address>替换为你要读取或写入的寄存器的地址,<value>替换为你要写入的值(如果是写入操作的话)。[w]表示值的位宽,可以是b(字节)、w(字)或l(长整数),根据寄存器的大小来选择。

  1. $ sudo i2cget -y 11 0x50 0x10 b
  2. 0x36
  • 写入寄存器:
sudo i2cset -y <bus> <device-address> <register-address> <value> [w]

参数含义等同读取。

  1. $ sudo i2cset -y 11 0x50 0x80 0x55 b
  2. $ sudo i2cget -y 11 0x50 0x80 b
  3. 0x55
  4. $ sudo i2cset -y 11 0x50 0x80 0x00 b
  5. $ sudo i2cget -y 11 0x50 0x80 b
  6. 0x00

5.5 16位地址的读写

前面的命令中,地址都是8位地址,如果是16位地址,需要通过i2ctransfer实现。

i2ctransfer [-f] [-y] [-v] [-V] [-a] I2CBUS DESC [DATA] [DESC [DATA]]...

 -f: 强制模式,如果目标 I2C 设备未响应,则不等待超时并立即返回。

-y: 对于读取操作,如果读取的数据少于请求的字节数,则不会报错。

-v: 详细模式,显示更多输出信息。

-V:版本信息,显示 i2ctransfer 的版本。

-a:在每次 I/O 操作后,显示 I2C 总线的地址和值。

I2CBUS: 指定要使用的 I2C 总线。通常是一个数字,例如 01 等,可以使用 ls /dev/i2c-* 来查看可用的 I2C 总线。

DES: 描述符,用于指定 I2C 消息的属性。例如写的格式:w[len]@[addr],读的格式:r[len]@[addr]。

DATA:可选,数据,一般写的时候需要写。

比如从16位地址0x0000读入4字节的命令:

sudo i2ctransfer -y 11 w2@0x50 0x00 0x00 r4

从16位地址0x0000写4字节0x11 0x22 0x33 0x44的命令:

sudo i2ctransfer -y 11 w6@0x50 0x00 0x00 0x11 0x22 0x33 0x44

如果是8位地址,只要把后面接的写地址部分改为1个字节就可以。

  1. sudo i2ctransfer -y 11 w1@0x50 0x00 r4
  2. sudo i2ctransfer -y 11 w5@0x50 0x00 0x11 0x22 0x33 0x44

6. 通过libi2c交互I2C设备(C语言)

6.1 安装libi2c

sudo apt-get install i2c-tools libi2c-dev

6.2 加载i2c内核模块

  1. sudo modprobe i2c-core
  2. sudo modprobe i2c-dev
  3. sudo modprobe i2c-smbus

不知道为什么,lsmod只能看到i2c-smbus。

6.3 C语言使用范例

6.3.1 头文件

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <fcntl.h>
  4. #include <unistd.h>
  5. #include <sys/ioctl.h>
  6. #include <linux/i2c-dev.h>

6.3.2 找到FT260的总线编号

定义宏定义:

  1. #define DEVICE_DIR "/sys/bus/i2c/devices/"
  2. #define BUFFER_SIZE 256
  3. #define TARGET_NAME "FT260 usb-i2c bridge\n"

创建函数findFT260, 返回总线编号,这个函数只能找到第一个FT260设备,如果是多个FT260设备,需要增加辨别判断,可以通过libusb获取serial number识别。

  1. int findFT260(void)
  2. {
  3. DIR *dir;
  4. struct dirent *entry;
  5. char device_path[PATH_MAX];
  6. char name_path[PATH_MAX];
  7. char buffer[BUFFER_SIZE];
  8. ssize_t bytesRead;
  9. int fd;
  10. // 打开目录
  11. dir = opendir(DEVICE_DIR);
  12. if (dir == NULL)
  13. {
  14. perror("opendir");
  15. return -1;
  16. }
  17. // 遍历目录条目
  18. while ((entry = readdir(dir)) != NULL)
  19. {
  20. }
  21. }

while循环中逐个读入name判断。

  1. // 构建设备名称文件的路径
  2. snprintf(name_path, sizeof(name_path), "%s%s/name", DEVICE_DIR, entry->d_name);
  3. // 打开设备名称文件
  4. fd = open(name_path, O_RDONLY);
  5. if (fd == -1) {
  6. perror("open");
  7. continue;
  8. }
  9. // 读取设备名称
  10. bytesRead = read(fd, buffer, BUFFER_SIZE - 1);
  11. close(fd);
  12. if (bytesRead > 0) {
  13. buffer[bytesRead] = '\0'; // 确保字符串以null结尾
  14. printf("Device name: %s\n", buffer);
  15. } else {
  16. perror("read");
  17. // 关闭文件
  18. return -2;
  19. }
  20. if (strcmp(buffer, TARGET_NAME) == 0)
  21. {
  22. int number = 0;
  23. int is_number = 0; // 标志位,表示是否开始读取数字
  24. // 遍历字符串
  25. for (size_t i = 0; entry->d_name[i] != '\0'; ++i)
  26. {
  27. if (isdigit(entry->d_name[i]))
  28. { // 如果字符是数字
  29. if (!is_number)
  30. { // 如果之前还没读取过数字,开始读取
  31. is_number = 1;
  32. number = 0; // 重置number为0,准备读取新的数字
  33. }
  34. number = number * 10 + (entry->d_name[i] - '0'); // 将数字添加到number中
  35. }
  36. else
  37. {
  38. is_number = 0; // 如果不是数字,则停止读取数字
  39. }
  40. }
  41. return number;
  42. }

6.3.3 打开设备

  1. int file;
  2. if ((file = open(i2c_path, O_RDWR)) < 0)
  3. {
  4. perror("Failed to open the i2c bus\n");
  5. exit(1);
  6. }

6.3.4 设置I2C设备地址

通过ioctl设置。

  1. if (ioctl(file, I2C_SLAVE, addr) < 0)
  2. {
  3. perror("Failed to acquire bus access and/or talk to slave");
  4. close(file);
  5. exit(1);
  6. }

6.3.5 从设备读数据

  1. int i2cRead(int fd, unsigned char slave_addr, unsigned char reg_addr_width,
  2. unsigned int reg_addr, unsigned char *pdat, unsigned int len)

fd - 设备句柄

slave_addr - 从机地址,7位地址

reg_addr_width -  从机内部寄存器地址宽度,有效值为0,8,16

reg_addr - 从机内部寄存器地址,reg_addr_width为0时这个参数无效

pdat - 读入数据的缓存

len - 读入字节数

读写都是可以通过ioctl,对于读来说,需要先写寄存器地址,在读入数据。

  1. unsigned char outbuf[2];
  2. int offset = 0;
  3. struct i2c_rdwr_ioctl_data packets;
  4. struct i2c_msg messages[2];

根据寄存器地址宽度配置写寄存器地址的数据

  1. if(reg_addr_width == 16)
  2. {
  3. outbuf[offset++] = (unsigned char)(reg_addr >> 8);
  4. outbuf[offset++] = (unsigned char)reg_addr;
  5. }
  6. else if (reg_addr_width == 8)
  7. outbuf[offset++] = (unsigned char)reg_addr;

如果有寄存器地址需要发送,需要发送2个信息给驱动,注意2个信息的flag的区别,0表示写。

  1. if (reg_addr_width > 0)
  2. {
  3. messages[0].addr = slave_addr;
  4. messages[0].flags = 0;
  5. messages[0].len = offset;
  6. messages[0].buf = outbuf;
  7. /* The data will get returned in this structure */
  8. messages[1].addr = slave_addr;
  9. messages[1].flags = I2C_M_RD/* | I2C_M_NOSTART*/;
  10. messages[1].len = len;
  11. messages[1].buf = pdat;
  12. /* Send the request to the kernel and get the result back */
  13. packets.msgs = messages;
  14. packets.nmsgs = 2;
  15. }

如果没有寄存器地址,则直接读数据即可。

  1. else
  2. {
  3. messages[0].addr = slave_addr;
  4. messages[0].flags = I2C_M_RD/* | I2C_M_NOSTART*/;
  5. messages[0].len = len;
  6. messages[0].buf = pdat;
  7. /* Send the request to the kernel and get the result back */
  8. packets.msgs = messages;
  9. packets.nmsgs = 1;
  10. }

最后发送出去

  1. if(ioctl(fd, I2C_RDWR, &packets) < 0)
  2. {
  3. perror("i2cRead ioctl fail");
  4. return -1;
  5. }
  6. return 0;

6.3.6 写数据到从设备

写数据必须一笔信息发送出去,其他类似读操作。

  1. int i2cWrite(int fd, unsigned char slave_addr, unsigned char reg_addr_width,
  2. unsigned int reg_addr, unsigned char *pdat, unsigned int len)
  3. {
  4. struct i2c_rdwr_ioctl_data packets;
  5. struct i2c_msg messages[1];
  6. unsigned char *outbuf = NULL;
  7. int offset = 0;
  8. unsigned int total = len;
  9. if(reg_addr_width == 16)
  10. total = len + 2;
  11. else if(reg_addr_width == 8)
  12. total = len + 1;
  13. else
  14. total = len;
  15. outbuf = malloc(total);
  16. if (!outbuf)
  17. {
  18. perror("Error: No memory for buffer");
  19. return -1;
  20. }
  21. if(reg_addr_width == 16)
  22. {
  23. outbuf[offset++] = (unsigned char)(reg_addr >> 8);
  24. outbuf[offset++] = (unsigned char)reg_addr;
  25. }
  26. else if(reg_addr_width == 8)
  27. outbuf[offset++] = (unsigned char)reg_addr;
  28. memcpy(outbuf + offset, pdat, len);
  29. messages[0].addr = slave_addr;
  30. messages[0].flags = 0;
  31. messages[0].len = total;
  32. messages[0].buf = outbuf;
  33. packets.nmsgs = 1;
  34. packets.msgs = messages;
  35. if(ioctl(fd, I2C_RDWR, &packets) < 0)
  36. {
  37. perror("i2cWrite ioctl fail");
  38. return -1;
  39. }
  40. return 0;
  41. }

6.3.6 设置频率

参考3.3的方式设置,首先是要找到设备的文件夹位置。

先建一个函数用于根据VID、PID找到设备的文件夹位置。在文件夹下读取idVendor和idProduct文件,判断VID和PID即可。

  1. int check_usb_device(const char *path, const char *vid, const char *pid)
  2. {
  3. char vid_path[1024];
  4. char pid_path[1024];
  5. char vid_buf[16];
  6. char pid_buf[16];
  7. ssize_t bytes_read;
  8. snprintf(vid_path, sizeof(vid_path), "%s/idVendor", path);
  9. snprintf(pid_path, sizeof(pid_path), "%s/idProduct", path);
  10. int vid_fd = open(vid_path, O_RDONLY);
  11. int pid_fd = open(pid_path, O_RDONLY);
  12. if (vid_fd == -1 || pid_fd == -1) {
  13. perror("open");
  14. if (vid_fd != -1) close(vid_fd);
  15. if (pid_fd != -1) close(pid_fd);
  16. return -1;
  17. }
  18. bytes_read = read(vid_fd, vid_buf, sizeof(vid_buf) - 1);
  19. if (bytes_read <= 0) {
  20. perror("read");
  21. close(vid_fd);
  22. close(pid_fd);
  23. return -1;
  24. }
  25. if(bytes_read > 4)
  26. bytes_read = 4;
  27. vid_buf[bytes_read] = '\0'; // Ensure string is null-terminated
  28. bytes_read = read(pid_fd, pid_buf, sizeof(pid_buf) - 1);
  29. if (bytes_read <= 0) {
  30. perror("read");
  31. close(vid_fd);
  32. close(pid_fd);
  33. return -1;
  34. }
  35. if(bytes_read > 4)
  36. bytes_read = 4;
  37. pid_buf[bytes_read] = '\0'; // Ensure string is null-terminated
  38. close(vid_fd);
  39. close(pid_fd);
  40. // Compare VID and PID
  41. if (strcmp(vid, vid_buf) == 0 && strcmp(pid, pid_buf) == 0) {
  42. return 1; // Found a match
  43. }
  44. return 0; // No match
  45. }

 找个这个文件夹后继续打开这个文件夹下名字带1.0的文件夹。

  1. int findClockPath(char *path, int len)
  2. {
  3. DIR *dir;
  4. struct dirent *entry;
  5. char full_path[1024];
  6. snprintf(full_path, sizeof(full_path), "%s:1.0/", path);
  7. dir = opendir(full_path);
  8. if (dir == NULL)
  9. {
  10. perror("opendir");
  11. return -1;
  12. }
  13. while ((entry = readdir(dir)) != NULL)
  14. {
  15. // 忽略.和..目录项
  16. if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
  17. {
  18. continue;
  19. }
  20. // 构建完整路径
  21. char *last_slash = strrchr(path, '/');
  22. snprintf(full_path, sizeof(full_path), "%s/%s:1.0/%s", path, last_slash, entry->d_name);
  23. printf("full path:%s\n", full_path);
  24. // 检查是否是目录,并且名称包含指定的vendor_product_id
  25. struct stat st;
  26. if (stat(full_path, &st) == 0 && S_ISDIR(st.st_mode))
  27. {
  28. // 检查目录名是否包含指定的vendor_product_id
  29. if (strstr(entry->d_name, "0403:6030") != NULL)
  30. {
  31. printf("Found directory: %s\n", full_path);
  32. snprintf(path, len, "%s", full_path);
  33. closedir(dir);
  34. return 0;
  35. }
  36. }
  37. }
  38. return -1;
  39. }

设置频率的函数,将设置的频率写入clock文件即可。

  1. int i2cSetFreq(int freq)
  2. {
  3. DIR *dir;
  4. struct dirent *entry;
  5. char path[1024];
  6. dir = opendir("/sys/bus/usb/devices/");
  7. if (dir == NULL) {
  8. perror("opendir");
  9. return 1;
  10. }
  11. while ((entry = readdir(dir)) != NULL) {
  12. if (entry->d_type == DT_DIR && entry->d_name[0] != '.') {
  13. snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s", entry->d_name);
  14. if (check_usb_device(path, VID, PID) == 1) {
  15. printf("Found FT260 device at: %s\n", path);
  16. closedir(dir);
  17. if(findClockPath(path, sizeof(path)) == 0)
  18. {
  19. int fd;
  20. char buffer[6];
  21. char clockFilePath[2048];
  22. snprintf(buffer, sizeof(buffer), "%d\n", freq);
  23. snprintf(clockFilePath, sizeof(clockFilePath), "%s/clock", path);
  24. // 尝试以写入模式打开文件
  25. printf("clock:%s\n", clockFilePath);
  26. fd = open(clockFilePath, O_WRONLY);
  27. if (fd == -1)
  28. {
  29. // 如果打开失败,打印错误并退出
  30. perror("open");
  31. return -2;
  32. }
  33. // 写入数据到文件
  34. ssize_t bytes_written = write(fd, buffer, strlen(buffer));
  35. if (bytes_written == -1) {
  36. // 如果写入失败,打印错误并关闭文件
  37. perror("write");
  38. close(fd);
  39. return -3;
  40. }
  41. // 关闭文件
  42. if (close(fd) == -1) {
  43. // 如果关闭失败,打印错误但忽略,因为数据已经写入
  44. perror("close");
  45. return -4;
  46. }
  47. return 0;
  48. }
  49. }
  50. }
  51. }
  52. closedir(dir);
  53. return 0;
  54. }

进入这个文件夹,应该以:0403:6030为关键字找到这个特殊的文件夹

7 通过libgpiod控制GPIO(C语言)

7.1 安装libgpiod

sudo apt-get install libgpiod-dev

7.2 找到GPIO

  1. $ ls /sys/class/gpio/
  2. export gpiochip512 unexport
  3. $ ls /sys/class/gpio/gpiochip512
  4. base device label ngpio power subsystem uevent
  5. $ cat /sys/class/gpio/gpiochip512/label
  6. ft260_0003:0403:6030.000F
  7. $ cat /sys/class/gpio/gpiochip512/base
  8. 512
  9. $ cat /sys/class/gpio/gpiochip512/ngpio
  10. 14

只要找到base的值。

  1. int findGpio(int *base)
  2. {
  3. DIR *dir;
  4. struct dirent *entry;
  5. char full_path[1024];
  6. dir = opendir("/sys/class/gpio/");
  7. if (dir == NULL)
  8. {
  9. perror("opendir");
  10. return -1;
  11. }
  12. while ((entry = readdir(dir)) != NULL)
  13. {
  14. // 忽略.和..目录项
  15. if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
  16. {
  17. continue;
  18. }
  19. printf("folder:%s\n", entry->d_name);
  20. if (strstr(entry->d_name, "gpiochip") != NULL)
  21. {
  22. snprintf(full_path, sizeof(full_path), "/sys/class/gpio/%s/label", entry->d_name);
  23. printf("full path:%s\n", full_path);
  24. int fd;
  25. fd = open(full_path, O_RDONLY);
  26. if (fd == -1)
  27. {
  28. // 如果打开失败,打印错误
  29. perror("open label");
  30. continue;
  31. }
  32. char buffer[256];
  33. ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
  34. if (bytes_read == -1)
  35. {
  36. // 如果写入失败,打印错误并关闭文件
  37. perror("");
  38. close(fd);
  39. continue;
  40. }
  41. // 关闭文件
  42. if (close(fd) == -1)
  43. {
  44. // 如果关闭失败,打印错误但忽略,因为数据已经写入
  45. perror("close");
  46. continue;
  47. }
  48. printf(" label:%s\n", buffer);
  49. if (strstr(buffer, "ft260") != NULL)
  50. {
  51. snprintf(full_path, sizeof(full_path), "/sys/class/gpio/%s/base", entry->d_name);
  52. fd = open(full_path, O_RDONLY);
  53. if (fd == -1)
  54. {
  55. // 如果打开失败,打印错误
  56. perror("open");
  57. continue;
  58. }
  59. ssize_t bytes_read = read(fd, buffer, strlen(buffer));
  60. if (bytes_read == -1)
  61. {
  62. // 如果写入失败,打印错误并关闭文件
  63. perror("");
  64. close(fd);
  65. continue;
  66. }
  67. buffer[bytes_read] = '\0';
  68. // 关闭文件
  69. if (close(fd) == -1)
  70. {
  71. // 如果关闭失败,打印错误但忽略,因为数据已经写入
  72. perror("close");
  73. continue;
  74. }
  75. char *endptr;
  76. *base = strtol(buffer, &endptr, 10);
  77. printf("gpio base=%d\n", *base);
  78. return 0;
  79. }
  80. }
  81. }
  82. return -1;
  83. }

7.3 打开和关闭GPIO CHIP

路径在/dev/中,类似“/dev/gpiochip0”

  1. struct gpiod_chip *gpiochipFT;
  2. gpiochipFT = gpiod_chip_open("/dev/gpiochip0");
  3. if (!gpiochipFT)
  4. {
  5. perror("gpio open fail");
  6. return;
  7. }

关闭:

gpiod_chip_close(gpiochipFT);

7.4 获取GPIO句柄和释放

获取某个GPIO的句柄

  1. struct gpiod_line *gpio2;
  2. gpio2= gpiod_chip_get_line(gpiochipFT, 2);
  3. if (!gpio2)
  4. {
  5. gpiod_chip_close(gpiochipFT);
  6. perror("gpio2 get line fail");
  7. return;
  8. }

注意对应的GPIO要先设置为GPIO模式,否则会返回错误。

用完要释放:

 gpiod_line_release(gpio2, &req);  

7.5 设置输出或输入

设置为输出:

  1. req = gpiod_line_request_output(gpio2, "blink", 0);
  2. if (req)
  3. {
  4. gpiod_chip_close(gpiochipFT);
  5. fprintf(stderr, "GPIO2 request error.\n");
  6. return;
  7. }

字符串“blink”表示该GPIO的用户名,0表示默认电平为低电平。

可以通过gpiod_line_request_input设置为输入

req = gpiod_line_request_input(gpio2, "blink");

7.6 输出高低

  1. while (1)
  2. {
  3. /* 设置引脚电平 */
  4. gpiod_line_set_value(gpio2, 1);
  5. printf("set GPIO2 to 0\n");
  6. usleep(500 * 1000);
  7. gpiod_line_set_value(gpio2, 0);
  8. printf("set GPIO2 to 1\n");
  9. usleep(500 * 1000);
  10. }

7.6 读入

  1. while (1)
  2. {
  3. int value;
  4. /* 设置引脚电平 */
  5. gpiod_line_set_value(gpio2, 1);
  6. value = gpiod_line_get_value(gpio2);
  7. printf("set GPIO2 to %d\n", value);
  8. usleep(500 * 1000);
  9. gpiod_line_set_value(gpio2, 0);
  10. value = gpiod_line_get_value(gpio2);
  11. printf("set GPIO2 to %d\n", value);
  12. usleep(500 * 1000);
  13. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/577479
推荐阅读
相关标签
  

闽ICP备14008679号