Using the Uno, I would like to read a bit stream that is synced with an externally generated 1MHz clock. Is this possible? I cannot control the data source. The data comes in packets with a start-byte and an end-byte, which could be used for syncing, but using an Uno digital input pin is too slow, isn't it?
SPI presents a sync issue and there are no stop bits between bytes in the signal for use with serial.
Is there some other method I could use?
SPI presents a sync issue and there are no stop bits between bytes in the signal for use with serial.
Is there some other method I could use?
SPI doesn't use stop bits or start bits. In slave mode it can be externally clocked. The only problem (not a big one) is that the standard library doesn't support the slave mode of SPI.
Do you have more information about the serial data you wanna read?
standard library doesn't support slave mode
Maybe the library doesn't but it is certainly possible to program a slave for responding.
The lower half of Nick Gammon's page here has examples for a master Arduino and slave Arduino communicating.
Read all the way at the bottom for a discussion of clock rates.
Here is the code I tried. I want to just read in the data and spit it out on serial to prove that I'm receiving correctly.
#include <SPI.h>
byte data;
void setup() {
Serial.begin(115200);
SPCR=B11100001; //The bitshift commands in the example confused me so I just set the register outright.
//Interrupt enable, SPI enable, LSB first, slave mode, idle low, rising edge, 1MHz
SPI.attachInterrupt(); //Is this redundant with setting the bit in the CR?
}
ISR(SPI_stc_vect) {
data = SPDR;
Serial.write(data);
}
void loop() {
}
When I upload the program I get nothing on the serial. It seems to me that the interrupt is not occurring.
I have SS tied to GND, the data coming in on pin 11, clock in on pin 13.
The input clock is 1MHz and runs continuously. The data is 15 words every 2.5ms with about 2ms of that idle (high). Do I need to toggle SS between packets?
I also tried running a program that just continuously read SPDR and spit it out on serial. I would get a repeated pattern of 26 bytes of 0xFF and then three bytes of random data. The three bytes of random data would go away when I disconnected either the clock line or the data line, so some data is being received.
AncillaryRedoubt:
SPI.attachInterrupt(); //Is this redundant with setting the bit in the CR?
Yes it is, SPI.attachInterrupt() just sets the SPIE bit in SPCR.
ISR(SPI_stc_vect)
I might try ISR(SPI_STC_vect) ... probably case-sensitive.
Once the slave sees SS go low, the clock edges start the transfer of bytes.
I don't know if a byte at a time is expected, or if a stream of bytes can be accepted.
SS must go when the byte is over.
Does the SPI slave care what the frequency of the input clock is? My 1MHz clock is actually 1.0152MHz. I am getting approximately the correct amount of data now, but it doesn't look synced and I think this might be the problem.
Does the SPI slave care what the frequency of the input clock is?
It cares about some limits (I think on the Arduino the maximum speed is about 4MHz) but the bus is designed to not being dependent on fixed speeds, that's why there is a separate line for the clock signal. If you're loosing bytes the most probable reason is your code. Receiving some data with 1MHz means the Arduino has to finish processing a received byte within 256 main clock cycles. On the first view this sound like a lot but in this time every else has to be done too (like interrupts for sending some bytes out of the serial interface for debugging or the like). Optimize your code for speed and you probably get "synced" again.
Assuming your using the same code you posted above, does the Serial.write in the ISR cause problems?
ISR(SPI_stc_vect) {
data = SPDR;
Serial.write(data);
}
Assuming your using the same code you posted above, does the Serial.write in the ISR cause problems?
That's a possible race condition. If the serial buffer is not emptied fast enough you produce a dead lock with these instructions. Serial.write() fills just an internal buffer (64 bytes by default). An interrupt is responsible to fill the next character from this buffer into the USART register as soon as the hardware has delivered the last one. Because inside an interrupt handler all interrupts are disabled the emptying interrupt will never be called and Serial.write() will wait forever for the buffer to empty at least one byte.
I haven't been able to get the interrupt to work how I want it to (i.e. I get nothing on the serial output) so I moved the real action to the main loop. Also, I realized that the serial writes were taking too long and I was missing data so I now write to a buffer. See below:
#include <SPI.h>
byte data[1024];
void setup() {
Serial.begin(115200);
pinMode(10,INPUT);
pinMode(11,INPUT);
pinMode(13,INPUT);
SPCR=B01101101;
}
void loop() {
for(int i=0; i<1024; i++) {
while(!(SPSR & (1<<SPIF)));
data[i] = SPDR;
}
Serial.write(data,1024);
delay(5000);
}
I know those pinMode commands are redundant, but I'm trying different things try to learn and figure things out.
I copied the while statement from some examples. Is it the same as:
while (!(SPSR & (1<<SPIF)))
{
data = SPDR;
}
OK, I see that while statement is waiting for the data to be ready.
Here is my last try before I decide that I can't do this with the Uno. Any guidance would be greatly appreciated. The data is still not syncing correctly.
#include <SPI.h>
const int buffer_size = 32;
byte data[buffer_size];
void setup() {
Serial.begin(115200);
pinMode(2,OUTPUT);
digitalWrite(2,LOW); //Disable data device
SPCR=B11100001; //Set SPI
digitalWrite(2,HIGH); //Enable data device
}
ISR(SPI_STC_vect) {
while(!(SPSR & (1<<SPIF))); //Wait for SPI data to be ready
data[0] = SPDR; //Get SPI byte
if(data[0]!=0xFF) { //Data line idle state is high, so wait
//for meaningful byte which signals
//the start of packet
SPCR=B01100001; //Disable interrupt
for(int i=1; i<buffer_size; i++) { //Fill the buffer with data
while(!(SPSR & (1<<SPIF)));
data[i] = SPDR;
}
}
}
void loop() {
if(SPCR==B01100001) { //Write buffer to serial when full
//digitalWrite(2,LOW);
Serial.write(data,buffer_size);
delay(1000);
//digitalWrite(2,HIGH);
SPCR=B11100001; //Re-enable interrupt
}
}
while(!(SPSR & (1<<SPIF))); //Wait for SPI data to be ready
Quite a bad idea, the interrupt flag is cleared when entering the interrupt handler.
Remove that line.
for(int i=1; i<buffer_size; i++) { //Fill the buffer with data
while(!(SPSR & (1<<SPIF)));
data[i] = SPDR;
}
Next bad idea. You're waiting for the interrupt flag to be set again. You're interrupt will later be called because of this. Either use these while loops to wait for the interrupt flag to be set or use the interrupt handler but not both.
For the sake of posterity, here is what I got to work.
void setup() {
Serial.begin(230400);
pinMode(11,INPUT);
pinMode(2,OUTPUT);
digitalWrite(2,LOW);
SPCR=B01000001;
digitalWrite(2,HIGH);
delay(2000);
attachInterrupt(1,get_data,FALLING);
}
void loop() {
// while(!(SPSR & (1<<SPIF))); //Wait for new data
// data[i]=SPDR;
if(i>=buffer_size) {
noInterrupts();
for(int u=0; u<buffer_size; u++) {
data[u]=IMU_data[u];
}
interrupts();
front_shift();
if(data_ready==true) {
remove_zeros();
ship_out();
}
if(data_ready==true) {
Serial.write(data_out,32);
}
// Serial.write(data,buffer_size);
// Serial.write(line_break,14);
// delay(1000);
i=0;
}
} // end loop
void get_data() {
while(i<buffer_size) {
IMU_data[i]=SPDR;
while(!(SPSR & (1<<SPIF))); //Wait for new data
i++;
}
}
Then I performed all my operations on data[].
Serial.begin(230400);
Whew! Cranking!
Are there a bunch of unshown functions to go with that? I don't see where these 5 are coming from:
interrupts();
front_shift();
if(data_ready==true) {
remove_zeros();
ship_out();