Bootstrap

ftdi_sio应用学习笔记 3 - GPIO

目录

1. 查找gpiochip

2. 打开GPIO

2.1 libgpiod库方式

2.2 系统方式

3. 关闭GPIO

3.1 libgpiod库方式

3.2 系统方式

4. 设置方向

4.1 libgpiod库方式

4.2 系统方式

5. 设置GPIO电平

5.1 libgpiod库方式

5.2 系统方式

6. 读取GPIO电平

6.1 libgpiod库方式

6.2 系统方式

7. 验证

7.1 测试读写

7.2 速度测试

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

:/sys/bus/gpio# ls devices
gpiochip0

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

:/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关键字。例如下面的信息:

:/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)。

#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"

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设备

Found: gpiochip0
Found: gpiochip1

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

:/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。

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指定。

int ftdi_sio_gpio::open_gpio(int pid, int n, int gpiochip, int gpio_num)

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

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口。

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文件

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

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是之前打开时返回的值。

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文件。

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. 设置方向

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设置方向。

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方向。

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电平

int ftdi_sio_gpio::set_value(int fd, int value)

参数:

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

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

返回0表示设置成功。

5.1 libgpiod库方式

通过gpiod_line_set_value设置。

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即可。

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电平

int ftdi_sio_gpio::get_value(int fd)

参数:

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

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

6.1 libgpiod库方式

通过函数gpiod_line_get_value获取GPIO电平。

return gpiod_line_get_value(gpio[fd % FTDI_DEVICE_MAX_GPIO]);

6.2 系统方式

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

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. 验证

先遍历设备

ftdi_sio_gpio gpio;
int fd1, fd2;
gpio.find_devices();

打开和关闭gpio

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输入,

gpio->set_direction(fd1, 0, 0);
gpio->set_direction(fd2, 1, 0);

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

    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)循环中交替输出。

while(1) {
    gpio->set_value(fd1, i & 0x01);
    i++;
}

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

7.3 测试MPSSE GPIO

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

;