Linux FT260驱动内核学习笔记

news2024/9/22 23:24:38

目录

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);
}

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

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

相关文章

私活更好用:SpringBoot开源项目!!【送源码】

今天分享一款非常香的SpringBoot大屏开源项目&#xff0c;非常适合接私活用。 这是一款基于SpringBoot代码生成器的快速开发平台&#xff01;采用前后端分离架构&#xff1a;SpringBoot&#xff0c;Mybatis&#xff0c;Shiro&#xff0c;JWT&#xff0c;Vue&Ant Design。强…

C++ 日志库 log4cpp 编译、压测及其范例代码 [全流程手工实践]

文章目录 一、 log4cpp官网二、下载三、编译1.目录结构如下2.configure 编译3.cmake 编译 四、测试五、压测源码及结果1.运行环境信息2.压测源码3.压测结果 文章内容&#xff1a;包含了对其linux上的完整使用流程&#xff0c;下载、编译、安装、测试用例尝试、以及一份自己写好…

使用VMware或VirtualBox安装eNSP Pro并使用CRT连接设备

文章目录 使用Oracle Virtual Box安装eNSP Pro创建虚拟机配置网卡配置带外管理网络 使用VMware Workstation安装eNSP Pro转换文件格式及虚拟磁盘模式配置网卡创建虚拟机配置使用CRT连接管理设备 前一段时间是开放了eNSP Pro的账号权限&#xff0c;但是在写博客时&#xff0c;权…

[董晓算法]搜索相关题目及模板

前言&#xff1a; 本系列是学习了董晓老师所讲的知识点做的笔记 董晓算法的个人空间-董晓算法个人主页-哔哩哔哩视频 (bilibili.com) 动态规划系列&#xff08;还没学完&#xff09; 【董晓算法】动态规划之线性DP问题-CSDN博客 【董晓算法】动态规划之背包DP问题&#xff…

html--互动星空

<!doctype html> <html> <head> <meta charset"utf-8"> <title>互动星空</title><style> html,body {margin:0;overflow:hidden;width:100%;height:100%;cursor:none;background:black;background:linear-gradient(to bot…

第十四届蓝桥杯大赛软件赛国赛C/C++ 大学 B 组 拼数字

//bfs只能过40%。 #include<bits/stdc.h> using namespace std; #define int long long int a,b,c,dp[2028]; struct s {int x,y,z;string m; }; map<vector<int>,int>k; signed main() {ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);cin>>a…

大模型时代下的先行者:景联文科技引领数据标注新时代

在大模型时代&#xff0c;数据标注不再是简单的分类标注&#xff0c;而是一项融合了技术革新、专业技能、法律合规和精细化管理的综合性任务&#xff0c;对推动AI技术的发展和落地应用具有重要意义。 景联文科技作为AI基础行业的数据供应商&#xff0c;可协助人工智能企业解决整…

使用 cloudflare 免费服务,搭建临时邮箱,无需暴露自己的真实邮箱地址,保护个人隐私

使用 cloudflare 免费服务&#xff0c;搭建临时邮箱 地址 在线演示 &#x1f310;Github地址 https://github.com/find-xposed-magisk/cloudflare_temp_email 功能/TODO Cloudflare D1 作为数据库 使用 Cloudflare Pages 部署前端 使用 Cloudflare Workers 部署后端 email 转…

【合成孔径雷达】合成孔径雷达的多视角理解和时/频成像算法的统一解释

文章目录 一、什么是雷达成像&#xff08;1&#xff09;主要的遥感探测手段&#xff1a;光学、红外和雷达&#xff08;2&#xff09;从数学的角度&#xff1a;雷达成像主要研究什么&#xff1f;数据采集&#xff1a; y T x n yTxn yTxn信息提取&#xff1a; y − > x ? y…

【Image captioning】基于检测模型网格特征提取——以Sydeny为例

【Image captioning】基于检测模型网格特征提取——以Sydeny为例 今天,我们将重点探讨如何利用Faster R-CNN检测模型来提取Sydeny数据集的网格特征。具体而言,这一过程涉及通过Faster R-CNN模型对图像进行分析,进而抽取出关键区域的特征信息,这些特征在网格结构中被系统地…

鸿蒙HarmonyOS开发:List组件结合Badge、swipeAction实现微信消息列表侧滑删除效果

文章目录 一、ListItem的swipeAction属性二、Badge组件三、Search组件四、示例代码五、效果 一、ListItem的swipeAction属性 名称参数类型描述swipeAction{start?: CustomBuilder,end?:CustomBuilder,edgeEffect?: SwipeEdgeEffect,}用于设置ListItem的划出组件。- start: …

[数据集][目标检测]纸箱子检测数据集VOC+YOLO格式8375张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;8375 标注数量(xml文件个数)&#xff1a;8375 标注数量(txt文件个数)&#xff1a;8375 标注…

Zynq UltraScale+ RFSoC 配置存储器器件

Zynq UltraScale RFSoC 配置存储器器件 下表所示闪存器件支持通过 Vivado 软件对 Zynq UltraScale RFSoC 器件执行擦除、空白检查、编程和验证等配置操 作。 本附录中的表格所列赛灵思系列非易失性存储器将不断保持更新 &#xff0c; 并支持通过 Vivado 软件对其中所列…

Spring编程使用DDD的小把戏

场景 现在流行充血领域层&#xff0c;在原本只存储对象的java类中&#xff0c;增加一些方法去替代原本写在service层的crud&#xff0c; 但是例如service这种一般都是托管给spring的&#xff0c;我们使用的ORM也都托管给spring&#xff0c;这样方便在service层调用mybatis的m…

PostgreSQL扩展之PGroonga:多语言全文搜索

简介 PGroonga 是一个 PostgreSQL 扩展&#xff0c;它增加了基于 Groonga 的全文搜索索引方法。虽然原生的 PostgreSQL 支持全文索引&#xff0c;但它仅限于基于字母和数字的语言。PGroonga 提供了更广泛的字符支持&#xff0c;使其成为 PostgreSQL 支持的语言的超集&#xff…

malloc_consolidate

此文章用于详细介绍malloc_consolidate。 众所周知&#xff0c;fastbin一般是不能合并&#xff0c;但在malloc_consolidate中是个例外。 1.触发机制 首先构造这样的堆块结构 一个0x40的堆块在fastbin中&#xff0c;一个0x110的堆块在unbin中 随后我们尝试分配一个0x300的堆…

NSSCTF | [SWPUCTF 2021 新生赛]easyupload2.0

先传一个普通的一句话木马试一试 GIF89a <?php eval($_POST[shell]);?> 可以看到回显&#xff0c;不允许上传php文件。 使用Burpsuite抓包只修改ContentType后发现也不能绕过&#xff0c;说明服务器使用了黑名单后缀限制&#xff0c;那么我们可以使用其他的后缀代替ph…

ubuntu CUDA 驱动更新,版本更新,多CUDA版本管理

1 新版本驱动下载 前面介绍过window CUDA驱动更新&#xff0c;但是对于ubuntu 的驱动更新&#xff0c;没有一键操作。 本人笔记本电脑n年前装的CUDA DRIVER仅支持到cuda10.2&#xff0c;实在无法满足这日新月异的科技更新。 左 旧的驱动版本 右 新下载的硬件支持的驱动版本&…

使用RN的kitten框架的日历组件的修改

官方网页地址 下面就是我参考官方封装的时间日期组件&#xff08;主要是功能和使用方法&#xff0c;页面粗略做了下&#xff0c;不好看勿怪&#xff09; import React, {useState} from react; import {StyleSheet, View, TouchableOpacity, SafeAreaView} from react-native; …

运用MongoDB Atlas释放开发者潜能同时把控成本

在当下的商业环境中&#xff0c;不可预测性已经成为常态&#xff0c;工程团队负责人必须在把控不可预测性和优化IT成本的双重挑战下谋求平衡。 咨询公司德勤2024 MarginPLUS调查收集了300多位企业负责人的见解&#xff0c;报告中重点介绍了面对动荡的全球经济环境&#xff0c;…