Wednesday, October 1, 2014

BeagleBone Black: Enabling SPI0

Setting up SPI for Beaglebone Black has been described many times on other websites such as the following:
http://elinux.org/BeagleBone_Black_Enable_SPIDEV#SPI0
http://www.linux.com/learn/tutorials/746860-how-to-access-chips-over-the-spi-on-beaglebone-black

This post will follow the steps described from the previous websites and provide insight and extra information so other do not run into the same problems I did.

This post assumes the BeagleBone Black is flashed with Angstrom 3.8.13.  If not, update your board here http://beagleboard.org/getting-started#update.

 
Pin mapping for SPI ports on BeagleBone Black.
The HDMI overlay must be removed to use SPI1.







SPI0 pins
Chip Select - CS0     - pin 17
Clock          - SCLK  - pin 22
MOSI          - D0       - pin 21
MISO          - D1       - pin 18


Device Tree Overlay

First, open a new document in gedit (if using BBB standalone) or nano (if using Terminal).  Copy the following code into the new document.  The following instructions assume the user is user nano in Terminal.

 nano BB-SPI0-01-00A0.dts  

 /dts-v1/;  
 /plugin/;  
 / {  
   compatible = "ti,beaglebone", "ti,beaglebone-black";  
   /* identification */  
   part-number = "spi0pinmux";  
   fragment@0 {  
     target = <&am33xx_pinmux>;  
     __overlay__ {  
       spi0_pins_s0: spi0_pins_s0 {  
         pinctrl-single,pins = <  
          0x150 0x30 /* spi0_sclk, INPUT_PULLUP | MODE0 */  
          0x154 0x30 /* spi0_d0, INPUT_PULLUP | MODE0 */  
          0x158 0x10 /* spi0_d1, OUTPUT_PULLUP | MODE0 */  
          0x15c 0x10 /* spi0_cs0, OUTPUT_PULLUP | MODE0 */  
         >;  
       };  
     };  
   };  
   fragment@1 {  
     target = <&spi0>;  
     __overlay__ {  
        #address-cells = <1>;  
        #size-cells = <0>;  
        status = "okay";  
        pinctrl-names = "default";  
        pinctrl-0 = <&spi0_pins_s0>;  
        spidev@0 {  
          spi-max-frequency = <24000000>;  
          reg = <0>;  
          compatible = "linux,spidev";  
       };  
     };  
   };  
 };  

Save the file with Ctrl-o ("write out") and exit nano with Ctrl-x.  Now compile the Device Tree File.
dtc - Device Tree Compiler
dts - Device Tree Source
dtbo - Device Tree Binary Out

 dtc -O dtb -o BB-SPI0-01-00A0.dtbo -b 0 -@ BB-SPI0-01-00A0.dts  

Copy the compiled binary file to /lib/firmware/:

 cp BB-SPI0-01-00A0.dtbo /lib/firmware/  

Now enable the Device Tree overlay with the following:

 echo BB-SPI0-01 > /sys/devices/bone_capemgr.*/slots  

My board only has bone_capemgr.8.  Don't worry if yours is different.

Now go into the file name uEnv.txt.  The appears in the external device BEAGLEBONE if you have the board plugged into a computer.  If using an external monitor go to Computer click the BEAGLEBONE device and open uEnv.txt with gedit.

Delete any lines that may appear in uEnv.txt and add the following and save.  This change will tell the board to apply the SPI0 Device Tree Overlay we created on startup.

 optargs=quiet drm.debug=7 capemgr.enable_partno=BB-SPI0-01  

Reboot the BeagleBone Black.
Check that the overlay is enabled.  My board shows spidev1.0.

 ls -al /dev/spidev*  

Also check the pingroups:

 cat /sys/kernel/debug/pinctrl/44e10800.pinmux/pingroups  


Now try a test with your new overlay.  Create the following file.

 nano spi_test.c  

And paste and save the following code.  (Ctrl-o to save, Ctrl-x to exit nano).  The test code is a slightly modified version of https://www.kernel.org/doc/Documentation/spi/spidev_test.c.  Some macros and struct variables were removed because of compilation errors.  There is some disparity between the spidev library on the board and the online documentation.

 /*  
  * SPI testing utility (using spidev driver)  
  *  
  * Copyright (c) 2007 MontaVista Software, Inc.  
  * Copyright (c) 2007 Anton Vorontsov <avorontsov@ru.mvista.com>  
  *  
  * This program is free software; you can redistribute it and/or modify  
  * it under the terms of the GNU General Public License as published by  
  * the Free Software Foundation; either version 2 of the License.  
  *  
  * Cross-compile with cross-gcc -I/path/to/cross-kernel/include  
  */  
 #include <stdint.h>  
 #include <unistd.h>  
 #include <stdio.h>  
 #include <stdlib.h>  
 #include <getopt.h>  
 #include <fcntl.h>  
 #include <sys/ioctl.h>  
 #include <linux/types.h>  
 #include <linux/spi/spidev.h>  
 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))  
 static void pabort(const char *s)  
 {  
      perror(s);  
      abort();  
 }  
 static const char *device = "/dev/spidev1.0";  
 static uint32_t mode;  
 static uint8_t bits = 8;  
 static uint32_t speed = 500000;  
 static uint16_t delay;  
 static void transfer(int fd)  
 {  
      int ret;  
      uint8_t tx[] = {  
           0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,  
           0x40, 0x00, 0x00, 0x00, 0x00, 0x95,  
           0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,  
           0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,  
           0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,  
           0xDE, 0xAD, 0xBE, 0xEF, 0xBA, 0xAD,  
           0xF0, 0x0D,  
      };  
      uint8_t rx[ARRAY_SIZE(tx)] = {0, };  
      struct spi_ioc_transfer tr = {  
           .tx_buf = (unsigned long)tx,  
           .rx_buf = (unsigned long)rx,  
           .len = ARRAY_SIZE(tx),  
           .delay_usecs = delay,  
           .speed_hz = speed,  
           .bits_per_word = bits,  
      };  
      if (mode & SPI_TX_QUAD)  
           tr.tx_nbits = 4;  
      else if (mode & SPI_TX_DUAL)  
           tr.tx_nbits = 2;  
      if (mode & SPI_RX_QUAD)  
           tr.rx_nbits = 4;  
      else if (mode & SPI_RX_DUAL)  
           tr.rx_nbits = 2;  
      if (!(mode & SPI_LOOP)) {  
           if (mode & (SPI_TX_QUAD | SPI_TX_DUAL))  
                tr.rx_buf = 0;  
           else if (mode & (SPI_RX_QUAD | SPI_RX_DUAL))  
                tr.tx_buf = 0;  
      }  
      ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);  
      if (ret < 1)  
           pabort("can't send spi message");  
      for (ret = 0; ret < ARRAY_SIZE(tx); ret++) {  
           if (!(ret % 6))  
                puts("");  
           printf("%.2X ", rx[ret]);  
      }  
      puts("");  
 }  
 static void print_usage(const char *prog)  
 {  
      printf("Usage: %s [-DsbdlHOLC3]\n", prog);  
      puts(" -D --device  device to use (default /dev/spidev1.1)\n"  
         " -s --speed  max speed (Hz)\n"  
         " -d --delay  delay (usec)\n"  
         " -b --bpw   bits per word \n"  
         " -l --loop   loopback\n"  
         " -H --cpha   clock phase\n"  
         " -O --cpol   clock polarity\n"  
         " -L --lsb   least significant bit first\n"  
         " -C --cs-high chip select active high\n"  
         " -3 --3wire  SI/SO signals shared\n"  
         " -N --no-cs  no chip select\n"  
         " -R --ready  slave pulls low to pause\n"  
         " -2 --dual   dual transfer\n"  
         " -4 --quad   quad transfer\n");  
      exit(1);  
 }  
 static void parse_opts(int argc, char *argv[])  
 {  
      while (1) {  
           static const struct option lopts[] = {  
                { "device", 1, 0, 'D' },  
                { "speed",  1, 0, 's' },  
                { "delay",  1, 0, 'd' },  
                { "bpw",   1, 0, 'b' },  
                { "loop",  0, 0, 'l' },  
                { "cpha",  0, 0, 'H' },  
                { "cpol",  0, 0, 'O' },  
                { "lsb",   0, 0, 'L' },  
                { "cs-high", 0, 0, 'C' },  
                { "3wire",  0, 0, '3' },  
                { "no-cs",  0, 0, 'N' },  
                { "ready",  0, 0, 'R' },  
                { "dual",  0, 0, '2' },  
                { "quad",  0, 0, '4' },  
                { NULL, 0, 0, 0 },  
           };  
           int c;  
           c = getopt_long(argc, argv, "D:s:d:b:lHOLC3NR24", lopts, NULL);  
           if (c == -1)  
                break;  
           switch (c) {  
           case 'D':  
                device = optarg;  
                break;  
           case 's':  
                speed = atoi(optarg);  
                break;  
           case 'd':  
                delay = atoi(optarg);  
                break;  
           case 'b':  
                bits = atoi(optarg);  
                break;  
           case 'l':  
                mode |= SPI_LOOP;  
                break;  
           case 'H':  
                mode |= SPI_CPHA;  
                break;  
           case 'O':  
                mode |= SPI_CPOL;  
                break;  
           case 'L':  
                mode |= SPI_LSB_FIRST;  
                break;  
           case 'C':  
                mode |= SPI_CS_HIGH;  
                break;  
           case '3':  
                mode |= SPI_3WIRE;  
                break;  
           case 'N':  
                mode |= SPI_NO_CS;  
                break;  
           case 'R':  
                mode |= SPI_READY;  
                break;  
           case '2':  
                mode |= SPI_TX_DUAL;  
                break;  
           case '4':  
                mode |= SPI_TX_QUAD;  
                break;  
           default:  
                print_usage(argv[0]);  
                break;  
           }  
      }  
      if (mode & SPI_LOOP) {  
           if (mode & SPI_TX_DUAL)  
                mode |= SPI_RX_DUAL;  
           if (mode & SPI_TX_QUAD)  
                mode |= SPI_RX_QUAD;  
      }  
 }  
 int main(int argc, char *argv[])  
 {  
      int ret = 0;  
      int fd;  
      parse_opts(argc, argv);  
      fd = open(device, O_RDWR);  
      if (fd < 0)  
           pabort("can't open device");  
      /*  
       * spi mode  
       */  
      ret = ioctl(fd, SPI_IOC_WR_MODE32, &mode);  
      if (ret == -1)  
           pabort("can't set spi mode");  
      ret = ioctl(fd, SPI_IOC_RD_MODE32, &mode);  
      if (ret == -1)  
           pabort("can't get spi mode");  
      /*  
       * bits per word  
       */  
      ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);  
      if (ret == -1)  
           pabort("can't set bits per word");  
      ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);  
      if (ret == -1)  
           pabort("can't get bits per word");  
      /*  
       * max speed hz  
       */  
      ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);  
      if (ret == -1)  
           pabort("can't set max speed hz");  
      ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);  
      if (ret == -1)  
           pabort("can't get max speed hz");  
      printf("spi mode: 0x%x\n", mode);  
      printf("bits per word: %d\n", bits);  
      printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000);  
      transfer(fd);  
      close(fd);  
      return ret;  
 }  


Connect pin 21 and pin 18 on P9.  Compile and run the code.

 gcc -Wall spi_test.c -o spi_test.o  

 ./spi_test.o  
 spi mode: 0x0  
 bits per word: 8  
 max speed: 500000 Hz (500 KHz)  
 FF FF FF FF FF FF   
 40 00 00 00 00 95   
 FF FF FF FF FF FF   
 FF FF FF FF FF FF   
 FF FF FF FF FF FF   
 DE AD BE EF BA AD   
 F0 0D   

Common Problems

Poor Pin Connection

If pins 21 and 18 on P9 are not connected properly you will see:

 ./spi_test.o  
 spi mode: 0x0  
 bits per word: 8  
 max speed: 500000 Hz (500 KHz)  
 FF FF FF FF FF FF   
 FF FF FF FF FF FF  
 FF FF FF FF FF FF   
 FF FF FF FF FF FF   
 FF FF FF FF FF FF   
 FF FF FF FF FF FF   
 FF FF   


Problems with Device Tree Overlay

If the Device Tree Overlay is not enabled you will see:

 ./spi_test.o  
 spi mode: 0x0  
 bits per word: 8  
 max speed: 500000 Hz (500 KHz)  
 00 00 00 00 00 00  
 00 00 00 00 00 00  
 00 00 00 00 00 00  
 00 00 00 00 00 00  
 00 00 00 00 00 00  
 00 00 00 00 00 00   
 00 00  

I ran into this problem when I had both UART2 and SPI0 device tree overlays enabled.  The two use some common pins so of course problems will occur if the BeagleBone tries to use both.  If the uEnv.txt file looks something like:

 optargs=quiet drm.debug=7 capemgr.enable_partno=BB-SPI0-01,BB-UART2  

with both BB-SPI0-01 and BB-UART2 (order is of no consequence), then delete BB-UART2 and use a different UART port.



9 comments:

  1. Thank you very much for your help, now SPI on my BBB works :-)

    ReplyDelete
  2. 'Delete any lines that may appear in uEnv.txt and add the following and save. This change will tell the board to apply the SPI0 Device Tree Overlay we created on startup.'

    This causes my BBB not to boot.

    ReplyDelete
  3. In my case, I've all zeros with spidev-test.
    Unfortunately, there is no UART2 enabled.
    I've try the same with SPI1 with HDMI disabled, but got the same zeros.

    ReplyDelete
  4. I have the same problem, Martin Ayotte.

    ReplyDelete
  5. This comment has been removed by the author.

    ReplyDelete
  6. Ok, problem about zeros with spidev-test, solved.

    edit boot/uEnv.txt

    cape_disable=bone_capemgr.disable_partno=BB-BONELT-HDMI,BB-BONELT-HDMIN

    cape_enable=bone_capemgr.enable_partno=BB-SPI1-01

    My linux kernel version needs cape_disable or cape_enable and bone_capemgr.

    ReplyDelete
    Replies
    1. UART2 may also be enabled in the /etc/default/capemgr file.

      https://stackoverflow.com/questions/35881761/enabling-uart-on-beaglebone-black

      Delete
  7. This comment has been removed by the author.

    ReplyDelete
  8. Hi,
    i see table
    SPI0 pins
    Chip Select - CS0 - pin 17
    Clock - SCLK - pin 22
    MOSI - D0 - pin 21
    MISO - D1 - pin 18

    but in dts file

    0x150 0x30 /* spi0_sclk, INPUT_PULLUP | MODE0 */
    0x154 0x30 /* spi0_d0, INPUT_PULLUP | MODE0 */
    0x158 0x10 /* spi0_d1, OUTPUT_PULLUP | MODE0 */
    0x15c 0x10 /* spi0_cs0, OUTPUT_PULLUP | MODE0 */

    so spi0_d1 set to OUTPUT and must be MOSI.

    What correct for MOSI/MISO pins ?

    Thanks in advance

    ReplyDelete