Last active
August 12, 2016 19:08
-
-
Save ptsneves/034de0e73ce2b69897704dc80d24cabb to your computer and use it in GitHub Desktop.
ubirch GmbH Assignment
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdint.h> | |
#include <stddef.h> | |
#include "bmp180Driver.h" | |
#define CHIP_ID 0x55 | |
typedef uint8_t BOOL; | |
/* | |
* BMP180@Page18 Figure 6 has a typo in the bit columns names like ADC_OUT | |
* i2c registers | |
*/ | |
typedef enum { | |
REGISTER_OUT_MSB = 0xF6, | |
REGISTER_OUT_LSB = 0xF7, | |
REGISTER_OUT_XLSB = 0xF8, | |
REGISTER_CTRL_MEAS = 0xF4, | |
REGISTER_CHIP_ID = 0xD0, | |
REGISTER_EEPROM_START = 0xAA | |
} RegisterAddress; | |
/* | |
* eeprom_coeff indexes for pressure calculation | |
*/ | |
typedef enum { | |
EEPROM_AC1 = 0, | |
EEPROM_AC2, | |
EEPROM_AC3, | |
EEPROM_AC4, | |
EEPROM_AC5, | |
EEPROM_AC6, | |
EEPROM_B1, | |
EEPROM_B2, | |
EEPROM_MB, | |
EEPROM_MC, | |
EEPROM_MD, | |
EEPROM_END_COEFFICIENTS = BMP180_EEPROM_WORDS | |
} EepromCoefficients; | |
typedef struct { | |
int32_t UT; | |
int32_t X1; | |
int32_t X2; | |
int32_t B5; | |
int32_t raw_temperature; | |
BOOL valid; | |
} RawTemperatureData; | |
// I2C burst read (reads from 7 bit I2C device 'address' 'size' bytes | |
//starting with register 'reg' into 'data') | |
extern void i2c_read(uint8_t address, uint8_t reg, uint8_t *data, size_t size); | |
// I2C burst write (writes to 7 but I2C device 'address' 'size' bytes, | |
//starting with register 'reg') | |
extern void i2c_write(uint8_t address, uint8_t reg, uint8_t *data, size_t size); | |
extern void usleep(uint32_t microsseconds); | |
/* | |
* Returns the power of 2 in a type agnostic way. | |
*/ | |
#define POW2(exp) (1 << exp) | |
/* | |
* A mode validation function. | |
* Returns whether the measuring mode is indeed part of the enum | |
*/ | |
static BOOL isValidMode(MeasureMode mode) { | |
switch (mode) { | |
case TEMPERATURE: | |
case PRESSURE_ULTRA_LOW_POWER: | |
case PRESSURE_STANDARD: | |
case PRESSURE_HIGH_RESOLUTION: | |
case PRESSURE_ULTRA_HIGH_RESOLUTION: | |
return 1; | |
break; | |
default: | |
break; | |
} | |
return 0; | |
} | |
/* | |
* BMP180@Page18: Chip-id (register D0h): This value is fixed to 0x55 and | |
* can be used to check whether communication is functioning. | |
* Return: 0 on false | |
*/ | |
static BOOL isConncected(BMP180Data * const bmp180) { | |
uint8_t chip_id = 0; | |
i2c_read(bmp180->device_address, REGISTER_CHIP_ID, &chip_id, | |
sizeof (chip_id)); | |
return (chip_id == 0x55); | |
} | |
/* | |
* BMP180@Page13:The data communication can be checked by checking that | |
* none of the words has the value 0 or 0xFFFF. | |
* Return: 0 on false | |
*/ | |
static BOOL isValidEEPROMWord(uint16_t word) { | |
return (word != 0xFFFF && word != 0); | |
} | |
/* | |
* Populates the eeprom_coeff membmer of bmp180. It only fecthes the | |
* the coefficients from the chip if bmp180 does not have the | |
* coefficients already. | |
* Returns 1 if all the coefficients were correctly read or available. | |
*/ | |
static BOOL readEEPROMCoefficients(BMP180Data * const bmp180) { | |
uint8_t coeff_address = REGISTER_EEPROM_START; | |
EepromCoefficients i; | |
for (i = EEPROM_AC1; i < EEPROM_END_COEFFICIENTS; i++) { | |
if (isValidEEPROMWord(bmp180->eeprom_coeff[i])) { | |
coeff_address += 2; | |
} | |
else { | |
uint8_t i2c_data[2] = {}; | |
i2c_read(bmp180->device_address, coeff_address, | |
i2c_data, 2); | |
coeff_address += 2; | |
bmp180->eeprom_coeff[i] = (i2c_data[0] << 8) + i2c_data[1]; | |
if (!isValidEEPROMWord(bmp180->eeprom_coeff[i])) | |
break; | |
} | |
} | |
return coeff_address == (0xBE + 2); //BF+1 due to the last increment | |
} | |
/* | |
* Makes the bmp180 sensor start ADC convertions and waits for them to | |
* be ready according to bmp180:Page21 Table 8. | |
* Returns 1 if the request was possible. | |
*/ | |
static BOOL requestMeasurement(BMP180Data * const bmp180) { | |
/* | |
* In C the MeasureMode is not guaranteed to actually be enum, | |
* just an int. We want to guarantee we don't use unsafe input | |
* as register input | |
*/ | |
if (!isValidMode(bmp180->measure_mode)) | |
return 0; | |
if (!isConncected(bmp180)) | |
return 0; | |
if (!readEEPROMCoefficients(bmp180)) | |
return 0; | |
i2c_write(bmp180->device_address, REGISTER_CTRL_MEAS, | |
(uint8_t*) & bmp180->measure_mode, 1); | |
//BMP180:Page21: Table 8 | |
uint16_t delay = 0U; | |
switch (bmp180->measure_mode) { | |
case TEMPERATURE: | |
case PRESSURE_ULTRA_LOW_POWER: | |
delay = 4500U; | |
break; | |
case PRESSURE_STANDARD: | |
delay = 7500U; | |
break; | |
case PRESSURE_HIGH_RESOLUTION: | |
delay = 13500U; | |
break; | |
case PRESSURE_ULTRA_HIGH_RESOLUTION: | |
default: //unreachable label | |
delay = 25500; | |
break; | |
} | |
usleep(delay); | |
return 1; | |
} | |
/* | |
* Internal function that gathers the temperature information | |
* alongside with coefficient information that can be used by | |
* readPressure and readTemperature. | |
* Returns RawTemperatureData, which may be valid or not according | |
* to valid data member. It has to be checked. | |
*/ | |
RawTemperatureData readRawTemperature(BMP180Data * const bmp180) { | |
uint8_t i2c_data[2] = {0}; | |
RawTemperatureData data = {}; | |
if (!requestMeasurement(bmp180)) | |
return data; | |
i2c_read(bmp180->device_address, REGISTER_OUT_MSB, i2c_data, 2); | |
data.UT = (i2c_data[0] << 8) + i2c_data[1]; | |
//TODO: Check valid values?? | |
int16_t *coeff = bmp180->eeprom_coeff; | |
uint16_t *u_coeff = (uint16_t*) coeff; //forcing to read these as unsigned | |
data.X1 = (data.UT - u_coeff[EEPROM_AC6]) * u_coeff[EEPROM_AC5] / POW2(15); | |
data.X2 = coeff[EEPROM_MC] * POW2(11) / (data.X1 + coeff[EEPROM_MD]); | |
data.B5 = data.X1 + data.X2; | |
data.raw_temperature = (data.B5 + 8) / POW2(4); | |
data.valid = 1; | |
return data; | |
} | |
double readTemperature(BMP180Data * const bmp180) { | |
double temperature = NAN; | |
if (bmp180 == NULL) { | |
return NAN; | |
} | |
if (bmp180->measure_mode != TEMPERATURE) | |
bmp180->measure_mode = TEMPERATURE; | |
RawTemperatureData raw_data = readRawTemperature(bmp180); | |
if (raw_data.valid) | |
temperature = raw_data.raw_temperature / 10.0; | |
return temperature; | |
} | |
double readPressure(BMP180Data * const bmp180) { | |
if (bmp180 == NULL) { | |
return NAN; | |
} | |
int32_t UP; | |
uint8_t i2c_data[3] = {}; | |
int32_t X1, X2, X3, B3, B6, p; | |
uint32_t B4, B7; | |
MeasureMode original_mode = bmp180->measure_mode; | |
bmp180->measure_mode = TEMPERATURE; | |
RawTemperatureData raw_temperature = readRawTemperature(bmp180); | |
if (!raw_temperature.valid) { | |
return NAN; | |
} | |
bmp180->measure_mode = original_mode; | |
if (!requestMeasurement(bmp180)) | |
return NAN; | |
int16_t *coeff = bmp180->eeprom_coeff; | |
uint16_t *u_coeff = (uint16_t*) coeff; | |
uint8_t oss = bmp180->measure_mode >> 6; | |
i2c_read(bmp180->device_address, REGISTER_OUT_MSB, i2c_data, 3); | |
UP = ((i2c_data[0] << 16) + (i2c_data[1] << 8) + i2c_data[2]) >> | |
(8 - oss); | |
B6 = raw_temperature.B5 - 4000; | |
X1 = (coeff[EEPROM_B2] * (B6 * B6 / POW2(12))) / POW2(11); | |
X2 = coeff[EEPROM_AC2] * B6 / POW2(11); | |
X3 = X1 + X2; | |
B3 = ((((int32_t) coeff[EEPROM_AC1] * 4 + X3) << oss) | |
+ 2) / 4; | |
X1 = coeff[EEPROM_AC3] * B6 / POW2(13); | |
X2 = (coeff[EEPROM_B1] * (B6 * B6 / POW2(12))) / POW2(16); | |
X3 = ((X1 + X2) + 2) / POW2(2); | |
B4 = u_coeff[EEPROM_AC4] * (uint32_t) (X3 + 32768) / POW2(15); | |
B7 = ((uint32_t) UP - B3) * (50000 >> oss); | |
if (B7 < 0x80000000) | |
p = (B7 * 2) / B4; | |
else | |
p = (B7 / B4) * 2; | |
X1 = (p / POW2(8)) * (p / POW2(8)); | |
X1 = (X1 * 3038) / POW2(16); | |
X2 = (-7357 * p) / POW2(16); | |
p = p + (X1 + X2 + 3791) / POW2(4); | |
return (double) p / 100.0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef BMP180DRIVER_H | |
#define BMP180DRIVER_H | |
#include <math.h> //Users need to know about NaN | |
#define BMP180_EEPROM_WORDS 11 //BMP180@Page13 [..]partitioned in 11 words [...] | |
/* | |
* BMP180:Page12 | |
* | |
*/ | |
typedef enum { | |
NO_SELECTION = 0, | |
TEMPERATURE = 0x2e, | |
PRESSURE_ULTRA_LOW_POWER = 0x34, | |
PRESSURE_STANDARD = 0x74, | |
PRESSURE_HIGH_RESOLUTION = 0xB4, | |
PRESSURE_ULTRA_HIGH_RESOLUTION = 0xF4 | |
} MeasureMode; | |
typedef struct { | |
uint8_t device_address; //i2c device address | |
int16_t eeprom_coeff[BMP180_EEPROM_WORDS]; //correction coefficients | |
MeasureMode measure_mode; //See MeasureMode | |
} BMP180Data; | |
/* | |
* Read temperature in degrees Celcius. | |
* If any error occurs the function will return a NAN. Use isnan | |
* to verify this condition. Failure happens if the device is | |
* not connected, is not valid or if bmp180 pointer is NULL. | |
*/ | |
double readTemperature(BMP180Data * const bmp180); // unit=C | |
/* | |
* Read pressure in hPa. If any error occurs | |
* If any error occurs the function will return a NAN. Use isnan | |
* to verify this condition. Failure happens if the device is | |
* not connected, is not valid or if bmp180 pointer is NULL. | |
*/ | |
double readPressure(BMP180Data * const bmp180); // unit=hPa | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <string.h> | |
#include <stdio.h> | |
#include "bmp180Simulator.h" | |
typedef enum { | |
AC1 = 0xAA, | |
AC2 = 0xAC, | |
AC3 = 0xAE, | |
AC4 = 0xB0, | |
AC5 = 0xB2, | |
AC6 = 0xB4, | |
B1 = 0xB6, | |
B2 = 0xB8, | |
MB = 0xBA, | |
MC = 0xBC, | |
MD = 0xBE, | |
OUT_MSB = 0xF6, | |
OUT_LSB = 0xF7, | |
OUT_XLSB = 0xF8, | |
CTRL_MEAS = 0xF4, | |
CHIP_ID = 0xD0 | |
} REGISTERS; | |
typedef enum { | |
NONE_SELECTED = 0, | |
TEMPERATURE = 0x2e, | |
PRESSURE_ULTRA_LOW_POWER = 0x34, | |
PRESSURE_STANDARD = 0x74, | |
PRESSURE_HIGH_RESOLUTION = 0xB4, | |
PRESSURE_ULTRA_HIGH_RESOLUTION = 0xF4, | |
} MeasureMode; | |
#define UT 27898 | |
#define UP 23843 | |
#define FIXED_BMP180_ID 0x55 | |
static MeasureMode mode_selected = NONE_SELECTED; | |
void i2c_read(uint8_t address, uint8_t reg, uint8_t *data, size_t size) { | |
if (address != SIMULATOR_ADDRESS) { | |
printf("Wrong address...doing nothing\n"); | |
} | |
uint32_t temporary = 0; | |
if (size == 3) { | |
switch (reg) { | |
case OUT_MSB: | |
printf("Accessed Register 3 byte OUT_MSB\n"); | |
if (mode_selected != TEMPERATURE) | |
temporary = UP; | |
else { | |
printf("Register %d for Temperature not available " | |
"in 3 bytes", reg); | |
return; | |
} | |
break; | |
default: | |
printf("Register %d not available in 3 bytes", reg); | |
return; | |
break; | |
} | |
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ | |
temporary = ((temporary << 8) & 0xFF00FF00) | ((temporary >> 8) & 0xFF00FF); | |
temporary = (temporary << 16) | ((temporary >> 16) & 0xFFFF); | |
temporary >>= 16; //due to memcopy reading 2 leading 0s | |
#endif | |
} | |
else if (size == 2) { | |
switch (reg) { | |
case AC1: | |
temporary = 408; | |
printf("Accessed Register AC1 "); | |
break; | |
case AC2: | |
temporary = -72; | |
printf("Accessed Register AC2 "); | |
break; | |
case AC3: | |
temporary = -14383; | |
printf("Accessed Register AC3 "); | |
break; | |
case AC4: | |
printf("Accessed Register AC4 "); | |
temporary = 32741; | |
break; | |
case AC5: | |
printf("Accessed Register AC5 "); | |
temporary = 32757; | |
break; | |
case AC6: | |
printf("Accessed Register AC6 "); | |
temporary = 23153; | |
break; | |
case B1: | |
printf("Accessed Register B1 "); | |
temporary = 6190; | |
break; | |
case B2: | |
printf("Accessed Register B2 "); | |
temporary = 4; | |
break; | |
case MB: | |
printf("Accessed Register MB "); | |
temporary = -32758; | |
break; | |
case MC: | |
printf("Accessed Register MC "); | |
temporary = -8711; | |
break; | |
case MD: | |
printf("Accessed Register MD "); | |
temporary = 2868; | |
break; | |
case OUT_MSB: | |
printf("Accessed Register OUT_MSB\n"); | |
if (mode_selected == TEMPERATURE) | |
temporary = UT; | |
break; | |
case CTRL_MEAS: //TODO | |
default: | |
printf("Wrong register for 2 bytes %x. Doing nothing\n", reg); | |
return; | |
break; | |
} | |
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ | |
temporary = ((temporary >> 8) & 0xFF) | (temporary << 8); | |
#endif | |
} | |
else if (size == 1) { | |
switch (reg) { | |
case CHIP_ID: | |
temporary = FIXED_BMP180_ID; | |
break; | |
case OUT_MSB: | |
printf("Accessed Register OUT_MSB\n"); | |
if (mode_selected == TEMPERATURE) | |
temporary = (UT & 0xFF00) >> 8; | |
else | |
temporary = (UP & 0xFF00) >> 8; | |
break; | |
case OUT_LSB: | |
printf("Accessed Register OUT_LSB\n"); | |
if (mode_selected == TEMPERATURE) | |
temporary = (UT & 0x00FF); | |
else | |
temporary = (UP & 0x00FF); | |
break; | |
case OUT_XLSB: | |
printf("Accessed Register OUT_XLSB\n"); | |
temporary = 0; | |
break; | |
default: | |
printf("Wrong register %x. Doing nothing\n", reg); | |
return; | |
} | |
} | |
else | |
printf("Wrong read size %lu\n", size); | |
memcpy(data, &temporary, size); | |
printf("\n"); | |
} | |
// I2C burst write (writes to 7 but I2C device 'address' 'size' bytes, | |
//starting with register 'reg') | |
#define REGISTER_CTRL_MEAS 0xF4 | |
void i2c_write(uint8_t address, uint8_t reg, uint8_t *data, size_t size) { | |
if (address != SIMULATOR_ADDRESS) { | |
return; | |
} | |
uint32_t temporary = 0; | |
memcpy(&temporary, data, size); | |
if (reg == REGISTER_CTRL_MEAS) { | |
if (temporary >= TEMPERATURE && | |
temporary <= PRESSURE_ULTRA_HIGH_RESOLUTION) | |
mode_selected = temporary; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef BMP180SIMULATOR_H | |
#define BMP180SIMULATOR_H | |
#include <stdint.h> | |
#include <stddef.h> | |
// I2C burst read (reads from 7 bit I2C device 'address' 'size' bytes | |
//starting with register 'reg' into 'data') | |
#define SIMULATOR_ADDRESS 0x10 | |
void i2c_read(uint8_t address, uint8_t reg, uint8_t *data, size_t size); | |
// I2C burst write (writes to 7 but I2C device 'address' 'size' bytes, | |
//starting with register 'reg') | |
void i2c_write(uint8_t address, uint8_t reg, uint8_t *data, size_t size); | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include "bmp180Simulator.h" | |
#include "bmp180Driver.h" | |
int main() { | |
BMP180Data bmp180 = {SIMULATOR_ADDRESS, | |
{}, TEMPERATURE}; | |
double temperature = readTemperature(&bmp180); | |
bmp180.measure_mode = PRESSURE_ULTRA_LOW_POWER; | |
double pressure = readPressure(&bmp180); | |
printf("Temp %f Press %f\n", temperature, pressure); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.DEFAULT_GOAL:=bmp180 | |
CC:=arm-none-eabi-gcc | |
CFLAGS=-I. | |
CPPFLAGS:=-c -Wall -g3 -fstack-check -ftrack-macro-expansion=0 \ | |
-fno-diagnostics-show-caret | |
DEPS:= bmp180Driver.h bmp180Simulator.h | |
OBJ_DRIVER:= bmp180Driver.o | |
OBJ_TEST:= $(OBJ_DRIVER) main.o bmp180Simulator.o | |
%.o: %.c $(DEPS) | |
$(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) | |
bmp180: $(OBJ_DRIVER) | |
$(AR) rcs libbmp180.a $(OBJ_DRIVER) | |
test: $(OBJ_TEST) | |
$(CC) $(OBJ_TEST) -o test |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment