[Solved] I2C troubles with LPS25H sensor

The LPS25H pressure sensor (Pololu product #2724) works fine with the Arduino sample code provided by Pololu engineers, running on a Pro Mini board (5V, 16 MHz). It also responds correctly to the Arduino program I2C-Scanner, which returns the (8 bit) device address 0xBA.

However, I prefer minimal code and to use the Atmel Studio IV / avr-gcc environment. I find that the LPS25H does not work with some very simple routines that make hardware I2C calls. I’ve been using these routines without any problems for several years on a variety of devices, including RTCs like the DS1307, memory chips like 24C32, Melexis thermal sensor, etc.

The following code is a simple address scanner, which properly detects a DS3231 RTC (0xD0) breakout also containing an 24C32 memory chip (0xAE), however, it does not detect the LPS25H. The response of the LPS25H to the START command transmitting the (8 bit) device address 0XBA is TWSR = 0x20, which is a NAK.

The LPS25H is connected directly to the Pro Mini with Vin=Vcc (5V), Gnd, SCL and SDA connected to PC5 and PC4, respectively. Those same connections work fine with the Arduino I2C Scanner, running in the Arduino environment.

I’ve been working on this for a couple of days, going back and forth between the Arduino environment and the Atmel Studio environment and not getting anywhere, so if someone can see what I’m missing, I would greatly appreciate it! I have looked through the Arduino Wire/twi.c library to see what that code is doing, and the calls to the I2C hardware look pretty much the same, so nothing has jumped out at me yet. Subtle timing problem?

[code]#define F_CPU 16000000UL
#include <util/delay.h>
#include <avr/io.h>
#include “uart.c”

static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);

#define LED PB5
// ---------------------------------------------------------------------------
// I2C (TWI) ROUTINES
//
// On the AVRmega series, PA4 is the data line (SDA) and PA5 is the clock (SCL
// The standard clock rate is 100 KHz, and set by I2C_Init. It depends on the AVR osc. freq.

#define F_SCL 100000L // I2C clock speed 100 KHz
#define TW_READBIT 1 //low bit of device address for read
#define TW_START 0xA4 // send start condition (TWINT,TWSTA,TWEN)
#define TW_STOP 0x94 // send stop condition (TWINT,TWSTO,TWEN)
#define TW_ACK 0xC4 // return ACK to slave
#define TW_NACK 0x84 // don’t return ACK to slave
#define TW_SEND 0x84 // send data (TWINT,TWEN)

#define TW_READY (TWCR & 0x80) // ready when TWINT returns to logic 1.
#define TW_STATUS (TWSR & 0xF8) // returns value of status register

#define I2C_Stop() TWCR = TW_STOP // inline macro for stop condition

void I2C_Init(){

// at 16 MHz, the SCL frequency will be 16/(16+2(TWBR)), assuming prescalar of 0.
// so for 100KHz SCL, TWBR = ((F_CPU/F_SCL)-16)/2 = ((16/0.1)-16)/2 = 144/2 = 72.
TWSR = 0; // set prescalar to zero
TWBR = ((F_CPU/F_SCL)-16)/2; // set SCL frequency in TWI bit register
}

unsigned char I2C_Detect(unsigned char addr){
// look for device at specified address; return 1=found, 0=not found
TWCR = TW_START; // send start condition
while (!TW_READY); //wait
TWDR = addr; // load device’s bus address
TWCR = TW_SEND; // and send it
while (!TW_READY);

// printf("%02X %02x ",addr,TW_STATUS);

return (TW_STATUS==0x18); // return 1 if found; 0 otherwise

}

void ShowDevices(void){
for (unsigned char addr=1; addr<128; addr++) { // search all 127 addresses
if (I2C_Detect(addr<<1)) // I2C detected?
printf(" .%02X ",addr<<1);
}
}

int main( void ) {

DDRB |= (1 << LED);			// set LED to output

// PORTC |= (1<<PC4)|(1<<PC5); //pullup on SDA, SDC

uart_init(9600);	
stdout = &mystdout; //Required for printf init

printf(“Init I2C\r\n”);
I2C_Init();
printf(“Showing devices”);
ShowDevices();
printf("\r\ndone\r\n");
while(1);
}[/code] code.zip (3.73 KB)

Problem solved! I ran across this comment in the Wire library:[quote]// WARNING: Nothing in the library keeps track of whether
// the bus tenure has been properly ended with a STOP. It
// is very possible to leave the bus in a hung state if
// no call to endTransmission(true) is made. Some I2C
// devices will behave oddly if they do not see a STOP.
[/quote]

The LPS25H is one of those devices. The corrected address finder code, which now works for the LPS25H too, is

[code]#define F_CPU 16000000UL
#include <util/delay.h>
#include <avr/io.h>
#include “uart.c”

static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);

#define LED PB5
// ---------------------------------------------------------------------------
// I2C (TWI) ROUTINES
//
// The standard clock rate is 100 KHz, and set by I2C_Init. It depends on the AVR osc. freq.

#define F_SCL 100000L // I2C clock speed 100 KHz
#define TW_READBIT 1 //low bit of device address for read
#define TW_START 0xA4 // send start condition (TWINT,TWSTA,TWEN)
#define TW_STOP 0x94 // send stop condition (TWINT,TWSTO,TWEN)
#define TW_ACK 0xC4 // return ACK to slave
#define TW_NACK 0x84 // don’t return ACK to slave
#define TW_SEND 0x84 // send data (TWINT,TWEN)

#define TW_READY (TWCR & 0x80) // ready when TWINT returns to logic 1.
#define TW_STATUS (TWSR & 0xF8) // returns value of status register

void I2C_Init(){
// at 16 MHz, the SCL frequency will be 16/(16+2(TWBR)), assuming prescalar of 0.
// so for 100KHz SCL, TWBR = ((F_CPU/F_SCL)-16)/2 = ((16/0.1)-16)/2 = 144/2 = 72.
TWSR = 0; // set prescalar to zero
TWBR = ((F_CPU/F_SCL)-16)/2; // set SCL frequency in TWI bit register
}

void I2C_Stop(void) {
TWCR=TW_STOP;
// wait for stop condition to be executed on bus
// TWINT is not set after a stop condition!
while(TWCR & _BV(TWSTO));
}

unsigned char I2C_Detect(unsigned char addr){
unsigned char ret;
// look for device at specified address; return 1=found, 0=not found
TWCR = TW_START; // send start condition
while (!TW_READY); //wait
TWDR = addr; // load device’s bus address
TWCR = TW_SEND; // and send it
while (!TW_READY);
ret = (TW_STATUS==0x18);
printf("\r\n%02X %02x",addr,TW_STATUS);
I2C_Stop();
return ret; // return 1 if found; 0 otherwise
}

void ShowDevices(void){
for (unsigned char addr=1; addr<128; addr++) { // search all 127 addresses
if (I2C_Detect(addr<<1)) // I2C detected?
printf(" .%02X",addr<<1);
}
}

int main( void ) {

DDRB |= (1 << LED);			// set LED to output

// PORTC |= (1<<PC4)|(1<<PC5); //pullup on SDA, SDC – doesn’t help

uart_init(9600);	
stdout = &mystdout; //Required for printf init

printf(“Init I2C\r\n”);
I2C_Init();
printf(“Showing devices”);
ShowDevices();
printf("\r\ndone\r\n");
while(1);
}

[/code]

Maybe add [Solved] to your title?

Sounds great, though! nice fix

Praise be to code comments, when they are sufficient and helpful.

Below is a set of simple C routines that reads out the pressure (hPa) and temperature (deg C) from the LPS25H – for those like me who prefer a non-Arduino environment.
Sample output: [quote]
Init I2C
Showing all I2C devices attached .BA
done
LPS25H Device Id= bd
Config= 0f
pt: 999.427, 25.00
pt: 999.472, 25.02
pt: 999.535, 25.05
pt: 999.527, 25.06
pt: 999.545, 25.07
pt: 999.470, 25.08
pt: 999.521, 25.08[/quote]
code.zip (4.96 KB)

[code]/*

Simple I2C routines for LPS25H pressure sensor
ATmega328 @ 16 MHz Atmel Studio IV/ avr-gcc

Jim Remington sjames.remington@gmail.com 8/22/2014

build options for floating point output:
add -Wl,-u,vfprintf to the LINKER options. This appears to support printf but not scanf.
add -lprintf_flt or -lscanf_flt to the linker options which is done by listing printf_flt as a library.
need to set -lm if using float in any case
*/

#define F_CPU 16000000UL
#include <util/delay.h>
#include <avr/io.h>
#include “uart.c”

//set up stdout and stdin
static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);

#define LED PB5

void delayms( uint16_t millis ) { //approximate 1 millisecond delay
while ( millis ) {
_delay_ms( 1 );
millis–;
}
}

// ---------------------------------------------------------------------------
// I2C (TWI) ROUTINES
//
// The standard clock rate is 100 KHz, and set by I2C_Init

#define F_SCL 100000L // I2C clock speed 100 KHz
#define READBIT 1 //low bit of device address for read
#define TW_START 0xA4 // send start condition (TWINT,TWSTA,TWEN)
#define TW_STOP 0x94 // send stop condition (TWINT,TWSTO,TWEN)
#define TW_ACK 0xC4 // return ACK to slave
#define TW_NACK 0x84 // return NACK to slave
#define TW_SEND 0x84 // send data (TWINT,TWEN)
#define TW_READY (TWCR & 0x80) // ready when TWINT returns to logic 1.
#define TW_STATUS (TWSR & 0xF8) // returns value of status register

// LPS25H register definitions

#define LPS25H 0xBA //8-bit device address with SA0 high as per Pololu breakout
#define STATUS 0x27
#define P_OUT_L 0x28
#define T_OUT_L 0x2B
#define CTRL_REG1 0x20
#define CTRL_REG2 0x21
#define CONFIG 0x10
#define ID 0x0F

void I2C_Init(){

// at 16 MHz, the SCL frequency will be 16/(16+2(TWBR)), assuming prescalar of 0.
// so for 100KHz SCL, TWBR = ((F_CPU/F_SCL)-16)/2 = ((16/0.1)-16)/2 = 72
TWSR = 0; // prescalar to zero
TWBR = ((F_CPU/F_SCL)-16)/2; // set SCL frequency in TWI bit register
}

void I2C_Stop(void) {

TWCR=TW_STOP;
 // wait for stop condition to be executed on bus
 // TWINT is not set after a stop condition!
while(TWCR & _BV(TWSTO));

}

unsigned char I2C_Detect(unsigned char addr){

// look for device at specified address; return 1=found, 0=not found
TWCR = TW_START; // send start condition
while (!TW_READY); //wait
TWDR = addr; // load device’s bus address
TWCR = TW_SEND; // and send it
while (!TW_READY);
return (TW_STATUS==0x18); // return 1 if found; 0 otherwise
}

void ShowDevices(void){

// search all 127 addresses, report those present on the I2C bus

for (unsigned char addr=1; addr<128; addr++) { 
    if (I2C_Detect(addr<<1)) // I2C detected?
       printf(" .%02X",addr<<1);
	I2C_Stop();
	}

}

void I2C_Start (unsigned char slaveAddr) {

I2C_Detect(slaveAddr);

}

unsigned char I2C_Write (unsigned char data) {

// sends a byte to slave

TWDR = data; // load data to be sent
TWCR = TW_SEND; // and send it
while (!TW_READY); // wait
return (TW_STATUS!=0x28); //0 if successful, 1 if not

}

unsigned char I2C_ReadACK () {

// reads a byte from slave

TWCR = TW_ACK; // ack = will read more data
while (!TW_READY); // wait
return TWDR;
//return (TW_STATUS!=0x28);

}

unsigned char I2C_ReadNACK () {

// reads a byte from slave

TWCR = TW_NACK; // nack = not reading more data
while (!TW_READY); // wait
return TWDR;
//return (TW_STATUS!=0x28);

}

void I2C_WriteByte (unsigned char busAddr, unsigned char data) {

// write byte to slave

I2C_Start(busAddr); // send bus address
I2C_Write(data); // then send the byte
I2C_Stop();

}

void I2C_WriteRegister(unsigned char busAddr, unsigned char deviceRegister, unsigned char data){

I2C_Start(busAddr); // send bus address
I2C_Write(deviceRegister); // first unsigned char = device register address
I2C_Write(data); // second unsigned char = data for device register
I2C_Stop();

}

unsigned char I2C_ReadRegister(unsigned char busAddr, unsigned char deviceRegister) {

// read single byte of data in register

unsigned char data = 0;
I2C_Start(busAddr); // send device address
I2C_Write(deviceRegister); // set register pointer
I2C_Start(busAddr+READBIT); // restart as a read operation
data = I2C_ReadNACK(); // read the register data
I2C_Stop(); // stop
return data;

}

// Read a two-byte word, low order first

signed int I2C_ReadTempRaw(unsigned char busAddr, unsigned char deviceRegister) {

unsigned int data = 0;
unsigned char l;
I2C_Start(busAddr); // send device address
I2C_Write(deviceRegister | 0x80); // set register pointer, autoincrement
I2C_Start(busAddr+READBIT); // restart as a read operation
l = I2C_ReadACK(); // read the register data
data |= I2C_ReadNACK(); //read next unsigned char
I2C_Stop(); // stop
return (signed int) ((data<<8)|l);

}

// read a 3 byte value, lowest order byte first

signed long I2C_ReadPressureRaw(unsigned char busAddr, unsigned char deviceRegister) {

unsigned char pxl,pl,ph;
I2C_Start(busAddr); // send device address
I2C_Write(deviceRegister | 0x80); // set register pointer, autoincrement
I2C_Start(busAddr+READBIT); // restart as a read operation
pxl = I2C_ReadACK(); // read ls byte
pl = I2C_ReadACK(); // read middle
ph = I2C_ReadNACK(); // read high
I2C_Stop(); // stop
return (int32_t)ph << 16 | (uint16_t)pl << 8 | pxl;

}

int main( void ) {

unsigned char id;
long p;
int t;
double pp,tt;

DDRB |= (1 << LED);			// set LED pin to output

uart_init(9600);	
stdout = &mystdout; //Required for printf init

printf("Init I2C\r\n");  
I2C_Init();
printf("Showing all I2C devices attached");
ShowDevices();
printf("\r\ndone\r\n");

id=I2C_ReadRegister(LPS25H,ID); //check internal device ID
printf("LPS25H Device Id= %02x\r\n",id);

// recommended one-shot operation (see AN4450 from ST)

I2C_WriteRegister(LPS25H,CONFIG,0x0f); //set to maximum averaging

id=I2C_ReadRegister(LPS25H,CONFIG); //read back configuration
printf("Config= %02x\r\n",id);

while(1) {

I2C_WriteRegister(LPS25H,CTRL_REG1,0x84); 	//power up, one shot mode, BDU=1
I2C_WriteRegister(LPS25H,CTRL_REG2,1); 		//initiate measurement

while(I2C_ReadRegister(LPS25H,CTRL_REG2));  //wait till done

p = I2C_ReadPressureRaw(LPS25H,P_OUT_L);
pp = p /4096.;  // sensitivity = 4096 LSB per hPa
t = I2C_ReadTempRaw(LPS25H,T_OUT_L);
tt = t/480. + 42.5;  //sensitivity = 480 LSB per degree C
printf("pt: %8.3f, %5.2f\r\n",pp,tt);
}

I2C_WriteRegister(LPS25H,CTRL_REG1,0x00); 	//power down reset
delayms(200); //give it a rest!

}
[/code]