SPI over GPIO in OpenWrt

The previous posts Controlling leds status in OpenWrt and Controlling GPIOs in OpenWrt were actually an introduction for this.


What if you want to connect some kind of SPI device to your OpenWrt device? Perhaps a microcontroller (AVR/Arduino, PIC, etc).
I recently worked on a project where i needed to have a web user interface and control an IR led emitter to emulate a remote. I considered that using an Arduino + Ethernet shield + some kind of flash storage + power supply + container box would cost me more than a TP-LINK MR3020 plus a bunch of components.

It turns out that the Linux kernel already has some modules to bitbang an SPI over the GPIO pins (spi-gpio + spi-bitbang) and also a module to expose the SPI to userland so that it can be accessed by our programs or scripts (spi-dev).
BUT there’s a problem. This stuff is not “directly” usable: it is used by other kernel drivers. We don’t have a way to dynamically say “hey, i want an SPI on those pins”. Instead we would need to rebuild the kernel adding some custom code to declare this SPI bus and also devices connected to it.
I don’t like the idea to recompile the kernel for something like this. I probably want to use this small linux box for tests, POCs, different projects, and i don’t want to rebuild the kernel and flash a new image each time.
So, i made a kernel module that allows to configure on-the-fly an SPI bus and its nodes. You can use it on a stock Attitude Adjustment image, without reflashing or recompiling anything.

By the way, if you wonder how fast this SPI can be, my tests show that it can go something above 1 MHz. Not bad at all.


I have submitted a patch to the OpenWrt developers. They may add it to the next release (not sure honestly) and be installable with opkg. But in the meantime i have built the module for the various platforms on Attitude Adjustment and you can install it manually.

  1. Click here to download the kernel module.
  2. Copy the file spi-gpio-custom.ko for your platform in /lib/modules/<kernel version>/  (TP-Link is ar71xx – for other boards it’s the same of the image you have downloaded)
  3. Run opkg install kmod-spi-gpio
  4. Run opkg install kmod-spi-dev

the patch has been included in OpenWrt trunk, so it is now available in nightly builds and will be in future releases starting from Barrier Breaker.
The installation is straightforward:
#opkg install kmod-spi-gpio-custom


You can use the module to configure up to 4 buses with up to 8 devices each, according to the parameters that you use when loading the module.

The command you’ll use is:
#insmod spi-gpio-custom <parameters>

Here is the official doc from the source:

 *  The following parameters are adjustable:
 *    bus0    These four arguments can be arrays of
 *    bus1    unsigned integers as follows:
 *    bus2
 *    bus3    <id>,<sck>,<mosi>,<miso>,<mode1>,<maxfreq1>,<cs1>,...   
 *  where:
 *  <id>       ID to used as device_id for the corresponding bus (required)
 *  <sck>      GPIO pin ID to be used for bus SCK (required)
 *  <mosi>     GPIO pin ID to be used for bus MOSI (required*)
 *  <miso>     GPIO pin ID to be used for bus MISO (required*)
 *  <modeX>    Mode configuration for slave X in the bus (required)
 *             (see /include/linux/spi/spi.h) 
 *  <maxfreqX> Maximum clock frequency in Hz for slave X in the bus (required)
 *  <csX>      GPIO pin ID to be used for slave X CS (required**)
 *    Notes:
 *    *        If a signal is not used (for example there is no MISO) you need
 *             to set the GPIO pin ID for that signal to an invalid value.
 *    **       If you only have 1 slave in the bus with no CS, you can omit the
 *             <cs1> param or set it to an invalid GPIO id to disable it. When
 *             you have 2 or more slaves, they must all have a valid CS.

Your platform will have GPIOs numbered in a certain range, for example 0-50 or 400-900 (see the OpenWrt Wiki for your router). Anything outside that range is an “invalid value” per the notes above.

For each device a file on /dev will created, named spidev<bus id>.<dev id>. For example, /dev/spidev1.0

Admittedly, it’s not easy to remember. But reading that reference when you want to change something is still less annoying than rebuilding and reflashing everything, right?


We all know, examples make everything so much easier to understand. (examples are for a TP-Link MR3020)

Single bus with id 1, using gpio 7 as CLK, 29 as MOSI, no MISO and single device in spi mode 0, max 1Khz, with no CS:
#insmod spi-gpio-custom bus0=1,7,29,100,0,1000
This will result in:

Single bus with id 1, using gpio 7 as CLK, 29 as MOSI, 26 as MISO, first device in spi mode 0, max 1Khz, with gpio 0 as CS, second device in spi mode 2, max 125Khz, with gpio 17 as CS:
#insmod spi-gpio-custom bus0=1,7,29,26,0,1000,0,2,125000,17
This will result in:
/dev/spidev1.0 and /dev/spidev1.1

Bus with id 1, using gpio 7 as CLK, 29 as MOSI, no MISO, with single device in spi mode 0, max 1Khz, with no CS and Bus with id 2 using gpio 26 as CLK, 17 as MOSI, no MISO with single device in spi mode 2, max 125Khz, with no CS:
#insmod spi-gpio-custom bus0=1,7,29,100,0,1000 bus1=2,26,17,100,2,125000
This will result in:
/dev/spidev1.0 and /dev/spidev2.0

Transferring data

Ok, now how do we transfer data?

Simplex communication is done by just writing and reading that /dev file.
For example,
#echo hello > /dev/spidev1.0
will send “hello\n”, where \n (LF) is added by echo.

For full duplex data transfer you need to use ioctl calls. See the spi-dev documentation. You can also see an example here.


If something fails, insmod will give you a description of the fault code, which is very generic and will usually tell you nothing about what happened.
To understand what’s wrong, see the kernel log running dmesg.

If you want to change your configuration, you need to unload the module and reload it with different parameters:
#rmmod spi-gpio-custom
#insmod spi-gpio-custom <new parameters>

Last, but not least, remember to unload other modules that may keep the gpio busy, for example leds_gpio:
#rmmod leds_gpio


<linux source>/Documentation/gpio.txt
<linux source>/Documentation/spi/spi-summary
<linux source>/Documentation/spi/spidev

About these ads

24 thoughts on “SPI over GPIO in OpenWrt

  1. Pingback: Getting SPI on a router

    • You can use any GPIO, i personally used pins 7 and 29 for just CLK and MOSI (same pins used for i2c on the wiki) as they were free. You can use other pins, like those connected to the LEDs if you don’t need them, or those connected to the switch (but in this case you need to remove it).

  2. Pingback: » Possible Kernel Module for SPI on OpenWRT Fuzzy Hypothesis Online

  3. Do you think this could work with a TL-703N to control a WS2801 LED strip ? I’m wondering if preemption would prohibit such an endeavor. Bandwith required for my application is around 500 kbits/s, would that leave enough CPU to do other things ?

    • If you want to continuously stream data at that rate i’d say it’s not going to work.
      If the strip it’s not very long and you can stream data only for a part of the time (send a “frame”, wait, then send the next) while keeping a decent refresh rate you could leave enough cpu time to do other things.
      I think that the main problem is probably to get a smooth sequence of frames as OpenWrt is not compiled to be a realtime OS.

      If you’re adventurous and feel comfortable with recompiling the kernel yourself, you could give try the following:
      – enable PREEMT_RT in kernel config and run the process which will drive the spi in high priority
      – build a custom gpio driver with statically assigned GPIOs. This will be more efficient than spi-gpio which uses dynamically assigned GPIOs. It should also be easy to do, see notes above #ifndef DRIVER_NAME in spi-gpio.c

      I have a 10×10 array of these leds controlled with an AVR, i didn’t consider this option so far. I’ll probably do some math to see if it’s feasible or not.

      • Thanks for the reply and the cues. Recompiling the kernel would be fun but I would move away from my original purpose… I am comtemplating using the Raspberry Pi then since I believe it has a free hardware SPI port to work with although it lacks WiFi out of the box. However, it seems it is pretty easy to add. I want to build a 32×10 frame hooked up to WiFi and have my PC drive it once in a while with live content through UDP at >30 fps. When the PC is not driving it, it would revert to playing previously recorded frames on an external memory stick/card. So it will be pretty intense bandwidth and CPU wise.

  4. This is great, I really appreciate the post and the driver.

    I’m having some trouble getting it working with Attitude Adjustment. After an error-free insmod, I see the new spi entries in /sys/class/spidev, but nothing appears in /dev. Is this expected? No errors, apart from this in dmesg:

    [ 2762.960000] Custom GPIO-based SPI driver version 0.1
    [ 2762.970000] spi_gpio spi_gpio.3: master is unqueued, this is deprecated

    Any thoughts? Thanks!

  5. You are the best! I’m with ODROID-U3 and it don’t have the SPI GPIO lines, I believe I can now :D Great job, best regards.

  6. Hi! I know that this was designed to work with attitude adjustment, but is it possible for it to be even slightly functional with Backfire? I have a linksys wrt54g and I am nervous to try and flash Attitude adjustment to it, as I hear it might be unstable on that platform.

  7. Thank you! With this you could connect a sd memory and boot from there as they currently do from a usb memory? I need to set kmod-mmc-spi or kmod-mmc-over-gpio?

  8. randomcoderdude,

    Thanks for the contribution! I’m using this on a pogoplug mobile (the $10 device) and while it’s working fine in 8-bit mode I’m seeing different behavior using spidev-test when I set bits per word to 16 (the /CS line doesn’t seem to get released between transfers). Are you aware of any limitations using this driver for non-8bit transfers?


  9. Hi Bob,
    my module only allows to dymanically create the SPI bus/devices and does not add any limitation on its own.
    I had a quick look but i can’t give you a precise answer, you probably need to play with the ‘bits_per_word’ and ‘len’ fields of the ‘struct spi_ioc_transfer’.

  10. Hi

    I tried to run this command:
    insmod spi-gpio-custom bus0=19,7,29,26,0,1000,0,2,125000,17

    After that in /dev folder I got this devices: spidev19.0 and spidev19.1
    According to https://git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/tree/Documentation/spi/spidev?id=refs/tags/v3.10.6 the naming of devices is like this: spidevB.C where B is the bus number and C is the chip select.

    As you can see only the bus number is ok (19). The chipselect for the first device is 0 and for the second device is 17 but according to the device names from /dev the chip selects are 0 and 1.
    Is there a way to see what GPIOs is every spidev device using after I created them?

    • Hi,
      I’m a bit rusty on the matter but i think the “chip select” on spidevB.C is just an “index” of the chips selects on that bus, not the actual GPIO number.
      I don’t know if there’s a way to see the used GPIOs given an existing spidev device. Technically an spidev could be using a hardware SPI instead of a bitbang one so i doubt the GPIOs are published somewhere as a “property” of that spidev.

  11. Hello randomcoderdude, thanks for your great job. But I have some problems when I use SPI module. First of all, I selected kmod-spi-bitbang, kmod-spi-dev,kmod-spi-gpio and kmod-spi-gpio-custom in menuconfig when I compiled the firmware, and then I did all the preparations just as your tutorial. But when I use the insmod spi-gpio-custom command it shows “kmod: module is already loaded – spi-gpio-custom”, and it has no spi file under /dev directory. I couldn’t find out what was wrong. Do you have any ideas about that? My platform is MT7620N ramips openwrt3.10.44.

    • If i remember correctly, you can either compile kernel extensions as “module” or “compiled-in”.
      Module means that a .ko file is produced and you can load/unload it in the kernel when you want using insmod/rmmod.
      Compiled-in means that the module is compiled directly in the kernel and thus permanently “loaded”. This won’t work of course because you want to load the module giving it the parameters.

      The whole idea of my module was to avoid recompiling the kernel: you should be able to use a stock OpenWrt image and just “opkg install kmod-spi-gpio-custom” (downloads the pre-compiled binaries for your platform).

      If you’re trying to save space by compiling stuff directly in the kernel, you can define the spi devices statically instead of using my module. Here’s an example: http://elinux.org/BeagleBoard/SPI
      I haven’t done this myself (it’s not what i wanted to do) so i can’t help you down this route. You should probably google “spi_board_info” and “spi_register_board_info”.

      Hope this helps.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Get every new post delivered to your Inbox.

%d bloggers like this: