基于CH347实现USB扩展SPI/I2C/GPIO Master主机应用方案
在安卓/Linux主机上经常会遇到CPU原生SPI/I2C/GPIO Master资源通道不够或者功性能不满足实际产品需求的情况,基于USB2.0高速USB转接芯片CH347,配合厂商提供的USB转MPSI(Multi Peripheral Serial Line)Master总线驱动(CH34X-MSPI-Master)可轻松实现为系统扩展SPI和I2C总线、GPIO Expander、中断信号等。
该驱动软件正常工作后,会在系统下创建新的SPI和I2C Master,拥有独立的bus num,原SPI和I2C器件的设备驱动可直接挂载到该总线上,无需任何修改。驱动会同时创建GPIO相关资源,各GPIO可通过sysfs文件系统或应用层软件直接访问,也可以由其他设备驱动申请该GPIO的访问权以及申请GPIO对应中断号并注册中断服务程序。
应用框图:
CH347是一款高速USB总线转接芯片,通过USB总线提供异步串口、I2C同步串行接口、SPI同步串行接口和JTAG接口等。本方案仅使用到CH347的高速SPI、I2C串行总线,以及GPIO功能,使用串口功能需要单独使用CH343SER串口驱动,使用JTAG功能或SPI和I2C的非总线模式应用可使用CH341PAR多功能USB设备驱动。
CH34X-MSPI-Master总线驱动特点:
1、支持CH347与CH341总线转接芯片;
2、支持SPI、I2C、GPIO、IRQ等接口和功能扩展;
3、支持SPI、I2C的bus总线号、GPIO编号、IRQ中断号的动态分配以及静态指定;
4、支持自动绑定spi通用设备驱动,创建/dev/spidev*;
Description
This driver can be used with ch347 usb to uart/jtag/spi/i2c/gpio adapter and ch341 usb to uart/spi/i2c/gpio adapter to connect spi/i2c/gpio slaves to a linux host.
This driver can only be used with spi/i2c/gpio interfaces. This document mainly introduces the relevant features of CH347.
CH347 SPI Interface
Pin | SPI Function | GPIO name |
---|---|---|
5 | SCS0 | gpio2 |
9 | SCS1 | gpio5 |
6 | SCK | gpio0 |
8 | MOSI | - |
7 | MISO | gpio1 |
The SPI hardware interface supports:
-
SPI Mode 0/1/2/3
-
Clock frequency 60MHz/30MHz/15Mhz/7.5MHz/3.75MHz/1.875MHz/937.5KHz/468.75KHz
-
MSB/LSB transfer
-
8/16 bits per word(16bits transfer to be continued...)
-
2 slaves at maximum
-
Chip selection high/low active
CH347 I2C Interface
Pin | I2C Function | GPIO name |
---|---|---|
11 | SCL | gpio3 |
12 | SDA | - |
The ch347 supports 4 different speeds: 20kHz, 100kHz, 400kHz and 750kHz. The driver inits i2c clock to 100kHz by default, and that currently cannot be dynamically changed. It is possible to change it with the ch34x_mpsi_i2c_init function.
Adding support for a device supported by Linux is easy. For instance:
modprobe bmi160_i2c echo "bmi160 0x68" > /sys/bus/i2c/devices/i2c-$DEV/new_device
or:
modprobe tcs3472 echo "tcs3472 0x29" > /sys/bus/i2c/devices/i2c-$DEV/new_device
Files from these drivers will be created somewhere in /sys/bus/i2c/devices/i2c-$DEV/
CH347 GPIO Interface
Pin | GPIO name |
---|---|
15 | gpio4 |
2 | gpio6 |
13 | gpio7 |
The GPIO hardware interface supports GPIO0~GPIO7 actually, but this driver only supports GPIO4, GPIO6 and GPIO7 cause the other GPIOs are multiplexed.
Driver Operating Overview
-
Compile the driver using "make" or other method according to your environment, you will see the module "ch34x_mpsi_master.ko" if successful
-
Type "sudo make load" or "sudo insmod ch34x_mpsi_master.ko" to load the driver dynamically, in this way the spi bus number and gpio base number will be allocated dynamically, also they can be specified by parameters.
exp: "sudo insmod ch34x_mpsi_master.ko spi_bus_num=3 gpio_base_num=60".
-
Type "sudo make unload" or "sudo rmmod ch34x_mpsi_master.ko" to unload the driver
-
Type "sudo make install" to make the driver work permanently
-
Type "sudo make uninstall" to remove the driver
Before the driver works, you should make sure that the ch341/ch347 device has been plugged in and is working properly, you can use shell command "lsusb" or "dmesg" to confirm that, VID of ch341/ch347 is [1A86].
If ch341/ch347 device works well, you can type "ls /sys/class/master" and "ls /sys/class/gpio" to confirm the master node.
Usage from user space
Using SPI slaves
Once the driver is loaded successfully, it provides up to 2 SPI slave devices on next available SPI bus, ch347 e.g.,
/dev/spidev0.0 /dev/spidev0.1
according to the naming scheme /dev/spidev<bus>.<cs>
. <bus>
is the bus number selected automatically by the driver and <cs>
is the chip select signal of the according pin.
Since linux-5.15 binding to spidev driver is required to make slave devices available via /dev/, e.g. for slave 1 on bus 0:
# echo spidev > /sys/class/spi_master/spi0/spi0.1/driver_override # echo spi0.1 > /sys/bus/spi/drivers/spidev/bind
For all devices handled by ch34x_mpsi_master driver:
# for i in /sys/bus/usb/drivers/mpsi-ch34x/*/spi_master/spi*/spi*.*; do echo spidev > $i/driver_override; echo $(basename $i) > /sys/bus/spi/drivers/spidev/bind; done
Standard I/O functions like open
, ioctl
and close
can be used to communicate with one of the slaves connected to the SPI.
To open an SPI device simply use:
int spi = open("/dev/spidev0.0", O_RDWR));
Once the device is opened successfully, you can modify SPI configurations and transfer data using ioctl
function.
uint8_t mode = SPI_MODE_0; uint8_t lsb = SPI_LSB_FIRST; ... ioctl(spi, SPI_IOC_WR_MODE, &mode); ioctl(spi, SPI_IOC_WR_LSB_FIRST, &lsb);
Function ioctl
is also used to transfer data:
uint8_t *mosi; // output data uint8_t *miso; // input data ... // fill mosi with output data ... struct spi_ioc_transfer spi_trans; memset(&spi_trans, 0, sizeof(spi_trans)); spi_trans.tx_buf = (unsigned long) mosi; spi_trans.rx_buf = (unsigned long) miso; spi_trans.len = len; int status = ioctl (spi, SPI_IOC_MESSAGE(1), &spi_trans); // use input data in miso
Attaching SPI NOR flash as MTD
E.g. flash IC is attached to bus 0 chip 0 (spi0.0):
# echo spi0.0 > /sys/bus/spi/drivers/spidev/unbind # echo spi-nor > /sys/bus/spi/devices/spi0.0/driver_override # echo spi0.0 > /sys/bus/spi/drivers/spi-nor/bind
Please note: this driver will create spidev devices by default, you can unbind the device using the above command, or undefine the macro "SPIDEV" in file ch34x_mpsi_master_spi.c.
Using GPIOs
To access GPIOs from user space, sysfs
can be used . For each configured GPIO, a directory
/sys/class/gpio/<gpio>/
is created by the system, where <gpio>
is the name of the GPIO as defined in the driver variable ch347_board_config
. These directories contain
-
the file
value
that can be used to read from and write to GPIOs -
the file
edge
that can be used to control whether and what type of interrupt is enabled -
the file
direction
that can be used to change the direction of the GPIO if possible
Please note: For read and write operations from and/or to these files, the user requires read and/or write permissions, respectively.
Possible interrupt types are
-
rising
for interrupts on rising signal edges, -
falling
for interrupts on falling signal edges, and -
both
for interrupts on rising as well as falling signal edges.
Open a GPIO
Before a GPIO can be used, file value
has to be opened
int fd;if ((fd = open("/sys/class/gpio/<gpio>/value", O_RDWR)) == -1) {perror("open");return -1; }
where <gpio>
is again the name of the GPIO.
Change the GPIO direction
To change the direction of a GPIO pin configured as input or output, simply write as root
keyword in
or keyword out
to the file direction
, e.g.
echo out > /sys/class/gpio/gpio4/direction
Write GPIO output
Once the file value
is opened, you can use standard I/O functions to read and write. To write a GPIO value, simply use function write
as following. The value is written to the GPIO out immediately.
if (write(fd, value ? "1" : "0", 1) == -1) {perror ("write");return -1; }
Read GPIO input
To read values from GPIOs immediately, you can simply use function read
as following:
char buf;if (read(fd, &buf, 1) == -1) {perror("read");return -1; }value = (buf == '0') ? 0 : 1;
After each read operation, file position has to be rewound to first character before the next value can be read.
if (lseek(fd, 0, SEEK_SET) == -1) {perror("lseek");return -1; }
Reacting on GPIO input interrupt
Complete gpio driver example to use GPIO interrupt function.
#include <linux/bitops.h> #include <linux/clk.h> #include <linux/delay.h> #include <linux/device.h> #include <linux/gpio.h> #include <linux/of_gpio.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/uaccess.h> #include <linux/irq.h> #include <linux/timer.h> #include <linux/interrupt.h>#define GPIO_NUMBER 509 /* modify with actual gpio number */static irqreturn_t gpio_interrupt(int irq, void *dev_id) {printk("gpio_interrupt callback.\\n");return IRQ_HANDLED; }static int __init ch34x_gpio_init(void) {unsigned long flags = IRQF_TRIGGER_FALLING;int ret;int irq;irq = gpio_to_irq(GPIO_NUMBER);printk("irq: %d\\n", irq);ret = gpio_request(GPIO_NUMBER, "gpioint");if (ret) {printk("gpio_request failed.\\n");goto exit;}ret = gpio_direction_input(GPIO_NUMBER);if (ret) {printk("gpio_direction_input failed.\\n");gpio_free(GPIO_NUMBER);goto exit;}irq_set_irq_type(irq, flags);ret = request_irq(irq, gpio_interrupt, 0, "gpio_handler", NULL);printk("%s - request_irq = %d result = %d\\n", __func__, irq, ret);exit:return ret; }static void __exit ch34x_gpio_exit(void) {int irq;irq = gpio_to_irq(GPIO_NUMBER);free_irq(irq, NULL);gpio_free(GPIO_NUMBER);printk("gpio driver exit.\\n"); }module_init(ch34x_gpio_init); module_exit(ch34x_gpio_exit);MODULE_LICENSE("GPL");
Please note: this driver will create gpio devices by default, users should undefine the macro "SYSFS_GPIO" in file ch34x_mpsi_master_gpio.c before using gpio interruption in kernel.
Note
ch341 supports 2 modes:
mode0: [uart]
mode1: [spi + i2c + gpio]
ch347 supports 4 modes:
mode0: [uart * 2] in vcp/cdc driver mode
mode1: [spi + i2c + uart * 1] in vcp driver mode
mode2: [spi + i2c + uart * 1] in hid driver mode
mode3: [jtag + uart * 1] in vcp driver mode
This driver only can be used with ch341 mode1 or ch347 mode1.
Any question, you can send feedback to mail: tech@wch.cn