![](templates/subSilver/images/CCSLogo.jpg) |
![CCS C Software and Maintenance Offers](templates/subSilver/images/forumAd6.jpg) |
View previous topic :: View next topic |
Author |
Message |
hammale
Joined: 15 May 2017 Posts: 8
|
dsPIC33 SPI Slave Interupt spi_read() Not Reading |
Posted: Mon May 15, 2017 5:34 am |
|
|
Hello,
I am using two dsPIC33EV256GM104 processors as a master and slave with SPI but am having troubles implementing an interrupt driven slave. I am currently using PCD compiler v5.071c. I am trying to send a one byte command from the master to the slave and have the slave respond with 4 bytes. This is my master code:
spi_master.c
Code: |
#include <spi_master.h>
void main(){
unsigned int8 b0,b1,b2,b3;
output_high(PIN_C4);
while(TRUE){
output_low(PIN_C4);//chip select pin
spi_xfer(0xFF);//slave should read the 0xFF but does not
b3 = spi_xfer(0);
b2 = spi_xfer(0);
b1 = spi_xfer(0);
b0 = spi_xfer(0);
output_high(PIN_C4);
signed int32 data = make32(b3,b2,b1,b0);//rebuild and send data via uart
fprintf(UART_PORT1,"Data %ld\n", data);
delay_ms(100);
}
}
|
spi_master.h
Code: |
#include <33EV256GM104.h>
#device ICSP=1
#use delay(internal=20000000)
#FUSES NOWDT //No Watch Dog Timer
#FUSES CKSFSM //Clock Switching is enabled, fail Safe clock monitor is enabled
#FUSES NOBROWNOUT //No brownout reset
#pin_select U1TX=PIN_B4
#pin_select U1RX=PIN_A8
#use rs232(UART1, baud=9600, stream=UART_PORT1)
#use spi(MASTER, SPI1, BAUD=100000, MODE=1, BITS=8, stream=SPI_PORT1)
|
And the slave:
spi_slave.c
Code: |
#include <spi_slave.h>
unsigned int8 cmd = 0;
signed int32 bigData = -2000000;
#INT_SPI1
void spi1_isr(void) {
if (input(PIN_C4) == 0) {//chip select
disable_interrupts(INT_SPI1);
cmd = spi_read();//this doesn't seem to be reading the received byte
if (cmd == 0xFF) {
spi_write(make8(bigData, 3));
spi_write(make8(bigData, 2));
spi_write(make8(bigData, 1));
spi_write(make8(bigData, 0));
}
clear_interrupt(INT_SPI1);
enable_interrupts(INT_SPI1);
}
}
void main() {
clear_interrupt(INT_SPI1);
enable_interrupts(INT_SPI1);
enable_interrupts(INTR_GLOBAL);
while (TRUE) { }
|
spi_slave.h
Code: |
#include <33EV256GM104.h>
#device ICSP=1
#use delay(internal=20000000)
#FUSES NOWDT //No Watch Dog Timer
#FUSES CKSFSM //Clock Switching is enabled, fail Safe clock monitor is enabled
#FUSES NOBROWNOUT //No brownout reset
#use spi(SLAVE, SPI1, MODE=1, BITS=8, stream=SPI_PORT1)
|
The master code seems to be working fine but the problem is with my slave. No matter what data I have the master send with the initial spi_xfer(xx) the slave never seems to receive it, the variable remains unchanged. If I remove the check for "cmd == 0xFF" in the slave the data is sent correctly but I need to be able to receive as well as send data. Below is a shot from a digital oscope (D2 is MOSI and D3 is MISO):
I am relatively new to working with SPI but everything looks correct to me. Am I missing something here? I have tried changing the baud rate of the master and using the spi_xfer_in() command in place of the spi_read() with no success. If I remove the interrupt and place the code in the main loop like:
Code: |
while (TRUE) {
if (input(PIN_C4) == 0) {//chip select
cmd = spi_read();
if (cmd == 0xFF) {
spi_write(make8(bigData, 3));
spi_write(make8(bigData, 2));
spi_write(make8(bigData, 1));
spi_write(make8(bigData, 0));
}
}
}
|
Everything works fine and I am able to read the initial 0xFF but I would like to move to an interrupt based solution. Any thoughts? |
|
![](templates/subSilver/images/spacer.gif) |
jeremiah
Joined: 20 Jul 2010 Posts: 1329
|
|
Posted: Mon May 15, 2017 7:28 am |
|
|
Lets try cleaning up some stuff first:
1. You don't need to disable the SPI interrupt while in it. The HW does this for you.
2. You don't need to clear the interrupt flag in the interrupt. CCS does that for you unless you manually turn that off.
3. You are mixing spi_read() with #use spi, which is a no no. It might work or it might not. It is not supported. If you use #use spi, then you need to use spi_xfer() which does both reading and writng. If you really want to use the spi_read() function, then get rid of #use spi and use setup_spi() instead. If so, you'll need to correctly set the mode (there isn't a MODE define for setup_spi(), so you'll have to pick the right options to get it). |
|
![](templates/subSilver/images/spacer.gif) |
hammale
Joined: 15 May 2017 Posts: 8
|
|
Posted: Mon May 15, 2017 9:00 am |
|
|
jeremiah wrote: | Lets try cleaning up some stuff first:
1. You don't need to disable the SPI interrupt while in it. The HW does this for you.
2. You don't need to clear the interrupt flag in the interrupt. CCS does that for you unless you manually turn that off.
3. You are mixing spi_read() with #use spi, which is a no no. It might work or it might not. It is not supported. If you use #use spi, then you need to use spi_xfer() which does both reading and writng. If you really want to use the spi_read() function, then get rid of #use spi and use setup_spi() instead. If so, you'll need to correctly set the mode (there isn't a MODE define for setup_spi(), so you'll have to pick the right options to get it). |
Thanks for the quick reply. I changed my SPI initilization to the following:
Code: | setup_spi(SPI_SLAVE | SPI_L_TO_H | SPI_SS_DISABLED); | and cleaned up my ISR as you suggested:
Code: | if (input(PIN_C4) == 0) {
cmd = spi_read();
if (cmd == 0xFF) {
spi_write(make8(bigData, 3));
spi_write(make8(bigData, 2));
spi_write(make8(bigData, 1));
spi_write( make8(bigData, 0));
}
} |
The code now works for the first read but then returns to outputting 0 at "cmd = spi_read()" forever. I added an interrupt to toggle a digital pin on SPI1E (SPI 1 error) and it is being triggered immediately after the first 8 clock pulses. I'm assuming this error is what is causing my problem. Do you have any idea what this error could be? I am relatively new to CCS and PIC processors in general, is there a way to debug this error? |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19384
|
|
Posted: Mon May 15, 2017 11:03 am |
|
|
A slave, only sends when the data is clocked out by the master.
So when you write in the SPI routine, a byte is loaded 'waiting to send', and this won't actually be sent, till the master sends you a byte.
You only need to load another, once the master sends you another byte (so the spi receive interrupt triggers again). When this happens, you need to read the incoming byte, then send the next byte.
Now outside the interrupt you can send the four bytes, but each slave send then waits for the master to send another byte.
Look at ex_spi_slave.c
You will see that this reads the byte received each time. Then if it needs to send data, it uses 'spi_prewrite', to load this into the output buffer (which is then sent on the next transmission from the master). |
|
![](templates/subSilver/images/spacer.gif) |
hammale
Joined: 15 May 2017 Posts: 8
|
|
Posted: Mon May 15, 2017 4:46 pm |
|
|
Ttelmah wrote: | A slave, only sends when the data is clocked out by the master.
So when you write in the SPI routine, a byte is loaded 'waiting to send', and this won't actually be sent, till the master sends you a byte.
You only need to load another, once the master sends you another byte (so the spi receive interrupt triggers again). When this happens, you need to read the incoming byte, then send the next byte.
Now outside the interrupt you can send the four bytes, but each slave send then waits for the master to send another byte.
Look at ex_spi_slave.c
You will see that this reads the byte received each time. Then if it needs to send data, it uses 'spi_prewrite', to load this into the output buffer (which is then sent on the next transmission from the master). |
I think I understand what you are getting at, I changed my ISR to the following (went back to #USE SPI so I could utilize spi_xfer & spi_prewrite):
Code: |
unsigned int8 data, lastCmd, dataPos = 0;
signed int32 bigData = -2000000;
#INT_SPI1
void spi1_isr(void) {
if (input(PIN_C4) == 0) {
data = spi_xfer_in();
if (dataPos == 0) {
lastCmd = data;
}
if (lastCmd == 0xFF) {
if (dataPos == 0) {
spi_prewrite(make8(bigData, 3));
} else if (dataPos == 1) {
spi_prewrite(make8(bigData, 2));
} else if (dataPos == 2) {
spi_prewrite(make8(bigData, 1));
} else if (dataPos == 3) {
spi_prewrite(make8(bigData, 0));
}
}
if (dataPos >= 3) {
dataPos = 0;
} else {
dataPos++;
}
}
}
|
with the following in my header to initialize SPI:
Code: | #use spi(SLAVE, SPI1, MODE=1, BITS=8, stream=SPI_PORT1) |
Unfortunately this code is exhibiting the exact same behavior, the first read is correct but it's all 0's after. The SPI error interrupt is still firing as well. I apologize if I am missing something simple here, I will continue to work with this code but I'd appreciate any feedback. |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19384
|
|
Posted: Tue May 16, 2017 1:03 am |
|
|
The interrupt must _always_ read the data.
The interrupt is saying 'there is a character needing to be read'.
You are ignoring this if PIN_C4 is high. This will mean that the peripheral won't interrupt again, since it'll say "he is ignoring me"...
Then the syntax will be:
data = spi_xfer_in(SPI_PORT1);
and
spi_prewrite(SPI_PORT1, make8(bigData, 0)); |
|
![](templates/subSilver/images/spacer.gif) |
hammale
Joined: 15 May 2017 Posts: 8
|
|
Posted: Tue May 16, 2017 2:26 am |
|
|
Ttelmah wrote: | The interrupt must _always_ read the data.
The interrupt is saying 'there is a character needing to be read'.
You are ignoring this if PIN_C4 is high. This will mean that the peripheral won't interrupt again, since it'll say "he is ignoring me"...
Then the syntax will be:
Code: | data = spi_xfer_in(SPI_PORT1); |
and
spi_prewrite(SPI_PORT1, make8(bigData, 0)); |
Wow that's a stupid mistake I should have caught, thanks! One minor correction with your statment above, the proper syntax for spi_xfer_in() to include the stream as per the manual is:
Code: | data = spi_xfer_in(SPI_PORT1, 8);//where 8 is the number of bits read |
without specifying the 8 I received unexpected results. For anyone who is curious the final slave ISR is as follows:
Code: | #INT_SPI1
void spi1_isr(void) {
data = spi_xfer_in(SPI_PORT1, 8);
if (input(PIN_C4) == 0) {//chip select
if (dataPos == 0) {
lastCmd = data;
}
if (lastCmd == 0xFF) {
if (dataPos == 0) {
spi_prewrite(SPI_PORT1,make8(bigData, 3));
} else if (dataPos == 1) {
spi_prewrite(SPI_PORT1,make8(bigData 2));
} else if (dataPos == 2) {
spi_prewrite(SPI_PORT1,make8(bigData, 1));
} else if (dataPos == 3) {
spi_prewrite(SPI_PORT1,make8(bigData, 0));
}
}
if (dataPos >= 3) {
dataPos = 0;
} else {
dataPos++;
}
}
} |
While there is some room for optimization, it illustrates the general idea. Thanks again! |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19384
|
|
Posted: Tue May 16, 2017 2:34 am |
|
|
I must admit I always specify the transfer length, but usually leave the setup in the #USE for length at the default (32 bits)....
It is meant to automatically handle the reduced length, it you specify bits=8 as you show, so slightly surprised it needs to have the length specified. However the PIC24/33 are very different in several areas here, since the default word length is 16bits for may things.
Glad it is working now. ![Smile](images/smiles/icon_smile.gif) |
|
![](templates/subSilver/images/spacer.gif) |
hammale
Joined: 15 May 2017 Posts: 8
|
|
Posted: Tue May 16, 2017 3:45 pm |
|
|
Ran into another issue with this code today. My system has 8 of these processors daisy chained together. While the previous code works perfectly with only one master and slave, when I connect a second slave the master only reads 0. My slave ISR is still firing correctly and as soon as I unplug the second slave things return to normal. I tried adding an output_float() with the MISO line at the end of the slave ISR but that had no effect. Any ideas? |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19384
|
|
Posted: Wed May 17, 2017 1:47 am |
|
|
For SPI to have multiple devices, with common lines, there has to be a slave select.
That's the point of the slave select option in the SSP setup. This must use the hardware pin. Then the slave will only enable it's output transceivers when selected.
If the device is supporting multiple slaves without a select, or proper daisy chaining, then it isn't actually normal SPI. Instead it's another form of synchronous bus....
Daisy chaining with SPI, has the data fed sequentially on from device to device. Not the wires all connected together (which seems to be what you are talking about....).
When you load a byte, it is in the SPI registers and only sends when the master clocks it out. The release would need to happen after this has occurred. Problem is that to release the bus, you would have to disable the SPI peripheral. However if you do this it won't then receive....
The only way it can receive is if it has been left enabled but deselected (with the slave select).
Look at the module block diagram for the SPI "SPIx MODULE BLOCK DIAGRAM". You will see that the SPI directly drives the output line, with the buffer controlled by the SS line.
You can disable the peripheral (look at the 'NOINIT' option, and the spi_init command), but this then disables the receiver as well.
One way would be to enable the SS option, and connect the pin to a non used I/O pin. Then when the command is seen enable this line (so the buffer gets turned on - set the pin low), then send you four bytes, and the interrupt _after_ this, turn off the line (set it high).
The other way multiple devices are done, is daisy chaining. Here the MOSI/MISO lines daisy chain from device to device, and the first device responds to the first command, and then 'hands on' the bytes as they overflow to the next device. The last slave has it's output line fed back to the master's input.
If you want to send to multiple devices, then consider switching to a multi device bus. I2C. Slower yes, but designed to support multiple devices without the complexity of sequential daisy chaining, or device select lines for every unit... |
|
![](templates/subSilver/images/spacer.gif) |
temtronic
Joined: 01 Jul 2010 Posts: 9178 Location: Greensville,Ontario
|
|
Posted: Wed May 17, 2017 5:01 am |
|
|
Perhaps you should consider RS-485 for the communications ? Daisy chaining SPI isn't the 'best' solution,as Mr.T points out due to timing though we don't know what speed or throughput you need. Also SPI isn't a 'long distance' bus unlike RS485( or others). It's fine for several devices on one PCB...2-3 inches NOT 2-3 feet or meters !
Even 'RS-232' might be an option,only 2 pins needed,reduced code to access the remotes.
options, always think of options before committing to the PCB !
Jay |
|
![](templates/subSilver/images/spacer.gif) |
hammale
Joined: 15 May 2017 Posts: 8
|
|
Posted: Wed May 17, 2017 5:14 am |
|
|
Thanks for the awesome feedback it really helps. Unfortunately I am stuck with my current hardware so I will have to find a way to "make it work". I have 4 SPI slaves connected in series (not proper daisy chaining" as mentioned above) with 4 separate chip select lines all wired back to the master. The problem I am facing now is how to use these SS lines with the #use_spi directive. Using the hardware SPI1 any pin I define to be chip select on the slave returns a compilation error ("invaid SS"). If I use the "FORCE SW" directive my spi_xfer_in and spi_prewrite commands no longer work. Is there a way to still use the hardware SPI1 but define my own chip select line?
EDIT: Just noticed Ttelmah's comment about "faking out" the hardware SS pin, I think I'm going to give that a try. |
|
![](templates/subSilver/images/spacer.gif) |
hammale
Joined: 15 May 2017 Posts: 8
|
|
Posted: Wed May 17, 2017 6:41 am |
|
|
So I enabled the SS pin in my slave #USE SPI directive (pin B0 on my chip) and modified the ISR to be the following:
Code: |
#INT_SPI1
void spi1_isr(void) {
data = spi_xfer_in(SPI_PORT1, 8);
if(dataPos >= 3){
output_high(PIN_B0);//deactivate hardware SS
dataPos = 0;
}
if (input(PIN_C4) == 0) {//my custom chip select
if (dataPos == 0) {
output_low(PIN_B0);//activate hardware SS
lastCmd = data;
}
//process command
if (lastCmd == 0xFF) {
if (dataPos == 0) {
spi_prewrite(SPI_PORT1,make8(bigData, 3));
} else if (dataPos == 1) {
spi_prewrite(SPI_PORT1,make8(bigData, 2));
} else if (dataPos == 2) {
spi_prewrite(SPI_PORT1,make8(bigData, 1));
} else if (dataPos == 3) {
spi_prewrite(SPI_PORT1,make8(bigData, 0));
}
//move position
dataPos++;
}
}
}
|
The SPI ISR doesn't seem to be firing at all anymore. I tried adding an "output_high(PIN_B0)" to my startup code but that didn't change anything. Thanks again for all your help so far, any thoughts on this? I should probably hook up an oscope and check the signals... |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19384
|
|
Posted: Wed May 17, 2017 7:10 am |
|
|
If you have slave select lines, then there is no problem....
To use select with the #use spi, you need to have done a PIN SELECT first for the select pin. However you may find it better if you are doing the multi byte transmissions, to cheat a little and not use this. The standard select is a 'per byte' select option.
1) Setup the #USE with the 'noinit' option. This leaves the SPI 'off'.
So (for example):
#USE SPI (SPI1, SLAVE, MODE=0, NOINIT, FORCE_HW, STREAM=SPIIN)
2) Connect the slave select to an input on the slave PIC that can generate an interrupt (on a falling edge).
3) Have this interrupt contain the line 'spi_init(TRUE);' that will enable the slave, and then disable this interrupt.
4) Then at the master. Drop the select. wait a short period (enough time for the slave to have got into the 'enable' interrupt), and do your SPI transaction.
5) Once the last transaction is done, raise the select line.
6) In the slave, after the last transaction, have the command 'spi_init(FALSE)', and then make sure the select has gone high, and when it does clear, and re-enable the input interrupt.
This way the SPI on each slave will be 'off' till the select line drops, and then be disabled again after the transaction.
Last edited by Ttelmah on Wed May 17, 2017 7:25 am; edited 1 time in total |
|
![](templates/subSilver/images/spacer.gif) |
hammale
Joined: 15 May 2017 Posts: 8
|
|
Posted: Wed May 17, 2017 7:23 am |
|
|
Ttelmah wrote: | If you have slave select lines, then there is no problem....
To use select with the #use spi, you need to have done a PIN SELECT first for the select pin. However you may find it better if you are doing the multi byte transmissions, to cheat a little and not use this. The standard select is a 'per byte' select option.
1) Setup the #USE with the 'noinit' option. This leaves the SPI 'off'.
So (for example):
#USE SPI (SPI1, SLAVE, MODE=0, NOINIT, FORCE_HW, STREAM=SPIIN)
2) Connect the slave select to an input on the slave PIC that can generate an interrupt.
3) Have this interrupt contain the line 'spi_init(TRUE);' that will enable the slave, and then disable this interrupt.
4) Then at the master. Drop the select. wait a short period (enough time for the slave to have got into the 'enable' interrupt), and do your SPI transaction.
5) Once the last transaction is done, raise the select line.
6) In the slave, after the last transaction, have the command 'spi_init(FALSE)', and then make sure the select has gone high, and when it does clear, and re-enable the input interrupt. |
Perfect thanks for the suggestion I'll give it a shot in a bit. There is no pin select to set the SS pin for SPI1, the only option in the included header is for SPI2. Thanks again I'll be sure to post results in a bit! |
|
![](templates/subSilver/images/spacer.gif) |
|
|
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
|