Wednesday, 3 July 2013

Bit Banging Data with the Arduino

Normally if you want to send data over SPI you would use the Arduino's built in hardware SPI controller, which is accessible via the SPI.h library. The problem with this is that you are confined to a specific set of pins, and to sending data in multiples of 8 bits. But what if you want to send data over a different set of pins? Or 12 bits of data for example? Or what if your microprocessor doesn't even have an SPI controller?

Bit banging is the technique of manually manipulating the microprocessor's pins in software to transmit the data, rather than using a dedicated hardware controller. Although it is a lot efficient than a hardware controller, it is a little more flexible.

To keep things simple, we will be looking at SPI mode 1 only. In this mode, the base value for the clock is 0, and data is captured on the rising edge.

Lets take a look at the SPI timing diagram for mode 1:


This first thing we need to do, to communicate with a slave, is pull the SS pin low. This is usually done in software anyway, and not by a dedicated controller, so this isn't really part of the bitbanging process.

Here are the steps we need to do to bitbang the data:

  1. First we need to set the MOSI (Master Out Slave In) pin to a 1 or a 0, according to the first bit in our data variable. 
  2. We then need to set the SCK (Clock) pin high. This causes the slave to capture the state of the MOSI pin (ie. the first 'bit' of the data) into its own memory.
  3. Next, we read the state of the MISO (Master In Slave Out) pin into the first bit of a new variable. When the transmission is complete, this variable will hold the data sent from the slave to the master.
  4. Now the SCK pin can be set low.
  5. Finally, we repeat this process, moving on to the next bit in the variable each time, for however many bits are in the data variable.

I wrote an example sketch to demonstrate this:

const int SS_pin = 11;
const int SCK_pin = 10;
const int MISO_pin = 9;
const int MOSI_pin = 8;

byte sendValue = 74;   // Value we are going to send
byte returnValue = 0;  // Where we will store the value sent by the slave

void setup()
{
  digitalWrite(SS, HIGH);  // Start with SS high
  
  pinMode(SS_pin, OUTPUT);
  pinMode(SCK_pin, OUTPUT);
  pinMode(MISO_pin, INPUT);
  pinMode(MOSI_pin, OUTPUT);
}

void loop()
{
  digitalWrite(SS_pin, LOW);        // SS low
  returnValue = bitBang(sendValue); // Transmit data
  digitalWrite(SS_pin, HIGH);       // SS high again 
}


byte bitBang(byte _send)  // This function is what bitbangs the data
{
  byte _receive = 0;
  
  for(int i=0; i<8; i++)  // There are 8 bits in a byte
  {
    digitalWrite(MOSI_pin, bitRead(_send, i));    // Set MOSI
    digitalWrite(SCK_pin, HIGH);                  // SCK high
    bitWrite(_receive, i, digitalRead(MISO_pin)); // Capture MISO
    digitalWrite(SCK_pin, LOW);                   // SCK low
  }
  
  return _receive;        // Return the received data
}

This is all well and good, but the problem with this code, specifically the bitBang function, is that it is really slow. The reason for this is that the digitalWrite, digitalRead, bitWrite, and bitRead functions are all inherently slow. This is no fault of the people over at arduino, but is just the way it is.

To improve the speed, we can replace these functions with direct port manipulations, and bitwise manipulations.

Here is the bitBang function again, but much faster:

byte bitBang(byte _send)   // This function is what bitbangs the data
{
  byte _receive = 0;
  
  for(int i=0; i<8; i++)   // There are 8 bits in a byte
  {
    if(_send & _BV(i))     // Set MOSI
      PORTB |= _BV(PORTB0);
    else
      PORTB &= ~_BV(PORTB0);
    
    PORTB |= _BV(PORTB2);  // SCK high
    
    if(PINB & _BV(PORTB1)) // Capture MISO
      _receive |= _BV(i);
    else
      _receive &= ~_BV(i);
      
    PORTB &= ~_BV(PORTB2); // SCK low
  }
  
  return _receive;         // Return the received data
}

Much more complicated, isn't it? But faster and more efficient than before. Unfortunately however, this in itself is not without problems. This code is far less portable than the previous example, meaning it probably wont work on a different Arduino board with a different microcontroller. Also, it is not as simple as changing a single variable to choose the pins you want, but the pins that you want to use must be hard coded into the sketch (or else it gets quite complicated).

As a result, we have struck a compromise between portability vs. speed.

If you don't know anything about port manipulations, I would stick with the first example. But if you know what you're doing, then clearly the second option is much better.

One last thing that I nearly forgot: You can change the number of loops in for loop if you want to send fewer or more bits (provided you change _send and _receive to something bigger than a byte).

1 comment:

  1. I tried your code. It doesnt seem to work fine. I am getting all 1's and hence 255 as the answer. I am expecting something else. Could you help ?

    ReplyDelete