ftdi_sio应用学习笔记 3 - GPIO

目录

[1. 查找gpiochip](#1. 查找gpiochip)

[2. 打开GPIO](#2. 打开GPIO)

[2.1 libgpiod库方式](#2.1 libgpiod库方式)

[2.2 系统方式](#2.2 系统方式)

[3. 关闭GPIO](#3. 关闭GPIO)

[3.1 libgpiod库方式](#3.1 libgpiod库方式)

[3.2 系统方式](#3.2 系统方式)

[4. 设置方向](#4. 设置方向)

[4.1 libgpiod库方式](#4.1 libgpiod库方式)

[4.2 系统方式](#4.2 系统方式)

[5. 设置GPIO电平](#5. 设置GPIO电平)

[5.1 libgpiod库方式](#5.1 libgpiod库方式)

[5.2 系统方式](#5.2 系统方式)

[6. 读取GPIO电平](#6. 读取GPIO电平)

[6.1 libgpiod库方式](#6.1 libgpiod库方式)

[6.2 系统方式](#6.2 系统方式)

[7. 验证](#7. 验证)

[7.1 测试读写](#7.1 测试读写)

[7.2 速度测试](#7.2 速度测试)

[7.3 测试MPSSE GPIO](#7.3 测试MPSSE GPIO)


对于FTDI全速设备普遍有CBUS管脚,例如FT230X,有4路CBUS型GPIO。

通过官方工具FT_PROG将对应的CBUS改为GPIO的模式,否则在Linux中是看不到对应的GPIO的,例如将CBUS2和CBUS3改为GPIO。

然后在Linux里面进入root模式,可以先看一下/sys/bus/gpio/devices下有几个gpiochip

bash 复制代码
:/sys/bus/gpio# ls devices
gpiochip0

这里是gpiochip0,然后运行gpioinfo 0查看该chip的gpio使用信息。

cpp 复制代码
:/sys/bus/gpio# gpioinfo 0
gpiochip0 - 12 lines:
	line   0:      unnamed       kernel   input  active-high [used]
	line   1:      unnamed       kernel   input  active-high [used]
	line   2:      unnamed       unused   input  active-high 
	line   3:      unnamed       unused   input  active-high 
	line   4:      unnamed       unused   input  active-high 
	line   5:      unnamed       unused   input  active-high 
	line   6:      unnamed       unused   input  active-high 
	line   7:      unnamed       unused   input  active-high 
	line   8:      unnamed       unused   input  active-high 
	line   9:      unnamed       unused   input  active-high 
	line  10:      unnamed       unused   input  active-high 
	line  11:      unnamed       unused   input  active-high 

对于X系列芯片来说,一共可以支持12个GPOI,而FT230X只有前面4个有效。这里的line0和line1都已经被使用(因为FT_PROG设置为非GPIO模式了)。

1. 查找gpiochip

首先要找到FTDI设备对应的gpiochip,和串口一样,先在USB设备中找到gpiochip关键字。例如下面的信息:

bash 复制代码
:/sys/bus/usb/devices/1-2:1.0# ls
authorized          bInterfaceSubClass  gpio       subsystem
bAlternateSetting   bNumEndpoints       gpiochip0  supports_autosuspend
bInterfaceClass     driver              interface  ttyUSB0
bInterfaceNumber    ep_02               modalias   uevent
bInterfaceProtocol  ep_81               power

可以看到ttyUSB0和gpiochip0,所以只需要将查找uart的方式中的信息改为"gpiochip"就可以了。将uart部分改为gpio,注意GPIOCHIP最大只会有2个(FT2232H或FT4232H)。

cpp 复制代码
#define FTDI_DEVICE_MAX_GPIOCHIP    2
#define FTDI_DEVICE_MAX_GPIO        20
struct ftdi_gpio_info {
    struct ftdi_gpio_info *next;
    char gpio_name[FTDI_DEVICE_MAX_GPIOCHIP][12];  //max gpio: 2(FT2232H/FT4232H), max string: "gpiochip999"
    int base[FTDI_DEVICE_MAX_GPIOCHIP];
    int pid;
    int vid;
    char serial_number[64];
};

将串口查找设备对比"ttyUSB"关键字的部分改为"gpiochip"

cpp 复制代码
while ((gpiochip_entry = readdir(gpiochip_dir)) != NULL) {
    //printf("        entry%s\n", gpiochip_entry->d_name);
    if (strstr(gpiochip_entry->d_name, "gpiochip") != NULL) {  
        printf("Found: %s\n", gpiochip_entry->d_name);  
        sprintf(dev_list->gpio_name[interface], "%s", gpiochip_entry->d_name);
    }
}

例如FT4232H的设备可以找到2个gpiochip设备

cpp 复制代码
Found: gpiochip0
Found: gpiochip1

对于GPIO还需要获取在Linux系统中的起始编号。可以获取到gpiochip的目录内的gpio目录,里面可以获取到起始编号。例如FT4232H的信息如下:

cpp 复制代码
:/sys/bus/usb/devices/2-1/2-1:1.0/gpio$ ls
gpiochip512
:/sys/bus/usb/devices/2-1/2-1:1.1/gpio$ ls
gpiochip520

FT4232H的2路MPSSE通道中GPIO对应的起始编号为512和520。

cpp 复制代码
while ((gpiochip_entry = readdir(gpiochip_dir)) != NULL) {
    //printf("        entry%s\n", gpiochip_entry->d_name);
    if (strstr(gpiochip_entry->d_name, "gpiochip") != NULL) {  
         DIR *gpio_dir;
         struct dirent *gpio_entry;
                    
         sprintf(dev_list->gpio_name[interface], "%s", gpiochip_entry->d_name);
         sprintf(name_path, "/sys/bus/usb/devices/%s:1.%d/gpio", entry->d_name, interface);
         //printf("gpio folder:%s\n", name_path);
         gpio_dir = opendir(name_path);
         while ((gpio_entry = readdir(gpio_dir)) != NULL) {
             if (strstr(gpio_entry->d_name, "gpiochip") != NULL) { 
                 //printf("gpio:%s\n", gpio_entry->d_name);
                 sscanf(gpio_entry->d_name, "gpiochip%d", &dev_list->base[interface]);
             }
        }
        closedir(gpio_dir);
        printf("Found: %s, base=%d\n", gpiochip_entry->d_name, dev_list->base[interface]);  
    }
}

2. 打开GPIO

操作gpio一般有2种方式,一种是通过libgpiod库操作,另外一种是通过直接通过/sys/class/gpio文件系统操作。设计2种参数打开设备,一种是通过PID过滤,如果有相同PID的多个设备,通过参数n指定。

cpp 复制代码
int ftdi_sio_gpio::open_gpio(int pid, int n, int gpiochip, int gpio_num)

另外一种就是通过系列号,因为序列号是唯一的,所以不需要参数n

cpp 复制代码
int ftdi_sio_gpio::open_gpio(char *serial_number, int gpiochip, int gpio_num)

参数:

pid - 设备的pid号

n - 第n个相同pid号设备

gpiochip - 需要打开的第几个gpiochip

gpio_num - 需要打开该gpiochip里面第几个gpio

serial_number - 序列号字符串

2.1 libgpiod库方式

gpiod_chip_open打开gpiochip(即整个芯片),而gpiod_chip_get_line打开的对应的gpio口。

cpp 复制代码
char name_path[buffer_size];
sprintf(name_path, "/dev/%s", dev_list->gpio_name[gpiochip]);
printf("Open device: %s\n", name_path);
if(chip[gpiochip] == NULL)
    chip[gpiochip] = gpiod_chip_open(name_path); 
if(gpio[gpio_num] == NULL)
    gpio[gpio_num] = gpiod_chip_get_line(chip[gpiochip], gpio_num);
fd = gpiochip * FTDI_DEVICE_MAX_GPIO + gpio_num;

这里返回gpio的内部编号fd,后面其他操作需要把这个编号传回。

2.2 系统方式

系统方式只需要把对应的gpio暴露处理,将gpio编号写入export文件

cpp 复制代码
char buffer[4];
int len;
fd = open(GPIO_EXPORT_FILE, O_WRONLY);
if (fd < 0) {
    perror("gpio/export open");
    return -1;
}
len = snprintf(buffer, sizeof(buffer), "%d", dev_list->base[gpiochip] + gpio_num);
if (write(fd, buffer, len) < 0) {
    perror("gpio/export write");
}
close(fd);
fd = dev_list->base[gpiochip] + gpio_num;

返回实际的gpio编号,所以这里加上起始编号。

3. 关闭GPIO

cpp 复制代码
void ftdi_sio_gpio::close_gpio(int fd)

参数:

fd - GPIO编号 (open返回的值)

3.1 libgpiod库方式

通过gpiod_line_release和gpiod_chip_close释放GPIO和GPIOCHIP。如果所有的GPIO都释放了才释放掉GPIOCHIP。而参数fd是之前打开时返回的值。

cpp 复制代码
int gpiochip = fd / FTDI_DEVICE_MAX_GPIO;
int gpio_num = fd % FTDI_DEVICE_MAX_GPIO;
int i;

gpiod_line_release(gpio[gpio_num]);
gpio[gpio_num] = NULL;
for(i = 0; i < FTDI_DEVICE_MAX_GPIO; i++) {
    if(gpio[i] != NULL)
        return;
}
gpiod_chip_close(chip[gpiochip]);
chip[gpiochip] = NULL;

3.2 系统方式

把对应的gpio隐藏,即gpio编号写入unexport文件。

cpp 复制代码
int pin = fd;
fd = open(GPIO_UNEXPORT_FILE, O_WRONLY);
if (fd >= 0) {
    char buffer[4];
    int len;
    len = snprintf(buffer, sizeof(buffer), "%d", pin);
    if(write(fd, buffer, len) < 0) {
        perror("gpio/unexport");
    }
    close(fd);
}

4. 设置方向

cpp 复制代码
int ftdi_sio_gpio::set_direction(int fd, int direction, int default_value)

参数说明

fd - GPIO编号(open返回的值)

direction - 方向,0表示输出,1表示输入。

default_value - 当设置为输出时,默认的电平,0表示低电平,1表示高电平。

返回值:返回0表示成功,反之为失败。

4.1 libgpiod库方式

通过API函数gpiod_line_request_output和gpiod_line_request_input设置方向。

cpp 复制代码
sprintf(name, "gpio%d", fd);
if(direction == 0) {
    gpiod_line_request_output(gpio[fd % FTDI_DEVICE_MAX_GPIO], name, default_value);
} else {
    gpiod_line_request_input(gpio[fd % FTDI_DEVICE_MAX_GPIO], name);
}

4.2 系统方式

通过写入字符串"out"或"in"到属性文件direction设置GPIO方向。

cpp 复制代码
int pin = fd;
int ret = 0;
snprintf(name, sizeof(name), GPIO_PIN_DIR(pin));
fd = open(name, O_WRONLY);
if (fd < 0) {
    perror("gpio/direction");
    return -1;
}
if(direction == 0) {
    ret = write(fd, "out", strlen("out"));
} else {
    ret = write(fd, "in", strlen("in"));
}
close(fd);

5. 设置GPIO电平

cpp 复制代码
int ftdi_sio_gpio::set_value(int fd, int value)

参数:

fd - GPIO编号(open返回的值)

value - 0表示低电平,1表示高电平

返回0表示设置成功。

5.1 libgpiod库方式

通过gpiod_line_set_value设置。

cpp 复制代码
if(value > 0) {
    return gpiod_line_set_value(gpio[fd % FTDI_DEVICE_MAX_GPIO], 1);
} else {
    return gpiod_line_set_value(gpio[fd % FTDI_DEVICE_MAX_GPIO], 0);
}

5.2 系统方式

写0或1到文件value即可。

cpp 复制代码
char path[buffer_size];
int pin = fd;
int ret = 0;
snprintf(path, sizeof(path), GPIO_PIN_VALUE(pin));
fd = open(path, O_WRONLY);
if (fd < 0) {
    perror("gpio/write open");
    return -1;
}
if(value > 0) {
    ret = write(fd, "1", strlen("1"));
} else {
    ret = write(fd, "0", strlen("0"));
}
close(fd);

6. 读取GPIO电平

cpp 复制代码
int ftdi_sio_gpio::get_value(int fd)

参数:

fd - GPIO编号(open返回的值)

返回GPIO电平,0表示低电平,1表示高电平。

6.1 libgpiod库方式

通过函数gpiod_line_get_value获取GPIO电平。

cpp 复制代码
return gpiod_line_get_value(gpio[fd % FTDI_DEVICE_MAX_GPIO]);

6.2 系统方式

和设置类似,从属性文件value中读入数值即可。

cpp 复制代码
char path[buffer_size];
char value_str[3];
int pin = fd;
snprintf(path, sizeof(path), GPIO_PIN_VALUE(pin));
//printf("gpio/read open %s\n", path);
fd = open(path, O_RDONLY);
if (fd < 0) {
    perror("gpio/read open");
    return -1;
}
if(read(fd, value_str, sizeof(value_str)) < 0) {
    perror("gpio/read read");
}
close(fd);
return atoi(value_str);

7. 验证

先遍历设备

cpp 复制代码
ftdi_sio_gpio gpio;
int fd1, fd2;
gpio.find_devices();

打开和关闭gpio

cpp 复制代码
fd1 = gpio.open_gpio(0x6015, 0, 0, 2);  //CBUS2
fd2 = gpio.open_gpio((char *)"FTWJFB9L", 0, 3);  //CBUS3

//测试部分

gpio.close_gpio(fd1);
gpio.close_gpio(fd2);
gpio.free_devices();

7.1 测试读写

将FT230X的CBUS2和CBUS3短接,CBUS2输出,CBUS3输入,

cpp 复制代码
gpio->set_direction(fd1, 0, 0);
gpio->set_direction(fd2, 1, 0);

通过CBUS2发送字符串,然后CBUS3读入IO数据。

cpp 复制代码
    char wr_data[] = "GPIO Data\n";
    char rd_data[128];

    for(int i = 0; i < (int)sizeof(wr_data); i++)
    {
        char level = wr_data[i];
        printf("%2x ", level);
        int rd;
        rd_data[i] = 0;
        for(int j = 0; j < 8; j++)
        {
            gpio->set_value(fd1, level & 0x01);
            level >>= 1;
            rd = gpio->get_value(fd2);
            if(rd > 0)
                rd = 1;
            else
                rd = 0;
            rd_data[i] |= rd << j;
        }
        printf("= %2x\n", rd_data[i]);
    }
    rd_data[sizeof(wr_data) + 1] = 0;
    printf("\nGPIO Read : %s\n", rd_data);

7.2 速度测试

测试CBUS2输出频率,在while(1)循环中交替输出。

cpp 复制代码
while(1) {
    gpio->set_value(fd1, i & 0x01);
    i++;
}

使用gpiod库的方式可以测试到频率为60Hz左右,而系统方式频率也差不多时60Hz。

7.3 测试MPSSE GPIO

使用FT4232H测试,将AD0短接BD1(即TXD0接RXD1),速度方面,最快可以做到5KHz左右。

相关推荐
冰帝海岸1 小时前
01-spring security认证笔记
java·笔记·spring
小二·2 小时前
java基础面试题笔记(基础篇)
java·笔记·python
朝九晚五ฺ3 小时前
【Linux探索学习】第十四弹——进程优先级:深入理解操作系统中的进程优先级
linux·运维·学习
wusong9995 小时前
mongoDB回顾笔记(一)
数据库·笔记·mongodb
猫爪笔记5 小时前
前端:HTML (学习笔记)【1】
前端·笔记·学习·html
Resurgence035 小时前
【计组笔记】习题
笔记
澄澈i5 小时前
设计模式学习[8]---原型模式
学习·设计模式·原型模式
爱米的前端小笔记6 小时前
前端八股自学笔记分享—页面布局(二)
前端·笔记·学习·面试·求职招聘
alikami7 小时前
【前端】前端学习
学习