SPI on the Raspberry Pi (again)

I’ve kept working on my Raspberry Pi SPI driver and the last few updates should be quite interesting to people following this. Aside from fixing a silly bug a few days ago (I was releasing the chipselect lines when I shouldn’t have been), I’ve done some work to no longer require the switchPinCtrl tool and to implement IRQ mode.

IRQ mode means that the driver no longer uses 100% of the CPU when doing transfers, running a busy loop checking the flags to read/write the FIFO. Instead, it waits until the SPI hardware tells it that it needs to do something, and means other software can get on and do things at the same time. In testing SPI transfers to/from some AT45DB161D flash memory chips, I’m using less than 5% of the Raspberry Pi’s CPU including the top process.

I’m testing the SPI with those chips at 7.8 MHz (250 MHz รƒยท 32) and it is working very reliably indeed, with no data errors reading and writing random data. At 15.6 MHz the process was quite unreliable, but I think that’s more to do with my jumble of jumper wires just not managing those sorts of speeds which is rather predictable. I have also tested with a DS3234 RTC chip which works very well.

I’ve also incorporated a small snippet of code to put the SPI pins in the correct ALT mode (ALT 0) so that the SPI hardware can drive them, instead of them being set up as GPIO pins. Previously I asked that folks run a command-line tool to do this but this was leading to a great deal of confusion (especially on my part!) so I put the code into my driver. This is not where the code belongs, really this should be in a pinmux/pinctrl driver, but it’ll do for now.

Update: Updated the link to the git branch, now points at the branch with both the I2C and SPI drivers in.

23 thoughts on “SPI on the Raspberry Pi (again)

  1. My apologies if this is a little silly but im kind of lost on how to use the spi interface. I’m guessing i’ll have to use the special file /dev/spidev0.0 for that, but my first attempts where rather unseccessful. Also I haven’t found any way to configurate the clock frequency with the dividers you mentioned. I would be very greatful for any hint on how to use this spi bus.

    • There are several ways to use the Linux SPI interface. The first two are from userspace using the /dev/spidevX.Y device nodes, and the other from kernel space.

      The first, and easiest way, is simply by opening the device node like any file, and reading/writing data to it. This will use the default data rate set in the machine definition (~500KHz in my kernel) and SPI mode 0. Every read/write transaction will assert the associated CS pin for the duration of a transfer, so you can’t for example send a command then read the result while keeping CS asserted.

      The second way is using an ioctl, which allows you much more control. You can change the bus speed, SPI mode, post-transfer delays, have multiple read/write transfers as part of the same message and tell the SPI stack when to assert CS or not. Take a look at /usr/include/linux/spi/spidev.h for the header and http://git.kernel.org/?p=linux/kernel/git/torvalds/linux.git;a=blob;f=Documentation/spi/spidev_test.c for an example of how to use it.

      In kernel space, you need to write an SPI client driver and then have your machine definition set it up as a platform device – but this is a much larger topic that I can’t really cover in a comment on my blog!

  2. Just wanted to say thanks. With your help I was finally able to communicate with my accelerometer after I figured out that I had to change the spi mode. ๐Ÿ™‚

    • For the “breadboard madness” I’m only using in-kernel drivers, not userspace drivers. This means modifying the tables that describe the attached SPI devices in the kernel source (or Device Tree in my development kernel). I believe you can use Flashrom to play with things such as SPI FLASH memory chips from userspace, as to other devices it’s probably best left to kernel drivers more often than not.

  3. Sorry, I am not really keen with kernel and C (I am Pythoneer and SQL guy mainly).

    Can you, please, be more specific about modifying in-kernel drivers (tables)?

    I would like to control the SCT2026 serial-interfaced LED driver
    http://www.starchips.com.tw/pdf/sct2026/

    Can you give some hints or snippets of your code how do you modify the kernel driver tables?

    BTW, do you think following Python bindings can be used?
    http://elk.informatik.fh-augsburg.de/da/da-49/trees/pyap7k/lang/py-spi/src/spimodule.c

    • That chip looks like it’s just a 16-bit shift register that happens to drive LEDs. Assuming you use the linked python SPI bindings (you don’t need to, you can just open /dev/spidev0.X as a file from your script for this) you just need to write out 2 bytes and the 16 LED lines will be set on or off depending on the bytes you sent. You may also need a GPIO line to control the /OE pin, though you can probably just tie that to ground and it’ll work. Connect SSCK on the Pi to CLK, MOSI to SDI, CE0 or CE1 to /LA and ignore MISO (or connect it to SDO). Have fun!

      I should add there’s no need for any kernel modifications to use that chip, it’s simple enough that you can drive it fully from userspace – though you probably won’t be able to clock data fast enough to use PWM.

  4. We intend to have about 400 of these chips daisy-chained on SPI bus (cooperative slaves). Because of that the SPI speed is a concern?

    Is there the way to set the clock frequency?

    BTW, what about Clock polarity and phase settings?

  5. Talking about PWM, we intend to use RPi PWM hardware to generate PWM singnal (Pin 12).

    BTW I am able to control GPIO (set/unset) using python-mmap directly form Python (I have rewritten/modified the GPIO part of the C code for Gertboard in pure Python and it works).

    But I do not want to rewrite the PWM and SPI part, when kernel drivers are here.

  6. Some testing on real hardware have been done.

    Measuring frequency on the SPI clock using multimeter, it looks it is working somewhere around 1,29 MHz (= 1,29 Mbit) by default.

    To get SPI working it is necessary:
    1) open /dev/dpidev0.0 for writing (file like)
    2) write binary data to this “file”
    3) and finaly CLOSE this “file”

    It looks like closing the file is a signal for the SPI hardware to start the CLK.

    I would like to know, if there is some other way how to say to the SPI hardware to start transmitting (to start the clock)?

    BTW, is there some interrupt or some other kind of signaling, to detect, when all data are sent from the SPI buffer (buffer is empty)?

  7. I want to access SPI via free pascal:
    this is my code part:
    fd:=fpOpen(‘/dev/spidev0.0’,O_RdWr);
    if fd < 0 then system.writeln('err fpopen');
    reg:=$00;
    arr[0]:=$00; arr[1]:=$00; arr[2]:=$00;
    mode:=SPI_MODE_1;
    result:=fpioctl(fd,SPI_IOC_RD_MODE,addr(mode)); if result < 0 then system.writeln('err ioctl');
    result:=fpWrite(fd,arr,1); if result < 0 then system.writeln('err fpwrite');
    result:=fpRead (fd,arr,2); if result < 0 then system.writeln('err fpread');
    system.writeln('DTC: ',arr[0], ' ', result);
    system.writeln('DVC: ',arr[1], ' ', result);
    if result=0 then ;
    fpclose(fd);

    But this is not working.
    Can you pls. post an appropriate C Code part for cross check.

    Thanx,
    Stefan

  8. @Chris Boot

    I compiled kernel from git hub. I turned on the option of SPI and rtc-ds3234.
    After loading the modules spidev and rtc-ds3234 and I have:

    lsmod
    Module Size Used by
    291 902 12 ipv6
    binfmt_misc 7436 1
    spidev 5920 0
    spi_bcm2708 5458 0
    i2c_bcm2708 3814 0
    rtc_pcf8583 3173 0
    i2c_dev 6255 0
    rtc_ds3234 2411 0
    snd_bcm2835 21 501 0
    81 018 1 snd_pcm snd_bcm2835
    1 snd_pcm snd_page_alloc 5351
    21 618 1 snd_pcm snd_timer
    57 297 3 snd_timer snd, snd_pcm, snd_bcm2835

    ls / dev / spi *
    / dev/spidev0.0 / dev/spidev0.1

    Testing spi-test.c work fine.

    but there is not: /dev/rtc0
    only: / sys/bus/spi/drivers/ds3234

    I use pinout spi from http://elinux.org/RPi_Low-level_peripherals
    MOSI, MISO, SCLK, CE0.
    Please help me what am I doing wrong? How to properly connect to the raspberry module ds3234?

  9. OK.
    I changing:

    static struct spi_board_info bcm2708_spi_devices[] = {
    {
    .modalias = “rtc-ds3234”,
    .max_speed_hz = 4000000,
    .bus_num = 0,
    .chip_select = 0,
    .mode = SPI_MODE_0,
    }, {
    .modalias = “spidev”,
    .max_speed_hz = 500000,
    .bus_num = 0,
    .chip_select = 1,
    .mode = SPI_MODE_0,
    }
    };
    “”

    and still no /dev/rtc0.

    What should I change? ;(

    • Does dmesg mention the ds3234 at all? If your changes were successful the kernel log should mention that is probing the device, which may be failing or something.

  10. dmesg | grep 3234
    is nothing…

    dmesg | grep spi
    [ 12.450507] bcm2708_spi bcm2708_spi.0: registered master spi0
    [ 12.450574] spi spi0.0: setup: cd 0: 4000000 Hz, bpw 8, mode 0x0 -> CS=00000000 CDIV=0040
    [ 12.450613] spi spi0.0: setup mode 0, 8 bits/w, 4000000 Hz max –> 0
    [ 12.450747] bcm2708_spi bcm2708_spi.0: registered child spi0.0
    [ 12.450796] spi spi0.1: setup: cd 1: 500000 Hz, bpw 8, mode 0x0 -> CS=00000001 CDIV=0200
    [ 12.450822] spi spi0.1: setup mode 0, 8 bits/w, 500000 Hz max –> 0
    [ 12.450945] bcm2708_spi bcm2708_spi.0: registered child spi0.1
    [ 12.450972] bcm2708_spi bcm2708_spi.0: SPI Controller at 0x20204000 (irq 80)

    I delete bcm2708.o before compiling. Please help.
    What is wrong?

  11. Hi.
    I change “m” to “y”.

    CONFIG_SPI=y
    CONFIG_SPI_MASTER=y
    CONFIG_SPI_BCM2708=y
    CONFIG_SPI_SPIDEV=y

    Progress is:

    dmesg | grep spi
    [ 0.850975] bcm2708_spi bcm2708_spi.0: registered master spi0
    [ 0.851048] spi spi0.1: setup: cd 1: 500000 Hz, bpw 8, mode 0x3 -> CS=0000000d CDIV=0200
    [ 0.851076] spi spi0.1: setup mode 3, 8 bits/w, 500000 Hz max –> 0
    [ 0.851628] bcm2708_spi bcm2708_spi.0: registered child spi0.1
    [ 0.851715] spi spi0.0: setup: cd 0: 500000 Hz, bpw 8, mode 0x0 -> CS=00000000 CDIV=0200
    [ 0.851743] spi spi0.0: setup mode 0, 8 bits/w, 500000 Hz max –> 0
    [ 0.851881] bcm2708_spi bcm2708_spi.0: registered child spi0.0
    [ 0.851913] bcm2708_spi bcm2708_spi.0: SPI Controller at 0x20204000 (irq 80)
    [ 1.057110] ds3234 spi0.0: setup: cd 0: 500000 Hz, bpw 8, mode 0x3 -> CS=0000000c CDIV=0200
    [ 1.057140] ds3234 spi0.0: setup mode 3, 8 bits/w, 500000 Hz max –> 0
    [ 1.058301] ds3234 spi0.0: Control Reg: 0xff
    [ 1.058464] ds3234 spi0.0: Ctrl/Stat Reg: 0xff
    [ 1.059257] ds3234 spi0.0: rtc core: registered ds3234 as rtc0
    [ 1.064568] ds3234 spi0.0: hctosys: unable to read the hardware clock


    sometimes (!) at startup RPI last line synchronizes the time
    but
    hwclock -r not working

    hwclock -r –debug
    “/dev/rtc0 does not support interrupts….waiting for …… synchronization failed”

    frequency may be wrong?
    may irq nesscary?


    static struct spi_board_info bcm2708_spi_devices[] = {
    {
    .modalias = “spidev”,
    .max_speed_hz = 500000,
    .bus_num = 0,
    .chip_select = 1,
    .mode = SPI_MODE_1,
    }, {
    .modalias = “ds3234″,
    .max_speed_hz = 500000,
    .bus_num = 0,
    .chip_select = 0,
    }
    };

    what I have wrong?

Comments are closed.