Friday, November 2, 2012

How SPI works with an SD card

SD cards have two main operating modes.
Their default mode is high-speed through 4-bit wide port but we're going to be working with the "legacy" SPI (two-wire) mode.

In SPI mode, the master device (our microcontroller) talks to the slave device (the sd card) using a data and a clock line. Every time the clock line goes from low-to-high (or, if you prefer, from high-to-low - you can change this to suit the application needs) the receiving device looks at the data line. If it's high, it receives the single-bit value 1, if it's low, zero.


in this example, when the clock line goes from low-to-high (sometimes called a rising edge trigger) as denoted by the red vertical lines in the clock timing diagram, the state of the data line is converted into a value

The great thing about SPI is that it's not time dependent. Because the master device sends the clock line along with the data, it can be speeded up and slowed down (this is not possible using methods such as UART/serial, which has a fixed data rate; ie. the data has to be moved within a specific time period).

Using this clock-and-data method, we can send commands to the sd card, to tell it to do specific things. So we can send a specific value (the sd format sets out specific values to send for specific commands) to get it to reset, for example.

When an SD card has received and understood a command, it can remain in a busy state for quite some time. It is important to wait until the card has finished doing whatever you asked it to do, before blasting more data or commands at it. To do this, we poll the card (continuously ask it for data) until it gives us a "ready" token.

SPI is actually a data exchange mechanism. There's no difference between reading and writing bytes between the devices. As one device sends a byte of data, so the other transmits one. After sending a single byte of data from an SPI buffer another (possibly different) byte may appear in it's place - this is the byte that has been received.

So to read a byte from the sd card, we have to send it a byte too. It's normal to send either all zeros (0x00) or all ones (0xFF in hex is 255 in decimal, which is 11111111 in binary) for "don't care" bytes - so if you're just reading data from the other device, and it's not important what data you send, it's common to either send 0x00 or 0xFF.

Whenever a command is sent to an SD card, it follows a specific format:
There is a single command byte
There are four "parameter" bytes - data which tells the recipient how to perform the command requested
There is a single CRC (checksum) byte to prove that the previous bytes have been transmitted correctly.

To send data to our SD card, we need a couple of functions:



UInt8 sdSpiByte(UInt8 data){
     ssp1buf = data;
     while(!(ssp1stat & (1<<BF)));
     return ssp1buf;
} 



static inline void  sdSendCommand(UInt8 cmd, UInt32 param){     
     UInt8 send[6];
     
     send[0] = cmd | 0x40;
     send[1] = param >> 24;
     send[2] = param >> 16;
     send[3] = param >> 8;
     send[4] = param;
     send[5] = (sdCrc7(send, 5, 0) << 1) | 1;
     
     for(cmd = 0; cmd < sizeof(send); cmd++){
          sdSpiByte(send[cmd]);
     }
}


This provides us with a simple method of sending commands (and their parameters) to the SD card.

The first function simply puts a value into the hardware SPI buffer then waits for the SPI busy register value to go "not-busy" before returning the value it finds in the buffer (which is now the value received from the other device - data exchange remember!)

The second function actually sends commands to the sd card. Every command byte sent to an SD must have bit 6 set (so the device can recognise it as a command and not some data).
Bit 6 in binary is 01000000 which is 64 in decimal or 0x40 in hex.
So to make sure that every command byte has bit 6 set, we always OR the command byte with 0x40 (so when we send command zero, for example, it's actually transmitted as 0x40, command one is sent as 0x41 and so on).

But every time we send a command (and it's parameters), we have to wait for a not-busy response from the SD card. While the SD card is busy, it holds its "output pin" high - the data clocked out of it is always 11111111 (or 0xFF) So we build these little functions:


static inline UInt8  sdReadResp(void){
     UInt8 v, i = 0;     
     do{                
          v = sdSpiByte(0xFF);
     }while(i++ < 128 && (v == 0xFF));     
     return v;
}

static UInt8 sdCommandAndResponse(UInt8 cmd, UInt32 param){   
     UInt8 ret;          
     sdSpiByte(0xFF);
     sdSendCommand(cmd, param);
     ret = sdReadResp();     
     return ret;
}



This sdCommandAndResponse function sends a "dummy byte" to the sd card.
It then sends the command byte, followed by the 4-byte parameter value(s).
Next it calls the read-response function, which continuously sends the dummy byte 0xFF to the sd card, until the response back is not busy. When the response goes not busy, the response value is returned to the sdCommandAndResponse function. The response could either be "all ok" or it may be some kind of error code to explain why the command given could not be completed.

One last function to mention is the CRC generating function.
Strictly speaking, once we've told our card to work in SPI legacy mode, we don't actually need to generate the CRC values, but it's included here for completeness.

Note: We didn't actually create this function, we ported it from another sd card library for another platform:

static UInt8 sdCrc7(UInt8* chr,UInt8 cnt,UInt8 crc){
     UInt8 i, a;
     UInt8 Data;

     for(a = 0; a < cnt; a++){          
          Data = chr[a];          
          for(i = 0; i < 8; i++){               
               crc <<= 1;
               if( (Data & 0x80) ^ (crc & 0x80) ) {crc ^= 0x09;}               
               Data <<= 1;
          }
     }     
     return crc & 0x7F;
}


Before we can actually start sending data over SPI, we need to set up the PIC to use the hardware SPI peripheral. This means writing some values to particular registers in the chip. The names of these registers should be similar across different PIC models, but may not be exactly the same if you're using a different chip:


static void sdSpiInit(void){
     ssp1add          = 21;              //slow clock down to < 400khz
     ssp1con1     = 0b00101010;          //spi master, clk=timer2
     ssp1stat     = 0b11000000;          //CPHA = 0
}


The important registers here are the SSP1ADD (multiplier value) ad SSP1CON1 register.
When the last four bits of SSP1CON1 are 1010 this slows down the SPI clock speed - by how much depends on the value in the SSP1ADD register (and the actual speed, as in time taken to send each clock pulse, is dependent on the overall processor clock speed so will change with each chip model). When the last four bits are set to 0000, the SPI clock changes on every instruction cycle. When it's set to 1010, it changes on every x clock cycles, where x is the value held in the SSP1ADD register.

The SD card initialisation routine after powering up is:

  • Set the clock speed to less than 400khz
  • Hold the chip select line on the card low and send in about 80 clock pulses
  • Pull the chip select line high to tell the card we're talking to it
  • Send in the "soft reset" command (CMD0)
  • Wait for the sd card to respond "ok" with the value 0x01
  • Send in the "initialise card" command (CMD1)
  • Repeat sending CMD1 until the card responds with an ok value of 0x00
  • Set the sector size using CMD16 with a parameter 512 (each sector is 512 bytes)
  • Turn of the CRC requirement by sending CMD59
  • (all future transmissions do not require a valid crc value)
  • The next time the card responds with an "ok" value, it has been initialised and we can ramp the clock up to full speed.


All of the initialisation routines have to be carried out at the relatively slow clock speed of not more than 400khz. So we need a couple of extra functions to enable us to set the clock speed and enable the chip select line:


static void sdClockSpeed(Boolean fast){
     if(fast){
          ssp1con1 = 0b11110000
     }else{
          ssp1con1 = 0b11111010
     }
     
}

void sdChipSelect(bool active){
     portc.3 = !active;
}


For debugging, we've written a simple "fatal error" function to let us know which part of the initialisation failed (if there are any problems). When a fatal error is hit, this function reports it, then puts the microcontroller into "sleep mode" so that the program flow is permanently interrupted.


void fatal(UInt8 val){          //fatal error: flash LED then go to sleep
     UInt8 i, j, k;

     for(j = 0; j < 5; j++){     
          for(k = 0; k < val; k++)
          {
               delay_ms(100);
               P_LED = 1;
               delay_ms(100);
               P_LED = 0;
          }
          
          UARTLog("Error",val);
          
          delay_ms(250);
          delay_ms(250);
     }
     
     while(1){
          asm sleep
     }
}



By using all these functions together, we can now write our sdInit routine:


Boolean sdInit(){     
     UInt8 v, tries = 0;
     Boolean SD;
     
     SD = false;
     
     sdSpiInit(); // initialise SPI
     sdClockSpeed(false); // slow clock     
     sdChipSelect(false); // CS inactive          
     
     for(v = 0; v < 20; v++) {
          sdSpiByte(0xFF);     //lots of clocks to give card time to init
     }

     sdChipSelect(true); // CS active
     
     v = sdCommandAndResponse(0, 0, true);
     if(v != 1){fatal(2);}

     v = sdCommandAndResponse(1, 0, true);     
     for(int i=0;v != 0 && i<50;++i){
          delay_ms(50);
          v = sdCommandAndResponse(1, 0, true);
     }
     if(v){fatal(3);}
     
     v = sdCommandAndResponse(16, 512, true);          //sec sector size
     if(v){fatal(5);}
     v = sdCommandAndResponse(59, 0, true);          //crc off
     if(v){fatal(6);}
     
     // now set the sd card up for full speed
     sdClockSpeed(true);                    
     return true;
}


If this function successfully returns true, the SD card has been successfully initialised and the SPI lines set to run at maximum speed for the fastest (spi-based) data transfer.

Thursday, November 1, 2012

Talking to an SD card from a PIC 16F1825 microcontroller

Following on from our earlier post, we're continuing with our create-an-audio-module project which reads wav file data from an SD card and plays it through a speaker.

So far, we've written the basic framework, using Sourceboost C so now it's time to start fleshing it out.
Before we actually start making any sound, we're going to make extensive use of the UART/serial comms to report back at every stage of the data transfer, so we can pinpoint any problem areas. We're aiming to read an entire file off the sd card and stream it's contents back to the PC over serial.
Once we've done this, we'll modify the code to send the data to a PWM module to actually play the sound.

First up, with any Sourceboost project, we need our main() function.


void main(){
     unsigned char response;
     Boolean ret;     

     init();
     delay_ms(50);     
     ret = sdInit();
     if(!ret){
          UARTPrintLn("SD init failed");
          fatal(1);
     }else{
     
          setupFAT();
          response=openFile(1);
          if(response){
                         
               readCluster();
                              
               while(response!=0xFF){
                    response=openNextBlock();
                    if(response!=0xFF){
                         readCluster();
                    }else{
                         UARTPrintLn("end of file clusters");
                    }
               }
          }
          
          //just to prove we got here!
          UARTPrintLn("Done");
               
     }

     while(1){}
}


The main function pretty much speaks for itself, since it's calling a whole heap of other (hopefully appropriately named) functions:

Firstly, we initialise the microcontroller.
This involves setting the registers to define how all our different multi-function pins are set up. One of the great things about the PIC range of microcontrollers is their versatility - each pin can have multiple functions and be used for a variety of different things. Unfortunately, this can also mean hours and hours of headaches, as it can make debugging quite difficult.
So in the main, we should always call an initialise routine after powering up, to put the microcontroller into a "known state". In Oshonsoft, this includes commands such as
  • CONFIG PORTA=OUTPUT 
  • ALLDIGITAL 
etc.
These BASIC commands are simply macros to put specific values into specific registers on the chip. In this program, we're going to to that manually, following the datasheet.


void init(){
     osccon                    = 0b11110000;     //32 MHz clock     
     intcon                    = 0b10000000;     //ints enabled, all sources masked
     apfcon0                    = 0b01000000;    //MOSI on RA4
     trisa                    = 0b11001100;      //out on 0, 1, 4, 5, in on
     trisc                    = 0b00000010;      //portc.1 is our spi input
     ansela                    = 0;              //no analog inputs
     anselc                    = 0;              //no analog inputs
     option_reg               = 0b10000000;      //no weak pull-ups, timer0=osc/8
     wdtcon &= ~(1<          
     UARTInit();
     curPos=0;     
}



The first register value OSCON sets the PIC to internal oscillator, 32Mhz.
The INTCON register is where we set up interrupts (bit 7 is the global interrupts bit)
APFCON0 is the alternative pin configuration register - we've put the SPI data out (MOSI = master out, slave in) onto pin RA.4 - on this particular chip the SPI out pin can go on different pins if required.
TRISA is the register where we set which pins are inputs and which are outputs. A 1 makes the corresponding bit an input, zero makes it an output (so bit 7 of the TRISA register maps to pin 7 on PORTA).
ANSELA and ANSELC are the analogue registers for ports A and C - setting these both to zero is the same as the Oshonsoft command ALLDIGITAL.

Once we've got the chip up and running, and in a known state, we allow a few milliseconds just for all the internal voltages to settle and allow any multi-function pins to take the state requested.

Next, we call the initialise SD card routine.
This function will return a value to say whether or not the SD card started up correctly. If it didn't we report an error back and stop the code executing there (there's no point trying to read data back if the SD card isn't responding as we expect it to!)

If the SD card does respond to say it's set up correctly and ready to receive commands, we then call a function to read the FAT16 tables on the card. This involves finding the boot sector on the disk, finding the location of the actual FAT tables themselves, finding the location of the root directory on the disk and calculating where the actual data starts.
This is a long and complicated process and will be explained fully over a number of blog posts! It's hard going, but worth sticking with - being able to read and write data in a PC readable format is very very useful.

The setupFAT function returns a value to say whether or not the PIC was able to make sense of the contents of the card, and if it did, we open a file and start reading clusters of data from the card.
As long the file consists of more data, the function will keep calling and reading data back.

But already things are getting a bit confusing - clusters, sectors, boot sectors..... it's time to look into how FAT works

A short interruption for a demo

Here's a quick movie showing our final audio player actually being used in a final product.
The PIC has been programmed to read a value from eeprom, increase it by one, rollover back to zero once a certain value is reached, and use this value to decide which sound to play.
In doing this, we can get our alarm clock to play a different sound each morning!


The original alarm clock mechanism has been kept the same - when the alarm time is reached, the clock mechanism pulls a line low. We use this as the ground for our board, so that it is switched on an off by the alarm clock mechanism (the mechanism keeps the line low for about 15 minutes after passing the alarm time). By doing this, we're conserving the batteries, since the entire circuit is only every powered for about 30 minutes each day (once for 15 mins in the morning, once in the evening, unless the alarm on/off switch has been moved to disconnect the ground rail from the circuit).

Tuesday, October 30, 2012

Video: Everything you need to know about the FESTO robotic bird



Drones have drastically changed over the years. Some resemble planes, UFO's, and even birds. Specifically, when I say birds, I mean FESTO. It's by far the most impressive robotic bird to date. Now I'm not going into detail about this robotic bird, but you can view the video above to learn about it.

Monday, October 29, 2012

DARPA's new "Pet-Proto" robotcan avoid obstacles and climb stairs



A few days ago, DARPAtv (the youtube channel for DARPA) uploaded a video showing off their latest robot named Pet-Proto. The robot was given the challenge of climbing stairs and avoiding a huge gap in a given path. With all the new technology that was out into this robot, it was able to climb the stairs with little effort. It also got past the gap by spreading it's arms and legs to grip the nearby wall to traverse right through. All these tasks were achievable because of Pet-Proto's specially designed legs. If your interest in this bot, then check out the video above!

Playing audio with a PIC 16F1825 microcontroller and SD card

It's taken literally weeks to get right, but finally, we've got a working example of how to play a raw/wav file from an SD card using a PIC16F1825 microcontroller.

In a departure from previous projects, we've had to put our favoured Oshonsoft compiler to one side and learn how to use Sourceboost C compiler. This is because the 16F1825 isn't supported by Oshonsoft, and we're using this micro because
a) it has loads of RAM and ROM (compared to similar chips in the same price range)
b) it runs at 32Mhz from an internal oscillator (we need plenty of clock cycles to keep the sound playing)
c) of course, it's cheap

Rather than spend time writing about each and every frustration we had during this project (and there were plenty!) we decided to get the thing working, then write up what's going on - there's nothing more frustrating than reading how someone did something, only to get to the end to be told "it didn't work, so don't you do it like this in future".
One of the biggest hurdles was moving from software to hardware periperhals.
Had we a super-fast processor, we'd probably have bit-banged everything (i.e. done all the interfacing  - PWM and SPI - through software) but we needed the data to keep flowing without interruption so that the sound played smoothly, so we had to use hardware SPI and hardware PWM. This isn't always as straightforward as the datasheets suggest!

This is the first of a multi-part post.
We're going to, first of all, interface with a SD card via SPI. Once we've got this interface working, we're going to investigate how FAT works and finally, read some files off the disk.

The first thing to do is hook up our PIC and SD card.
This particular PIC has registers which allow you to move the pins around (e.g. put the SPI output onto different pins) so there are probably lots of different ways you can hook things up. But this is how we did it, and it works ;-)



For testing, we've driven the speaker through a BS170 FET on the ground line. This isn't the best way to do it and in the final version we'll use a proper amp (like a LM386 or TS922) but it's good enough for now! Note that putting the transistor to ground does give a slightly better/louder signal than putting it on the powered side (but both will work).

Now before we get started, we need a couple of header files and some "serial comms helper functions" - these are functions we'll use to write messages back to the PC so we can see what's going on inside that little black box!


#ifndef _SD_H_
#define _SD_H_

#include "common.h"
#define SD_BLOCK_SIZE 512

Boolean sdInit();
UInt8 sdSpiByte(UInt8 byte);

Boolean sdReadStart(UInt32 sec);
void sdNextSec();
void sdSecReadStop();

#endif

This is our sd.h file



#ifndef _COMMON_H_
#define _COMMON_H_

#include

typedef signed char Int8;
typedef unsigned char UInt8;
typedef unsigned char Boolean;
typedef unsigned long UInt32;
typedef unsigned short UInt16;

#define inline

#define true 1
#define false 0

void log(UInt8);
void fatal(UInt8 val);
#define P_LED portc.2

#endif

This is our common.h file

And in the main file, main.c, we need a few functions to get us started.


#include "common.h"
#include "SD.h"

#define FLAG_TIMEOUT            0x80
#define FLAG_PARAM_ERR            0x40
#define FLAG_ADDR_ERR            0x20
#define FLAG_ERZ_SEQ_ERR      0x10
#define FLAG_CMD_CRC_ERR      0x08
#define FLAG_ILLEGAL_CMD      0x04
#define FLAG_ERZ_RST            0x02
#define FLAG_IN_IDLE_MODE      0x01

#pragma CLOCK_FREQ 32000000
#pragma DATA _CONFIG1, _FOSC_INTOSC & _WDTE_SWDTEN & _PWRTE_ON & _MCLRE_OFF & _CP_ON & _CPD_ON & _BOREN_OFF & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF
#pragma DATA _CONFIG2, _WRT_OFF & _PLLEN_OFF & _STVREN_ON & _LVP_OFF

void UARTInit(){
      //
      // UART
      //
      baudcon.4 = 0;      // SCKP      synchronous bit polarity
      baudcon.3 = 0;      // BRG16      enable 16 bit brg
      baudcon.1 = 0;      // WUE      wake up enable off
      baudcon.0 = 0;      // ABDEN      auto baud detect
     
      txsta.6 = 0;      // TX9      8 bit transmission
      txsta.5 = 1;      // TXEN      transmit enable
      txsta.4 = 0;      // SYNC      async mode
      txsta.3 = 0;      // SEDNB      break character
      txsta.2 = 0;      // BRGH      high baudrate
      txsta.0 = 0;      // TX9D      bit 9

      rcsta.7 = 1;      // SPEN serial port enable
      rcsta.6 = 0;      // RX9 8 bit operation
      rcsta.5 = 1;      // SREN enable receiver
      rcsta.4 = 1;      // CREN continuous receive enable
     
      spbrgh = 0;      // brg high byte
      spbrg = 51;      // brg low byte ()
     
      apfcon0.2=1;      // tx onto RA.0            
}

void UARTSend(unsigned char c){
      txreg = c;
      while(!txsta.1);
}

void UARTPrint(char *s){
      while(*s) {
      UARTSend(*s);
      s++;
      }     
}

void UARTPrint(char c){UARTSend(c);}

void UARTPrintNumber(unsigned long n){
      unsigned long k=1000000000;
      while(k>0) {
            UARTSend('0'+n/k);
            n%=k;
            k/=10;
      }
}

void UARTPrintLn(char *s){
      UARTPrint(s);
      UARTPrint("\r\n");
}

void UARTByte(unsigned char b){
      const char *hex="0123456789abcdef";
      UARTPrint("0x");
      UARTSend(hex[b>>4]);
      UARTSend(hex[b & 0xf]);
}

void UARTLog(char *s, unsigned short iv){
      UARTPrint(s);
      UARTPrint(" ");
      UARTPrintNumber(iv);
      UARTPrintLn(" ");
}

void fatal(UInt8 val){            //fatal error: flash led then go to sleep
      UInt8 i, j, k;

      for(j = 0; j < 5; j++){     
            for(k = 0; k < val; k++)
            {
                  delay_ms(100);
                  P_LED = 1;
                  delay_ms(100);
                  P_LED = 0;
            }
           
            UARTLog("Error",val);
           
            delay_ms(250);
            delay_ms(250);
      }
     
      while(1){
            asm sleep
      }
}



We'll be calling on these functions a lot, so it's good to get them down good and early!
They should be pretty self explanitory if you're familiar with the C language; the things to look out for are the PIC internal registers used to set up the hardware peripherals.

UARTInit( ) is where the hardware UART/serial comms is set up.
We've put the serial TX pin onto pin RA.2 because we're using a PicKit2 (clone) to program our PIC. Why does this matter? Because if we use the same pins for serial data as we do for programming, we can also use the built in serial/UART tool built into the PicKit2 software without having to change any wiring!

PicKit2 in UART mode


PicKit2 in programming mode

It just so happens that in data mode, pin4 on the programmer is used for receiving serial/UART data (RX). In programming mode, pin4 on the programmer is programming data (PGD). The programming pin on the PIC is RA.0 (pin 13). By putting the UART TX pin onto RA.0 in the UARTInit function we can leave the programmer connected and simply flip between programming and UART/serial modes in the PicKit2 software.

Thursday, October 25, 2012

Video Thursdays: Nao Gangnam style



Out of all the humanoid robots in the world, Nao (in my opinion) has performed Gangnam Style the best. By enabling Nao to execute short an efficient leg movements, TheAmazel succeeded at getting his robot to perform the internet's favorite dance.