Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Download as pdf or txt
Download as pdf or txt
You are on page 1of 17

NDSU

SPI Communications

ECE 376

SPI Communications
Goal:
Send and receive serial data using SPI protocol.

Why:
Efficiency. You can send an unlimited amount of data using 3+ lines: CLK: Clocks the serial data in and out SDO SDI SS Serial Data Out. Serial Data In Slave Select. SS enabled is required for the slave to drive its SDO line.

How: Hardware
Pick one processor to serve as the Master. This device drives the clock line The other devices serve as slaves. These devices also send data on the SDO line and receive on their SDI line, but, they only do so as commanded by the bus master. The master determines when the data is sent (SCK) and which slave is allowed to talk on the slave SDO line (SS). You usually need a separate SS (slave select) line for each slave device.

Master Clock = output RC5/SDO RC4/SDI RC3/SCK SS0

Slaves Clock = input RC4/SDI RC5/SDO RC3/SCK SS RC4/SDI RC5/SDO RC3/SCK

SS1

SS RC4/SDI RC5/SDO RC3/SCK

SS2

SS

JSG

rev April 18, 2005

NDSU
SPI - Master Mode

SPI Communications

ECE 376

The bus master controls all communications. The master


Determines when data is sent / received Provides the clock Provides the SS line (optional). This determines which slave is active. Writes to the SDO line Reads the SDI line.

Timing:

The master controls the clock line and pulses it once for each bit that is sent. You can adjust the clock's phase and sign using CKP and CKE. Data is sent out on the SDO line most significant bit first. At the same time, data is read in on the SDI line, most significant bit first. After eight bits are sent (one byte), SSPIF is set, signifying that the SPI port is ready to be read to see what data the slave sent, and ready to send a new byte.

JSG

rev April 18, 2005

NDSU
How: Software:
1. Set up PORTC as follows:

SPI Communications

ECE 376

Bit input / output

TRISC (address 0x__ - Bank __) 7 6 5 4 3 2 SDO SDI 0 1 SPICLK 0 -

1 -

0 -

2. Set up the conditions for the interrupt SSPSTAT (address 0x94 - Bank 1) Bit Name Value SMP: Sample bit: SPI Master Mode
1 = Input data sampled at end of data output time 0 = Input data sampled at middle of data output time

7 SMP 0

6 CKE 1

5 D/A x

4 P x

3 S x

2 R/W x

1 UA x

0 BF 0

SPI Slave Mode


SMP must be cleared when SPI is used in slave mode

CKE: SPI Clock Edge Select CKP = 0


1 = Transmit happens on transistion from active clock state to idle clock state 0 = Transmit happens on transistion from idle clock state to active clock state

CKP = 1
1 = Data transmitted on falling edge of SCK 0 = Data transmitted on rising edge of SCK

BF: Buffer Full Status bit Receive (SPI and I2C modes)
1 = Receive complete, SSPBUF is full 0 = Receive not complete, SSPBUF is empty

JSG

rev April 18, 2005

NDSU
Bit Name Value 7 0 6 0

SPI Communications

ECE 376

SSPCON (address 0x14 - Bank 0) 5 1 4 CKP 0 3 0 2 0 1 a 0 b WCOL SSPOV SSPEN SPI Master Clock Freq

WCOL: Write Collision Detect bit Master Mode:


1 = A write to SSPBUF was attempted while the I2C conditions were not valid 0 = No collision

Slave Mode:
1 = SSPBUF register is written while still transmitting the previous word (must be cleared in software) 0 = No collision

SSPOV: Receive Overflow Indicator bit 1 = A new byte is received while SSPBUF holds previous data. Data in SSPSR is lost on overflow.
In slave mode the user must read the SSPBUF, even if only transmitting data, to avoid overflows. In master mode the overflow bit is not set since each operation is initiated by writing to the SSPBUF register. (Must be cleared in software).

0 = No overflow SSPEN: Synchronous Serial Port Enable bit. In SPI mode, when enabled, these pins must be properly configured as input or output. 1 = Enables serial port and configures SCK, SDO, SDI, and SS as the source of the serial port pins 0 = Disables serial port and configures these pins as I/O port pins CKP: Clock Polarity Select bit 1 = Idle state for clock is a high level 0 = Idle state for clock is a low level bit 3-0: SSPM3:SSPM0: Synchronous Serial Port Mode Select bits 0000 = SPI master mode, clock = FOSC/4 0001 = SPI master mode, clock = FOSC/16 0010 = SPI master mode, clock = FOSC/64 0011 = SPI master mode, clock = TMR2 output/2 0100 = SPI slave mode, clock = SCK pin. SS pin control enabled. 0101 = SPI slave mode, clock = SCK pin. SS pin control disabled. SS can be used as I/O pin

SSPSM3:SSPSM0 00000 0001 0010

Clock FOSC /4 FOSC /16 FOSC /64

with a 20MHz crystal 5MHz 1.25MHz 312.5kHz

Note that SPI communications is very fast: up to 5 million bits / second being transferred each way.

JSG

rev April 18, 2005

NDSU
3. Enable an INT interrupt SSPIE = 1

SPI Communications

ECE 376

4. Enable all interrupts: PEIE = 1; GIE = 1: enable all interrupts

At this point, you're ready to send and receive 8 bits of data using the on-board SPI port.

JSG

rev April 18, 2005

NDSU
Example: Write a routine which Passes 8 bits on SDO and Returns the 8-bits received on SDI

SPI Communications

ECE 376

Determine how long it takes to send data at the maximum bit rate Calling Format:
Result = SPI_RW('I'); // 'I' is sent on SDO // the data from SDI is // returned in Result

Subroutines:
void Init_SPI(void) { SSPCON = 0x20; TRISC5 = 0; TRISC4 = 1; TRISC3 = 0; SSPIE = 0; }

// SPI is on, 5Mbps // set up PORTC for SPI

// turn off interrupts // use polling instead

unsigned char SPI_RW(unsigned char Data) { unsigned char Result; SSPIF = 0; SSPBUF = Data; while (!SSPIF); Result = SSPBUF; return(Result); }

// load the SPI data buffer // wait until data sent // load what the slave sent // and return it

To send 8 bits, this routine takes


1 (8 bits) 5Mbps = 1.6uS to send the data

+ overhead to call the subroutine, read a character, and return a character.

SPI communication is a convenient and fast way to send data from one chip to another on the same circuit board.

JSG

rev April 18, 2005

NDSU
SPI - Slave

SPI Communications

ECE 376

SPI slave mode is just like the master mode, save the clock line is driven by someone else (CLK = input for slaves). If using more than one slave, tie the slave select lines from the master to RA5. This causes the slave to ignore the data unless SS=0. It also synchronizes the data to the falling edge of SS.

Note: Since you can't control how fast the data is sent, the slave often times Uses an interrupt to receive each byte (you don't have time to wait since the next byte may be coming within the next 1.6us) Save each byte in a stack for temporary storage (you don't have time to decode the data as it comes in. The next byte may be coming in the next 1.6us). The main routine can then figure out what the data means and what it's supposed to to with this data once it's been received.

Timing:

JSG

rev April 18, 2005

NDSU
How: Software:
1. Set up PORTC as follows:

SPI Communications

ECE 376

Bit input / output

TRISC (address 0x__ - Bank __) 7 6 5 4 3 2 SDO SDI 0 1 SPICLK 0 -

1 -

0 -

2. Set up the conditions for the interrupt SSPCON (address 0x14 - Bank 0) 6 5 4 3 CKP 0 0 0 1

Bit Name Value

7 0

2 1

1 0

0 a

WCOL SSPOV SSPEN

SPI Slave

a = 0: SS disabled (SS is pin RA5) a = 1: SS enabled. (RA5)

3. Enable an SPI interrupt (The slave really needs to use interrupts since it can't control how fast the data comes in. The slave needs to read the data and save it as fast as possible.) SSPIE = 1

4. Enable all interrupts: PEIE = 1; GIE = 1: enable all interrupts

JSG

rev April 18, 2005

NDSU
Example Code:
1. Initialization Routine for SPI Slaves:
void Init_SPI_Slave(void) { TRISA5 = 1; SSPCON = 0x25; SSPIF = 0; SSPIE = 1; PEIE = 1; GIE = 1; }

SPI Communications

ECE 376

// RA5 = Slave Select // turn on SPI slave mode // enable interrupts

2. Interrupt Service Routine: In this example, load the data which is incoming into a buffer, SPI_STACK, as it is read in. Assume that no data is sent back to the master.

Interrupt Service Routine: Read the SPI port and place the data into a circular stack of six
// Global unsigned char Stack_Pointer; unsigned char SPI_Stack[6]; // Interrupt Service Routine void interrupt IntSer(void) @ 0x10 { if (SSPIF == 1) { SPI_Stack[Stack_Pointer] = SSPBUF; Stack_Pointer += 1; Stack_Pointer = Stack_Pointer % 6; // // SSPBUF = DATA;

// got a byte

// if you are going to // return data, do it here

SPIF = 0; } }

JSG

rev April 18, 2005

NDSU

SPI Communications

ECE 376

Example: DS1267 Digital Potentiometer: Write a routine which sets the value of a DS1267 digital potentiometer. Make the calling function
void Set_Pot(unsigned char A, unsigned char B)
A where A and B are the values of the two potentiometers: R = R 0 255 .

Solution: Step 1. Find the data sheets for a DS1267.

Step 2. Set up the hardware connections. Connect DQ to the SPI data line (SDO) CLK to the SPI clock line (SCK) RST to some other pin on the PIC chip (sort of a slave select line.) Assume RC0 for now.

Step 3. In the data sheets, find a timing diagram. This is given in Figure 9 and Figure 1:

JSG

10

rev April 18, 2005

NDSU

SPI Communications

ECE 376

From these diagrams, it appears you need to... Send 24 bits (17 bits, rounded up to a multiple of eight) This data is shifted through a shift register on the DS1267 so that you can cascade several digital pots. The last 17-bits send are the ones that matter. These 24-bits should look like the following: Flip the bits in each byte. The data bits are received LSB first for the DS1267 while the PIC sends the data MSB first. Either write your own SPI routine or write a flip routine. First byte sent x x x x x x x padding bits and Stack Select Second Byte Sent Pot 1 value - LSB first Third Byte Sent Pot #2 value - LSB first

0 b0 b1 b2 b3 b4 b5 b6 b7 b0 b1 b2 b3 b4 b5 b6 b7

Step 4. Write some code: Option #1: Use the On-Chip SPI routine. Assume Y=flip(X) flips all bits:
void Set_Pot(unsigned char A; unsigned char B) { static bit RST @ ((unsigned)&PORTC*8+0); // RST = RC0 RST = 0; RST = 1; SSPIF = 0; SSPBUF = 0; while (!SSPIF); SSPIF = 0; SSPBUF = flip(A); while (!SSPIF); SSPIF = 0; SSPBUF = flip(B); while (!SSPIF); RST = 0; }
JSG 11 rev April 18, 2005

// select the DS1267

// send first byte

// send second byte

// send third byte // deselect the DS1267

NDSU

SPI Communications

ECE 376

Option #2: Write your own SPI port driver:


static bit RST static bit DQ static bit CLK @ ((unsigned)&PORTC*8+0); @ ((unsigned)&PORTC*8+5); @ ((unsigned)&PORTC*8+3);

void Set_Pot(unsigned char A; unsigned char B) { unsigned char i; RST = 0; SCK = 0; RST = 1; DQ = 0; SCK = 1; // default levels for RST // and clock // select the DS1267 // Stack select = 0 // pulse the clock

SCK = 0;

for (i=0; i<8; i++) { if (((A>>i) & 1) == 0) DQ = 0; else DQ = 1; SCK = 1; SCK = 0; } for (i=0; i<8; i++) { if (((B>>i) & 1) == 0) DQ = 0; else DQ = 1; SCK = 1; SCK = 0; } RST = 0; }

// send each bit of A // LSB first

// send each bit of B // LSB first

// after 17 bits, you're done

JSG

12

rev April 18, 2005

NDSU
What can you do with a digital pot?

SPI Communications

ECE 376

1) Design an amplifier which the PIC can monitor and adjust. The PIC monitors the amplitude of the signal at RA0. If it is too small, increases the gain (increase R). If it is too large and is saturating the amplifier (RA0 too close tp +5V), reduce the gain (reduce R).

Amplified Signal

RA0 DS1267 100k 1uF 1k Stage 1 gain = 1..100 R (optional)

Stage 2 Envelope Detector

2) Design a tunable low-pass filter. Set the corner from 100Hz to 1kHz. By adjusting R, you set the corner frequency. A 1-stage filter is shown on the left. A 3-stage Butterworth filter is shown on the right. All three R's should have the same value.
C2 0.0224 100k 100k 100k 100k

C
C1 0.0088 C3 0.0013

3-Stage Butterworth Filter 1-Stage Butterworth Filter

max corner = 0 (R=0)

(from ECE 321 text)


1

min corner = 2 f = 100 kC

JSG

13

rev April 18, 2005

NDSU
2-Wire SPI Communications:

SPI Communications

ECE 376

Some companies, such as Dallas Semiconductors, use a 2-wire SPI communication scheme. This uses A clock line from the master (as a normal SPI communication scheme), but A single data line. The concept is that often times you only have communication going one way. If you constrain all communications to happen in one direction only (termed half duplex), you can save one data line: When the master is talking, it drives the data line and the slave listens. When the slave replies, the master switches its data line to input (high z) and the slave drives the data line. Since the data can go both ways, add a 1k resistor in-between as a buffer. This limits the current flow if the PIC and the DS1620 try to drive the data line at the same time. This should never happen, if the program is written properly. It doesn't hurt to be safe, though.
PIC16F876 (Master) RC3 DS1620 (Slave) DQ Vdd 2.7V to 5.5V

Data (R=1k)

Clock
RC2

CLK

Slave Select
RC1 RST

GND

Step 1. Find the data sheets for a DS1620

Step 2. Find the hardware connections. Arbitrarily, let RC1: RST (1 = start comm. 0 = terminate) RC3: DQ (1k resistor between) RC2: CLK (pulse low = clock)

Step 3. Find the timing (figure 4 from the data sheets):

JSG

14

rev April 18, 2005

NDSU

SPI Communications

ECE 376

You Start with CLK = 1, RST = 0. Pull RST high to start communications Set DQ = output for the PIC to drive the data line. Clock out 8 bits of data with the data valid on the rising edge of the clock. This is the command for the DS1620 processor. If you are sending data to the DS1620, keep DQ as output. Clock out 8 more bits, LSB first. If you are receiving data, switch DQ to input. Clock in 9 bits of data, LSB first. Data valid on clock low (not obvious on the data sheets but found experimentally in lab)

Step 4. Find in the data sheets what commands you need to use to start a temperature conversion, read temperature, etc.

JSG

15

rev April 18, 2005

NDSU
Example:

SPI Communications

ECE 376

Write routines to Define the bits used in a more meaningful way that RC0... Send 8 bits to the DS1620 Receive 9 bits from the DS1620 Initialize the DS1620 to continuous temperature readings, and Read the temperature

a) Define the bits in a more meaningful way:


static bit DQ static bit CLK static bit RST @ ((unsigned)&PORTC*8+3); @ ((unsigned)&PORTC*8+2); @ ((unsigned)&PORTC*8+1); // DS1620 - DQ // DS1620 - CLK // DS1620 - RST

b) A subroutine to send 8 bits to the DS1620:


void DS1620_Write(unsigned char Data) { unsigned char i; TRISC1 = 0; TRISC2 = 0; TRISC3 = 0; CLK = 1; for (i=1; i<=8; i++) { DQ = (Data & 1); CLK = 0; CLK = 1; Data = Data >> 1; } } // set up data pin I/O // The PIC drives the data // line here // // // // // default is CLK=1 for the DS1620 Ship out each bit LSB first and pulse the clock line

c) A subroutine to read 9 bits from the DS1620


int DS1620_Read(void) { unsigned int Data; unsigned int Temp; unsigned char i; TRISC1 TRISC2 TRISC3 CLK Data Temp = = = = = = 0; 0; 1; 1; 0; 1;

// here, DQ is driven by the // DS1620

for (i=1; i<=9; i++) { CLK = 0;


JSG 16

// read in 9 bits // data valid when CLK=0


rev April 18, 2005

NDSU

SPI Communications

ECE 376

if (DQ == 1) Data += Temp; Temp = Temp * 2; CLK = 1; } return(Data); }

// data comes in LSB first

// after 9 bits return Data

A Subroutine to initialize the DS1620 to continuous conversion mode


void DS1620_Init(void) { RST = 0; RST = 1; DS1620_Write(0x0C); DS1620_Write(0x00); RST = 0; RST = 1; DS1620_Write(0xEE); RST = 0; }

// Start communications // with RST going high // x0C = Write Config Reg = 00 // (sets up continuous conv)

// xEE = Start T conversion

A subroutine to read the temperature


int DS1620_Read_Temperature(void) { int Data; RST = 0; RST = 1; DS1620_Write(0xAA); Data = DS1620_Read(); RST = 0; return(Data); } // Start communications // xAA = read T // next 9 bits = temp reading

JSG

17

rev April 18, 2005

You might also like