Altimeter, the bit shift malfunction

I was getting wrong lectures from the pressure sensor. But it was obvious that there was nothing wrong with it, because the very same sensor was giving good results when attached to an Arduino UNO.

In the snippet below we can see how Adafruit implements the operations needed for obtaining actual pressure out of raw readings and calibration values.


int32_t Adafruit_BMP085::readPressure(void) {
int32_t UT, UP, B3, B5, B6, X1, X2, X3, p;
uint32_t B4, B7;

UT = readRawTemperature();
UP = readRawPressure();

// do temperature calculations
X1=(UT-(int32_t)(ac6))*((int32_t)(ac5))/pow(2,15);
X2=((int32_t)mc*pow(2,11))/(X1+(int32_t)md);
B5=X1 + X2;

// do pressure calcs
B6 = B5 - 4000;
X1 = ((int32_t)b2 * ( (B6 * B6)>>12 )) >> 11;
X2 = ((int32_t)ac2 * B6) >> 11;
X3 = X1 + X2;
B3 = ((((int32_t)ac1*4 + X3) << oversampling) + 2) / 4; X1 = ((int32_t)ac3 * B6) >> 13;
X2 = ((int32_t)b1 * ((B6 * B6) >> 12)) >> 16;
X3 = ((X1 + X2) + 2) &gt;&gt; 2;
B4 = ((uint32_t)ac4 * (uint32_t)(X3 + 32768)) >> 15;
B7 = ((uint32_t)UP - B3) * (uint32_t)( 50000UL >> oversampling );

if (B7 &lt; 0x80000000) { p = (B7 * 2) / B4; } else { p = (B7 / B4) * 2; } X1 = (p >> 8) * (p >> 8);
X1 = (X1 * 3038) >> 16;
X2 = (-7357 * p) >> 16;

p = p + ((X1 + X2 + (int32_t)3791)>>4);

return p;
}

The code makes extensive use of bit shifts; though the datasheet demonstrates those operations using multiplications and divisions in the form of y × 2right and y / 2left. That is equivalent to ‘<<‘ for the former and ‘>>’ for the latter; but only if the compiler has arithmetic shift instructions, otherwise, the sign of signed variables is lost. And that’s why I was getting wrong pressure values, because PICs just have rotate and rotate through carry, and don’t bother with arithmetic or logical shift instructions.

As I didn’t want to trouble myself with rotate through carry shifts, and as math operations like pow() waste a lot of memory —the PIC18LF14K50 has only 16 kB of program memory—, I just did the calculations by hand and placed the numbers  as multipliers and divisors as we can seen just below.


long readPressure(void) {
long UT, UP, B3, B5, B6, X1, X2, X3, p;
unsigned long B4, B7;

UT = readRawTemperature();
UP = readRawPressure();

// do temperature calculations
X1 = (UT-(long)(ac6)) * ((long)(ac5)) / 32768;
X2 = ((long)mc*2048)/(X1 + (long)md);
B5 = X1 + X2;

// do pressure calculations
B6 = B5 - 4000;
X1 = ((long)b2 * ((B6 * B6) / 4096)) / 2048;
X2 = ((long)ac2 * B6) / 2048;
X3 = X1 + X2;
B3 = ((((long)ac1*4 + X3) << oversampling) + 2) / 4;
X1 = ((long)ac3 * B6) / 8192;
X2 = ((long)b1 * ((B6 * B6) / 4096)) / 65536;
X3 = ((X1 + X2) + 2) / 4;
B4 = ((unsigned long)ac4 * (unsigned long)(X3 + 32768)) / 32768;
B7 = ((unsigned long)UP - B3) * (unsigned long)(50000 >> oversampling);
if (B7 < 0x80000000) {
p = (B7 * 2) / B4;
} else {
p = (B7 / B4) * 2;
}
X1 = (p / 256) * (p / 256);
X1 = (X1 * 3038) / 65536;
X2 = (-7357 * p) / 65536;

p = p + ((X1 + X2 + (long)3791) / 16);

return p;
}

Now the altimeter just works like a charm.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.