CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

how to transmit data in spi slave mode?
Goto page 1, 2, 3, 4  Next
 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Mon Jun 08, 2009 12:29 pm     Reply with quote

Here's a demo program that shows one way for an SPI master to read
single bytes from an SPI slave. It doesn't demonstrate writing data to
the slave (only reading). It sends commands to the slave and gets back
a data byte for each command.

Here's a typical output of the program, as displayed on a terminal
window on a PC:
Quote:

Commands to slave are:
A: Read ADC
B: Read switch
C: Read counter
Press a key to send a command

ADC value = 78
ADC value = 9E // Started turning trimpot knob
ADC value = AE
ADC value = CB
ADC value = F7
Counter = 00
Counter = 01
Counter = 02
Counter = 03
Counter = 04
Switch = 01
Switch = 01
Switch = 01
Switch = 01
Switch = 00 // Pressed and held down the switch
Switch = 00
Switch = 00
Switch = 01 // Released it
Switch = 01


Connections between the boards:
Code:

Master   Slave
SCLK --> SCLK
SDO  --> SDI
SDI  <-- SDO
\CS  --> \SS
GND <--> GND

Also, the Master board should be connected to a PC with a RS-232
cable, and a terminal window should be opened. This allows the user
to type in commands and see the data returned by the SPI slave.

This program is very simple. It doesn't use a complicated protocol.
Commands are non-zero bytes. The list is given in the source code
below. The master sends a command byte. This will be received
by the slave and it will get an #int_ssp interrupt immediately after
getting the byte. The switch-case statement in the isr will interpret
the command and put the response byte into the SSP buffer.

The master must wait for several microseconds to give the slave
enough time to respond, before the master requests the response byte.
This is done by the master calling the spi_read(0) function with a
parameter of 0. Because the parameter is 0, the switch-case statement
in the isr will not interpret it as a command. The previously filled SSP
buffer contents in the slave will be clocked into the master, and the
master will thus get the response byte. (An #int_ssp interrupt will
occur when the master requests the response byte, but the isr won't
do anything, since a "command" of 0x00 is ignored by the switch-case
code).

SPI Master:
Code:
// This program demonstrates an SPI Master that can
// read bytes from a SPI slave. (Single bytes only).

#include <16F877.H>
#fuses XT, NOWDT, NOPROTECT, BROWNOUT, PUT, NOLVP
#use delay(clock=4000000)
#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7, ERRORS)

#define SPI_SS   PIN_C2

// SPI mode definitions.
#define SPI_MODE_0  (SPI_L_TO_H | SPI_XMIT_L_TO_H)
#define SPI_MODE_1  (SPI_L_TO_H)
#define SPI_MODE_2  (SPI_H_TO_L)
#define SPI_MODE_3  (SPI_H_TO_L | SPI_XMIT_L_TO_H)


// SPI commands accepted by the SPI Slave PIC.
// The slave will respond to each of these commands
// with a single byte.   These must be the same
// values as defined in the Slave source code.
#define READ_ADC_CMD     1
#define READ_COUNTER_CMD 2
#define READ_SWITCH_CMD  3

//======================================
void main()
{
char c;
int8 result;

output_high(SPI_SS);  // Initial Slave Select to a high level

// Initialize the hardware SSP for SPI Master mode.
setup_spi(SPI_MASTER | SPI_MODE_0 | SPI_CLK_DIV_4);

printf("Commands to slave are:\n\r");
printf("A: Read ADC\n\r");
printf("B: Read switch\n\r");
printf("C: Read counter\n\r");
printf("Press a key to send a command\n\r");

// If the user presses a key on the PC, get the
// character, convert it to an SPI command byte,
// and send it to the slave.  Then get the response
// byte from the slave and display it.
while(1)
  {
   c = getc();
   c = toupper(c);
 
   switch(c)
    {
     case 'A':     // Read ADC on Slave PIC
       output_low(SPI_SS);
       spi_write(READ_ADC_CMD);  // Send command to slave
       output_high(SPI_SS);

       delay_us(100);  // Give slave some time to respond

       output_low(SPI_SS);
       result = spi_read(0);     // Read response from slave
       output_high(SPI_SS);
   
       printf("ADC value = %X \n\r", result);
       break;

     case 'B':     // Read switch on Slave PIC
       output_low(SPI_SS);
       spi_write(READ_SWITCH_CMD); // Send command to slave
       output_high(SPI_SS);

       delay_us(100);

       output_low(SPI_SS);
       result = spi_read(0);     // Read response from slave
       output_high(SPI_SS);
   
       printf("Switch = %X \n\r", result);
       break;


     case 'C':     // Read counter on Slave PIC
       output_low(SPI_SS);
       spi_write(READ_COUNTER_CMD); // Send command to slave
       output_high(SPI_SS);

       delay_us(100);

       output_low(SPI_SS);
       result = spi_read(0);     // Read response from slave
       output_high(SPI_SS);
   
       printf("Counter = %X \n\r", result);
       break;

     
     default:
       printf("%c is an invalid command character.\n\r", c);
       break;
    }

  }

}


SPI Slave:
Code:

// This program demonstrates an SPI slave that can
// be read by the master.   Three different types
// of data (single bytes only) are returned upon
// command by the master.

#include <16F877.H>
#device adc=8
#fuses XT, NOWDT, NOPROTECT, BROWNOUT, PUT, NOLVP
#use delay(clock=4000000)
#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7, ERRORS)


#byte SSPBUF = 0x13  // Register address for 16F877


// SPI modes for setup_spi() function.
#define SPI_MODE_0  (SPI_L_TO_H | SPI_XMIT_L_TO_H)
#define SPI_MODE_1  (SPI_L_TO_H)
#define SPI_MODE_2  (SPI_H_TO_L)
#define SPI_MODE_3  (SPI_H_TO_L | SPI_XMIT_L_TO_H)

// SPI commands accepted by this slave program.
// These must be non-zero values.
#define READ_ADC_CMD     1
#define READ_COUNTER_CMD 2
#define READ_SWITCH_CMD  3

// There is a push-button switch on pin B0, with a
// pull-up resistor on the pin.  Pressing the switch
// puts ground on the PIC pin.
#define SWITCH_PIN  PIN_B0

// This global variable is accessed both in the #int_ssp isr
// and in main().  It holds the current reading of the ADC.
int8 adc_result;

//----------------------------
// An int_ssp interrupt will occur whenever the SPI master
// transmits a byte to this slave PIC.   The following
// interrupt service routine (isr) decodes the command byte
// and sends back the appropriate response byte to the master.
// After sending the command byte with spi_write(), the master
// must call spi_read(0) to get the data byte.

#int_ssp
void ssp_isr(void)
{
int8 command;
static int8 counter = 0;

command = SSPBUF;

switch(command)   // Act on it
  {
   case READ_ADC_CMD:
     SSPBUF = adc_result;
     break;

   case READ_COUNTER_CMD:
     SSPBUF = counter;
     counter++;         
     break; 

   case READ_SWITCH_CMD:
     SSPBUF = input(SWITCH_PIN);
     break;
  }

}


//======================================
void main()
{
// Setup the ADC to read the voltage from the trimpot
// connected to pin A0.
setup_adc_ports(AN0);
setup_adc(ADC_CLOCK_DIV_8);
set_adc_channel(0);
delay_us(20);
adc_result = read_adc();

// Initialize the hardware SSP for SPI Slave mode 0.
setup_spi(SPI_SLAVE | SPI_MODE_0);

// Enable interrupts for the SPI slave.
clear_interrupt(INT_SSP);
enable_interrupts(INT_SSP);
enable_interrupts(GLOBAL);

// Update ADC value every 100 ms.
while(1)
  {
   adc_result = read_adc();
   delay_ms(100);
  }

}
starfire151



Joined: 01 Apr 2007
Posts: 195

View user's profile Send private message

PostPosted: Mon Jun 08, 2009 1:49 pm     Reply with quote

Thanks very much for this. I will test this out.
germanr52
Guest







Excellent!
PostPosted: Sat Sep 05, 2009 3:34 pm     Reply with quote

This code works very well. Show bidirectional communications, use interrupt. It solves all my questions.

Thank you PCM Programmer
nia



Joined: 23 Dec 2010
Posts: 8

View user's profile Send private message

PostPosted: Thu Dec 23, 2010 1:04 am     Reply with quote

Yes. this program works well. thanks PCM Programmer.

if i were to read continuous ADC from slave and pass to master, is there a faster way (because we have to provide delay_us(100); // Give slave some time to respond) ?

my objective is to continuously read real-time adc from 40 slave PIC and pass the results to master. if i reduce delay to 10us, the result returned false data.
Ttelmah



Joined: 11 Mar 2010
Posts: 19477

View user's profile Send private message

PostPosted: Thu Dec 23, 2010 2:51 am     Reply with quote

The time needed for the delay, depends on the speed of the slave, and what else it may be doing.
Typically it takes about 30 instructions to get 'into' an ISR. The slave code then has to read the command byte, and do a branch dependant on the value (perhaps 20 instructions), so with a 4MHz slave, I'd suggest that 75uSec for the delay would probably be OK. 100uSec, is a 'slightly larger, safe value'. However if the slave was running at 20MHz for example, though the number of instructions needed remains the same, the time needed would only be 1/5th.
Conversely though, if the slave was also doing some other job, like handling serial using an ISR, the 'worst case' instruction count, would be where it has just entered the serial ISR at the moment the SPI request arrives, where you would have to allow time for it to complete this ISR, exit, and then have the new ISR called.....
Remember though you can commonly provide a delay, by doing something else. So (for example), it'd perhaps make sense to see if you could do something like send the SPI command, and then write the 'header' text to your display, then read the value, and write the actual returned data. That way you are making 'use' of the time needed doing something worthwhile.

Best Wishes
nia



Joined: 23 Dec 2010
Posts: 8

View user's profile Send private message

PostPosted: Thu Dec 23, 2010 5:22 am     Reply with quote

thanks. i gonna try it and let you know the results.
nia



Joined: 23 Dec 2010
Posts: 8

View user's profile Send private message

PostPosted: Fri Dec 24, 2010 1:53 am     Reply with quote

I changed to PIC18F13K22 and use the internal 64MHZ frc. However, the delay_us(100) can only reduced to 26us. If I reduced to 25us, the adc result passed to master will be incorrect. So, I guess the delay also provide some room for adc conversion to complete. Thanks Ttelmah!!!

To overcome the problem, I decided to use ADS7884, an adc with 3Msps and SPI interface.

I have one more question. I am about to interface the with 70 SPI slave devices. Do I need to put buffer/pull up for sck, sdo, sdi or cs lines? Each slave will be on their own pcb.

Just to share my code as below.

Is my internal 64MHz PLL declaration is correct? Any code improvement suggestion?

MASTER:
Code:
#include <18F4580.h>
#fuses HS,NOWDT,NOPROTECT,NOLVP
#use delay(clock=20000000)
#use rs232(baud=38400, xmit=PIN_C6, rcv=PIN_C7)

int8 instr;

void main() {
   output_bit( PIN_E0, 1);
   output_bit( PIN_E1, 1);
   output_bit( PIN_E2, 1);
   
setup_spi(SPI_MASTER | SPI_L_TO_H | SPI_XMIT_L_TO_H);

   while(true)
   {

////////////////interlock switch///////////////////////
//Simulate swicth pressing
   while (input(PIN_B0));     //while HIGH stay here.
   while (!input(PIN_B0));     //while LOW stay here.
///////////////////////////////////////////////////////


   
/////////////////////////////////////////////////// Slave 1   
      output_bit( PIN_E0, 0);
      spi_write(0x01);                    //send dummy ack to slave
      output_bit( PIN_E0, 1);
     
      delay_us(30);                        // Give slave some time to respond
     
      while(!spi_data_is_in());
     
      output_bit( PIN_E0, 0);
      instr = spi_read(0);
      output_bit( PIN_E0, 1);
     
      putc(instr);
     
//////////////////////////////////////////// Slave 2
      output_bit( PIN_E1, 0);
      spi_write(0x01);                    //send dummy ack to slave
      output_bit( PIN_E1, 1);
     
      delay_us(30);                        // Give slave some time to respond
     
      while(!spi_data_is_in());
     
      output_bit( PIN_E1, 0);
      instr = spi_read(0);
      output_bit( PIN_E1, 1);
     
      putc(instr);
     
////////////////////////////////////////////// Slave 3
      output_bit( PIN_E2, 0);
      spi_write(0x01);                    //send dummy ack to slave
      output_bit( PIN_E2, 1);
     
      delay_us(30);                        // Give slave some time to respond
     
      while(!spi_data_is_in());
     
      output_bit( PIN_E2, 0);
      instr = spi_read(0);
      output_bit( PIN_E2, 1);
     
      putc(instr);
     
//////////////////////////////////////////////// END

   }

}


SLAVE:
Code:
#include <18F13K22.h>
#device adc=8
#fuses INTRC,NOWDT,NOLVP
#use delay(clock=16M, internal)
#byte SSPBUF = 0x0FC9                      // Register address for 18F13K22

unsigned int8 adc_result, command;


/////////////Interrupt SPI///////////////////////
#int_ssp
void ssp_isr(void)

   command = SSPBUF;                         //read dummy sent by master

   adc_result = Read_ADC();
   
   SSPBUF = adc_result;                      //pass adc result to spi buffer

}
/////////////////////////////////////////////////////


void main(void)

   setup_oscillator(OSC_64MHZ);              //sets the internal oscillator to 64MHz (PLL enabled)
 
   setup_adc( ADC_CLOCK_INTERNAL );
   setup_adc_ports (sAN0);
   set_adc_channel( 0 );
   delay_us(100);

   setup_spi(SPI_SLAVE | SPI_L_TO_H | SPI_XMIT_L_TO_H);

// Enable interrupts for the SPI slave.
   clear_interrupt(INT_SSP);
   enable_interrupts(INT_SSP);
   enable_interrupts(GLOBAL);


   while(true)
   {   
   }
   
   
}
Ttelmah



Joined: 11 Mar 2010
Posts: 19477

View user's profile Send private message

PostPosted: Fri Dec 24, 2010 5:56 am     Reply with quote

The reason for your need for such a long delay, is doing the ADC conversion in the interrupt. The ADC conversion takes 12 cycles of the ADC clock. Move the conversion into the main loop. Just have this taking a reading every few mSec, and putting the value into a variable. Then in the SSP code, just transfer the contents of this byte. You will find your delay can plummet...
Also though, read the data sheet. Is ADC_CLOCK_INTERNAL, legal at 16MHz.....

Best Wishes
hayee



Joined: 05 Sep 2007
Posts: 252

View user's profile Send private message

PostPosted: Tue Mar 08, 2011 12:01 am     Reply with quote

I simply want to send data through Master to Slave, no response or feedback from slave is required. Also at the slave side I want to use interrupt, so that whenever master sends data slave receive's the data.
My question is that which mode I select for transmitting data as in the PCM programmer code there are 4 mode, I think I have to use mode 1 because it is unidirectional data transfer. What changes I have to do in interrupt routine for receiving data?
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Tue Mar 08, 2011 12:13 am     Reply with quote

This thread has a SPI slave example that only receives data from the
master. It saves the data in a circular buffer.
http://www.ccsinfo.com/forum/viewtopic.php?t=26888
The program also has code for a software SPI master. You can remove
the master routines.

There are 4 SPI modes. They have nothing to do with the direction
of transfer. Just use Mode 0.
hayee



Joined: 05 Sep 2007
Posts: 252

View user's profile Send private message

PostPosted: Tue Mar 08, 2011 12:51 am     Reply with quote

Thanks PCM Programmer
Can you give me a code which is based on hardware, the given link uses a software spi.
Also I want to send 3 floating numbers not integers from master to slave, kindly give examples for that also.
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Tue Mar 08, 2011 12:53 am     Reply with quote

The slave side uses hardware SPI.

I don't want to write the program for you. I only wanted to post the
example code.
hayee



Joined: 05 Sep 2007
Posts: 252

View user's profile Send private message

PostPosted: Tue Mar 08, 2011 1:00 am     Reply with quote

I am not trying to get the exact code, what i asked is an example routine for how to handle the floating numbers.
hayee



Joined: 05 Sep 2007
Posts: 252

View user's profile Send private message

PostPosted: Tue Mar 08, 2011 1:26 am     Reply with quote

I have done it a long time ago. Send the floating number from master to slave but it was not interrupt driven, but know I want it to do interrupt based.
I done something like this

For breaking float into byte I used the following macro
Code:

#define GetByte(x, offset)\
*((int8 )&x +offset)


for sending data i done like that
Code:

b0=GetByte(value1,0);
  b1=GetByte(value1,1);
  b2=GetByte(value1,2);
  b3=GetByte(value1,3);
     
  spi_write(b0);
  spi_write(b1);
  spi_write(b2);
  spi_write(b3);


At the slave side for combining byte into float I use this macro
Code:

#define MakeDWord(x, b3, b2, b1, b0)  \
*(int8 )&x = b0;      \
*((int8 )&x +1) = b1; \
*((int8 )&x +2) = b2; \
*((int8 )&x +3) = b3


and read the data like that
Code:

float readnum(void)
{
   int8 b0,b1,b2,b3;
   float result;
   
   output_low(PIN_A5);
   delay_us(100);
   b0=spi_read(0);
   b1=spi_read(0);
   b2=spi_read(0);
   b3=spi_read(0);
   MakeDWord(result, b3, b2, b1, b0);
   //printf("5 %f \n\r",result);
   output_high(PIN_A5);
   return result;
}

What I have to do is that I want to use interrupt for receiving data because both master and slave performing other tasks so it is better to use interrupt.
hayee



Joined: 05 Sep 2007
Posts: 252

View user's profile Send private message

PostPosted: Tue Mar 08, 2011 2:27 am     Reply with quote

How much time this loop will take if I am running processor at 20Mhz? My controller is 18f252.
Code:

b0=GetByte(value1,0);
b1=GetByte(value1,1);
b2=GetByte(value1,2);
b3=GetByte(value1,3);
     
spi_write(b0);
spi_write(b1);
spi_write(b2);
spi_write(b3);


At the receiver side if I use interrupt then the data received in SSPBUF buffer. I have to save that data in a variable which takes some time, so how much delay is required between two spi_write commands in order that I can transfer data from SSPBUF to variable?
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Goto page 1, 2, 3, 4  Next
Page 1 of 4

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group