在安卓/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*;
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.
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
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/
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.
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.
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
.
is the bus number selected automatically by the driver and
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
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.
To access GPIOs from user space, sysfs
can be used . For each configured GPIO, a directory
/sys/class/gpio//
is created by the system, where
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//value", O_RDWR)) == -1) { perror("open"); return -1; }
where
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#include #include #include #include #include #include #include #include #include #include #include #include #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.
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: [email protected]