16-bit Software SPI Implementation on PIC (PIC18F4620)
This article is part of a series describing communicating with the Sparkfun VS1103b BOB (Break-out board)
Please note: The project from which this article is taken is still ‘under development’. A full library of routines to drive the VS1103b will be made available when this is completed.
A link to a zip of a working set of the current software is included at the foot of the page.
The code below shows a re-written clone of Microchips WriteSWSPI routine, but now supporting 16-bit word transmit/receive and correcting the sloppy original which demonstrated glitches on the data line.
Using a PIC18F4620 with a 10Mhz crystal and the HSPLL operating, the pulse period is ~2.67us or a frequency of 374.5 Khz.
I’ve included commented-out sections showing calls to the Delay10TCYx delay routine (in delays.h) to indicate where these should be inserted if a lower frequency is required.
/********************************************************************
* Function Name: WriteIntSWSPI *
* Return Value: int: received data (throw away) *
* Parameters: int output : data to transmit *
* Description: Write/Read 16 bit to/from custom SPI port *
* Re-written from Microchip original to *
* support 16 bits *
* JWB 14th June 2010 *
********************************************************************/
//
// JWB 12th May 2010 - NOTE!! Have to add 40 ck cycles delay to each part of waveform
// if using CMOS level-shift as CD40109 not responding to fast waveforms.
//
unsigned int WriteIntSWSPI(unsigned int command, unsigned int output)
{
static unsigned long spi_val;
char BitCount; // Bit counter
spi_val = (long)command << 16; // catenate command, reg addr and data to write into 32-bit value
spi_val |= (unsigned long)output;
BitCount = 32; // ! Important! VS1103b transaction must be an atomic 32-bits
// Mode 0: This MODE is suitable for VS1103b
// SCK idles low
// Data output after falling edge of SCK
// Data sampled before rising edge of SCK
// ====================================================================
// JWB : rather than making the port pin 0, THEN 1 if the data bit is 1
// as in the Microchip-supplied code, test the putative outgoing bit
// properly before writing it to the pin
// ====================================================================
// Set Dout to MSB of data output (confusingly called input!)
if (spi_val & 0x80000000)
SW_DOUT_PIN = 1;
else
SW_DOUT_PIN = 0;
Nop(); // Adjust for jitter
Nop();
do // Loop for bit count
{
// ========================================================================
// JWB Again, rather than writing to the STATUS register TWICE in one case
// and ONCE in the other - test the bit BEFORE writing the STATUS carry bit
// ========================================================================
// Set the carry bit according to the Din pin
if (SW_DIN_PIN)
STATUSbits.C = 1;
else
STATUSbits.C = 0;
SW_SCK_PIN = 1; // Set the SCK pin high
_asm
rlcf spi_val,1,1 // rotate carry into least-sig bit of 'input'
rlcf spi_val+1,1,1
rlcf spi_val+2,1,1
rlcf spi_val+3,1,1
_endasm
Nop(); // Produces a 50% duty cycle clock
Nop();
Nop();
Nop();
Nop();
Nop();
Nop();
Nop();
Nop();
Nop();
Nop();
// Delay10TCYx(4); // set to 40 (4 * 10) ck delays - CMOS 40109 does not responding to higher freq
SW_SCK_PIN = 0; // Set the SCK pin low
// Set Dout to MSB of data output
if (spi_val & 0x80000000)
SW_DOUT_PIN = 1;
else
SW_DOUT_PIN = 0;
BitCount--; // Count iterations through loop for full bitcount
// Delay10TCYx(4); // set to 40 (4 * 10) ck delays - CMOS 40109 does not responding to higher freq
} while (BitCount);
return(spi_val & 0x0000ffff); // Return the received data
}
To control specific hardware, the above code should be suitably wrapped. A wrapper supporting writes to registers on the VLSI VS1103b MIDI synth DSP is shown below. This is taken from the routines supplied in the zip:
#include "sw_spi.h"
#include "VS1103b.h"
////////////////////////////////////////
//
// WriteSCI - Wait until the VS1103b
// signals it is read to receive, then
// Write a 16-bit word to its command
// interface, and/or read a 16-bit word back.
//
// Leaves VS1103b XCS & XDCS lines high
// (inactive)
// JWB 08 May 2010
////////////////////////////////////////
unsigned int WriteSCI (unsigned int command, unsigned int param)
{
unsigned int result;
while (SW_DREQ_PIN == 0); // wait 'til DREQ goes high
SW_XDCS_PIN = 1;
SW_XCS_PIN = 0; // select SCI
result = WriteIntSWSPI(command, param);
SW_XCS_PIN = 1; // de-select SCI
return result;
}
The above is further wrapped, together with a read routine in a procedure called: SCI_Command, provided in the source-code.
An example of using the SCI_Command routine to read a register is given below (my calls to the serial port are for debug purposes only):
#include "sw_spi.h"
#include "VS1103b.h" // software SPI for VS1103b interface
.
.
.
.
union SCI_REG_VAL dummy;
result = SCI_Command(READ_OP, SCI_VOL, dummy);
// output to serial port to observe result
TxSerial ( ( result >> 8 ) & 0x00ff);
TxSerial (result & 0x00ff);
Another example, this time writing LH and RH volume levels into the SCI_VOL register (at address 0xb)
#include "sw_spi.h"
#include "VS1103b.h" // software SPI for VS1103b interface
.
.
.
.
union SCI_REG_VAL sound_val;
sound_val.word_part = 0x407;
result = SCI_Command(WRITE_OP, SCI_VOL, sound_val);
The current source for the project is here. You should add these files to your project, and put the two includes shown above in your client code, as in my two examples above.


