|
|
View previous topic :: View next topic |
Author |
Message |
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Mon Jun 08, 2009 12:29 pm |
|
|
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
|
|
Posted: Mon Jun 08, 2009 1:49 pm |
|
|
Thanks very much for this. I will test this out. |
|
|
germanr52 Guest
|
Excellent! |
Posted: Sat Sep 05, 2009 3:34 pm |
|
|
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
|
|
Posted: Thu Dec 23, 2010 1:04 am |
|
|
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: 19504
|
|
Posted: Thu Dec 23, 2010 2:51 am |
|
|
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
|
|
Posted: Thu Dec 23, 2010 5:22 am |
|
|
thanks. i gonna try it and let you know the results. |
|
|
nia
Joined: 23 Dec 2010 Posts: 8
|
|
Posted: Fri Dec 24, 2010 1:53 am |
|
|
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: 19504
|
|
Posted: Fri Dec 24, 2010 5:56 am |
|
|
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
|
|
Posted: Tue Mar 08, 2011 12:01 am |
|
|
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
|
|
Posted: Tue Mar 08, 2011 12:13 am |
|
|
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
|
|
Posted: Tue Mar 08, 2011 12:51 am |
|
|
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
|
|
Posted: Tue Mar 08, 2011 12:53 am |
|
|
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
|
|
Posted: Tue Mar 08, 2011 1:00 am |
|
|
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
|
|
Posted: Tue Mar 08, 2011 1:26 am |
|
|
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
|
|
Posted: Tue Mar 08, 2011 2:27 am |
|
|
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? |
|
|
|
|
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
|