目录
对于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左右。