Adding GPIO functionality to a Linux desktop computer

Status
This old topic is closed. If you want to reopen this topic, contact a moderator using the "Report Post" button.
I have recently been using some low powered "CPU Onboard" (Intel) linux system in the mITX form factor for audio work. This hardware runs mainstream linux, which seems to provide much better OS and software reliability. Compared to systems like the Raspberry Pi and other SBCs there is more computing power on tap. What is sorely lacking, however, is GPIO functionality.

I like to build active DSP loudspeakers where the DSP is done on a small computer, in software. These systems are intended to be clients in a streaming audio system (software that I wrote) and can be turned on and off remotely. I would like to use GPIO that is triggered through user space as explained here:
Access GPIO from Linux user space
This approach is common on eg the Raspberry Pi, either done directly from the command line or through an application like WiringPi. The GPIO pin can, for example, be connected to a relay that toggles 12V connected to an amplifier's remote trigger input, or a relay could switch the AC mains itself. This would allow the system to be turned on and off remotely.

There are some USB GPIO boards available online, however, they are relatively expensive and use custom software (from the vendor) to operate. I finally found how to interface with a very inexpensive (<$5) Serial interface + GPIO board based on the CH341 IC and I finally got it up and running. I thought I would share my experience here in case anyone else is looking for this kind of solution.

Essentially I followed the info posted here by "Zoobab":
CH341 USB SPI I2C UART ISP dongle - .[ZooBaB].
Which refers to this linux kernel driver for the CH341:
GitHub - gschorcht/i2c-ch341-usb: A Linux kernel driver for ch341 emulating the I2C bus
I followed the instructions in the section "Installation of the driver" on a BayTrail (J1900) system having a fresh installation of Ubuntu 18.04. I plugged in the board and voila, 8 new GPIOs were now available to me through the operating system in the directory "/sys/class/gpio". Cool!

There was one problem: I needed to be superuser to toggle the GPIO pins. I realized that all the newly available GPIOs were owned by user "root". So I changed user to become root (via sudo su) and then changed the owner of each GPIO folder and the files within them to be owned by my regular user account. This eliminated the need to use sudo when toggling the value or changing the in/out pin mode.

Unfortunately, after I unplugged the Serial board and plugged it back in, the GPIOs were again created with root as the owner. I will need to figure out how to work around this, perhaps writing a udev rule that runs when the board is plugged in that will change the ownership. The board will always appear as a USB device with id "1a86:5512" so I should be able to launch a script off of that property upon plug-in. There is some info on the web about this that I have used to write custom scripts that run when an Arduino was plugged into the computer for another application. One good example is found here:
How to Write udev Rules for USB Devices
Other pages on this topic can be found here:
Tutorial on how to write basic udev rules in Linux - LinuxConfig.org
and here:
https://wiki.debian.org/udev

Here is a link to the boards I am using:
CH341 USB Programmer (USB, TTL, IIC, SPI, Printer, etc) - ElectroDragon
 
Last edited:
I was able to identify the USB Serial + GPIO board on my computer. It appears as a new device: /dev/gpiochip0. Using some udev tools I found:
Code:
charlie@BayTrail-2:/sys/class/gpio$ udevadm info --name=/dev/gpiochip0 --attribute-walk | grep 1a
    ATTRS{idVendor}=="1a86"
charlie@BayTrail-2:/sys/class/gpio$ udevadm info --name=/dev/gpiochip0 --attribute-walk | grep 5512
    ATTRS{idProduct}=="5512"
The attributes idVendor and idProduct will remain the same for all such boards from this vendor (Electrodragon). These will let me identify the board when it is plugged into the computer and, hopefully, change the owner of the GPIO pin directories and their contents so I do not need to toggle the pins as root or using sudo.
 
OK, based on my previous experience using udev and the info I found about the device via udev (see post #2 above) I came up with the following approach that allows a normal (non root) user to change the GPIO pin value (high/low) or set the direction (input/output) of the pin.

First, it is important to note that the code below will only work with the Electrodragon board that I linked to in post #1. The same approach will work for other boards but you will need to find out the unique attribute(s) to use in udev to identify the board when it is connected to the computer.

The solution consists of:
  • a udev rule that can identify the board and take action
  • a shell script that is called by udev when the board is plugged in
  • using chown in the shell script to change the owner of the value and direction files for the GPIOs associated with the Electrodragon board

UDEV RULE:
udev rules reside in the directory /etc/udev/rules.d (yes that is a directory). On my system with a clean Ubuntu install this directory was empty. Edit/create a new file by typing:
Code:
sudo nano /etc/udev/rules.d/z21_persistent-local.rules
If you do not like nano you can use vi, etc.
Now paste this code into the newly created file:
Code:
ACTION=="add" \
,ATTRS{idVendor}=="1a86" \
,ATTRS{idProduct}=="5512" \
,RUN+="/usr/local/bin/on_gpio_plugin.sh"
Save the file and exit.
This rule will execute the file "/usr/local/bin/on_gpio_plugin.sh" on the addition of a device with the listed vendor and product IDs. We will create this file next.

SHELL SCRIPT:
We will now create the script that makes the ownership changes. In a terminal window type:
Code:
sudo nano /usr/local/bin/on_gpio_plugin.sh
Then paste in the following script:
Code:
#!/bin/bash
#this script changes the ownership of the files 'value' and
#  'direction' within directories used to control GPIO pins
#  of an Electrodragon CH341 USB-to-Serial+GPIO board
#Substitue your own user name below for NEW_OWNER
#  then put the script in the directory /usr/local/bin/
#Call the script using a udev rule

NEW_OWNER=charlie

i=0
for f in /sys/class/gpio/gpio*; do
   if [[ $f =~ "gpio$i" ]]; then
      cd $f
      if [ -e value ]; then chown $NEW_OWNER value; fi
      if [ -e direction ]; then chown $NEW_OWNER direction; fi
      cd ..
      ((i++))
   fi
done
Next, change the value of the variable NEW_OWNER (on line 9) to your username, or the username of the user you would like to be able to use the GPIO pins. Save the file. Now you need to make this text file executable, so type:
Code:
sudo chmod +x /usr/local/bin/on_gpio_plugin.sh
With this change the shell script can be executed by the udev rule.

On my system there were no other GPIO pins, so the Electrodragon board GPIOs appeared as gpio0 through gpio7. You can check on your own system. GPIOs are exposed to userspace in the directory /sys/class/gpio, in which each GPIO pin appears as a directory named "gpioXXX" where XXX is a number starting with 0. The script will look through all the directories in /sys/class/gpio starting with gpioXXX=gpio0 and increasing XXX until all GPIOs present have their ownership changed to NEW_OWNER. If there were any other GPIOs present from other devices on the system, they will also have their ownership changed. This should not break anything.

In each gpioXXX directory, you will find some files and directories. Ignore the directories. The most pertinent files are "value" and "direction". The file "value" contains the current GPIO state, as a 0 (low) or 1 (high). The file "direction" should contain either the word "in" or "out" and sets whether the pin is configured as an output pin or an input pin. You should not write to the file "value" unless the direction is set to "out" ("out" was the default direction on my board as obtained from the MFG). When the direction is "in" you read the file instead to get the pin state.

USING THE ELECTRODRAGON BOARD GPIOs
With the udev rule and shell script in place, once the Electrodragon board is plugged in you can start using the GPIOs. It's always a good idea to set the direction first. Let's assume we want gpio0 to be an output. When logged in as the user you set for NEW_OWNER, type:
Code:
echo "out" > /sys/class/gpio/gpio0/direction
That's it!
Now let's set the pin high. Type:
Code:
echo 1 > /sys/class/gpio/gpio0/value
So easy!
Likewise, to set the pin low, type
Code:
echo 0 > /sys/class/gpio/gpio0/value
You can even blink an LED by putting these commands in a loop, like this:
Code:
#!/bin/bash
while :
do
   echo 0 > /sys/class/gpio/gpio1/value
   sleep 0.5
   echo 1 > /sys/class/gpio/gpio1/value
   sleep 1.5
done
Who doesn't want to blink an LED???

AUDIO USES:
So, this is a DIYaudio forum, so what are the audio uses? Answer: for triggering relays or signaling. The Electrodragon board can be set to 3.3V or 5V levels for GPIO. The current is limited to (guessing here) a couple of milliamps at best. So you will need a transistor and some other circuitry connected to a relay. You can buy these kinds of board on the web - they will have the appropriate interfacing circuitry. Then you can switch power on and off, do input switching, or anything you might come up with.
 
Actually I will need several GPIOs on x86 for controlling relays for automated calibration of Virtual balanced in/out from regular soundcard in linux - results in my Headless Amplifier Measurement Workstation

Another alternative to specialized driver/chip is using a regular arduino board with built-in USB-serial chip for 2USD Mini USB CH340 Nano 3.0 ATmega328P Controller Board Compatible For Arduino Nano CH340 USB Driver Nano V3.0 ATmega328-in Integrated Circuits from Electronic Components & Supplies on Aliexpress.com | Alibaba Group programmed with standard firmata firmware arduino/StandardFirmata.ino at master * firmata/arduino * GitHub . This will turn the arduino into a slaved device remotely controllable from the PC via standard firmata protocol. There are lots of firmata clients and libraries available Download - Firmata , my choice would be https://github.com/MrYsLab/PyMata , e.g. example https://github.com/MrYsLab/PyMata/blob/master/examples/pymata_blink.py (outline):


Code:
BOARD_LED = 13
# Create a PyMata instance
board = PyMata("/dev/ttyUSB0", verbose=True)
# Set digital pin 13 to be an output port
board.set_pin_mode(BOARD_LED, board.OUTPUT, board.DIGITAL)
board.digital_write(BOARD_LED, 1)
# Wait a half second between toggles.
time.sleep(.5)
# Set the output to 0 = Low
board.digital_write(BOARD_LED, 0)
Apart of GPIOs all arduino features are available, such as reading analog inputs, controlling motors/steppers for turning analog volume controls (pots), reading encoders for manual digital volume control, etc.
 
Yes, you can use an Arduino in a similar way and have all of its functionality available to you. I used an Arduino in this project:
Linux USB Preamp project
Using the arudino to interface with buttons and rotary encoders, and using udev to recognize it when it plugged in and launch a script to talk to it, I created a physical "control" interface for the computer, with code that could also launch DSP processing on my computer in response to control input by the user (e.g. changing "input" and volume settings of alsa controls).

I got it working and demoed it at a Burning Amp, but there wasn't much interest. I decided that a physical interface was not what I wanted to do, so I put it aside and worked more on my GSASysCon code, which can basically do the same thing via a software interface that I can access via WiFi from anywhere in my home.

I still have all the code on both sides (Linux on computer and Arduino code) that I could post or share. I learned a lot of neat stuff while developing the project. Like how to debounce buttons using a running average of the polled input state (works like a LP filter).

For my current needs the CH341 is better because it appears as a GPIO pin in the OS. Using it is very simple. For the Arduino I would need to create and call scripts to do stuff, and it's a little more involved and error prone. I will take a look at the Firmata stuff. That sounds interesting and might make using the Arduino more straightforward.
 
Last edited:
I tried to find a simple command line firmata client to call from a script, unfortunately to no avail. That makes using CH341 definitely simplier.

My project is controlled by a python daemon where using firmata is just a single include of pymata. I actually enjoy I will not have to code arduino for this case, the standard firmata firmware is widely used and likely well tested.

Just note the sys gpio interface is slowly being replaced by the new /dev/gpiochip approach Learn More About Linux's New GPIO User Space Subsystem & Libgpiod , with current drivers in linux/drivers/gpio at master * torvalds/linux * GitHub . But it will take a while before that CH341 driver gets incompatible (unless converted to the new style).
 
Just note the sys gpio interface is slowly being replaced by the new /dev/gpiochip approach Learn More About Linux's New GPIO User Space Subsystem & Libgpiod , with current drivers in linux/drivers/gpio at master * torvalds/linux * GitHub . But it will take a while before that CH341 driver gets incompatible (unless converted to the new style).

Thanks for mentioning that! It seems like a positive development. Hopefully the old way will be available for a few more years.

On my system with kernel 4.15 the gpiod is not installed by default but can be if you want to try it.
 
I was installing some software on another mini-ITX linux machine and I decided to add the USB-GPIO functionality. It didn't work! Whaaaa? Oops, I forgot to actually install the kernel module for the Electrodragon board (see post 1). Once I did that it worked perfectly. Nice!

So, for emphasis and since I sometimes refer back to these posts myself, here are the steps for getting this up and running:
  1. Buy one or more Electrodragon Serial+GPIO USB boards (they cost a mere $3 each!):
    Electrodragon CH341 USB Programmer (USB, TTL, IIC, SPI, Printer, etc)
  2. Install the kernel module for the CH341a by following the instructions under the heading "Installation of the driver" on this page:
    GitHub - gschorcht/i2c-ch341-usb: A Linux kernel driver for ch341 emulating the I2C bus
  3. Follow my instructions in post 4 for making the GPIO pin "value" and "direction" files accessible to non-root users (no need to use sudo after that!).

This makes is possible to combine a relay driver board, relays, and some mains AC sockets to switch external equipment on and off right from the command line. This was something I really missed from ARM-SBC platforms like the Raspberry Pi but I wanted access to more computing power and more mainstream kernel support that I have when using AMD/Intel CPUs in compact, fanless mITX systems.
 
I bought an CH341A board hoping to replicate this USB gpio functionality. On my machine I could not see gpio files populated in /sys/class/gpio directory. Could you please look if my system is different from yours ? I pretty much followed the same instructions as you did.

Ubuntu 18.04

Code:
dev@dell:~/Desktop/i2c-ch341-usb$ sudo make install
cp i2c-ch341-usb.ko /lib/modules/4.15.0-34-generic/kernel/drivers/i2c/busses
depmod
dev@dell:~/Desktop/i2c-ch341-usb$ sudo insmod i2c-ch341-usb.ko
dev@dell:~/Desktop/i2c-ch341-usb$ ls /sys/class/gpio
export  unexport
dev@dell:~/Desktop/i2c-ch341-usb$ lsmod | grep ch341
i2c_ch341_usb          20480  0
ch341                  16384  1
usbserial              45056  3 ch341



Code:
[  618.489080] ch341 3-6:1.0: device disconnected
[  671.825717] usbcore: registered new interface driver i2c-ch341-usb
[  675.759731] usb 3-6: new full-speed USB device number 6 using xhci_hcd
[  675.908551] usb 3-6: New USB device found, idVendor=1a86, idProduct=5523
[  675.908558] usb 3-6: New USB device strings: Mfr=0, Product=0, SerialNumber=0
[  675.909357] ch341 3-6:1.0: ch341-uart converter detected
[  675.909906] usb 3-6: ch341-uart converter now attached to ttyUSB0
 
I bought an CH341A board hoping to replicate this USB gpio functionality. On my machine I could not see gpio files populated in /sys/class/gpio directory. Could you please look if my system is different from yours ? I pretty much followed the same instructions as you did.

I see the following:

from dmesg:
Code:
[1270446.717891] usb 1-1.4: new full-speed USB device number 6 using xhci_hcd
[1270446.834772] usb 1-1.4: New USB device found, idVendor=1a86, idProduct=5512
[1270446.834777] usb 1-1.4: New USB device strings: Mfr=0, Product=0, SerialNumber=0
[1270446.940506] i2c_ch341_usb: loading out-of-tree module taints kernel.
[1270446.940575] i2c_ch341_usb: module verification failed: signature and/or required key missing - tainting kernel
[1270446.940917] i2c-ch341-usb 1-1.4:1.0: ch341_cfg_probe: output gpio0 gpio=0 irq=0
[1270446.940920] i2c-ch341-usb 1-1.4:1.0: ch341_cfg_probe: output gpio1 gpio=1 irq=1
[1270446.940923] i2c-ch341-usb 1-1.4:1.0: ch341_cfg_probe: output gpio2 gpio=2 irq=2
[1270446.940925] i2c-ch341-usb 1-1.4:1.0: ch341_cfg_probe: output gpio3 gpio=3 irq=3
[1270446.940928] i2c-ch341-usb 1-1.4:1.0: ch341_cfg_probe: input  gpio4 gpio=4 irq=4 (hwirq)
[1270446.940930] i2c-ch341-usb 1-1.4:1.0: ch341_cfg_probe: input  gpio5 gpio=5 irq=5
[1270446.940933] i2c-ch341-usb 1-1.4:1.0: ch341_cfg_probe: input  gpio6 gpio=6 irq=6
[1270446.940935] i2c-ch341-usb 1-1.4:1.0: ch341_cfg_probe: input  gpio7 gpio=7 irq=7
[1270446.941117] i2c-ch341-usb 1-1.4:1.0: ch341_i2c_probe: created i2c device /dev/i2c-9
[1270446.941120] i2c-ch341-usb 1-1.4:1.0: ch341_i2c_set_speed: Change i2c bus speed to 100 kbps
[1270446.943782] i2c-ch341-usb 1-1.4:1.0: ch341_usb_probe: connected
[1270446.943941] usbcore: registered new interface driver i2c-ch341-usb


From lsusb:
Code:
Bus 002 Device 002: ID 174c:3074 ASMedia Technology Inc. ASM1074 SuperSpeed hub
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 003: ID 05e3:0608 Genesys Logic, Inc. Hub
[B][COLOR="Blue"]Bus 001 Device 006: ID 1a86:5512 QinHeng Electronics CH341 in EPP/MEM/I2C mode, EPP/I2C adapter[/COLOR][/B]
Bus 001 Device 002: ID 174c:2074 ASMedia Technology Inc. ASM1074 High-Speed hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

I only know that what I did works for the Electrodragon board. I can't say with 100% certainty that it will work for other CH341A boards. Maybe, possibly, but not 100% sure.

I would re-read the instructions about how to insert the CH341 kernel module and maybe you will find some place where you made a mistake. If not, just order a few of the Electrodragon boards (they are cheap!).

I think there was some other info about different CH341 boards that were tried on that Zoobab web page:
CH341 USB SPI I2C UART ISP dongle - .[ZooBaB].
You might look that over, too.
 
Finally, I was able to enable GPIO and IIC functionality by bridging a solder pad underneath the board, here is what it looks like and the ACT + GND needs to be shorted to enable GPIO+IIC. There are also TEN+GND and V3+3V3 but they don't make a difference:

cjmcu-ch341a.jpg


On startup, it looked like this
Code:
[ 2054.703580] usb 3-6: New USB device found, idVendor=1a86, idProduct=5512
[ 2054.703587] usb 3-6: New USB device strings: Mfr=0, Product=0, SerialNumber=0
[ 2054.705020] i2c-ch341-usb 3-6:1.0: ch341_cfg_probe: output gpio0 gpio=0 irq=0 
[ 2054.705026] i2c-ch341-usb 3-6:1.0: ch341_cfg_probe: output gpio1 gpio=1 irq=1 
[ 2054.705030] i2c-ch341-usb 3-6:1.0: ch341_cfg_probe: output gpio2 gpio=2 irq=2 
[ 2054.705035] i2c-ch341-usb 3-6:1.0: ch341_cfg_probe: output gpio3 gpio=3 irq=3 
[ 2054.705040] i2c-ch341-usb 3-6:1.0: ch341_cfg_probe: input  gpio4 gpio=4 irq=4 (hwirq)
[ 2054.705059] i2c-ch341-usb 3-6:1.0: ch341_cfg_probe: input  gpio5 gpio=5 irq=5 
[ 2054.705064] i2c-ch341-usb 3-6:1.0: ch341_cfg_probe: input  gpio6 gpio=6 irq=6 
[ 2054.705068] i2c-ch341-usb 3-6:1.0: ch341_cfg_probe: input  gpio7 gpio=7 irq=7 
[ 2054.705369] i2c-ch341-usb 3-6:1.0: ch341_i2c_probe: created i2c device /dev/i2c-9
[ 2054.705374] i2c-ch341-usb 3-6:1.0: ch341_i2c_set_speed: Change i2c bus speed to 100 kbps
[ 2054.708782] i2c-ch341-usb 3-6:1.0: ch341_usb_probe: connected

Code:
Bus 002 Device 003: ID 8087:07da Intel Corp. 
Bus 002 Device 002: ID 8087:8000 Intel Corp. 
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 003: ID 0c45:649d Microdia 
Bus 001 Device 002: ID 8087:8008 Intel Corp. 
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 012: ID 1a86:5512 QinHeng Electronics CH341 in EPP/MEM/I2C mode, EPP/I2C adapter
Bus 003 Device 002: ID 279e:024e  
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
dev@dell:~/Desktop/i2c-ch341-usb$ ls /sys/class/gpio
export  gpio1  gpio3  gpio5  gpio7        unexport
gpio0   gpio2  gpio4  gpio6  gpiochip504


Cheers!
 

It is a regular STM32 + CH340 programmed in arduino with RPi connector pinout (i.e. compatible with hats). I do not think a usable audio I2S is possible by bit banging, you need a hardware solution for decent jitter. Also it uses a "proprietary" python library, I would not count on long-term maintenance of this kickstarter product.

16MHz Arduino + CH340 + firmata for low speed, Blue pill (STM32 at 72MHz) + firmata for higher speed (e.g. Firmata example * stm32duino/wiki Wiki * GitHub ), all easily programmed in Arduino IDE with firmware directly in the IDE. Standard protocol, libraries for any programming language available, single dollars cost shipped from China.
 
The long approach?

Hey, read the thread studying gpio (didnt even realize there was an "open source" audio forum like this, btw, thank you much @CharlieLaub and everyone in this discussion for the very wellspoken thread that brought me here), and i noticed you guys might be going about something the long way.

Although, when you plug a device into a Linux system, unless udev is configured like you have done, it's owner is root, its not often also a member of group root. if you lookup the group the device is created as a member of, you simply add yourself to that group. Should allow you to do most everything you'd otherwise need to be root to do, as group permissions are typically generated as equal to owner.
 
Status
This old topic is closed. If you want to reopen this topic, contact a moderator using the "Report Post" button.