[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?

#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.zip (3.73 KB)

Problem solved! I ran across this comment in the Wire library:

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

#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);
}

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:

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

code.zip (4.96 KB)

/*

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!
}