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 || true
	insmod hid-ft260.ko || true
	mkdir -p /usr/lib/modules/$(shell uname -r)/kernel/drivers/hid/ || true
	cp -f ./hid-ft260.ko /usr/lib/modules/$(shell uname -r)/kernel/drivers/hid/ || true
	depmod -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 addresses
     0  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;
    else
        total = 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);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯-线性动态规划问题背包问题进阶策略详解-

题目&#xff1a;蓝桥云课-青蛙吃虫 解题代码&#xff1a; #include <iostream> #include<cstring> #include<algorithm> using namespace std;const int N106;int f[N][N]; int a[N]; int t,l,r,k,n;int main() {cin>>t;while(t--){scanf("%d%…

ASP.NET一种基于C2C模式的网上购物系统的设计与实现

摘 要 网络购物已经慢慢地从一个新鲜的事物逐渐变成日常生活的一部分&#xff0c;以其特殊的优势而逐渐深入人心。本课题是设计开发一种基于C2C模式的网上购物系统。让各用户使用浏览器进行商品浏览。注册用户可以轻松的展示自己的网络商店&#xff0c;能对自己的用户信息进行…

Sping源码(七)—ConfigurationClassPostProcessor ——@PropertySources解析

序言 先来简单回顾一下ConfigurationClassPostProcessor大致的一个处理流程&#xff0c;再来详细的讲解PropertySources注解的处理逻辑。 详细的步骤可参考ConfigurationClassPostProcessor这篇帖子。 流程图 从获取所有BeanDefinition -> 过滤、赋值、遍历 -> 解析 -&…

璞华科技中标苏州工业园区“科技发展公司运营管理系统”升级改造项目

近日&#xff0c;璞华科技中标苏州工业园区科技发展有限公司“科技发展公司运营管理系统”升级改造项目。 苏州工业园区科技发展有限公司成立于2000年&#xff0c;是苏州工业园区管委会直属国有企业&#xff0c;聚焦以人工智能为引领的数字经济产业创新集群&#xff0c;重点布局…

Web自动化 - selenium

文章目录 一、selenium的使用selenium的安装 二、元素1. 定位选择元素1.id 定位2. class_name 定位find_element 和 find_elements的区别3. TAG_NAME 定位4. 超链接 定位 2. 操控元素1. 查询内容2. 获取元素文本内容3. 获取元素属性 3. 浏览器常用操作API4. 鼠标操作 - perform…

17 M-LAG 配置思路

16 华三数据中心最流行的技术 M-LAG-CSDN博客 M-LAG 配置思路 什么是M-LAG&#xff1f;为什么需要M-LAG&#xff1f; - 华为 (huawei.com) 1 配置 M-LAG 的固定的MAC地址 [SW-MLAG]m-lag system-mac 2-2-2 2 配置M-LAG 的系统标识符系统范围1到2 [SW-MLAG]m-lag system-nu…

【算法】动态规划之线性DP问题

前言&#xff1a; 本系列是看的B站董晓老师所讲的知识点做的笔记 董晓算法的个人空间-董晓算法个人主页-哔哩哔哩视频 (bilibili.com) 树塔-记忆化搜索 特点&#xff08;前提&#xff09;&#xff1a;从上向下的累加和是不能重复使用的&#xff0c;从下向上的累加和是可以重…

人民币汇率相关历史数据集(2006-2022年)

01、数据简介 汇率指的是两种货币之间兑换的比率&#xff0c;亦可视为一个国家的货币对另一种货币的价值。具体来说&#xff0c;汇率亦可视为一个国家的两种货币之间所存在的兑换比率&#xff0c;亦可视为一个国家的货币对另一种货币的价值。汇率的变动对一国的进出口贸易有着…

k8s 数据流向 与 核心概念详细介绍

目录 一 k8s 数据流向 1&#xff0c;超级详细版 2&#xff0c;核心主键及含义 3&#xff0c;K8S 创建Pod 流程 4&#xff0c;用户访问流程 二 Kubernetes 核心概念 1&#xff0c;Pod 1.1 Pod 是什么 1.2 pod 与容器的关系 1.3 pod中容器 的通信 2&#xff0c; …

94、动态规划-最长公共子序列

递归的基本思路&#xff1a; 比较两个字符串的最后一个字符。如果相同&#xff0c;则这个字符一定属于最长公共子序列&#xff0c;然后在剩余的字符串上递归求解。如果最后一个字符不相同&#xff0c;则分两种情况递归求解&#xff1a; 去掉 text1 的最后一个字符&#xff0c;保…

中国当代最具影响力的人物颜廷利:死神(死亡)并不可怕,可怕的是…

中国当代最具影响力的人物颜廷利&#xff1a;死神&#xff08;死亡&#xff09;并不可怕&#xff0c;可怕的是… 在中国优秀传统文化之中&#xff0c;汉语‘巳’字与‘四’同音&#xff0c;在阿拉伯数字里面&#xff0c;通常用‘4’来表示&#xff1b; 作为汉语‘九’字&#x…

mysql设置远程访问权限,允许其他IP访问

文章目录 更改mysql配置文件登录mysql 更改mysql配置文件 查找.ini或者.cnf文件 更改bind-address为0.0.0.0 [mysqld] character-set-serverutf8mb4 bind-address0.0.0.0 default-storage-engineINNODB [mysql] default-character-setutf8mb4 [client] default-character-s…

【getopt函数用法】

这里写目录标题 一、概述二、选项字符串规则&#xff1a;三、getopt 返回值四、会用到的全局变量&#xff1a;三、示例代码四、上机实验 一、概述 int getopt(int argc, char * const argv[], const char *optstring); extern char *optarg; //这个最常用&#xff0c;保存一个…

conan2 基础入门(06)-conanfile.py入门

conan2 基础入门(06)-conanfile.py入门 文章目录 conan2 基础入门(06)-conanfile.py入门⭐准备预备文件和Code ⭐使用流程指令 ⭐具体讲解conanfile.pyconan install END视频教学 ⭐准备 注意&#xff0c;如果想跟好的学习conanfile.py建议使用python来安装conan。 当然使用其…

C++之STL-priority_queue和仿函数的讲解

目录 一、priority_queue的介绍和使用 1.1 priority_queue的介绍 1.2 priority_queue的基本接口 二、仿函数的介绍 2.1 基本概念 2.2 适用场景 三、模拟实现priority_queue 3.1 向上调整算法 3.2 向下调整算法 3.3 整体框架 一、priority_queue的介绍和使用 1.1 prio…

U盘文件遇损?拯救“文件或目录损坏且无法读取”的秘籍!

在数字化时代&#xff0c;U盘已成为我们日常生活与工作中不可或缺的数据存储和传输工具。然而&#xff0c;有时我们可能会遇到一个非常令人沮丧的问题——U盘中的文件或目录突然损坏且无法读取。这种突发状况往往让人措手不及&#xff0c;甚至可能引发数据丢失的严重后果。那么…

【基于 PyTorch 的 Python 深度学习】6 视觉处理基础:卷积神经网络(2)

前言 文章性质&#xff1a;学习笔记 &#x1f4d6; 学习资料&#xff1a;吴茂贵《 Python 深度学习基于 PyTorch ( 第 2 版 ) 》【ISBN】978-7-111-71880-2 主要内容&#xff1a;根据学习资料撰写的学习笔记&#xff0c;该篇主要介绍了卷积神经网络的池化层部分和现代经典网络。…

2022CSP-S易错题解析

21.B i的取值分别是0、5、6、7、8、13&#xff0c;其中i5时&#xff0c;j运行3次&#xff1b;i6时&#xff0c;j运行2次&#xff1b;i7时&#xff0c;j运行1次&#xff1b;i13时&#xff0c;j运行4次。共10次。 25.D 第1次执行时&#xff0c;数字是按照三进制下的最低位从小到…

【C++】——string类

前言 在C语言里面我们用的字符串都是以\0结尾的字符合集&#xff0c;为了操作方便所以在c中推出了stirng类 一 string介绍 1.string是表示字符串的字符串类 2.因为是类&#xff0c;所以他会有一些常用的接口&#xff0c;同时也添加了专门用来操作string的常规操作 3.string…

什么是$t?$t的介绍及使用

目录 $t介绍&#xff1a; 作用&#xff1a; 安装国际化插件&#xff1a; 创建国际化资源文件 配置 vue-i18n &#xff1a; 切换语言&#xff1a; 下面为中文和英文状态下的效果&#xff1a; 如下面所示&#xff0c;这是一段前端代码&#xff1a; <el-form-item :label…