Monday, 29 October 2012

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.

Monday, 22 October 2012

SD card clock edge problems?

There's something still not right with our SD card initialisation routines.
We've suspected for a while that there's something going on with the clock edge for reading/writing responses to/from the SD card.

Our logic probe appears to be displaying what we expect to see, but in code, we're never trapping the correct responses back from the card. For example, we're looking for 0x01 from the first init routine but now we're getting none 0xFF responses, but they're not 0x01 from the card.

The probe says the card is returning 0x01 but the PIC is getting some other value, so we hacked in a bit of debugging code that we can watch on the "output" window:


// send the cs line low to send commands
spi_assert();

// send the initialise command
r=sendCommand(CMD_GO_IDLE_STATE, 0);
if (r != IN_IDLE_STATE ) {
      state=r;
      sendClocks(2, 0xFE);
      sendClocks(2, state);
      spi_deassert();
      return SD_RETCODE_NOT_IDLE;
}else{

      // do rest of init code here

}



The resulting output looked like this....


The logic probe says we're being sent 0x01 from the SD card but when we echo the incoming value back onto the SPI line, we get 0x7F.

So we're expecting (and the probe says we're getting) 0b00000001
And the PIC says it's getting 0b01111111

However, comparing these to the timing graph, the 0x01 actually comes in during the transfer of the byte 0xFE (our error code to say we've had a non-0xFF response from the card). So the PIC has already decided that it's time to raise an error, although the probe says we've had an 0xFF response.

It looks like the response from the card 0x00 followed by 0xFF is being merged by the PIC - it's taking the last bit of 0x00 and appending the following 7 bits of 0xFF - giving us 0b01111111 (or 0x7F in hex).
So somewhere in this little lot, the responses from the SD card look like they're being read on the "wrong" edge of the clock pin.......

SD card send/receive still not quite there

After breathing a massive sign of relief once the SD card starting responding to SPI commands, we thought it'd be relatively straight forward to get data into and out of the card. We've already worked with NAND flash memory (in our case AT45DB041D eeprom chips), and stream-reading/writing data, so it shouldn't be too difficult....

We're still having problems getting a reasonable response from the SD card.
Everything points to the clocking edge being either incorrect or incorrectly configured, but it's just not making sense! We got an ok response from the SD card after initialising it. So now we send command 18 and wait for an ok before streaming the data back from the card:


void testReadData(){
      // send command 18, param x10 (start multi-block read from address 0x10)
      // and wait for an ok response
      sendCommand(CMD_READ_MULTIPLE_BLOCKS, 0x10);
     
      // what follows should be data?
      r=SPISendAndReceive(0x00);
      r=SPISendAndReceive(0x00);
      r=SPISendAndReceive(0x00);
      r=SPISendAndReceive(0x00);
      r=SPISendAndReceive(0x00);
     
}


But here's the weird thing - after we've successfully initialised the card, we get some sort of noisy signal back from the card after sending the second byte from the (redundant) CRC check.

So that we can see where our init routine ends, we send a single byte 0xC0 to the SD card once we think the init routine has successfully completed. This can be seen clearly in the screenshot below:


What is unusual is the noisy signal coming back from the card. The logic probe decodes it as 0xFF but there are definitely peaks and troughs occuring in the signal in between the clock cycles.


Here, we're sending a command to the SD card, to initiate a multi-block read so we send a command byte 18, followed by the sector to begin reading from (we chose 0x10) and a CRC value of 0x95 (the CRC is not actually used when in SPI mode, but must be included as part of the data "packet"). In the screen grab above, we seem to be getting a noisy signal back from the SD card while sending the sector address to read from.

The send command function includes a check for an ok response before returning:


unsigned char sendCommand(unsigned char command, unsigned long param) {
      r=SPISendAndReceive(0xFF); // dummy byte
      r=SPISendAndReceive(command | 0x40);
      r=SPISendAndReceive(param>>24);
      r=SPISendAndReceive(param>>16);
      r=SPISendAndReceive(param>>8);
      r=SPISendAndReceive(param);
      r=SPISendAndReceive(0x95); // correct CRC for first command in SPI
      // after that CRC is ignored, so no problem with
      // always sending 0x95
      r=SPISendAndReceive(0xFF); // ignore return byte
      return waitForNot(0xFF);
}


But we never seem to get a non 0xFF response back from the card.....


Saturday, 20 October 2012

Initialising an SD card with a 16F1825 PIC microcontroller

After nearly two days of headache and heartache - and thanks in no small part to the brilliant Arduino/AVR code from Chris McClelland- we finally managed to successfully initialise an SD card with our PIC microcontroller.

Before we stuff it up and it stops working, here's some demo code:

#include <system.h>
// ##################################
// using a 32 Mhz internal oscillator
#pragma DATA _CONFIG1, 0x0804
#pragma DATA _CONFIG2, 0x1DFF
#pragma CLOCK_FREQ 32000000

unsigned char arrayMarker;
unsigned char arrayPointer;
unsigned long currSector;
unsigned char state;
unsigned char r;
unsigned char sample;
unsigned char playSample;

// ##################################

#define TOKEN_SUCCESS 0x00
#define TOKEN_READ_CAPACITY 0xFE
#define TOKEN_READ_SINGLE 0xFE
#define TOKEN_READ_MULTIPLE 0xFE
#define TOKEN_WRITE_SINGLE 0xFE
#define TOKEN_WRITE_MULTIPLE 0xFC
#define TOKEN_WRITE_FINISH 0xFD
#define IN_IDLE_STATE 0x01
#define ERASE_RESET (1<<1)
#define ILLEGAL_COMMAND (1<<2)
#define COMMAND_CRC_ERROR (1<<3)
#define ERASE_SEQUENCE_ERROR (1<<4)
#define ADDRESS_ERROR (1<<5)
#define PARAMETER_ERROR (1<<6)

#define CMD_GO_IDLE_STATE 0
#define CMD_SEND_OP_COND 1
#define CMD_SEND_CSD 9
#define CMD_STOP_TRANSMISSION 12
#define CMD_READ_SINGLE_BLOCK 17
#define CMD_READ_MULTIPLE_BLOCKS 18
#define CMD_WRITE_SINGLE_BLOCK 24
#define CMD_WRITE_MULTIPLE_BLOCKS 25
#define CMD_APP_SEND_OP_COND 41
#define CMD_APP_CMD 55

#define SD_RETCODE_SUCCESS 0x00
#define SD_RETCODE_NOT_IDLE 1
#define SD_RETCODE_ERROR_INIT 2
#define SD_RETCODE_ERROR_READ 3
#define SD_RETCODE_ERROR_NO_DATA 4
#define SD_RETCODE_ERROR_READ_CAPACITY 5
#define SD_RETCODE_ERROR_WRITE 6
#define SD_RETCODE_ERROR_NOT_READY 7
#define SD_RETCODE_TIMEOUT_ERROR      8


unsigned char SPISendAndReceive(unsigned char b){
sspbuf=b; //send data
while(!(ssp1stat & (1<<BF)));
r=sspbuf;            
      return(r);
}

void sdClockSpeed(bool fast){     
      ssp1con1 = (ssp1con1 & 0b11110000) | (fast ? 0b0000 : 0b1010);
}

void spi_deassert(){
      // pull the /cs pin high on the slave
      porta.1=1;
}

void spi_assert(){
      // drop the /cs pin low on the slave
      porta.1=0;
}

unsigned char waitForNot(unsigned char response){
      unsigned short i;
      unsigned char byte;
      i=0xFFFF;
      byte=response;
      while(byte==response && --i){
            byte=SPISendAndReceive(0xFF);
      }     
      return byte;
}

unsigned char waitFor(unsigned char response){
      unsigned short i;
      unsigned char byte;
      i=0xFFFF;
      byte=response+1;
      while(byte!=response && --i){
            byte=SPISendAndReceive(0xFF);
      }     
      return byte;
}

void sendClocks(unsigned char numClocks, unsigned char byte) {
      unsigned char i;
      for ( i = 0; i < numClocks; i++ ) {
            r=SPISendAndReceive(byte);
      }
}

unsigned char sendCommand(unsigned char command, unsigned long param) {
r=SPISendAndReceive(0xFF); // dummy byte
r=SPISendAndReceive(command | 0x40);
      r=SPISendAndReceive(param>>24);
      r=SPISendAndReceive(param>>16);
      r=SPISendAndReceive(param>>8);
      r=SPISendAndReceive(param);
r=SPISendAndReceive(0x95); // correct CRC for first command in SPI
// after that CRC is ignored, so no problem with
// always sending 0x95
r=SPISendAndReceive(0xFF); // ignore return byte
      return waitForNot(0xFF);
}

unsigned char initialiseSD(){
      unsigned char i;
     
      // initialise the SPI module on the PIC
      ssp1add            = 0x19;                        //slow clock will be 32,000,000/100 = 320khz
     
      //ssp1con1.ckp=1;                              //spi idle clock high (bitx)
      ssp1con1      = 0b00111010;                  //spi master, clk=timer2, spi_clck idle high
     
      //ssp1stat.cke=1;                              // cke=bit6
      //ssp1stat.smp=0;                              // smp=bit7
      ssp1stat      = 0b00000000;                  //spi mode 3?
     
      sdClockSpeed(false);      //      load the SD card in low speed <400khz mode
     
      // hold the cs line high when powering up to enter spi mode
      spi_deassert();
     
      // power up the sd card and allow time to stabilise
      porta.0=1;
     
      // with CS high, send 256 clocks (32*8=256, 1.024ms @250kHz)
      // with DI low while card power stabilizes
      sendClocks(32, 0x00);
     
      // with CS high send 80 clocks (10*8=80, 0.32ms @250kHz)
      // with DI high to get the card ready to accept commands
      sendClocks(10, 0xFF);
     
      // send the cs line low to send commands
      spi_assert();
     
      // send the initialise command
      if (sendCommand(CMD_GO_IDLE_STATE, 0) != IN_IDLE_STATE ) {
            sendClocks(2, 0xFE);
            spi_deassert();
            return SD_RETCODE_NOT_IDLE;
      }else{
     
            // Tell the card to initialize itself with ACMD41.
            sendCommand(CMD_APP_CMD, 0);
            r = sendCommand(CMD_APP_SEND_OP_COND, 0);
     
            // Send CMD1 repeatedly until the initialization is complete.
            i = 0xFFFF;
            while ( r != TOKEN_SUCCESS && --i ) {
                  r = sendCommand(CMD_SEND_OP_COND, 0);
            }
     
            sendClocks(2, 0xFF);
            spi_deassert();
     
            if ( i == 0x0000 ) {
                  sendClocks(2, 0xFD);
                  spi_deassert();
                  return SD_RETCODE_TIMEOUT_ERROR;                 
            }
     
           
            // for debugging, turn off the LED/card
            porta.0=0;
           
            // we should now be in true SPI mode
            sdClockSpeed(true);
      }
     
}

void preloadTimer1(){
      //------------------------------------
      // pre-load timer1 with 65172 (0xFE94)
      //------------------------------------
      // 1/22050 = 0.00004535147
      // at 32mhz, fosc=8,000,000
      // and 8m/22050 = 362.812
      // so we want our timer1 to only count up to 363
      // timer1 is a 16-bit timer (0-65535) so we need to
      // preload it with 65535-363 = 65172

      tmr1h=0xfe;
      tmr1l=0x94;
}     

void startMultiBlockRead(){
      // send the spi commands to start a multi-block read from the sector value
      // held in the variable currSectorValue
     
}

void initialise(){

      osccon=0b11110000;            // oscillator=32mhz
      delay_ms(50);                   // wait for internal voltages to settle
      state=0;
      playSample=0;
     
      intcon=0b11000000;            // global interrupts enabled and peripheral interrupts
      apfcon0=0b01000000;
      trisa=0b11001100;            // out on 0,1,4,5 input on 2, DNK on 3
      trisc=0b00000010;            // all portC lines are output except pin1 (RC1=spi in)     
      ansela=0x00;                  // turn off analogue pins on portA
      anselc=0x00;                  // turn off analogue pins on portC
     
      option_reg=0b10000000;      // no weak pull-ups

           
      //-------------------------------------
      // initialise the SD card
      //--------------------------------------
      initialiseSD();
     
}

void nothing(){

      //----------------------------------------------------
      // start a multi-block read from the appropriate place
      //----------------------------------------------------
      currSector=1;
      startMultiBlockRead();
     
      //------------------------------------
      // start a pwm carrier signal @ 125khz
      //------------------------------------
      // Disable the CCPx pin output driver by setting the associated TRIS bit.
      // (for CCP1, the output pin is RC.5)
      trisc.5=1;
     
      // Load the PRx register with the PWM period value:     
      pr2=0x00;      // this will change every 1/22050th second, start at zero
     
      // Configure the CCP module for the PWM mode by loading the CCPxCON register with the appropriate values:
      // Load the CCPRxL register and the DCxBx bits of the CCPxCON register, with the PWM duty cycle value.
      pr2=0x3F;                        // this is the value to create a 125khz carrier (from datasheet)
      ccp2con = 0b00001100;       // puts the CCP module into PWM mode     
      ccptmrs = 0x00;             // all comparitors are using PWM based on timer2
     
      // Configure and start Timer2/4/6:
      t2con = 0b00000100; // start timer2, no scaling

      // Enable PWM output pin (CCP2 out = RC.5)
      trisc.5=0;
     
      //-------------------------------------------
      // interrupt on timer1 22050 times per second
      // ------------------------------------------
      // set up timer1 as a 16-bit timer, count on instruction cycle (fosc/4)
      t1con=0b00001000;      // set bit zero to 1 to actually start the timer
      // set up the actual timer1 interrupt:
      pie1=0x01;            // timer1 rollover interrupt is bit zero
      intcon=0x192;      // global interrupts (bit7) + peripheral interrupts (bit6)     
      preloadTimer1();
     
}

void playNextSample(){
      // dump the next sample from the buffer to the PWM module
      //pwm=sample;
      ccpr2l=(sample>>2);
      ccp2con.1=sample.1;
      ccp2con.0=sample.0;
     
      // read the next byte from the SD card
      sample=SPISendAndReceive(0x00);
     
}

void main(){
      initialise();
      while(1){
            if(playSample==1){
                  // drop the current sample to the PWM module
                  playSample=0;
                  playNextSample();                 
            }
      }
}

void interrupt(void){
      // bit zero of pir1 is tmr1 overflow interrupt
      if(pir1.0==1){
            // clear the timer1 interrupt flag
            pir1.0=0;
            // preload timer1 to get the next interrupt to occur in a timely manner
            preloadTimer1();
            // set the flag to play next sample (background task)
            playSample=1;
      }
}

With a microSD card connected in an SD card holder, using RA0 to power the card through a transistor and using RA1 as the chip select line, with the SPI lines are per the hardware peripheral (SPI_Clock = RC0, SPI_MISO/data in =RC1, SPI_MOSI/data out=RC2/RA3) here's the output from the logic probe from boot-up:


There's also the start of our PWM/timer1 interrupt code in there, just so that we have a global interrupt handler. The PWM control is already coded up, but was mostly stripped out to allow us to focus on getting the SD card working.

And thanks to Chris McClelland, it looks like it is!
Thanks again Chris.
You're ace!

Friday, 19 October 2012

SD Card SPI timing graphs

This whole SD-card-access-via-SPI is proving to be a bit of a headache.
Just to make sure we're not having a timing issue (cards need to be interfaced at < 400khz until properly initialised) we've even resorted to bit-banging the SPI and introducing delays between pin wiggling on the clock line.

The frustrating thing is that despite numerous attempts, we're still getting nothing back from the SD card. We found a cut down FAT16 library for Hi-tech C compiler, and tried converting that back to SourceBoost with limited success.

Using this hacked-up code, we did manage to get something back from the SD card:


The response we're looking for is 0x01. This is visible on the probe output (above) and the probe software correctly identifies it as 0x01 but for some reason, our code doesn't interpret it as such. It may be because the clock cycles - complete with stretching where the crazy library-based code tries to re-sync the clock line (and, it looks like, it fails!)

At least we're getting something back from the SD card, so we can presume it is actually powered up!
However, following the SD guide, sending the command bit 0x40, then 4 bytes of 0x00 and a CRC checksum of 0x95 should, after a few clock cycles, see 0x01 being returned from the SD card.

Sadly, we never see this...



#include <system.h>

//config1 = 0x0044      //68 =4+64
//config2 = 0x1DFF      //7676 = 1+2+4+8+16+32+64+128+256+1024+2048+4096

#pragma DATA _CONFIG1, 0x0804
#pragma DATA _CONFIG2, 0x1DFF
#pragma CLOCK_FREQ 32000000

typedef unsigned char byte;
unsigned char r;
unsigned char goSlow;
unsigned char spiIn;
unsigned char spiOut;

// ################################################################################

void toggleClock(){
      portc.0=1;
      if (goSlow=1){delay_us(10);}
      portc.0=0;
      if (goSlow=1){delay_us(8);}
      portc.0=1;
}

unsigned char sendAndReceiveSPI(unsigned char in){
      unsigned char tmp;
      tmp=0;
      spiOut=in;
      spiIn=0x00;
     
      if(spiOut.7==1){ porta.4=1;}else{porta.4=0;}
      toggleClock();
      if(portc.1==1){spiIn+=128;}
     
      if(spiOut.6==1){ porta.4=1;}else{porta.4=0;}
      toggleClock();
      if(portc.1==1){spiIn+=64;}
     
      if(spiOut.5==1){ porta.4=1;}else{porta.4=0;}
      toggleClock();
      if(portc.1==1){spiIn+=32;}
     
      if(spiOut.4==1){ porta.4=1;}else{porta.4=0;}
      toggleClock();
      if(portc.1==1){spiIn+=16;}
     
      if(spiOut.3==1){ porta.4=1;}else{porta.4=0;}
      toggleClock();
      if(portc.1==1){spiIn+=8;}
     
      if(spiOut.2==1){ porta.4=1;}else{porta.4=0;}
      toggleClock();
      if(portc.1==1){spiIn+=4;}
     
      if(spiOut.1==1){ porta.4=1;}else{porta.4=0;}
      toggleClock();
      if(portc.1==1){spiIn+=2;}
     
      if(spiOut.0==1){ porta.4=1;}else{porta.4=0;}
      toggleClock();
      if(portc.1==1){spiIn+=1;}
     
      return(spiIn);
}

void assertCS(){
      porta.1=0;
}

void deassertCS(){
      porta.1=1;
}

void initSDCard(){
      unsigned char i;
     
      // pull the SD card power pin low
      porta.0=0;
      delay_ms(1);
           
      // pull all the other pins low - they should be low
      // during the SD card power-up cycle
      portc.0=0;
      portc.1=0;
      portc.2=0;
      porta.3=0;
      porta.1=1;      // hold CS high during power up
      portc.0=1;      // hold clock high during rest
     
      // power up the SD card
      // (this is also the trigger on the logic probe)
      porta.0=1;
     
      // clock a few pulses to clear buffers etc.
      deassertCS();
      for(i=0; i<10; i++){
            r=sendAndReceiveSPI(0x00);
      }

     
      // enable the CS on the SD card
      assertCS();
                       
      // send the reset command (0x40 | 0b10000000)
      r=sendAndReceiveSPI(0x40);
     
      // send four blank/null bytes
      for(i=0; i<4; i++){
            r=sendAndReceiveSPI(0x00);
      }
      // send the CRC/checksum
      r=sendAndReceiveSPI(0x95);
           
      while(r!=0x01){
            r=sendAndReceiveSPI(0xFF);
      }
     
      // turn off debugging
      porta.0=0;
}

void initialise(){
     
      osccon=0b11110000;            // oscillator=32mhz
      intcon=0b11000000;            // global interrupts enabled and peripheral interrupts
      apfcon0=0b00000000;            // alternative pin function - bit6: SDO1 function is on RA4=1, RC2=0
      trisa=0x00;                        // all outputs on portA
      trisc=0b00000010;            // all portC lines are output except pin1 (RC1=spi in)     
      ansela=0x00;                  // turn off analogue pins on portA
      anselc=0x00;                  // turn off analogue pins on portC     
      option_reg=0b10000000;      // no weak pull-ups
     
      goSlow=1;
}

void main(){
      initialise();
      delay_ms(10);     
      initSDCard();
}

void interrupt(void){
      // bit zero of pir1 is tmr1 overflow interrupt
      if(pir1.0==1){
            // clear the timer1 interrupt flag
            pir1.0=0;
            // preload timer1 to get the next interrupt to occur in a timely manner           
      }
}

Once we get the basic SD initialise working, we'll look at putting it back onto the SPI hardware (bit-banging is soooo last year) but for now we're just trying to get the SD card to even acknowledge it's presence!


PIC 16F1825 pinout for SPI/SD card interface:

SPI_CLOCK:  RC0
SPI_MISO (in): RC1
SPI_MOSI (out): RC2 / RA3 (can be changed in firmware)

We're using RA0 to turn the SD card on through a FET
SPI_CS (chip select): RA1


Sourceboost SD Card reader

We're having a bit of a time of it at the minute, just trying to get our PIC16F1825 to correctly initialise an SD card. It's not helped by the fact that we're using a new chip we've never used before, in a language/syntax we've not used for a long time, with a compiler we've never seen, using hardware peripherals we've only ever simulated in software. These last few days have been a very steep learning curve!

At last night's BuildBrighton meeting, Jason spent a good few hours going over all our registers and code to see if there was anything obviously wrong, but found nothing. But we couldn't even get the SPI hardware module on the PIC to operate, let alone send data using it!

Jason has a Scanalogic Logic Probe/Analyser which has proved invaluable in finding out what's going on at a pin-wiggling level. Using it, it took just a few minutes to realise that one of our fuse settings was incorrect and that instead of sending data over SPI, we were outputting the PIC clock signal onto the (multiplexed) SPI_OUT pin. D'oh.

With the Scanalogic Probe in place, we were able to actually see pin activity as data was exchanged between the devices



Using this helped us identify where things were initially going wrong and after three hours of getting nowhere, we finally manged to see SPI data being sent over the pins. Now our problem is, there's nothing coming back from the SD card!


In the image above, the blue line is the SPI clock. The yellow is the SPI data going from the PIC to the SD Card. Red is the trigger pin (used to start the probe reading the data) and the green is *supposed* to be the data coming back from the SD card.....

Wednesday, 17 October 2012

Oshonsoft v Sourceboost

Well, it's a sad day at Nerd Towers, as we've had to wave goodbye to our favourite PIC compiler, Oshonsoft's PIC18 Simulator - for the next few projects at least.

It's a great compiler, the best simulator bar none, makes USB/HID coding a doddle, but - sadly - it's lack of support means we've started to outgrow it. The number of chips supported is starting to look a little thin, now we've discovered these great little PICs - 16F1825

The thing is, to use some of the newer Microchips, we're going to have to use a different compiler. MPLAB (even version X) is still a mess and difficult to use (not to mention sloooow) so we've been looking at alternatives. Jason from BuildBrighton suggested SourceBoost which, at £60 for a full licence, looks pretty good value.

Now here's the dilemma:
Stick with a BASIC-based compiler for USB/HID and switch between languages/compilers for other stuff, or slowly upgrade everything to a C-based compiler/language and leave Oshonsoft behind for good?




Saturday, 13 October 2012

Hardware PWM PIC/AVR

PWM is one of those things that we've always done in software. It allows us to set the frequency and duty cycle easily but at a cost of cpu cycles. In fact, until recently we didn't actually know what frequency and duty cycle meant and because our PWM experience was mostly with servo- and motor-control, trying to understand them in these terms was more confusing than it needed to be.

Consider how a servo works.
We have to send a continuous series of pulses between 1ms and 2ms wide (in practice, between 0.6ms and 2.4ms) at least every 20ms. This is where frequency, duty cycle and comparing to hardware pwm gets confused.

With a servo, you need send only one pulse, up to 2ms wide, every 20ms.
This means that for 18 out of 20 milliseconds, the output doesn't actually need to do anything:


So the first thing we need to do is to forget all about servo control.
With that out of the way, we need to understand how PWM is produced inside our microcontroller. If we were doing it in software, this is probably how we'd do it:

First, set up a timer. Every time the timer rolls over, switch on an output pin.
Secondly, set up a CCP (compare, capture, pwm) interrupt.
Put simply, in compare mode, we give this register a value. Whenever the timer value reaches this "milestone" or "waypoint" or whatever term you want to give it, an interrupt occurs. When this interrupt occurs, we turn the output pin off.


The end result is a PWM square wave.
The frequency is determined by the timer1 interrupt. In this example, we're assuming a 16-bit timer1 so the timer counts up to 65,535 and raises the interrupt every time the timer rolls over to zero.
If we pre-loaded timer1 with a value each time, we could reduce the number that timer1 counts up to (actually, we'd start it from a higher value; it always raises an interrupt on rollover to zero, but the principle is the same). If timer1 is only counting up to, say, 20,000 instead of 65,535 (so we'd start the timer1 off at 65535-20000 = 45,535 to get it to count up to 20,000) then the frequency (number of times something occurs) is increased.

Duty cycle is a whole other thing and is dependent on which "value we're counting up to".
In terms of servo control, duty cycle doesn't actually mean anything - we're dealing with definite lengths of time. Turn on a pwm pin, then after 1.5ms, turn it off again. This 1.5ms isn't actually a percentage of a frequency, it's just an amount of time to leave a pin on for.

But if we're creating a continuous, repeating square wave, duty cycle (as a percentage) depends on which value we're counting up to.

If we wanted a 50% duty cycle (half the square wave is high, half low) we need to know what value we count up to, in order to create the timer1/reset interrupt. Then we set our CCP (turn-output-pin-off) interrupt to half this value.

It seems that this is where confusion creeps in when working with hardware PWM.
The datasheets talk about frequency, duty cycle, resolution and all this sort of crazy stuff that just gets confusing - especially when you're trying to load a value to 25% into a register to set the duty cycle.

In order to create a 25% duty cycle, you need to first know the "timebase" for the PWM signal. This is the same as the number the timer is counting up to. Take 25% of this and put that value into the duty cycle register. So if we have an 8-bit PWM cycle (timer is counting from 0-255) in order to create a 25% duty cycle, we need to set the CCP value to 256*25% = 256/4 = 64.

BUT
If we're using a 16-bit timer (counting from 0-65,535) then a 25% duty cycle would need a CCP value of 65536*25% = 65535/4 = 16,384

So in summary -
To increase the frequency, we need our PWM module to count up to a lower value.
To create a duty cycle (in percentage terms) we need to know what number we're counting up to, calculate the number to count up to, in order to create the % based duty cycle and put that value into the duty cycle register.

This is nothing like the way we've been creating PWM for servos in code but does provide us with a really quick and easy way of playing PCM based wav files. Here's a snippet from the datasheet for a PIC16F1825


So by loading the value 0x1F into our PWM register, the hardware will automatically reset the PWM output pin 250,000 times per second. Because of this, the maximum "resolution" - maximum number we can set the CCP interrupt to is a 7-bit value (0-127). In order to create a 250khz carrier wave, the timer1 "count-to-this" value (or hardware equivalent) has to be less than 255 to ensure it can be reset enough times during one second. Because "timer1" is counting up to a value less than 255, the CCP interrupt point cannot be an 8-bit value (0-255) so the maximum resolution is a 7-bit value (0-128).

For our audio playback example, we can set the PWM output "carrier wave" to 250khz - an inaudible frequency except for small animals - and then set the "duty cycle" using the value of each sample in the wav file. It needs a little adjustment, since we're reading 8-bit values from the wav file. Luckily these eight-bit values represent duty cycles in binary (so a 50% amplitude in the sound wav is recorded as 128 in the wav file - i.e. 50% of the maximum 256). So if we could convert an 8-bit value to a 7-bit value, we could just load this straight into our CCP value register in PWM hardware.

To convert 8-bits to 7-bits is quite easy.
We simply bit-shift the entire value to the right by one place.
This is the same as dividing the value by two (but bit-shifting is a quicker operation to do).

What this does is makes all max values of 256 a new value 128.
All minimum values of 1 are discarded (or treated as zero) and the new minimum value becomes 2 (which when bit-shifted or divided by two is one). The overall result is a tiny loss of definition in the sound - but given the low-quality hardware we'll be playing it back on, is of little concern.

So there we have it.
A quick round up of how to play sounds from a 22khz using PWM hardware:


  • Set up the "carrier wave" frequency to be 250khz by loading PR1 with the value 0x1F. 
  • Every 1/22050th of a second, load another byte from the wav file, bitshift right by one position (divide by two) and set CCPR1L.


That's it!

Homebrew cnc milling machine progress

Justin from BuildBrighton is cracking on with his milling machine and called round to Nerd Towers for some laser cutting action to get his x-axis working last night.


The mounting is beautifully simple. Firstly we made a laser-cut rack-and-pinion type gear set (made at this website - http://woodgears.ca/gear_cutting/template.html - just search for "online gear generator") with 4mm tooth spacing and 16 teeth, with a second gear with zero teeth. This produced the cog/pulley and linear rail in a single file which was then "printed" to CutePDF to create a vector-based PDF which we could then edit in Inkscape

A laser cut piece of acrylic was mounted onto the two horizontal drawer rails and the stepper motor mounted onto this. The cog and rack interfaced by simply sticking the linear rail onto the back of the lower drawer runner. With the cog on the stepper motor shaft, and the rail position adjusted to make it perfectly parallel to the horizontal rail, everything meshed together nicely.


For this prototype, the rail was held in place with double-sided tape. After a successful test run, this will probably be fixed in place using a more permanent adhesive like two-part expoy. The steppers, btw, were salvaged from an old inkjet printer and the frame made from some £3 MDF offcuts from B&Q. Justin really is taking showstring-budget-CNC to a new level!

Turning the motor shaft by hand causes the entire carriage to move across the gantry, left-to-right.
Justin promises us a video very soon, showing (at least) the x-axis being controlled by his own custom software on a PC. We can't wait!

Friday, 12 October 2012

Hardware requirements for 22khz audio with PIC microcontroller

We've spent quite some time looking into different implementations of playing audio from an SD card. There are quite a few out there, using both PIC and AVR (atTiny) . Rather than just use some existing code (although we're not ruling that out completely) we've been thinking about how to implement audio playback on a microcontroller.

At first, it seems quite straight forward: get a byte or two of data from an SD card, send to a PWM routine and make a speaker vibrate. The numbers involved don't sound too shocking either - until you start to look into them.

For example:
An audio sample recorded at 8-bit, 22khz requires the variable voltage on the speaker to change 22050 times per second. That's not a bit deal. Is it? We've millions of clock cycles and need to vibrate a speaker tens of thousands of times a second. No problem! Surely?

We're running our microcontroller at a maximum of 16Mhz (because the max supply voltage is 3v - for USB and 5v designs, you can go much higher, but at relatively low voltages, the maximum oscillator value is affected. The maximum speed you can run a low- to mid-end PIC at about 3v is 16Mhz). With an 8-bit PIC, the operating frequency is fosc/4 (i.e. one instruction takes 4 clock cycles). So running at maximum speed, from a 3v (2xAA battery) supply our PIC can do 16/4 = 4MIPS (million instructions per second).

That's still not shoddy.
But when we consider that we need to change our speaker voltage 22050 times per second, we must divide 4 million by 22050. Which is 181.4
So every 181 clock cycles, we need to change our speaker frequency.
And changing the PWM speed will eat into these precious clock cycles.
Which means we're going to end up with actually very few clock cycles for reading the data from the SD card.

Conclusion -
We may end up down-sampling our wavs to 11khz or lower to get a reasonable playback sound. Or we might just have to move over to AVR/atTiny and "borrow" some existing code!

Sunday, 7 October 2012

CNC milling machine

Following our success with our CNC drilling machine, we're now helping Justin from BuildBrighton hack our own device to create a CNC milling machine (that change in two little letters makes all the difference!)
Whereas our drilling machine had a simple peck-and-drill motion, and there were no lateral forces to contend with, Justin is looking to mill PCBs with a dremel or similar drilling head, which means the chassis needs to be able to resist some twisting forces on the uprights.

As a quick-and-dirty prototype, we put together an MDF-based frame and used drawer runners from an earlier prototype of our own drilling machine.


Justin is going for a moving gantry system, rather than a moving bed for the y-axis. We ran out of drawer slide rails to finish the x-axis but the frame so far looks very promising!

Alarm clock project - playing WAV files with a PIC microcontroller

An interesting project has come up as a fund-raiser for BuildBrighton members - to "hack" some alarm clocks and modify their behaviour so that instead of playing a single sample when the alarm sounds, one of up to seven different sounds can be played.


The clock hardware looks simple enough - three AA batteries are used; one single battery keeps the mechanical clock mechanism moving, and the other two are wired in series and connected to a sound playing device whenever either the alarm time is reached, or the user presses a button on the clock casing:



We're looking to reuse as much of the hardware as possible and this looks perfectly possible - with the exception, perhaps, of the simple PCB with black epoxy blob hiding what's going on! We're going to have to guess at how the clock alarm works, but it's not unreasonable to suspect that there's a microcontroller on-board, powered by the 2xAA batteries, which is waiting for a low signal on an input pin. When this input signal is received, the controller wakes from sleep, plays a sound then goes back into sleep/suspend mode.
The simple on/off slide switch breaks continuity between the low signal from the clock mechanism when in the off position (so you can turn the alarm off at weekends, for example) and the push button simply connects in parallel to the alarm mechanism - so the user can activate the sound just as if the alarm time had been reached on the clock.

With these assumptions in place, we're looking to replace the sound playing device with one of our own. Because a number of these clocks will be required, cost is an important consideration - though as is complexity of the circuit/software, as this will be a group project.

With all this in mind, we're proposing a PIC based solution (of course) using a DAC (digital-to-analogue converter) with the different sound samples stored on a regular SD card (read back via SPI).
There are loads of mcu-based sound players on the 'net, but many require expensive "wave shields" or third-party code libraries; we're after a solution that we can fully control ourselves, is not subject to restrictive licencing, is easy to understand and cheap to implement. That means building something from scratch....

To kick off with, we need to understand how we're going to play a sound.
Sound is generated when a speaker fluctuates backwards and forwards by varying amounts. Sounds can be represented by a "sound wave" and it is these "waves" that we're looking to capture and digitize so we can play them back on demand,.

To digitize a sound, we need to approximate the sound wave as a number of numerical values:


There are two important values when converting sound to digital values - bitrate and sampling frequency. These are terms that get thrown about quite a lot, but few people actually understand what they mean! In our example, we need to divide our sound wave up into a number of (vertical) slices.
This is the sampling frequency - how often we approximate the shape of the graph every second.
Common sampling rates are 44.1khz (CD quality), 22khz (tape quality), 11khz (radio quality) and 8khz (telephone quality). (A brief description of sampling can be found here - http://en.wikipedia.org/wiki/Sampling_rate)

Now we've divided our sound wave/graph into vertical slices, we need to assign a value to each "slice". The precision of these values is called the "bitrate" - i.e. the number of bits used to represent each slice of the graph.

For our sound playback, we're going to use 8-bit samples and 22khz frequency (half the quality of CD quality sound). This should give a reasonable playback quality, given that we're only using very cheap hardware and a transistor and cheap speaker to actually play the sound(s). We decided on 8-bit sound samples to keep things nice and simple in the microcontroller (the PIC micrcontroller range(s) we like to use - 16F and 18F - are 8-bit microcontrollers).

The sounds will be converted into 8-bit, 22khz format - using some software like Audacity (http://audacity.sourceforge.net/) and the raw sound data downloaded onto an SD card. The card will be connected to our PIC so that when we want to play back a sound, we give the SD card a byte/register value starting point and simply stream the data back off the card in a single SPI stream, one byte at a time (most SD cards support SPI as a legacy/fallback format).

If we're playing an 8-bit, 22khz sound file, this means that 22050 times per second, we need to read a single byte value from the SD card, convert this value into a variable voltage, and send it to the speaker (e.g. where the byte from sound wav is half value - 128 or 0x80 - this represents half the waveform amplitude so we want to set the output voltage to half of maximum). This all sounds fairly straight-forward so far. Where it gets complicated is getting this digital value out as an (analogue) voltage:

Audio out using PWM:

A microcontroller pin can take one of two states - high or low. This equates to full voltage and no voltage. There's no easy way of providing "roughly half voltage". One way to fake it, is to use "pulse width modulation". This basically means flicking an output pin on and off really quickly so that the average output voltage can be increased and reduced over a specific time period


If we connect our speaker to an output pin, add a smoothing capacitor then flick the output pin on and off really quickly, we should hear a sound. By changing the ratio between on and off, we can change the overall voltage level coming out of the pin (helped by the smoothing capacitor) and therefore change the sound played by the speaker.
PWM is a cheap way of creating a variable output voltage from a single output pin.


Audio out using a DAC (digital-to-analogue converter)

Another option would be to send our wavform value to a dedicated DAC chip. Simply put, you send your 8-bit value to a DAC and a variable voltage is presented on the DAC_Out pin. So if you send the value 128 (0x80 in hex) which is half the max value, you get 2.5v (or whatever half the supply voltage is) on the DAC_Out pin. Similarly, the value 64 (0x40) sees a quarter of maximum (1.25v) on DAC_Out and the value 192 (0xC0) three-quarters of a maximum (3.75v).

Most DAC chips consist of a simple resistor ladder which causes the output voltage to change, depending on which pins in the value-to-convert are high or low:


A lot of DAC chips support different interface methods - some are parallel (you connect your mcu outputs to each individual bit of the value to be represented) but most support some form of serial interface - either SPI or I2C. Although we can generate variable voltages through our PIC output pin using PWM, offloading our digital-to-analogue conversion to an external chip does provide some important benefits, even if it means introducing extra hardware.

The most important benefit is that we're saving clock cycles (as well as reducing code size).
Generating PWM means our PIC is doing a lot of work, looking after when the output pin needs to go high, when it needs to go low, and how often this should repeat in order to get an average voltage which matches the value in our waveform "slice". As we increase the sampling frequency (number of vertical slices) so the more work the PIC has to do, keeping track of all this PWM data.

Let's say we want to convert the value 128 (0x80) to a variable voltage, and we're reading sound samples recorded at 22khz. This means that we're changing the PWM rate 22050 times every second. But we can't just send a single pulse high and a single pulse low for equal time to generate our PWM value of 128 (50% on, 50% off) once every 1/22050th of a second. We need to send this value loads of times within our 1/22050th second window. All this means that our PIC is doing a lot of work just generating PWM signals - we still have to leave enough time to read data from our SD card in-between generating on/off output signals. Even with something as big as a 20Mhz crystal, the PIC would struggle to play anything above 32khz samples.

By offloading the analogue voltage to a DAC, the PIC is freed up to get on with other things.
The playing of a wav file is reduced to "read a byte, send it to the DAC" and repeating this once every 1/22050th of a second (in fact, we'll probably read a few bytes into a buffer but the theory is the same). Because of this, we're not only simplifying the code required, we can even run it on a low-end PIC (something like a 16F628A) from it's internal RC oscillator and do away with the need for a high-speed crystal. This in turn reduces current consumption and extends battery life since the PIC is running at a lower speed. It may even be possible to play higher-quality 44.1khz samples with such a simple code routine.

For this little project we'll be using

Microchip's MCP4902 8-bit SPI DAC
PIC 16LF628A (low voltage)
Cheap SD or SDHC card
Audacity sound editing software

The software's downloaded and installed, the wav files edited and everything is on order. All we're waiting for now is the postie to bring everything and we can get cracking!

EDIT - November 2012: We got this working using a PIC16F1825 and have written up how it works in a series of blog posts, starting here