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

  • 25
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FT5246是一款触摸屏控制器芯片,针对Linux系统的驱动程序是为了让该芯片能够与Linux操作系统兼容并正常工作。 FT5246驱动程序的功能主要包括: 1. 硬件初始化:驱动程序需要初始化FT5246芯片的寄存器、配置寄存器等硬件相关参数,以确保芯片能够正常工作。 2. 触摸数据获取:驱动程序通过与FT5246通信,读取芯片上的触摸数据,并将数据传递给操作系统。 3. 触摸事件处理:通过解析触摸数据,驱动程序可以识别用户的触摸动作,例如单击、双击、滑动等,它将这些动作转化为相应的操作系统事件,以供应用程序响应。 4. 多点触控支持:FT5246驱动程序还可以支持多点触控,即同时识别多个触摸点的坐标和动作。 5. 驱动程序与操作系统接口:驱动程序需要与操作系统进行适当的接口,以便与其他设备、应用程序进行通信和配合工作。 由于每个Linux系统的内核版本和配置可能不同,因此针对FT5246的驱动程序也需要根据具体的Linux系统进行相应的定制和适配,以确保驱动程序能够正确运行并与操作系统兼容。 在安装FT5246触摸屏控制器的Linux驱动程序之后,我们可以在Linux系统上实现对该触摸屏的使用。比如,在移动设备上,我们可以通过触摸屏进行手势操作、界面滑动等;在工业控制设备或嵌入式系统中,通过触摸屏可以实现对设备的控制、导航和输入。同时,通过相应的应用程序,我们还可以根据触摸屏的操作来实现特定功能的交互和应用。 综上所述,FT5246触摸屏控制器的Linux驱动程序是为了使该芯片能够在Linux系统中正常工作,并提供触摸数据获取、处理和传递的功能。驱动程序的安装和配置可以实现触摸屏的多种交互操作和应用场景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值