Temperature Sensor with SPI

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

Hi,

 

I'm wondering if I can get some feedback on my temperature sensor code.

I'm using an Adafruit BMP280 with an Atmega 328p via SPI.

 

I'd like to read the temperature on the sensor, and send that value to the terminal via UART.

The problem is that I'm getting a value of zero.

 

I've attached my whole code, but here are a couple areas that I was unsure about specifically.

 

One is that the program got stuck while waiting for the SPI data to buffer, so I used delays instead of the buffer code. Any reason that it might have gotten stuck?

/********************************************
SPI WRITE
********************************************/

void WriteByteSPI( int byteword)
{
	SPDR = byteword; // put the byteword into data register
	_delay_ms(30);
	//while(!(SPSR & (1<<SPIF)));	// Wait for transmission complete
	byteword=SPDR;	// clear SPIF
}

/********************************************
SPI READ
********************************************/

int ReadByteSPI(int addr)
{
	SPDR = addr;					//Load byte to Data register
	_delay_ms(30);
	//!(SPSR & (1<<SPIF)); 	// Wait for transmission complete. The 7th bit goes to one when data is done getting exchanged
	addr=SPDR;
	return addr;
}

 

Here's where I try and get the temperature,

/********************************************
Temperature read
********************************************/

int ReadTemp(void) {
	int temp;
	PORTB &= ~(1<<SS);
	WriteByteSPI(0xFA); // Call on register address for MSB temperature byte
	temp = ReadByteSPI(0xFF); // Exchange a garbage byte for the temperature byte
	PORTB |= (1<<SS);
	return temp; // Return the 8 bit temperature
}

Lastly, this is from the datasheet where they say to 'burst read' the values instead of cherry-pick them like I think I'm doing. What exactly is burst reading, and should it be a problem if I don't do it? 

 

Thanks for the help.

 

--------------------------

 

Here is a link to the BMP280 datasheet, and a screenshot of the registers.

https://cdn-shop.adafruit.com/da...

Attachment(s): 

This topic has a solution.
Last Edited: Fri. Jul 28, 2017 - 09:18 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

main() never calls SPIinit() which is probably why the SPI doesn't work properly.


You will need to connect and control the CSB input to the sensor before it will respond to your SPI commands.


'Burst read' is simply reading several consecutive registers in one SPI transaction.
eg. set CSB low, send 0xFA, read 2 bytes of temperature (temp_msb and temp_lsb), set CSB high,
instead of
set CSB low, send 0xFA, read temp_msb, set CSB high,
set CSB low, send 0xFB, read temp_lsb, set CSB high.

Last Edited: Thu. Jul 27, 2017 - 07:05 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

main() never calls SPIinit() which is probably why the SPI doesn't work properly.

It never ceases to amaze me, the dumb mistakes I can make. Thank you, I'm getting a value now. (I also added the CSB input, and edited my OP to show that.)

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

As mikech has suggested,  you need an spi_init() function.

I would suggest that you should use a single write/read function e.g.

 

uint8_t spi(uint8_t c)
{
    SPDR = c;
    while ((SPSR & (1<<SPIF)) == 0)
        ;
    return SPDR;
}

Then you can use this in your application function e.g.

int ReadTemp(void) {
	int temp;
	PORTB &= ~(1<<SS);
	spi(0xFA);         // Call on register address for MSB temperature byte
	temp = spi(0xFF);  // Exchange a garbage byte for the temperature byte
	PORTB |= (1<<SS);
	return temp; // Return the 8 bit temperature
}

A Burst read means that you set the "first" register address.  Then read consecutive results e.g.

void ReadBurst(uint8_t reg, uint8_t block[], uint8_t n) {
	PORTB &= ~(1<<SS);
	spi(reg);         // set "start" register
	while (n-- != 0) { 
            *block++ = spi(0xFF);  // 
	}
        PORTB |= (1<<SS);
}

You can use this "universal" function for anything e.g.

uint8_t temp_msb, temp_lsb, id;   //simple variables
uint8_t BMP280_buf[10];       //buffer to read a burst of registers

ReadBurst(0xFA, &temp_msb, 1); //read a single register
ReadBurst(0xFA, &BMP280_buf[6], 3); //read msb, lsb, xlsb in one burst
ReadBurst(0xF3, &BMP280_buf[0], 9); //read status through to xlsb
ReadBurst(0xD0, &id, 1);       //read chip ID

Untested.  I typed into Browser.

It looks as if the calibration data can only be read one byte at a time.   I would try it in a burst and compare with individual reads.

 

The important lesson is :  always use a single spi() function. 

 

David.

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

david.prentice wrote:
The important lesson is : always use a single spi() function.
+1

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

Thanks, I haven't used SPI before, so it feels good to know those little conventions.

 

If you get a chance, how does this look? I changed the code around, because I thought the address might have to be accessed inside the loop, and incremented to hit all the registers (0xF7, 0xF8..)

void BurstRead(unsigned char addr, unsigned char *buffer, unsigned char countdown) {

	PORTB &= ~(1<<SS);
	while (countdown-- != 0) {
		rwSPI(addr++);
		*buffer++ = rwSPI(0x00);
	}
	PORTB |= (1<<SS);
}

 

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

That will not work properly, you will lose byte 2, 4, 6 ....
For a 'multiple-byte-read' (== 'burst read') operation you send the address of the first register and then keep reading bytes, because the sensor will internally auto-increment the address after each read.
See section 5.3.2 SPI read in the datasheet.

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

My apologies for my writing style.    This is a functional equivalent:

void ReadBurst(uint8_t reg, uint8_t block[], uint8_t n) {
        PORTB &= ~(1<<SS);
	spi(reg);         // set "start" register
	for (int i = 0; i < n; i++) {
            block[i] = spi(0xFF);  // arrays are easier to understand
	}
        PORTB |= (1<<SS);
}

As mikech explained,  a Burst means one write to select the 'start' register.   Each read will auto-increment the register.

Many I2C devices work like this.   e.g. DS1307 or 24C256

 

Never assume increment.   Not all I2C devices work the same.   Always read the datasheet.

Likewise with your BMP280.   It definitely can read a burst within the 0xF3-0xFC range.   I am not so sure about 0x88-0xA1 range (Calibration).

 

Incidentally,   array access is easier than pointers for humans to understand.   Always use the style that you feel most comfortable with.

 

David.

Last Edited: Fri. Jul 28, 2017 - 12:18 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Oh man, thanks for that. Now I see it in the datasheet:

The following transactions are supported:

Single byte write

multiple byte write (using pairs of register addresses and register data)

single byte read

multiple byte read (using a single register address which is auto-incremented)

And thanks David, that function works great.

 

I have one last question.

If I start by using my homemade code with the bmp280, I get constant values; like 128, 128, 128 in the temperature's msb.

On the other hand, if I load in Adafruit's pre-made code for this in Arduino, and then reload my code in AS7, it works normally again, with varying values if I touch the sensor and what not.

 

Could there be a lock bit, or something like that, that Adafruit's code gets at, but not mine?

Last Edited: Fri. Jul 28, 2017 - 05:15 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just paste / attach your code.

 

I always start with a new chip by:

1. writing the functions for writing to a register and reading from a register

2. test read function by reading ID register (or any known value register)

3. test write function by writing a value to a register and reading it back.

 

Adafruit code is generally well written and easy to understand.

Don't be proud.    Study and steal their code.

 

The other tip is:

4. always put a "hello world" statement at the start of every program.

5. this reassures you that Serial, I2C, SPI, ... or whatever is working

 

David.

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

Thanks for the tips. I got a new ADC and DAC today, so I'm going to give those steps a try.

 

I studied Adafruit's code, but didn't catch what I'm missing.

I attached my C code, a screenshot of the buggy, unchanging values (from when I just run the sensor after a long break with my code), and the nice, changing values when I first load up Adafruit's Arduino code and then load my code.

 

 

..for what it's worth, the buggy, unchanging value is 524288 which is 0x80000 in hex

 

EDIT: I could be wrong but I have a feeling it has to do with the sleep or normal mode, so I've tried to add a power on function, and a temperature/pressure sensor enable function. Albeit with the same result so far

 

/********************************************
SENSOR ENABLE & POWER ON
********************************************/

void PowerOn(void) {
	PORTB &= ~(1<<SS);
	rwSPI(0xF4);
	rwSPI(0xFF);
	PORTB |= (1<<SS);
}

 

Attachment(s): 

Last Edited: Fri. Jul 28, 2017 - 08:04 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I suggest that you do things in stages.  e.g. just display the hex values

	sprintf(out_str, "press = %02X %02X %02X\r\n", data[0], data[1], data[2]); // raw data

Likewise for the temperature.

 

Note that "useful" registers start with 0xF3 or STATUS.    So it is worth reading the full 10-byte burst 0xF3 .. 0xFC

I would display STATUS value.

My example got the sums wrong.   I had miscalculated as 9-bytes.

 

But the first stage is to simply read the ID.    If that is not correct,   you will get nowhere !!

Oh,   I suggest that you use = instead of |= when initialising SPCR.    Only use |= for individual bits e.g. in PORT or DDR

 

David.

Last Edited: Fri. Jul 28, 2017 - 08:06 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks David for those suggestions, I've included them in my code.

I managed to figure it out too.

 

It was having to do with the power-on, since it's automatically in sleep mode.

I had to start the SPI address with a 'zero' to write to the power mode.

So, 0b01110100, instead of 0b11110100 (aka 0xf4).

 

/********************************************
SENSOR ENABLE & POWER ON
********************************************/

void PowerOn(void) {
	PORTB &= ~(1<<SS);
	rwSPI(0b01110100);
	rwSPI(0xFF);
	PORTB |= (1<<SS);
}

Thanks all couldn't have done it on my own!

Last Edited: Fri. Jul 28, 2017 - 09:14 PM