ATmega8 SPI

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

Hello everyone (my 1st post for 1st uC project). I got a ATMega8 mC and LPS331AP sensor. uC can "talk" with LPS331AP using I2C or SPI interface. For my project I've chosen to use SPI option (no real reason, I thought it would be simpler...). LPS331AP uses following pins for SPI:
SPC - clock
SDA (SDI) - SPI data input
SDO - SPI output
CS - chip select
As far as I know the idea of SPI is simple: connect SPC to SCK(PB5 on ATmega8), SDA to MOSI(PB3), SDO to MISO(PB4) and CS to chosen GPIO configured as output. 
Then I need to configure ATMega as master using SPCR register (in data sheet I read that SS on ATMega have to be configured as output(PB2) to prevent "dropping" to slave), here is my SPI_init function:

void SPI_init()
{
    //Pin MOSI, SCK, SS configuration as output
    DDRB = ((1<<DDB3)|(1<<DDB5)|(1<<DDB2));

    /*
     * SPI control register setup
     * spi enable
     * configure as master
     */
    SPCR = ((1<<SPE)|(1<<MSTR));
}

I have left bits SPR1 and SPR0 to set f_cpu/4 fro SCK frequency
Next this is my transmit function, straightforward - put data to sent to SPDR, wait until SPIF bit is set in SPSR and then return SPDR if needed:

uint8_t SPI_tx(uint8_t data)
{

	//transmit
	SPDR = data;
	//Wait for end of tx
	while(!(SPSR & (1<<SPIF)))
		;
        uint8_t recv = (uint8_t)SPDR;
	return recv;
}

and here is main function:

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

#define F_CPU 4000000UL

#define LPS331AP_CS_PIN DDB0
#define LPS331AP_READ_CMD 0x80
//sensor registers
#define WHO_AM_I 0x0F

void peripherial_init()
{
	//LEDS
	DDRD |= (1<<DDD2);
	//LPS331AP
	DDRB |= (1<<LPS331AP_CS_PIN);
	PORTB |= (1<<LPS331AP_CS_PIN);
}

int main(void)
{
	SPI_init();
	init_USART(25); //for 9600 baud rate with 4Mhz oscilator
	PORTD |= (1<<DDD7);
	while(1)
	{

		uint8_t recv = 1;
		PORTB &= ~(1<<LPS331AP_CS_PIN); //CS goes low, start
		SPI_tx(LPS331AP_READ_CMD | WHO_AM_I);
		recv = SPI_tx(0x00);
		PORTB |= (1<<LPS331AP_CS_PIN); //CS high, stop

		if(recv == 0xBB) //test sensor. This will be removed in final
		{
			PORTD |= (1<<DD2);
		}

		USART_tx(recv);
	}
}

And as topic of this post suggest code above do not work: recv is never set to 0xBB (value in WHO_AM_I register of sensor), it is always equal to 0xFF (as I see in serial monitor, and LED on DD2 is down).
I am using USART to send recv value to arduino UNO:
 

#include <SoftwareSerial.h>

int pinRx = 9;
int pinTx = 8;

SoftwareSerial atmega(pinRx, pinTx);

void setup() {
  atmega.begin(9600);
  Serial.begin(9600);
}

void loop()
{
  if(atmega.available())
  {
    Serial.println(atmega.read(),HEX);
  }
}

I've tested LPS331AP using arduino UNO to be sure that it is not damaged, and it works as expected:

#include <SPI.h>

/*
SPI.h sets these for us in arduino
const int SDI = 11;
const int SDO = 12;
const int SCL = 13;
*/
int CS_LPS331AP = 2;

byte WHO_AM_I = 0x0F;

byte read_cmd = 0x80;

void enable_LPS331AP()
{
  digitalWrite(CS_LPS331AP, LOW);
}

void disable_LPS331AP()
{
  digitalWrite(CS_LPS331AP, HIGH);
}

void pinConfigure()
{
  pinMode(CS_LPS331AP, OUTPUT);
  digitalWrite(CS_LPS331AP, HIGH);
}

void setup()
{
  pinConfigure();
  Serial.begin(9600);
  SPI.begin();
}

void loop()
{
  byte ret = 0x01;
  enable_LPS331AP();
  SPI.transfer(read_cmd | WHO_AM_I);
  ret = SPI.transfer(0x00);
  disable_LPS331AP();
  Serial.println("read:");
  Serial.println(ret,HEX);
  delay(1000);
}

So where I made a mistake in ATMega code? Thanks in advance.

This topic has a solution.
Last Edited: Thu. Mar 28, 2019 - 08:23 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You need to pay attention to the required SPI mode, which defines the polarity and phase of when data is valid relative to the clock.

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

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

On my very old installation of Arduino I see the SPI.cpp and SPI.h files in:

 

D:\arduino-1.8.6\hardware\arduino\avr\libraries\SPI\src

 

As far as i can see there's no mention of CPOL/CPHA support there so it would appear to be using default just as OP's C code will implicitly do too.

 

If it were me I'd dig out a scope or logic analyser and compare the wire activity in the go/no-go cases.

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

You may need some small delay from CS going low to the start of the SPI transmission, the Arduino may have more overhead then your direct implementation and that my explain why one works and the other does not.  Check the datasheet for timing requirements of the CS signal.

 

Jim

 

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274
https://www.onegold.com/join/7134f67c2b814c5ca8144a458eccfd61

 

 

 

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

glitch wrote:

You need to pay attention to the required SPI mode, which defines the polarity and phase of when data is valid relative to the clock.


I also thought that this may be the issue, but no (I even tried all 4 combinations as last chance ....  truly un-enginieering way of looking for solution).
  

clawson wrote:

On my very old installation of Arduino I see the SPI.cpp and SPI.h files in:

 

D:\arduino-1.8.6\hardware\arduino\avr\libraries\SPI\src

 

As far as i can see there's no mention of CPOL/CPHA support there so it would appear to be using default just as OP's C code will implicitly do too.

 

If it were me I'd dig out a scope or logic analyser and compare the wire activity in the go/no-go cases.

I did it too. The only difference is transfer/recieve function:
 

  inline static uint8_t transfer(uint8_t data) {
    SPDR = data;
    /*
     * The following NOP introduces a small delay that can prevent the wait
     * loop form iterating when running at the maximum speed. This gives
     * about 10% more speed, even if it seems counter-intuitive. At lower
     * speeds it is unnoticed.
     */
    asm volatile("nop");
    while (!(SPSR & _BV(SPIF))) ; // wait
    return SPDR;
  }

precise this "nop" instruction.

clawson wrote:

If it were me I'd dig out a scope or logic analyser and compare the wire activity in the go/no-go cases.

I know that this is the best way for looking for issue in this case, but I do not have logic analyser or oscilloscope (yet :-))
ki0bk wrote:

You may need some small delay from CS going low to the start of the SPI transmission, the Arduino may have more overhead then your direct implementation and that my explain why one works and the other does not.  Check the datasheet for timing requirements of the CS signal.

 

I've added _delay_ms( ) with some value from 1-5 and it did not help. 

I have also changed CS pin and outcome is still the same.

Last Edited: Mon. Mar 25, 2019 - 09:38 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You are going to need >>some<< method of debugging.  (could it be as simple as the wrong SPI mpode?)  A poor man might slow the bit rate WAY down and then put an LED on the SCK line.  And on the chip-select.  And on MOSI sending a pattern, or alternate bytes.  Or ...

 

Suggestion for avatar for your account:

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

Thanks for avatar idea! In previous post I forgot to mention that I connected master out to master in to check if spi is working - I saw on Arduino serial out byte 0x8F. This was expected result so I guess that I should look for root cause in clock signal.

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

did you try adding that NOP between the writing to SPDR, and the while wait loop? It is needed due to the fact that the bit is cleared after it would be sampled for the next instruction, so in this case the SPIF bit is not cleared by the time you're checking it in the loop, and you will exit out immediately, before the transfer is actually complete.

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

Last Edited: Tue. Mar 26, 2019 - 05:13 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

glitch wrote:

did you try adding that NOP between the writing to SPDR, and the while wait loop? It is needed due to the fact that the bit is cleared after it would be sampled for the next instruction, so in this case the SPIF bit is not cleared by the time you're checking it in the loop, and you will exit out immediately, before the transfer is actually complete.


yes I did, same result. 

I've ordered logic level analyser so I soon be able to conduct proper problem investigation. 

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

I have tested my code and setup using logic analyzer (cheapest possible, I totally not recommend it if you only use windows, but after a fight I got it working under linux), and then I have added some _delay_ms and led. It turns out that CS PIN NEVER GO LOW! Do you have any ide why? I have switched ports and pins but I have same result always.

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

This line does not look right to me:

#define LPS331AP_CS_PIN DDB0

DDB0 i'm sure is defined in the header as an address where that register located  rather then as a bit mask.

 

But I could be wrong....

 

 

Jim

 

Edit:  No just looked it up in the header file, it is a bit mask = 0, my bad!

Personally I would have used PB0 instead...

 

 

 

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274
https://www.onegold.com/join/7134f67c2b814c5ca8144a458eccfd61

 

 

 

Last Edited: Wed. Mar 27, 2019 - 08:17 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ki0bk wrote:

This line does not look right to me:

#define LPS331AP_CS_PIN DDB0

DDB0 i'm sure is defined in the header as an address where that register located  rather then as a bit mask.

 

But I could be wrong....

 

 

Jim

 

Nah, it is just an unsigned ( https://github.com/vancegroup-mirrors/avr-libc/blob/master/avr-libc/include/avr/iom16.h )
 

#define DDRB    _SFR_IO8(0x17)
#define DDB0    0
#define DDB1    1
#define DDB2    2
#define DDB3    3
#define DDB4    4
#define DDB5    5
#define DDB6    6
#define DDB7    7

 

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

Hi,
I've found root cause of problem! But first I must say that I have learned some things: working with led as a logic analyzer and working with proper logic analyzer. Also I did a lot of measuring with voltmeter and dig through  technical docs, so in the end I feel like I gained some experience in uC world.
So my code (yes the code posted here!) have 2 serious bugs: 

1. void SPI-init() 

void SPI_init()
{
    [...]
    DDRB = ((1<<DDB3)|(1<<DDB5)|(1<<DDB2)); <<<<<<<< HERE!!!
    [...]
}

I have used a assignment operator instead of logical OR, so this ruined all configuration that was set before initialization of SPI, but what was more dreadful and ultimately prevented me for working with sensor was:

2. int main(void)

I was checking pins using voltmeter and pin that was (I thought it was ...) configured as CS had some strange value of 3.7V (or something about that, this was strange because I have used 5V to power up circuit). I found in  LPS331AP that CS of sensor is pulled-up, then I disconnected all wires from sensor, I've only left GND and VIN. So you probably guessed: when I measured voltage on CS it was 3.7V - so configuration of ATMeaga pins was wrong!
Now look at my code - I've wrote a fancy: void peripherial_init() fuction to keep all configuration in one place ... and never called it in int main(void)! In effect CS pin was input all the time, and sensor CS was always UP because it is pulled-up.

If someone will look in this thread in future here are fixed functions:

void SPI_init()
{
    //Pin MOSI, SCK, SS configuration as output
    DDRB |= ((1<<DDB3)|(1<<DDB5)|(1<<DDB2));

    SPCR = ((1<<SPE)|(1<<MSTR));
}
 

int main(void)
{
        peripherial_init();
	SPI_init();
	init_USART(25); //for 9600 baud rate with 4Mhz oscilator
	PORTD |= (1<<DDD7);
	while(1)
	{

		uint8_t recv = 1;
		PORTB &= ~(1<<LPS331AP_CS_PIN); //CS goes low, start
		SPI_tx(LPS331AP_READ_CMD | WHO_AM_I);
		recv = SPI_tx(0x00);
		PORTB |= (1<<LPS331AP_CS_PIN); //CS high, stop
		USART_tx(recv);
	}
}

I hope in future I will have more complex problem with some more awesome solution :-)