12.2 SPI Master Usage

Your Ad Here

Generic Driver

In the first part of this tutorial, we looked at a general overview of the SPI communications interface. In this section we take a look at how the PIC24’s SPI module can be used in its master mode.

There are several ways to set the SPI master up. However this is how I do it.

The code is taken from a driver I wrote that is written is a generic manner such that I may be used in both PIC24F and PIC24H. If you are just using it for a small project and have no intention of encapsulating any of the code, just keep the main functions and delete the “#if defined” directives. I am also using the Microchip’s peripheral library. For those that don’t know, the peripheral library is installed with your version of the C30 compiler; another reason to upgrade from the assembler.

(code from spi.h)

/*
Engscope.com
JL
Created		Jul 1, 2010
Modified	Jul 1, 2010
headers for spi device drivers
*/

//define i2c ports so that it can be
//called using constants instead of numbers
//declare only if ports are available
enum SPIPorts{
	SPIPORT1 = 1
	#if defined(spi_v1_1) || defined (spi_v1_3)
	, SPIPORT2
	#endif
	#if defined (spi_v1_3)
	, SPIPORT3
	#endif
};

//with selectable ports

unsigned char spiWrite( unsigned port, unsigned char i);

unsigned char spi1Write( unsigned char i );
void spi1Init(unsigned int prescale);

//spi port 2
#if defined(i2c_v1_2) || defined (i2c_v1_3)
unsigned char spi2Write( unsigned char i );
void spi2Init(unsigned int prescale);
#endif

//spi port 3
#if defined (i2c_v1_3)
unsigned char spi3Write( unsigned char i );
void spi3Init(unsigned int prescale);
#endif

(code from spi.c)

/*
NVIS Inc.
Jianyi Liu
Created		Jul 1, 2010
Modified	Jul 1, 2010
source for spi device drivers
*/
#include "spi.h"
#include "nlib_spi.h"

//write using selectable ports
unsigned char spiWrite( unsigned port, unsigned char i)
{
    switch(port){
        default:
            case 1: return spi1Write(i);
        #if defined(spi_v1_1) || defined (spi_v1_3)
            case 2: return spi2Write(i);
        #endif
        #if defined (spi_v1_3)
            case 3: return spi3Write(i);
        #endif
    }
}	

//init Spis
void spi1Init(unsigned int prescale){
    OpenSPI1(0x0120 | prescale, 0x0000, 0x8000);
}	

// send one byte of data and receive one back at the same time
unsigned char spi1Write( unsigned char i )
{
    // write to buffer for TX, wait for transfer, read
    SPI1BUF = i;
    while(!SPI1STATbits.SPIRBF);
    return SPI1BUF;
}//spiWrite2

#if defined(spi_v1_1) || defined (spi_v1_3)
void spi2Init(unsigned int prescale){
    OpenSPI2(0x0120 | prescale, 0x0000, 0x8000);
}	

// send one byte of data and receive one back at the same time
unsigned char spi2Write( unsigned char i )
{
    // write to buffer for TX, wait for transfer, read
    SPI2BUF = i;
    while(!SPI2STATbits.SPIRBF);
    return SPI2BUF;
}//spiWrite2
#endif

#if defined (spi_v1_3)
void spi3Init(unsigned int prescale){
    OpenSPI3(0x0120 | prescale, 0x0000, 0x8000);
}	

// send one byte of data and receive one back at the same time
unsigned char spi3Write( unsigned char i )
{
    // write to buffer for TX, wait for transfer, read
    SPI3BUF = i;
    while(!SPI3STATbits.SPIRBF);
    return SPI3BUF;
}//spiWrite3
#endif

Several things are happening here. The “spi.h” file is located in the installation folder of your C30 compiler. This folder should already be included in the compilation path, but if it is not, you will need to find it and added to the “include search path” parameter under project options.

Most likely you will not need to do this step. There are two basic functions included in the file; spixInit() and spixWrite(). You will use the spixInit() function to initialize the master SPI module, and both read and write will be completed with the spixWrite() function. Pretty easy huh?

spixInit() is set up to use several very specific options, so your mileage may vary. You may need to modify the init function to your liking, but in its current state it uses the following options:

-Serial output data changes on transition from active clock state to Idle clock state
-Idle state for clock is a low level; active state is a high level
-Master mode

The “prescale” parameter in the init refers to this tidbit in the datasheet:

Your Ad Here

It determines how fast you will be sending your data out of the SPI master module. If your Fcy is running at 16MHz, and you scale it to 1:1, then you will be sending the SPI clock at 16Mhz. However, note that the SPI protocol usually only goes up to 10Mhz, so prescale appropriately.

Anyways, after the initiation function is called, you can pretty much just call the spixWrite() function to read or write. How is this accomplished? Well, reading and writing under the SPI protocol is basically the same function. Usually, the hardware implementation of a SPI slave or master has two internal bit shift registers. One shifts the incoming bits, and one register shifts out the communicated data. Below is a diagram of how the shift registers are arranged in a SPI connection.

This is fortunate because we can actually send data and receive data within the same function, since the two buffers function separately.

Example EEPROM Interface

These are basic functions. However, the package on its own is still not very useful. If we want to interact with an EEPROM for example, we need to make higher level functions that take into account the intricacies of the specific EEPROM.

In this next example, I will be interfacing with a Microchip SPI EEPROM 25AA160A/B. A packet sent to the EEPROM has to have the following actions. First the slave select (SS) line must be lowered. Next an 8 bit instruction must be sent to the slave, followed by a 16 bit address. During a write, the master finishes the operation by sending the data. During a read, the master finishes the operation by sending clock pulses, and reading the incoming serial bits. Here is the instruction set and sequences taken directly from the datasheet.



Well what does this mean? It means that we can write a driver set specific to this device, or any device that follows this convention. Here is my version.

(nlib_spi_ee.h)

/*
Engscope.com
JL
Created		Jul 1, 2010
Modified	Jul 1, 2010
header for eeproms, spi
*/

//instruction set
#define EEPROM_CMD_READ     (unsigned)0b00000011
#define EEPROM_CMD_WRITE    (unsigned)0b00000010
#define EEPROM_CMD_WRDI     (unsigned)0b00000100
#define EEPROM_CMD_WREN     (unsigned)0b00000110
#define EEPROM_CMD_RDSR     (unsigned)0b00000101
#define EEPROM_CMD_WRSR     (unsigned)0b00000001

//struct for the status register
struct  STATREG{
	unsigned    WIP:1;
	unsigned    WEL:1;
	unsigned    BP0:1;
	unsigned    BP1:1;
	unsigned    RESERVED:3;
	unsigned    WPEN:1;
};

union _EEPROMStatus_{
	struct  STATREG Bits;
	unsigned char	Char;
};

//initiate a port for this eeprom
void spiEeInit(unsigned char p);

//read the status regsiter
extern union _EEPROMStatus_ EEPROMReadStatus(void);

//set the macro for active SPI
#define spiEeSsLow()      SPIEE_SS = 0;

//set the macro for inactive SPI
#define spiEeSsHigh()     SPIEE_SS = 1;

//writes to the eeprom device
extern void spiEeByteWrite(unsigned int, unsigned char);

//reads from the eeprom device
extern unsigned char spiEeByteRead(unsigned int);

//enable write by changing status register
extern void spiEeWriteEnable(void);

//disable write by changing status register
extern void spiEeWriteDisable(void);

//polls to see if the SPI EEPROM is present
unsigned char spiEePoll();

(nlib_spi_ee.c)

/*
Engscope.com
JL
Created		Jul 1, 2010
Modified	Jul 1, 2010
source for eeproms, spi
*/

//include correct headers
#ifdef __PIC24F__
    #include "p24fxxxx.h"
#elif defined __PIC24H__
    #include "p24hxxxx.h"

#else
    #error No valid target device
#endif

#include "nlib_sys.h"
#include "nlib_spi.h"
#include "nlib_spi_ee.h"
#include "bsp.h"

//make sure user defines chip select in bsp.h
#ifndef SPIEE_SS
    #error Must define symbol SPIEE_SS, chip enable bar, \
    make sure associated TRIS is high
#endif

//variable keeps track of which port to use
static unsigned char port;	

//initiate a port for this eeprom
void spiEeInit(unsigned char p)
{
    //assign port
    port = p;

    // Set IOs directions for EEPROM SPI
    //disable SS signal
    SPIEE_SS = 1;
}

//writes to the eeprom device
void spiEeByteWrite(unsigned int Address, unsigned char Data)
{
    unsigned char var;
    spiEeWriteEnable();
    spiEeSsLow();

    var = spiWrite(port, EEPROM_CMD_WRITE);

    var = spiWrite(port, INTHI(Address));
    var = spiWrite(port, INTLO(Address));

    var = spiWrite(port, Data);

    spiEeSsHigh();

    // wait for completion of previous write operation
    while(EEPROMReadStatus().Bits.WIP);

    spiEeWriteDisable();
}

//reads from the eeprom device
unsigned char spiEeByteRead(unsigned int Address)
{
    unsigned char var;

    spiEeSsLow();

    var = spiWrite(port, EEPROM_CMD_READ);

    var = spiWrite(port, INTHI(Address));
    var = spiWrite(port, INTLO(Address));

    var = spiWrite(port, 0);

    spiEeSsHigh();
    return var;
}

//enable write by changing status register
void spiEeWriteEnable()
{
    unsigned char var;
    spiEeSsLow();
    var = spiWrite(port, EEPROM_CMD_WREN);
    spiEeSsHigh();
}

//disable write by changing status register
void spiEeWriteDisable()
{
    unsigned char var;
    spiEeSsLow();
    var = spiWrite(port, EEPROM_CMD_WRDI);
    spiEeSsHigh();
}

//read the status regsiter
union _EEPROMStatus_ EEPROMReadStatus()
{
    unsigned char var;

    spiEeSsLow();
    var = spiWrite(port, EEPROM_CMD_RDSR);
    var = spiWrite(port, 0);
    spiEeSsHigh();

    return (union _EEPROMStatus_)var;
}

//polls to see if the SPI EEPROM is present
unsigned char spiEePoll()
{
    unsigned char temp = 0;
    spiEeWriteEnable();
    temp = EEPROMReadStatus().Bits.WEL;
    spiEeWriteDisable();
    return temp;
}

Here you have all the higher level functions to interact with a SPI EEPROM device. You will need to use the #define directive to define the pin that acts as the SS signal, also make sure the pin is set as an output, not an input by using the appropriate TRIS register. Next set the SPI port. This is in association with the PIC24F set of microcontrollers, which usually has multiple SPI ports. Then you can just call the read and write functions when needed.

Your Ad Here

Have fun.

-J

10 Responses to 12.2 SPI Master Usage

  1. Thomas says:

    Hi. I’m having a bit of trouble getting your SPI code working. I eventually want to communicate with a 25AA1024 EEPROM device but for now I am trying to simulate the SPI module in MPLAB.

    I initiate the SPI1CON1 (0×0020), SPI1CON2 (0×0000) and SPI1STAT (0×8000) registers and then write to SPI1BUF but I am not seeing anything on the SCK1 or SDO1 pins when I simulate it in MPLAB.

    I have the pins set up as follows;

    RPINR20bits.SDI1R = 6;
    RPOR3bits.RP7R = 9;
    RPOR4bits.RP8R = 8;
    RPOR4bits.RP9R = 7;

    Can you see anything that i’m missing?

    Any help you could give would be greatly appreciated.
    Great tutorials by the way!!

    Thanks,
    Thomas

  2. jliu83 says:

    Hi Thomas,
    You are using the SS_Bar pin wrong, at least with my firmware. Specifically the RPOR3bits.RP7R = 9. This uses your SS_Bar pin for as part of the module. Uncomment, and use the pin as a regular IO pin set to OUT (TRIS set to 0). Then use the firmware and define SPIEE_SS as the SS_Bar pin.

    Tell me how it goes.
    -J

  3. jliu83 says:

    Wait.. you are simulating? Hmm.. You know in my experience simulation is not always accurate, especially with modules. This is because of erratas and silicon revisions and other factors. Take two hours and make a circuit, and use scopes. That method is always accurate.

    -J

  4. Thomas says:

    Thanks for the speedy reply.

    I’ve connected it up to a scope and it works; I’m getting a clock pulse on SCK, the data on SDO and the chip select signal on SPIEE_SS.

    Thanks very much for the help.

  5. Franz says:

    can i use spi1 and spi2 at the same time?

  6. jliu83 says:

    Hi Franz,
    I would suggest that you get a chip and play around with it to understand the capabilities of the device. All the questions in the world will not get you the all answers, but spending a week with a PIC24F will probably answer a whole bunch. Roll up your sleeves and get dirty, and all your questions will be cleared.

    -J

  7. ubbbiii says:

    i am unable to find “nlib_spi.h”.where do i get this??

  8. jliu83 says:

    It contains some other functions that I wrote that are wrappers. You don’t really need it here (just take out the line to get it to compile). You would probably want to include your “Chip Select” define directives in there.

    -J

  9. Alfredo says:

    Hi, i don’t understand well your code, for example from what library do you get the spiWrite function and how does this function interact with the module?

  10. jliu83 says:

    @Alfredo. The spiWrite() function is found in the peripherals library that comes with your C30 compiler.

    The header files are found at: [C30 install directory]\support\peripheral_24F\
    The library itself is located at: [C30 install directory]\lib\PIC24F

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>