Sunday, February 26, 2012

Python controlling SPI Bus on Beagleboard XM, and Raspberry Pi

UPDATE: 8/6/2012 - works for Raspberry Pi too, enjoy!  

So i'm writing my second article on how to do stuff on the Beagleboard Xm. This time i'm going to focus on how to write python code to control the Beagleboard Xm SPI bus ports.

Most the time I could stick with programming C since my last posts required doing a SPI test that comes when you compile the kernel and test the ports. Coworkers I work with use Python on a daily bases so I figured this would help since my next goal is to write a GUI application that has different SPI commands or inputs and seems simple enough to do with Python. So Python here we come.

I'm going to start from the beginning and then include a download link of my final files to help you along the way.


Step One
It's important to understand that you have access to the hardware that you're looking to control. So before we move forward make sure you have on your bealgboard access to a spidev. This can be checked by going into the /dev/ folder and looking for spidev3.0 or such.

cd~/
cd /dev/

Look and see if you see something along the lines of spidev3.0 or spidev3.1 or spidev4.0, if you don't please refer to my older post where I walk through patching the kernel and getting to this stage. Getting SPI working on the Beagleboard XM.


Step Two
Next its important to have some understanding of Python. Now if you're completely new to this like I am than taking Google's Python Class will really help you out. It's a two day class that may take you longer as it did me (time restraints), but you'll learn a lot of basic information that should get you going on the language.


Step Three
Ok, now we can start with communicating with the /dev/spidev3.0 device that is our SPI port on our Beagleboard XM. The first thing I read through was the C example that was provided by Kernel's Documentation on SPI.  There are a few things that are very important so lets first make sure we have the right packages installed so we can do Python programming.

sudo apt-get install python-dev

Now that the right packages are installed on your Beagleboard XM let's take a moment to figure out how we want to go about controlling it. The methods I came up with were either one, figure out the right commands that are comparable to C and get them working in Python. Two, make a C module that could be called anytime in Python and passed in a string of Hex values.

Honestly I really liked option two so this is what i'll be demonstrating. This whole process was not easy as I searched much of the web trying to figure out strategy of examples and blog posts and such. Once again here I am leading you to understand what I've found. So the first thing to understand about passing a C module into Python is its very similar to a DLL for windows. We will have a module that has a list of function that can be called from our Python variable and will transfer our data. My Goal is to take in a Python string like this and get a C hex like the following.

Pyhon String Input:
data = ["FFFF40009545"]

C Hex output:
uint8_t list = {0xFF, 0xFF, 0x40, 0x00, 0x95, 0x54, 0x45};

The reason for wanting to do this is simplicity. There may be better options and if so I would love to hear about them. Add a comment at the bottom of this post please!

To do the transformation there must first be a C program that takes in both the string data and the length of string. This will guaranteeing the data is being parsed out right in C as it was expected in Python. My Python test file I'll call spitest.py and it will have something along the following lines on the Beagleboard Xm.


                                                       (click to enlarge)

Click here to download >>>  spitest.py

As you can see in spitest.py there are a few commands that are called out, such as spi.SPI, transfer, and close. These are the three main commands i'm centering my C module around and will be the ones I call out and use in Python. One opens, one transfers the data, and one closes the SPI port. When the time comes to test this I'm going to place this test file in the following directory that should be created after I compile spimodule.c. It's important to have the testing file in the same directory for now as the compiled spi.so file. It's also important to have the "import spi" which will import into our python code our spi.so module we are going to create in the future steps.

/home/ubuntu/build/lib.linux-armv7l-2.7


Then when the time comes I'll call the following command to run my test from that directory.

sudo python spitest.py



Step Four
Yes I know like this feels like we're going back a step but I had to explain the python first. So now we're going to create the spimodule.c file that will do the conversion of the incoming string to a hex value that can be transferred to the hardware.

To do so there has to be a understanding of two things one, how to convert from a string to a hex and two how to write an extension module. Neither are easy if you're a novice programmer like myself. As much as I like to go into details, i'm not going to cover how to write a extension module for Python. More info on that can be found here Python Documentation for writing extensions. Great examples can also be found at Tutorials Points. The original code I found and edited and based my module from is documented here.

Within this spimodule.c that is being used is the following code I wrote that allows for the conversion as well as documents much of the code to transfer the data.


(click to enlarge photo)

(click to enlarge photo)

(click to enlarge photo)



Download the full module here at >>> spimodule.c

This code is the transfer function (not differential equations kind), but the one what will transfer our SPI data. It takes the string parses out the first two characters and saves them into a variable called hexbyte. Then using sscanf and scans it into a uint8_t type as a hex value using %X. Here is a table to explain the different sscanf formats and how to pass in different value types.

                                                            (click to enlarge)

A good rule of thumb for helping with debugging and using different types of parsing and type conversion is to print your results and to separate them in your code so you can keep track and debug it later. You can see in my code that I often do printf so that I know where my data is and what it should be.


Step Five
Now that my spimodule is made and should be working lets build it and run everything. Feel free to try my files above but note they are there for advice not to be your secure work around.

NOTE: THERE IS NO ERROR CHECKING IN THE CODE

So the first thing to do is to compile the code. I have my spimodule.c and a setup.py file located in my main home directory. The setup file is used as a compiling file that calls the C file and any other extension or author notes that are needed.

Download into home directory with spimodule.c >> setup.py


cd~/
sudo python setup.py build

This will build your spi.so file and it will be located in the following folder. If it isn't this is because it was built somewhere else and after building it it will tell you were it is located. There may be some warnings when you compile it but thats ok, as long as there aren't errors.

cd build/lib.linux-armv7l-2.7

check the directory,

ls

You should see a file spi.so and this is where you'll place your spitest.py from step three. Now lets run the spitest.py. One thing to mention is I setup the spimodule.c that got compiled into the spi.so to give some printf functions for every 2 hex value both as a string and later when it is type casted into a unsigned integer 8-bit size.

sudo python spitest.py

You should get a result like this:


                                                   (click to enlarge)

As you can see the string has been passed, and separated and then converted to Hex format. Then the data shows you what is going to be sent and what is received. If they are the same, than everything is a success. Now go on and program Python to pass your data to SPI bus devices!

NOTE: make sure you have a wire across the MOSI and MISO pins on the beagleboard xm. 



Please leave comments, feedback, or code corrections!!



Bibliography 


String to Hex example
http://forums.devshed.com/c-programming-42/convert-char-string-to-hex-format-586165.html


Other good links
http://www.nerdkits.com/videos/printf_and_scanf/

https://wikis.oracle.com/display/DTrace/Types,+Operators+and+Expressions

http://www.cplusplus.com/reference/clibrary/cstdio/sscanf/


GREAT EXAMPLE OF IMPORTING C as a module
http://en.wikibooks.org/wiki/Python_Programming/Extending_with_C

http://www.tutorialspoint.com/python/python_further_extensions.htm

http://docs.python.org/extending/newtypes.html



Kernel Documentation Spidev
http://www.kernel.org/doc/Documentation/spi/spidev

http://www.kernel.org/doc/Documentation/spi/spidev_test.c


Google Python Class
http://code.google.com/edu/languages/google-python-class/

10 comments:

  1. [hex(int(data[s:s+2], 16)) \
    for s in range(0, len(data), 2)]

    ReplyDelete
  2. Is there a way we can make omxplayer to play a playlist?

    ReplyDelete
  3. The code works great. I found a couple of typos in the comments that might make it confusing for those that want to extend the code. Comment on Line 121 states "Passing the 2 byte string into a Hex unsigned int 8-bit." Really, it should say something like "Passing the 1-byte or(2-nibble, 8-bit) string into a Hex unsigned int 8-bit." There's a similar typo in line 18 of 'spitest.py.' The comment states "Calculates the length, and devides by 2 for two bytes of data sent." Instead of "two bytes," it's "one byte (or two nibbles or eight bits)". Anyway, thanks a lot for the code.

    ReplyDelete
  4. What Ray had with one improvement...
    >>> ",".join([hex(int(data[s:s+2], 16)) for s in range(0, len(data), 2)])
    '0xff,0xff,0x40,0x0,0x95,0x45'

    Such data can be sent via a socket connection to where ever needed (hardware, internet, etc).

    Love the site. Likely talked me into a Beagleboard or a Rasberry Pi.

    ReplyDelete
  5. So does this stuff work for Raspberry Pis as well?

    ReplyDelete
    Replies
    1. Yes it works for the Raspberry Pi. I've used it on my Raspberry Pi Wii remote project. Check out the video.

      http://www.youtube.com/watch?v=pl4GxuIw-sA

      Cheers,
      Brian

      Delete
  6. I am getting stdio.h file not found?

    /usr/lib/gcc/arm-angstrom-linux-gnueabi/4.5.4/include-fixed/limits.h:169:61: no include path in chiech to search for limitsh
    In file included from spimodule.c:19:0:
    /usr/include/python2.7/Python.h:33:19: fatal error: stdio.h: No such file or directory

    I did a find and can find stdio.h

    ReplyDelete
  7. more info.. I am trying to use Beaglebone not B..board

    ReplyDelete
  8. This may be the answer. I will post if this works

    http://www.gigamegablog.com/2012/09/09/beaglebone-coding-
    101-spi-output/

    ReplyDelete
  9. I'm using a slightly different option: I made a Cython interface to the SPI ioctl's. It takes and returns binary data. It's still a little less efficient than I'd like because it makes a memory copy when creating the result; there are definitely ways around that but I figured First Make It Work.

    It's part of the control interface for the Nanomixer, an FPGA-based digital audio mixer: https://github.com/msegado/nanomixer/blob/master/spidev.pyx

    ReplyDelete