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查看:

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

2. 编译ft260源码

下载ft260驱动源代码

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

进入hid-ft260,编译

make

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

install: rmmod hid-ft260 || trueinsmod hid-ft260.ko || truemkdir -p /usr/lib/modules/$(shell uname -r)/kernel/drivers/hid/ || truecp -f ./hid-ft260.ko /usr/lib/modules/$(shell uname -r)/kernel/drivers/hid/ || truedepmod -a

3. 通过sysfs配置ft260设备

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

$ . ./setenv.sh
sysfs_i2c_11
sysfs_ttyFT0

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

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

查看2个接口的信息:

$ echo $sysfs_i2c_11
/sys/bus/hid/drivers/ft260/0003:0403:6030.0007
$ echo $sysfs_ttyFT0
/sys/bus/hid/drivers/ft260/0003:0403:6030.0008/tty

查看接口的所有属性:

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

以chip_mode为例,查看该属性

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

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

$ cat $sysfs_i2c_11/chip_mode
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结果如下:

$ . ./setenv.sh
sysfs_i2c_11
sysfs_ttyFT0
$ cat $sysfs_i2c_11/gpio2_func
1
$ cat $sysfs_i2c_11/gpioa_func
3
$ cat $sysfs_i2c_11/gpiog_func
6

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

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

 运行结果如下:

$ sudo bash -c "echo 0 > $sysfs_i2c_11/gpio2_func"
$ cat $sysfs_i2c_11/gpio2_func
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/

$ ls /sys/class/gpio
export  gpiochip512  unexport

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

sudo bash -c 'echo 514 > /sys/class/gpio/export'
sudo bash -c 'echo 518 > /sys/class/gpio/export'
sudo bash -c 'echo 524 > /sys/class/gpio/export'

设置为输出

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

输出高电平:

sudo bash -c 'echo 1 > /sys/class/gpio/gpio514/value'
sudo bash -c 'echo 1 > /sys/class/gpio/gpio518/value'
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设备。

$ ls /sys/bus/usb/devices
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
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

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

找到对应文件clock

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

操作这个文件即可

$ sudo bash -c 'echo 400 > /sys/bus/usb/devices/3-4:1.0/0003:0403:6030.0023/clock'
$ cat /sys/bus/usb/devices/3-4:1.0/0003:0403:6030.0023/clock
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探测设备

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

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

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

5.3 读取所有寄存器数据

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

$ sudo i2cdump -y 11 0x50           
No size specified (using byte-data access)0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 45 31 03 04 30 60 00 00 a0 32 46 54 44 49 2c 0a    E1??0`..?2FTDI,?
10: 36 0c 00 00 60 20 cf be 00 00 00 00 00 00 00 00    6?..` ??........
20: 40 00 00 00 00 00 00 00 00 00 00 00 0a 03 46 00    @...........??F.
30: 54 00 44 00 49 00 0c 03 46 00 54 00 32 00 36 00    T.D.I.??F.T.2.6.
40: 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    0...............
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
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(长整数),根据寄存器的大小来选择。

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

参数含义等同读取。

$ sudo i2cset -y 11 0x50 0x80 0x55 b
$ sudo i2cget -y 11 0x50 0x80 b
0x55
$ sudo i2cset -y 11 0x50 0x80 0x00 b
$ sudo i2cget -y 11 0x50 0x80 b
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个字节就可以。

sudo i2ctransfer -y 11 w1@0x50 0x00 r4
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内核模块

sudo modprobe i2c-core
sudo modprobe i2c-dev
sudo modprobe i2c-smbus

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

6.3 C语言使用范例

6.3.1 头文件

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>

6.3.2 找到FT260的总线编号

定义宏定义:

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

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

int findFT260(void)
{DIR *dir;struct dirent *entry;char device_path[PATH_MAX];char name_path[PATH_MAX];char buffer[BUFFER_SIZE];ssize_t bytesRead;int fd;// 打开目录  dir = opendir(DEVICE_DIR);if (dir == NULL){  perror("opendir");return -1;  }  // 遍历目录条目  while ((entry = readdir(dir)) != NULL) {  }
}

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

        // 构建设备名称文件的路径  snprintf(name_path, sizeof(name_path), "%s%s/name", DEVICE_DIR, entry->d_name);// 打开设备名称文件  fd = open(name_path, O_RDONLY);  if (fd == -1) {  perror("open");  continue;  }  // 读取设备名称  bytesRead = read(fd, buffer, BUFFER_SIZE - 1); close(fd);if (bytesRead > 0) {  buffer[bytesRead] = '\0'; // 确保字符串以null结尾  printf("Device name: %s\n", buffer);  } else {  perror("read");// 关闭文件return -2;}if (strcmp(buffer, TARGET_NAME) == 0){int number = 0;int is_number = 0; // 标志位,表示是否开始读取数字// 遍历字符串  for (size_t i = 0; entry->d_name[i] != '\0'; ++i) {  if (isdigit(entry->d_name[i])){ // 如果字符是数字if (!is_number){ // 如果之前还没读取过数字,开始读取is_number = 1;number = 0; // 重置number为0,准备读取新的数字}  number = number * 10 + (entry->d_name[i] - '0'); // 将数字添加到number中} else {is_number = 0; // 如果不是数字,则停止读取数字}}return number;}

6.3.3 打开设备

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

6.3.4 设置I2C设备地址

通过ioctl设置。

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

6.3.5 从设备读数据

int i2cRead(int fd, unsigned char slave_addr, unsigned char reg_addr_width, 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,对于读来说,需要先写寄存器地址,在读入数据。

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

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

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;

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

if (reg_addr_width > 0)
{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;
}

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

else
{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;
}

最后发送出去

if(ioctl(fd, I2C_RDWR, &packets) < 0)
{perror("i2cRead ioctl fail");return -1;
}
return 0;

6.3.6 写数据到从设备

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

int i2cWrite(int fd, unsigned char slave_addr, unsigned char reg_addr_width, unsigned int reg_addr, unsigned char *pdat, unsigned int len)
{struct i2c_rdwr_ioctl_data packets;struct i2c_msg messages[1];unsigned char *outbuf = NULL;int offset = 0;unsigned int total = len;if(reg_addr_width == 16)total = len + 2;else if(reg_addr_width == 8)total = len + 1;elsetotal = len;outbuf = malloc(total);if (!outbuf) {perror("Error: No memory for buffer");return -1;}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");return -1;}return 0;
}

6.3.6 设置频率

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

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

int check_usb_device(const char *path, const char *vid, const char *pid) 
{  char vid_path[1024];  char pid_path[1024];  char vid_buf[16];  char pid_buf[16];  ssize_t bytes_read;  snprintf(vid_path, sizeof(vid_path), "%s/idVendor", path);  snprintf(pid_path, sizeof(pid_path), "%s/idProduct", path);  int vid_fd = open(vid_path, O_RDONLY);  int pid_fd = open(pid_path, O_RDONLY);  if (vid_fd == -1 || pid_fd == -1) {  perror("open");  if (vid_fd != -1) close(vid_fd);  if (pid_fd != -1) close(pid_fd);  return -1;  }  bytes_read = read(vid_fd, vid_buf, sizeof(vid_buf) - 1);  if (bytes_read <= 0) {  perror("read");  close(vid_fd);  close(pid_fd);  return -1;  }  if(bytes_read > 4)bytes_read = 4;vid_buf[bytes_read] = '\0'; // Ensure string is null-terminated  bytes_read = read(pid_fd, pid_buf, sizeof(pid_buf) - 1);  if (bytes_read <= 0) {  perror("read");  close(vid_fd);  close(pid_fd);  return -1;  }  if(bytes_read > 4)bytes_read = 4;pid_buf[bytes_read] = '\0'; // Ensure string is null-terminated  close(vid_fd);  close(pid_fd);  // Compare VID and PID  if (strcmp(vid, vid_buf) == 0 && strcmp(pid, pid_buf) == 0) {  return 1; // Found a match  }  return 0; // No match  
}  

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

int findClockPath(char *path, int len)
{DIR *dir;struct dirent *entry;  char full_path[1024];snprintf(full_path, sizeof(full_path), "%s:1.0/", path);dir = opendir(full_path);  if (dir == NULL) {perror("opendir");return -1;}while ((entry = readdir(dir)) != NULL) {// 忽略.和..目录项  if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {continue;}// 构建完整路径 char *last_slash = strrchr(path, '/'); snprintf(full_path, sizeof(full_path), "%s/%s:1.0/%s", path, last_slash, entry->d_name);printf("full path:%s\n", full_path);// 检查是否是目录,并且名称包含指定的vendor_product_id  struct stat st;  if (stat(full_path, &st) == 0 && S_ISDIR(st.st_mode)) {  // 检查目录名是否包含指定的vendor_product_id  if (strstr(entry->d_name, "0403:6030") != NULL) {printf("Found directory: %s\n", full_path);  snprintf(path, len, "%s", full_path);closedir(dir); return 0;}}}return -1;
}

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

int i2cSetFreq(int freq)
{DIR *dir;  struct dirent *entry;  char path[1024];  dir = opendir("/sys/bus/usb/devices/");  if (dir == NULL) {  perror("opendir");  return 1;  }  while ((entry = readdir(dir)) != NULL) {  if (entry->d_type == DT_DIR && entry->d_name[0] != '.') {  snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s", entry->d_name);  if (check_usb_device(path, VID, PID) == 1) {  printf("Found FT260 device at: %s\n", path);  closedir(dir);  if(findClockPath(path, sizeof(path)) == 0){int fd;  char buffer[6];char clockFilePath[2048];snprintf(buffer, sizeof(buffer), "%d\n", freq);snprintf(clockFilePath, sizeof(clockFilePath), "%s/clock", path);// 尝试以写入模式打开文件  printf("clock:%s\n", clockFilePath);fd = open(clockFilePath, O_WRONLY);  if (fd == -1) {// 如果打开失败,打印错误并退出  perror("open");  return -2;  }  // 写入数据到文件  ssize_t bytes_written = write(fd, buffer, strlen(buffer));  if (bytes_written == -1) {  // 如果写入失败,打印错误并关闭文件  perror("write");  close(fd);  return -3;  }  // 关闭文件  if (close(fd) == -1) {  // 如果关闭失败,打印错误但忽略,因为数据已经写入  perror("close");  return -4;} return 0;}}  }  }  closedir(dir);  return 0;  
}

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

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

7.1 安装libgpiod

sudo apt-get install libgpiod-dev

7.2 找到GPIO

$ ls /sys/class/gpio/
export  gpiochip512  unexport
$ ls /sys/class/gpio/gpiochip512
base  device  label  ngpio  power  subsystem  uevent
$ cat /sys/class/gpio/gpiochip512/label
ft260_0003:0403:6030.000F
$ cat /sys/class/gpio/gpiochip512/base
512
$ cat /sys/class/gpio/gpiochip512/ngpio
14

只要找到base的值。

int findGpio(int *base)
{DIR *dir;struct dirent *entry;  char full_path[1024];dir = opendir("/sys/class/gpio/");  if (dir == NULL) {perror("opendir");return -1;}while ((entry = readdir(dir)) != NULL) {// 忽略.和..目录项  if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {continue;}printf("folder:%s\n", entry->d_name);if (strstr(entry->d_name, "gpiochip") != NULL){snprintf(full_path, sizeof(full_path), "/sys/class/gpio/%s/label", entry->d_name);printf("full path:%s\n", full_path);int fd;fd = open(full_path, O_RDONLY);  if (fd == -1) {// 如果打开失败,打印错误perror("open label");  continue;  }char buffer[256];ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);  if (bytes_read == -1) {  // 如果写入失败,打印错误并关闭文件  perror("");  close(fd);  continue;}  // 关闭文件  if (close(fd) == -1) {  // 如果关闭失败,打印错误但忽略,因为数据已经写入  perror("close");  continue;} printf("    label:%s\n", buffer);if (strstr(buffer, "ft260") != NULL){snprintf(full_path, sizeof(full_path), "/sys/class/gpio/%s/base", entry->d_name);fd = open(full_path, O_RDONLY);  if (fd == -1) {// 如果打开失败,打印错误perror("open");  continue;  }ssize_t bytes_read = read(fd, buffer, strlen(buffer));  if (bytes_read == -1) {  // 如果写入失败,打印错误并关闭文件  perror("");  close(fd);  continue;}  buffer[bytes_read] = '\0';// 关闭文件  if (close(fd) == -1) {  // 如果关闭失败,打印错误但忽略,因为数据已经写入  perror("close");  continue;}char *endptr;*base = strtol(buffer, &endptr, 10);printf("gpio base=%d\n", *base);return 0;}} }return -1;
}

7.3 打开和关闭GPIO CHIP

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

struct gpiod_chip *gpiochipFT;
gpiochipFT = gpiod_chip_open("/dev/gpiochip0");
if (!gpiochipFT)
{perror("gpio open fail");return;
}

关闭:

gpiod_chip_close(gpiochipFT);

7.4 获取GPIO句柄和释放

获取某个GPIO的句柄

struct gpiod_line *gpio2;
gpio2= gpiod_chip_get_line(gpiochipFT, 2);
if (!gpio2)
{gpiod_chip_close(gpiochipFT);perror("gpio2 get line fail");return;
}

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

用完要释放:

 gpiod_line_release(gpio2, &req);  

7.5 设置输出或输入

设置为输出:

req = gpiod_line_request_output(gpio2, "blink", 0);
if (req)
{gpiod_chip_close(gpiochipFT);fprintf(stderr, "GPIO2 request error.\n");return;
}

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

可以通过gpiod_line_request_input设置为输入

req = gpiod_line_request_input(gpio2, "blink");

7.6 输出高低

while (1)
{/* 设置引脚电平 */gpiod_line_set_value(gpio2, 1);printf("set GPIO2 to 0\n");usleep(500 * 1000);gpiod_line_set_value(gpio2, 0);printf("set GPIO2 to 1\n");usleep(500 * 1000);
}

7.6 读入

while (1)
{int value;/* 设置引脚电平 */gpiod_line_set_value(gpio2, 1);value = gpiod_line_get_value(gpio2);printf("set GPIO2 to %d\n", value);usleep(500 * 1000);gpiod_line_set_value(gpio2, 0);value = gpiod_line_get_value(gpio2);printf("set GPIO2 to %d\n", value);usleep(500 * 1000);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1420751.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

web前端之纯CSS实现简单酷炫的照片墙效果、排除元素的伪类、scale

MENU 效果htmlstylescale:not() 效果 html <div class"container"><div class"box"><img src"../../image/1_.jpg"></div><div class"box"><img src"../../image/2_.jpg"></div>…

【Mac】Ghost Buster Pro(苹果电脑内存清理专家) v3.2.5安装教程

软件介绍 Ghost Buster pro是一款针对Mac系统的电脑清理和优化工具&#xff0c;可以帮助用户清理系统垃圾、修复注册表错误、卸载不需要的软件、管理启动项等&#xff0c;从而提高系统性能和稳定性。 安装教程 1.打开镜像包&#xff0c;拖动「Ghost Buster Pro」到应用程序中…

[AutoSar]BSW_Diagnostic_004 ReadDataByIdentifier(0x22)的配置和实现

目录 关键词平台说明背景一、配置DcmDspDataInfos二、配置DcmDspDatas三、创建DcmDspDidInfos四、创建DcmDspDids五、总览六、创建一个ASWC七、mapping DCM port八、打开davinci developer&#xff0c;创建runnabl九、生成代码 关键词 嵌入式、C语言、autosar、OS、BSW、UDS、…

编写Ansible角色实现分布式LNMP安装

前言 本文将介绍如何使用 Ansible 编写角色&#xff0c;在分布式环境下完成 LNMP&#xff08;Linux、Nginx、MySQL、PHP&#xff09;的自动化&#xff08;编译&#xff09;安装和配置&#xff0c;并验证 PHP 与 MySQL 数据联通性&#xff0c;实现博客和论坛页面的展示。 常规…

Vue3路由及登录注销功能、设置导航守护功能模块

路由 在vue中&#xff0c;页面和组件都是.vue文件&#xff0c;可以说是一样的&#xff0c;结构、内容和生产方法都是一样&#xff0c;但是组件可以被反复使用&#xff0c;但页面一般只被使用一次。 路由的作用就是网页地址发生变化时&#xff0c;在App.vue页面的指定位置可以加…

HTTP 连接详解

概述 世界上几乎所有的 HTTP 通信都是由 TCP/IP 承载的&#xff0c;客户端可以打开一条TCP/IP连接&#xff0c;连接到任何地方的服务器。一旦连接建立&#xff0c;客户端和服务器之间交换的报文就永远不会丢失、受损或失序 TCP&#xff08;Transmission Control Protocol&…

2-1 EXTI外部中断(gd32)

中断的概念 中断硬件结构/软件结构 EXTI中断 EXTI硬件结构 注&#xff1a;EXTI线在同一时刻只能连接一个GPIO口&#xff0c;如果我们先连接了PA0,然后又连接了PB0那么此时PA0这个IO口就失去作用。 中断触发函数 中断优先级 中断优先级 数值越小优先级越高&#xff0c;抢占优先级…

加州大学欧文分校英语高级语法专项课程01:Verb Tenses and Passives 学习笔记

Verb Tenses and Passives Course Certificate Course Intro 本文是学习 Verb Tenses and Passives 这门课的学习笔记。 文章目录 Verb Tenses and PassivesWeek 01: Simple, Progressive, and Perfect Verb Tenses ReviewLearning Objectives Present Perfect Tense Review L…

企业级通用业务 Header 处理方案

目录 01: 处理 PC 端基础架构 02: 通用组件&#xff1a;search 搜索框能力分析 03: 通用组件&#xff1a;search 搜索框样式处理 04: 通用组件&#xff1a;Button 按钮能力分析 05: 通用组件&#xff1a;Button 按钮功能实现 06: 通用组件&#xff1a;完善 search 基本…

Photoshop 2022 for Mac/win:释放创意,打造专业级的图像编辑体验

在数字图像编辑的世界里&#xff0c;Adobe Photoshop 2022无疑是那颗璀璨的明星。这款专为Mac和Windows用户设计的图像处理软件&#xff0c;以其卓越的性能和丰富的功能&#xff0c;赢得了全球数百万创作者的青睐。 Photoshop 2022在继承前代版本强大功能的基础上&#xff0c;…

Appium 2.x 安装及使用

由于安全问题&#xff0c;Appium 1.x 版本不再被维护&#xff0c;但想要继续使用Appium进行自动化可以使用 Appium 2.x。 1. 安装Appium 2.x 在过往文章中有介绍过Appium 1.x 的安装&#xff0c;所以一些必备的软件(如&#xff1a;JDK、SDK、node.js、Python)安装就不再细嗦&…

STM32串口通信入门

文章目录 一、串口协议和RS-232标准&#xff0c;以及RS232电平与TTL电平的区别1.串口通信协议2.RS-232标准3.RS232电平与TTL电平的区别4.USB/TTL转232“模块&#xff08;CH340芯片为例&#xff09; 二、补充实验&#xff08;一&#xff09;几个常见的库函数、结构体1.时钟配置函…

C++小程序:同一路由器下两台计算机间简单通信(2/2)——客户端

客户端的程序结构前半部分与服务器端基本相同&#xff0c;后半部分也相对简单。相关函数的解释可以参考前文服务器端的内容。有关客户端的内容除个别地方外&#xff0c;就不再做长篇大论的解释。强调一点&#xff0c;如果将此程序移到其它电脑上运行&#xff0c;编译需要releas…

Vue路由拆分

1.在src下建立router&#xff0c;在router中建立文件index 2.将main.js中部分内容复制 App <template> <div><a href"#/friend">朋友</a><br><a href"#/info">信息</a><br><a href"#/music&quo…

支持播放h265的插件

插件源码地址&#xff1a;GitCode - 开发者的代码家园https://gitcode.com/mirrors/nanguantong/flv-h265.js/overview 1. 下载代码 运行以下命令 npm i npm run build npm run build 后生成 flv.min.js 文件&#xff0c;引入使用 2. 调用 js 文件&#xff1a; let flvj…

完全背包问题(c++)

完全背包问题 当前有 N 种物品&#xff0c;第 i 种物品的体积是 ci​&#xff0c;价值是 wi​。 每种物品的数量都是无限的&#xff0c;可以选择任意数量放入背包。 现有容量为 V 的背包&#xff0c;请你放入若干物品&#xff0c;使总体积不超过 V&#xff0c;并且总价值尽可…

msvcp140dll怎么修复,分享5种有效的解决方法

MSVCP140.dll文件丢失这一现象究竟是何缘由&#xff0c;又会引发哪些令人头疼的问题呢&#xff1f;在探索这个问题的答案之前&#xff0c;我们先来深入了解这个神秘的DLL文件。MSVCP140.dll是Microsoft Visual C Redistributable Package的一部分&#xff0c;它扮演着至关重要的…

景源畅信:抖音小店的商品怎么同步到橱窗?

在数字营销的海洋中&#xff0c;抖音小店与橱窗的同步操作无疑是商家们关注的焦点。这不仅能增加商品的曝光度&#xff0c;还能提高交易的可能性。那么&#xff0c;如何将抖音小店的商品同步到橱窗呢? 一、核心步骤解析 要实现商品从抖音小店同步到橱窗&#xff0c;你需要确保…

汇昌联信:做拼多多网点需要具备什么能力?

在当前电商行业高速发展的背景下&#xff0c;拼多多以其独特的商业模式迅速崛起&#xff0c;成为众多创业者和商家关注的焦点。想要运营一家成功的拼多多网点&#xff0c;不仅需要对平台规则有深入的了解&#xff0c;还需要具备多方面的能力。这些能力是确保网点稳定运营并实现…

VBA_NZ系列工具NZ07:日期录入控件

我的教程一共九套及VBA汉英手册一部&#xff0c;分为初级、中级、高级三大部分。是对VBA的系统讲解&#xff0c;从简单的入门&#xff0c;到数据库&#xff0c;到字典&#xff0c;到高级的网抓及类的应用。大家在学习的过程中可能会存在困惑&#xff0c;这么多知识点该如何组织…