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?