SPI on ATMEGA324A with STK 500

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

Hello all,

I have spent quite a few hours trying to set the SPI interface for the ATMEGA324A.

I wrote the code following the example on the data sheet and by reading other threads on this forum. Unfortunately, I wasn't able to make it work.
I am new to Atmel chips, but I have SPI programming experience with other microcontrollers, so this is becoming frustrating.

 

I am programming the microcontroller using Atmel studio 7.0 and the STK 500. The SPI is not connected to any chip for now.

I notice the SCK signal is not coming out from PortB.

To check if the code is running, I am also using PortC to turn on and off some LEDs, and this works fine.

 

Below is the code. Hope some of you can help me understand what I do wrong.

 

 


#ifndef F_CPU
#define F_CPU 16000000UL // 16 MHz clock speed
#endif

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

void SPI_MasterInit(void)
{
	PORTB |= (1<<4);             // set SS as output
	/* Set /SS, MOSI and SCK output, all others input */
	DDRB |= (1<<DDB4)|(1<<DDB5)|(1<<DDB7);
	//PRR0 &= ~(1<<PRSPI);		//Enable SPI Clock
	/* Enable SPI, Master, set clock rate fck/16 */
	SPCR0 |= ( (1<<SPE0) | (1<<MSTR0) | (1<<SPR00) );	//Enable SPI Master, clock rate fck/16

}

void SPI_MasterTransmit(unsigned char cData)
{
	PORTB &= ~(1<<4);             // set SS low
	/* Start transmission */
	SPDR0 = cData;
	/* Wait for transmission complete */
	while(!(SPSR0 & (1<<SPIF0)));
	PORTB |= (1<<4);			  // set SS high
}

char ReadByteSPI(char addr)
{
	SPDR0 = addr;					//Load byte to Data register
	while(!(SPSR0 & (1<<SPIF0))); 	// Wait for transmission complete
	addr=SPDR0;
	return addr;
}

int main(void)
{
	
	SPI_MasterInit();
	
	DDRC = 0xFF; //Makes PORTC as Output
	while(1) //infinite loop
	{
		PORTC = 0xFF; //Turns ON All LEDs
		_delay_ms(1000); //1 second delay
		PORTC= 0x00; //Turns OFF All LEDs
		_delay_ms(1000); //1 second delay
		SPI_MasterTransmit(0x20);
		
	}
}

 

This topic has a solution.
Last Edited: Thu. Sep 21, 2017 - 11:56 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Your SS control looks "suspicious". First you have this:

PORTB |= (1<<4);             // set SS as output

at the moment that line runs it does not do what the comment suggests. All it does (because PB.4 and all other pins default to input) is to turn on the pull up resistor. But then I guess you do:

	/* Set /SS, MOSI and SCK output, all others input */
	DDRB |= (1<<DDB4)|(1<<DDB5)|(1<<DDB7);

At that point it does become an output (good, it's the best solution to avoid the usual AVR SPI "gotcha"!) and the previous line has now ensured it is high.

 

But it's only in MasterTransmit but not in ReadByte that you then control it? So for the read it is going to be high and hence the device is not selected.

 

Also have you noticed how similar Read and Transmit actually are? That's because with SPI you always do the same thing (send one byte, receive another) so you might as well just have one common routine anyway:

char SPIExchange(char addr)
{
    PORTB &= ~(1<<4);             // set SS low
    SPDR0 = addr;					//Load byte to Data register
    while(!(SPSR0 & (1<<SPIF0))); 	// Wait for transmission complete
    addr=SPDR0;
    PORTB |= (1<<4);			  // set SS high    
    return addr;
}

Then use this for (a) transmit (ignore the return) (b) receive (just send 0xFF if nothing better to send) or (c) both

 

Anyway, the lack of SCK - you haven't got your ISP programmer still connected by any chance? I've noticed this in the past with STK500 where it "holds" the lines. Just remove the cable from the ISP6PIN header and see what happens.

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

Hello Clawson, thanks for the explanation and suggestions.

 

From what your wrote I understand my code is fairly correct and should work properly, right?

I tried to remove the ISP6PIN, but I can't still see any message and clock coming out of the SPI port of the chip. I didn't modify the functions to SPIExchange yet, but the program as it is now should at least be able to transmit data. Unfortunately, it doesn't. :(

 

 

 

 

 

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

You are correct, your function should transmit, though I would echo clawsons comments, so how are you verifying operation, scope or LA?

Does the SS pin wiggle when you tx? 

 

Can you post a picture of your setup?

 

Jim

 

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

clawson wrote:
Also have you noticed how similar Read and Transmit actually are? That's because with SPI you always do the same thing (send one byte, receive another) so you might as well just have one common routine anyway:

I tend to disagree.  If you want to make a general SPI send/receive primitive, that IMO is no place to fuss with chip-select.  It would be very rare to have nothing but single-byte send/receive transactions.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

Hello Jim,

I am verifying the operations with a scope. And using the LEDs on the STK500 (PortD connected to LEDs) to check if any signal is coming out. Attached is a picture of the setup, as you can see, only LED7 is on corresponding to the SCK that is therefore a constant signal (high).

 

STK500 and ATMEGA324A

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

Your code "looks" as if it should work.   But your comments are mendacious.

I would correct them first.   Especially since you have already been advised.

 

The STK500 ribbon connector should make no dfference.   The ISP lines are put into 3-state when the ISP completes.

This applies to most programmers.   They are unobtrusive when programming session has finished.   Note that a connected programmer MUST be powered or the 3-state does not work.   Your STK500 is fine.

 

Since STK500 LEDs are buffered,  they are unobtrusive too.

 

The STK500 LEDs are active-low.   In SPI mode #0,  SCK is active-high (quiescent low).   The LED will show inactivity.   I doubt if you would notice any blink during SPI.

 

I am intrigued.   I might try it for myself.

 

David.

 

p.s. why do non-Australians post photos upside down?

 

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

Hi David,

 

Even on the scope (a waveJet 314), I see a constant signal coming out of the microcontroller pins. I am running out of options.

 

PS: I actually posted the photo vertically, but it was published rotated by 90deg :)

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well,  I built your project with a ATmega324PA chip and 12MHz crystal on my STK500.

 

Sure enough,  you can see very little on the LEDs.   Basically because they are active-low.

 

So I re-wrote in my style:

#define F_CPU 12000000UL          // 12MHz Crystal on my STK500

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

void SPI_MasterInit(void)
{
	PORTB |= (1<<4);                  // pullup on SS before we enable output
	DDRB = (1<<DDB4)|(1<<DDB5)|(1<<DDB7);  // /SS, MOSI and SCK output
	SPCR0 = (1<<SPE0)|(1<<MSTR0)|(3<<SPR00)|(3<<CPHA0); //SPI Master, F_CPU/256, mode#3
}

uint8_t SPI_transfer(uint8_t cData)       // universal SPI function
{
	PORTB &= ~(1<<4);                 // set SS low
	SPDR0 = cData;                    // start the TX
	while((SPSR0 & (1<<SPIF0)) == 0) ; //Wait for TX to complete
	uint8_t ret = SPDR0;              // always read the reply from RX
	PORTB |= (1<<4);	          // set SS high
	return ret;
}

int main(void)
{
	SPI_MasterInit();
	DDRC = 0xFF;                      //Makes PORTC as Output
	while(1)                          //infinite loop
	{
		PORTC = 0xFF;             //Turns OFF All LEDs
		_delay_ms(1000);          //1 second delay
		PORTC= 0x00;              //Turns ON All LEDs
		_delay_ms(1000);          //1 second delay
		SPI_transfer(0b10000001); //start and finish with 1. light with 0
	}
}

I have added accurate comments (I hope).   Note that your DDR and LED ideas were wrong.

I used = instead of |= where appropriate.

I changed to mode#3 so that SCK would be 1 when quiescent.   i.e. LED off

I made the slowest SCK so that I had some chance of seeing anything.

 

Oh,  and I never removed the ISP cable.

If you had used an Arduino-style board with active-high LEDs,  SPI mode#0 and 0x20 would have shown up on LEDs.

(Arduino Users are not able to understand active-low)

 

David.

Last Edited: Thu. Sep 21, 2017 - 11:37 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
Arduino Users are not able to understand active-low

I think it my be a tad unfair to single-out Arduino users in that ...

 

wink

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

David,

I see my mistakes! It works now. Thanks so much for helping me and for your patience in helping me understand!

 

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

Before Arduino was invented, LEDs were typically active-low. It is the most natural way from an electrical point of view.
.
No disrespect to Arduino users. Humans in general are happier with positive logic. I certainly feel more comfortable from a software design perspective. It just means that you use XOR to invert for active-low hardware pins.
.
Likewise, active-low buttons are better from an electrical perspective. I read a PINx register and invert bits with XOR.
.
David.

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

Could you please post what your found for the sake of others who read this thread at a later time?

 

Jim

 

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

ki0bk wrote:

Could you please post what your found for the sake of others who read this thread at a later time?

 

I was making 2 big (and stupid) mistakes:

1. I was running the chip with a very fast clock, so I could not really see the LEDs turning on and off at that rate. To many they seemed off all the time, but they were actually flickering. Reducing the clock speed with this command by David, helped.

SPCR0 = (1<<SPE0)|(1<<MSTR0)|(3<<SPR00)|(3<<CPHA0); //SPI Master, F_CPU/256, mode#3

2. I was thinking with a positive logic (active-high) when locking at the LEDs to test the SPI. So this was making me thing the code was not working at all.

 

Also, there was no need to remove the ISP cable to make the board work.

 

 

 

 

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

You were using LEDs to check for SPI CLK ?? I thought you meant you had an o'scope or LA !

 

As you can buy an LA for <$10 on ebay can I suggest that might be a good investment?

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

Yes, I was using an scope too, but I wasn't able to see much on it (Not sure why yet).

LEDs were the backup for checking the signals.

 

 

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

It is easy to search for activity with the Saleae Logic Analyser software. I doubt if a scope can search.
I have got a bit further with PulseView but it crashes my PC. And it does not seem to navigate as conveniently as Saleae Logic.
.
David.

Last Edited: Fri. Sep 22, 2017 - 09:50 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
 I doubt if a scope can search.

Basic scopes trigger on a level or an edge.

 

There is usually an "auto" mode where the scope just keeps scanning, irrespective of trigger.

You can usually use this to distinguish "by eye" whether "something" or "nothing" is happening - would certainly be better than a LED for seeing if a clock is present or not!

 

One of the key differentiators between cheap and higher-end scopes is the triggering capabilities - the more you pay, the more & better triggering capabilities you get.

 

Many scopes nowadays are able to trigger on & decode things like SPI, UART, I2C comms, etc - it is no longer the sole reserve of top-end, top-dollar kit.

 

 

 

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

Hello all,

it's me again with some updates on the SPI programming.

 

The code is working fine, but I now would like to use it to set and read data on an ADC: LTC1867L.

 

Since it requires 8 bits to configure each channel and it returns 16 bits on the MISO line, I modified the code above by redefining the SPI_transfer() as follow:

 

char SPI_transfer(char cData)
{
	SPDR0 = cData;                    // start the TX
	while((SPSR0 & (1<<SPIF0)) == 0) ; //Wait for TX to complete
	char ret = SPDR0;              // always read the reply from RX
	return ret;
}

 

Since I need to read 16 bits out of the ADC, the main() function now contains the following lines:

 

                PORTB &= ~(1<<4);                 // set SS low
		SPI_transfer(CH0); //set and read channel 0 on ADC
		SPI_transfer(0x00); //read channel 0 on ADC
		PORTB |= (1<<4);	          // set SS high

with CH0 = 0x84

 

 

On the scope I am trying to read the data coming from the MISO line to make sure the ADC is being set correctly, but I don't think it is working.

Am I doing something wrong by using that SPI_trasnfer(0x00) to read the second half of the data coming from the ADC?

 

 

 

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

francuz wrote:
Am I doing something wrong by using that SPI_trasnfer(0x00) to read the second half of the data coming from the ADC?
Nope, that's a standard technique - some people call values like 0x00 "stuffing bytes" as they are just dummy/rubbish bytes who's value does not matter but that are simply used to trigger the further transfer of more 8 bit parts of the thing to be read.

 

However if 16 bit are coming back surely it's something like:

        SPI_transfer(CH0); //set and read channel 0 on ADC
        res_low = SPI_transfer(0x00); //read channel 0 on ADC (low? byte)
        res_high = SPI_transfer(0x00); //read channel 0 on ADC (high? byte)        
        result = (res_high << 8) | res_low;

Reorder the low/high read order if big endian rather than little endian.

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

It is always wise to use uint8_t (unsigned char) for primitive functions.

God gave you a return value.   Use it.  e.g.

                PORTB &= ~(1<<4);                 // set SS low
		uint8_t hi = SPI_transfer(CH0); //set and read channel 0 on ADC
		uint8_t lo = SPI_transfer(0x00); //read channel 0 on ADC
		uint16_t result = (hi << 8) | lo;
                PORTB |= (1<<4);	          // set SS high

The datasheet shows you the timing sequence.    All you need to do is allow sufficient time for tCONV to complete.

As far as I can see,  your command sets up the channel for the next conversion.    The result is what the chip had stored from the previous conversion.

 

Edit.  the result is big-endian and MSB first.   As can be seen from the timing diagram

 

David.

Last Edited: Wed. Sep 27, 2017 - 02:50 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi clawson, thanks!

 

since the SPI is full-duplex, shouldn't do I use the first SPI_transfer() to read the first 8 bit as well?

 

Would this work?

	res_high = SPI_transfer(CH0); //set and read channel 0 on ADC
        res_low = SPI_transfer(0x00); //read channel 0 on ADC (low byte)
        result = (res_high << 8) | res_low;

Also, I am trying to read the MISO data on the scope, but they are not always the same, even if the input analog voltage is constant (maybe because of the added noise).
How can I display a numerical value. I guess just writing printf() in main() would not be enough.

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

Life is much simpler with an Arduino.   You just use Serial.print() and it does what you expect.

 

It is fairly trivial to setup printf() on GCC.   I just add the same file to each project (that handles the UART and attaches to printf() ).

 

Of course other Compilers provide printf() straight out of the box.   e.g. CodeVision

 

David.

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

francuz wrote:
Would this work?
How could the receiving end know that you were about to send "CH0" and to have already pre-loaded the first half of that result? The usual sequence is transfer_byte(command to say what you want), then N times part_result=transfer_byte(something to prompt the transfer).

 

But the device you are connecting to must surely have a datasheet - I imagine it tells you the write/read sequences for the various commands it supports?