Solved: SPI on m328p/Arduino UNO R3

Go To Last Post
35 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello everyone, I return once again to this trusty forum. I am in the midst of establishing an SPI interface between my m328p/Arduino and my Artix-7/Basys 3. Something is not working and I can't say which side is at fault.

I just want to run the C code through you guys so you can maybe catch an error. There might not be an error anywhere, so don't stare yourselves blind.

In particular, I wonder if it's common practice to drive the SS pin low in that manner before initializing a transmission (see tranceiver method).

 

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

//Initialize SPI Master Device
void spi_init_master (void)
{
	PRR |= (1 << PRTWI) | (1 << PRTIM2) | (1 << PRTIM0) | (1 << PRTIM1) | (1 << PRUSART0) | (1 << PRADC); //disable some unused modules
	PRR &= ~(1 << PRSPI); // but not the SPI
	DDRB |= (1 << DDB5) | (1 << DDB3) | (1 << DDB2); //Set SS, MOSI, SCK as Output (MISO is input by default in master mode)
	SPCR |= (1 << SPE) | (1 << MSTR); //Enable SPI, Set as Master
}

//Function to send and receive data
unsigned char spi_tranceiver (unsigned char data)
{
	PORTB &= ~(1 << DDB2);			   // SS low
	SPDR = data;                       //Load data into the buffer
	while(!(SPSR & (1 << SPIF)));      //Wait until transmission complete
	PORTB |= (1 << DDB2);			   // SS high
	return(SPDR);                      //Return received data
}

//Main
int main(void)
{
	spi_init_master();                  //Initialize SPI Master

	unsigned char data = 0;

	while(1) // here I'm just transmitting 0 through 255 every .5 sec.
	{
		data = (data < 255) ? data + 1 : 0; // Question : do I have to reset to 0 or can I just add 1 and it'll "wrap around" from 255 to 0 again?
		spi_tranceiver(data);
		_delay_ms(500);
	}
}

Much of the code is credit of http://maxembedded.com/2013/11/t...

sol i sinne - brun inne

Last Edited: Fri. Jun 9, 2017 - 01:16 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The ss is used to frame a message. Normally a spi message is more than one byte.
Yes, if you keep adding 1 to data, it will roll around.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:
The ss is used to frame a message. Normally a spi message is more than one byte. Yes, if you keep adding 1 to data, it will roll around.
Thanks for your reply! Yes, I only have one slave so the SS pin deal should be quite straight forward. You ground it and that initiates the slave to start receiving.

Longer than a byte you say, what does that mean for the design? The SPDR register is obviously one byte, you mean there are more bits transferred/received than are actually written to the SPDR register?

sol i sinne - brun inne

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The usual spi message consists of more than one byte. However if you only need to send/receive one byte, then well and good.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:
The usual spi message consists of more than one byte. However if you only need to send/receive one byte, then well and good.
You mean that I deselect the slave a bit early, ie after just transmitting one byte. Yes that may well be, thanks for the tip. However, since I'm just trying to get a sign of life out of the ordeal, one byte is fine.

sol i sinne - brun inne

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm confused. I was interested to know more about the device mentioned in #1 so I googled and found...
.
https://www.google.co.uk/url?sa=t&source=web&rct=j&url=https://reference.digilentinc.com/_media/basys3:basys3_rm.pdf&ved=0ahUKEwigyrTGtKTUAhXDJ8AKHVjyBJMQFggzMAE&usg=AFQjCNH8nDwTiHZ64nYBZw1iFx68hHjdrw&sig2=46q7AjhNmM7x_ppsDJz2Ng

That seems to suggest the only SPI is a connection to SPI flash? So are you trying to make the AVR emulate a SPI Flash slave?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
That seems to suggest the only SPI is a connection to SPI flash? So are you trying to make the AVR emulate a SPI Flash slave?
It is a FPGA development board, so I would guess the design/application loaded onto the FPGA contains a SPI slave.

Stefan Ernst

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sternst wrote:

clawson wrote:
That seems to suggest the only SPI is a connection to SPI flash? So are you trying to make the AVR emulate a SPI Flash slave?
It is a FPGA development board, so I would guess the design/application loaded onto the FPGA contains a SPI slave.

Correct, or at least that's my intention. I'll report back if I can come up with some working VHDL code. I just need to rule out the Arduino as the source of my problems.

sol i sinne - brun inne

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I fear you may be headed for "too many unknowns". Try adding some established SPI device (perhaps and EEPROM or an RTC or something) to the AVR and prove you can drive it OK. That will give you an AVR implementation you can trust. If you then implement SPI in the VHDL and connect the AVR to that and it does not work there is a pretty fair bet the problem is at the FPGA end. If both your AVR end and the FPGA end are untrusted you will have a job working out where any problems may lie.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I would not assume that an unsigned char will automatically roll over to 0 when you add 1 to 255.  It will in assembler, but it may not do so in a high-level language compiler.

 

With an Arduino, I humbly suggest first getting an application working using the standard Arduino libraries, and then adapting the code to the traditional ways of writing AVR peripheral interface code by directly manipulating the control registers (for speed or clarity).
 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Utter BS as usual. Of course 255+1 rolls over in uchar!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

"With an Arduino, I humbly suggest first getting an application working using the standard Arduino libraries,"

Well, standard Atduino libraries are very near to traditional ways of AVR, gcc programming . Their source can be found in (/cygdrive/e/ardu/Arduino)hardware/arduino/avr/libraries/SPI : there is a short SPI.h and a SPI.cpp.

For transmitting, there is a function, trnsfer, which writes and reads to and from the SPI (one is free to discard incoming data; however, if it linked to a SPI memery, reading like arduino does might be interesting , too)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm using Atmel Studio 6.2 btw, and Vivado 2016.4 for the Artix-7.

 

Here's a simulation of my VHDL code, I'm sort of sampling the SPI clock from the Arduino and "smoothing" it out. The implemented hardware gives the rising edge of the clock so I don't have to take into consideration the SPI which runs at 1MHz (I added a prescaler to the code in #1) and the FPGA which runs at 100MHz.

 

 

I'll get back to you about the rest, this was by far my biggest concern since the VHDL code I ran earlier was way out of whack. Thank god for simulators.

sol i sinne - brun inne

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You've got synchronisers in your vhdl code? Metastability is real and it is a real pain.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:
You've got synchronisers in your vhdl code? Metastability is real and it is a real pain.
Yes that's exactly what I was simulating (: I think.. Anyway, I think it works! I can receive the char and the leds on the Basys 3 lights up! The arduino code was A-OK. Not sure about my interaction with the SS pin just yet.

sol i sinne - brun inne

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The ss signal serves two roles: enabling the MISO signal to allow multiple slaves and syncing the comms thus ensuring the bit after SS low is the first bit

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here's the result!

https://youtu.be/9zZ2lt_MmNU

 

*I changed the code in the m328p to send two bytes of data, a uint16_t to light up all 16 leds of the basys 3.

sol i sinne - brun inne

Last Edited: Wed. Jun 7, 2017 - 12:49 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here's the RTL schematic for the synchronizer in my FPGA. I'm quite happy with it, it detects both rising- and falling edges of the signal and ignores disturbances that are one clock cycle long.

sol i sinne - brun inne

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The position of the X_NOR makes an asynchronous input to Delay_1. Depending on the frequency of clk, AND the timing between a change in io_in and clk, AND the internal configuration of D_flipflop_we, it is possible (even if remote) that a metastable condition could arise on the q output of Delay_1 which then propagates to io_rise and io_fall.

 

Only you can determine if the probability of synchronization failure is acceptable.

David

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The 'usual' solution is to use standard synchronisers then perform your edge detection.
Good catch froggy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes I think you're right froggy. I could make a "proper" one I guess but I was impatient (:

sol i sinne - brun inne

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

"It is true that impatience, the mother of stupidity, praises brevity..." Leonardo da Vinci

 

(Not meant to be cruel, but just couldn't resist...)smiley

David

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

frog_jr wrote:

"It is true that impatience, the mother of stupidity, praises brevity..." Leonardo da Vinci

 

(Not meant to be cruel, but just couldn't resist...)smiley

That should fix it!

sol i sinne - brun inne

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Personally, I don't like ANY logic in between the 2 D-flipflops of the synchronizer.

However, it depends on the clk frequency, FPGA characteristics (e.g. flipflop setup, hold and prop delay), and how much routing there is between the two flipflops.

YMMV

 

Edit: Remember, this is really dealing with probabilities. Single stage synchronizers "mostly" work. Two stage synchronizers "rarely" fail; however, there is a probability that even those may fail...

Edit2: Typically, the faster the FPGA and the slower the clk, the probability that every thing works as it should improves.

 

 

David

Last Edited: Thu. Jun 8, 2017 - 10:26 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Very insightful, thanks.

 

Okay, I want help with the next stage of development which is to receive data from the slave: Frog-Jay and anyone who's listening, thanks for your answers and please give me more!

 

Right. So, I'm still unsure exactly how the SPI interface works.. Correct me if I'm wrong:

The master slaps the SS to the ground and the slave and the master both load the data they want to send onto the MISO/MOSI line. The rising edge of the clock from the master triggers the slave to read the MOSI line and the master to read the MISO. The falling edge of the clock is the call to action for them both to load the new data bit onto their data lines. SS high stops everything.

 

I've done so in my VHDL code but the Arduino doesn't want to receive anything.. I tested the hardware and the MISO line does indeed send data. I don't know if it's the level shifter's fault, but I guess I can check that somehow. Please review my code:

 

#include <avr/io.h>
#include <util/delay.h>

//Initialize SPI Master Device
void spi_init_master (void)
{
	PRR |= (1 << PRTWI) | (1 << PRTIM2) | (1 << PRTIM0) | (1 << PRTIM1) | (1 << PRUSART0) | (1 << PRADC);
	PRR &= ~(1 << PRSPI);
	DDRB |= (1 << DDB5) | (1 << DDB3) | (1 << DDB2);						//Set SS, MOSI, SCK as Output
	DDRD |= (1 << DDD7) | (1 << DDD6) | (1 << DDD5);
	SPCR |= (1 << SPE) | (1 << MSTR);                             //Enable SPI, Set as Master
	SPSR;
	SPDR;
}

//Function to send and receive data
uint16_t spi_tranceiver (uint16_t data, uint16_t leds)
{
	uint16_t miso = 0;
	PORTB &= ~(1 << DDB2);			   // SS low

	SPDR = data >> 8;                  //Load data into the buffer
	while(!(SPSR & (1 << SPIF)));      //Wait until transmission complete
	SPDR = data;                       //Load data into the buffer
	while(!(SPSR & (1 << SPIF)));      //Wait until transmission complete

	SPDR = leds >> 8;                  //Load data into the buffer
	while(!(SPSR & (1 << SPIF)));      //Wait until transmission complete
	miso = SPDR;
	SPDR = leds;                       //Load data into the buffer
	while(!(SPSR & (1 << SPIF)));      //Wait until transmission complete
	miso <<= 8;
	miso |= SPDR;

	PORTB |= (1 << DDB2);			   // SS high
	return(miso);                      //Return received data
}

//Main
int main(void)
{
	spi_init_master();                  //Initialize SPI Master

	uint16_t miso = 0x00000000;
	uint16_t data = 0x0000;
	uint16_t leds = 0xE000;

	while(1)
	{
		if (leds == 0x7)
		{
			leds = 0x8003;
		} else if (leds == 0x8003)
		{
			leds = 0xC001;
		} else if (leds == 0xC001)
		{
			leds = 0xE000;
		} else
		{
			leds >>= 1;
		}

		spi_tranceiver(data++, leds);

		_delay_ms(100);

		if (miso != 0x0)
		{
			PORTD |= (1 << DDD6);
		} else {
			PORTD &= ~(1 << DDD6);
		}
	}
}

 

sol i sinne - brun inne

Last Edited: Thu. Jun 8, 2017 - 11:00 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you've got an Arduino, i prefer to send messages out the serial port. It gives you a bit more info to work with. Eg: printing your recieved value in hex allows to to see if you're out hy a bit.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:
If you've got an Arduino, i prefer to send messages out the serial port. It gives you a bit more info to work with. Eg: printing your recieved value in hex allows to to see if you're out hy a bit.
Umpf, maybe. Don't have the cable for it and I don't know how to do it either really. I've done it before but that was ages ago. My problem is that the Arduino doesn't seem to receive anything at all.

sol i sinne - brun inne

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes.. If I disconnect the MOSI from the FPGA, connect the MISO cable into the port that the MOSI used to be, I do indeed receive data. So the transfer from the FPGA over MISO works. I'm not sure if the level shifter's ports are bidirectional at the same time, i.e one port can send in the opposite direction of another. Texas Instruments TXB0104. The datasheet is not clear on this but I would assume that's not an issue.

 

*EDIT: Checked an appnote for the TXB0104 and it does indeed support independent direction transmission.

sol i sinne - brun inne

Last Edited: Fri. Jun 9, 2017 - 12:02 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

How do you program the arduino if you don't have a cable? The serial interface over usb is built in - no extra hardware required.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:
How do you program the arduino if you don't have a cable? The serial interface over usb is built in - no extra hardware required.
Touché, do I feel stupid.. I use avrdude to program my Arduino, if it makes a difference.. How do I use the terminal?

sol i sinne - brun inne

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Tickstart wrote:
The rising edge of the clock from the master triggers...
That is controlled by the CPOL and CPHA setting in SPSR (which do default to SPI mode 0).

In this senario, the falling edge of SS should be used (by the FPGA) to enable the MSB onto the pin connected to the m328's MISO pin (assuming DORD is 0). The m328 will also place the MSB of the SPDR on the MOSI pin of the m328. Each rising edge of SCLK will cause the m328 to sample the MISO pin and should cause the FPGA to sample the m328's MOSI pin. The falling edge of SCLK is when the m328 will change the MOSI pin to the next bit. The FPGA may change data being sent to the MISO pin either after the rising edge of the SCLK (as long as the FPGA holds previous output 10ns from the SCLK rising) or on the falling edge of SCLK (as long as the FPGA has data to the MISO pin of the m328 10ns before the next rising edge of SCLK).

 

Can you show a schematic of how you are using the TXB0104 in your circuit?

The TXB0104 is bi-directional. MISO and MOSI are unidirectional...

 

David

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The arduino app has a serial terminal, but you can use any of your choosing. I use putty but there's a zillion others.
I can also recommend getting a Saleae Logic or clone logic analyser. For a cost of a McMeal it will save you much time - especially when debugging spi/i2c/uart

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

frog_jr wrote:

Tickstart wrote:
The rising edge of the clock from the master triggers...
That is controlled by the CPOL and CPHA setting in SPSR (which do default to SPI mode 0).

In this senario, the falling edge of SS should be used (by the FPGA) to enable the MSB onto the pin connected to the m328's MISO pin (assuming DORD is 0). The m328 will also place the MSB of the SPDR on the MOSI pin of the m328. Each rising edge of SCLK will cause the m328 to sample the MISO pin and should cause the FPGA to sample the m328's MOSI pin. The falling edge of SCLK is when the m328 will change the MOSI pin to the next bit. The FPGA may change data being sent to the MISO pin either after the rising edge of the SCLK (as long as the FPGA holds previous output 10ns from the SCLK rising) or on the falling edge of SCLK (as long as the FPGA has data to the MISO pin of the m328 10ns before the next rising edge of SCLK).

 

Can you show a schematic of how you are using the TXB0104 in your circuit?

The TXB0104 is bi-directional. MISO and MOSI are unidirectional...

 


Thanks for clarifying. So basically my understading is correct, I haven't modified CPHA or anything.
My setup is simply Arduino's 5V and 4 SPI cables on one side of the TXB and Basys 3's 3.3V and 4 SPI cables on the other, Output Enable high through a pullup res. Straight forward. It's an Ada-1875 chip.

sol i sinne - brun inne

Last Edited: Fri. Jun 9, 2017 - 12:54 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:
The arduino app has a serial terminal, but you can use any of your choosing. I use putty but there's a zillion others.
I can also recommend getting a Saleae Logic or clone logic analyser. For a cost of a McMeal it will save you much time - especially when debugging spi/i2c/uart

Just saw the Seleaeaeae in a video actually :D funny that, it must be popular. Okay, I'll look into the serial at some point I guess, if nothing else works..

sol i sinne - brun inne

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Turns out I didn't even assign miso the returned value from the trancieve function blush. Problem solved.

I do get some weird glitching in the transmission sometimes though, will have to look into that. For a split second the screen displays some random number, often just "0000", like the Arduino sends a zero instead of whatever it was supposed to. This only occurs when I am sending something other than zero over miso, but only within a certain range on bits. Weird.

Anyway, thanks for your help!

 

*EDIT

I got rid of the glitching by lowering the transmission rate in the SPCR register. 

sol i sinne - brun inne

Last Edited: Wed. Jun 14, 2017 - 03:47 PM