niedziela, 1 lipca 2018

[36] STM32F4 - BME280

Ten post chciałbym poświęcić na opisanie sposobu odczytu temperatury, ciśnienia oraz wilgotności.

[Źródło: http://www.st.com/en/evaluation-tools/stm32f4discovery.html]

Opis czujnika:


BME280 jest to cyfrowy czujnik temperatury (od -40 do 85 st. C), wilgotności (od 0 do 100%Rh) oraz ciśnienia atmosferycznego (300 hPa do 1100 hPa). Maksymalny wynik pomiaru ciśnienia atmosferycznego pozwala na pomiar wysokości od -500 do 9000 m nad poziomem morza.

Komunikacja z czujnikiem odbywa się poprzez interfejs I2C.

Bibliotekę tworzyłem w oparciu o pliki udostępnione przez firmę Adafruit na GitHubie.

Opis biblioteki:


Przygotowana biblioteka została przetestowana na bibliotekach HAL'a oraz mikrokontrolerze STM32F4. Do komunikacji z czujnikiem wykorzystałem interfejs I2C_1.

Komunikacja z czujnikiem odbywa się za pomocą biblioteki HAL'a. Wykaz funkcji przesyłających oraz odbierających dane znajduje się poniżej:

Główna funkcja uruchamiająca czujnik:

  1. void BME280_Initial(BME280_standby_Time_E standbyTime, BME280_filter_E filter,
  2.                     BME280_overSamplingTemp_E tempOversampl, BME280_overSamplingPres_E presOversampl,
  3.                     BME280_overSamplingHum_E humOversampl, BME280_mode_E sensMode)
  4. {
  5.     BME280_Set.sensorID = bme280_ReadReg(BME280_REG_ID);
  6.     if(BME280_Set.sensorID != BME280_ID)
  7.     {
  8.         errorHandler();
  9.         return;
  10.     }
  11.     BME280_Set.standbyTime = standbyTime;
  12.     BME280_Set.filter = filter;
  13.     BME280_Set.tempOversampl = tempOversampl;
  14.     BME280_Set.presOversampl = presOversampl;
  15.     BME280_Set.humOversampl = humOversampl;
  16.     BME280_Set.sensMode = sensMode;
  17.     bme280_WriteReg(BME280_REG_SOFTRESET, BME280_SOFTRESET_VALUE);
  18.     while (bme280_ReadStatus() & BME280_STATUS_IM_UPDATE) ;
  19.     bme280_ReadCoefficients();
  20.     bme280_SetStandby(BME280_Set.standbyTime);
  21.     bme280_SetFilter(BME280_Set.filter);
  22.     bme280_SetOversamplingTemper(BME280_Set.tempOversampl);
  23.     bme280_SetOversamplingPressure(BME280_Set.presOversampl);
  24.     bme280_SetOversamplingHum(BME280_Set.humOversampl);
  25.     BME280_Set.measurementStatus = bme280_ReadReg(BME280_REG_CTRL_MEAS);
  26.     BME280_Set.measurementStatus = bme280_ReadReg(BME280_REG_CTRL_HUM) << 8;
  27.    
  28.     BME280_Set.tempOn = (BME280_Set.measurementStatus & BME280_OSRS_T_MSK) ? 1 : 0;
  29.     BME280_Set.presOn = (BME280_Set.measurementStatus & BME280_OSRS_P_MSK) ? 1 : 0;
  30.     BME280_Set.humiOn = ((BME280_Set.measurementStatus >> 8) & BME280_OSRS_H_MSK) ? 1 : 0;
  31.     bme280_SetMode(BME280_Set.sensMode);
  32. }

Powyżej znajduje się funkcja rozpoczynająca komunikację z czujnikiem. Ustawia ona wszystkie parametry pracy, sprawdza komunikacje i wprowadza czujnik w odpowiedni tryb pracy.

Funkcja odczytu temperatury:

  1. float BME280_ReadTemperature(void)
  2. {
  3.     float readTemp = 0.0f;
  4.     uint32_t readRawData = 0;
  5.    
  6.     bme280_ReadRegDataConvert24(BME280_REGISTER_TEMPDATA, &readRawData);
  7.  
  8.     if(readRawData == 0x800000)
  9.     {
  10.         return 0xFFFF;
  11.     }
  12.  
  13.     readRawData >>= 4;
  14.  
  15.     int32_t tmp_1 = ((((readRawData>>3) - ((int32_t)CalibData.tempValue.dig_T1 <<1))) *
  16.         ((int32_t)CalibData.tempValue.dig_T2)) >> 11;
  17.  
  18.     int32_t tmp_2 = (((((readRawData>>4) - ((int32_t)CalibData.tempValue.dig_T1)) *
  19.         ((readRawData>>4) - ((int32_t)CalibData.tempValue.dig_T1))) >> 12) *
  20.         ((int32_t)CalibData.tempValue.dig_T3)) >> 14;
  21.  
  22.     tFineValue = tmp_1 + tmp_2;
  23.     readTemp = ((tFineValue * 5 + 128) >> 8);
  24.     readTemp /= 100.0f;
  25.  
  26.     return readTemp;
  27. }

Funkcja odczytu ciśnienia:

  1. float BME280_ReadPressure(void)
  2. {
  3.     float pressFloat = 0.0f;
  4.     int64_t presureInt = 0;
  5.     uint32_t presureRaw = 0;
  6.     uint32_t presUint = 0;
  7.     int64_t tmp_1 = 0;
  8.     int64_t tmp_2 = 0;
  9.  
  10.     BME280_ReadTemperature();
  11.     bme280_ReadRegDataConvert24(BME280_REGISTER_PRESSUREDATA, &presureRaw);
  12.  
  13.     if (presureRaw == 0x800000)
  14.     {
  15.         return 0xFFFF;
  16.     }
  17.    
  18.     presureRaw >>= 4;
  19.  
  20.     tmp_1 = ((int64_t) tFineValue) - 128000;
  21.     tmp_2 = tmp_1 * tmp_1 * (int64_t)CalibData.presureValue.dig_P6;
  22.     tmp_2 = tmp_2 + ((tmp_1 * (int64_t)CalibData.presureValue.dig_P5) << 17);
  23.     tmp_2 = tmp_2 + ((int64_t)CalibData.presureValue.dig_P4 << 35);
  24.     tmp_1 = ((tmp_1 * tmp_1 * (int64_t)CalibData.presureValue.dig_P3) >> 8) + ((tmp_1 * (int64_t)CalibData.presureValue.dig_P2) << 12);
  25.     tmp_1 = (((((int64_t)1) << 47) + tmp_1)) * ((int64_t)CalibData.presureValue.dig_P1) >> 33;
  26.  
  27.     if (tmp_1 == 0) {
  28.         return 0;
  29.     }
  30.  
  31.     presureInt = 1048576 - presureRaw;
  32.     presureInt = (((presureInt << 31) - tmp_2) * 3125) / tmp_1;
  33.    
  34.     tmp_1 = (((int64_t)CalibData.presureValue.dig_P9) * (presureInt >> 13) * (presureInt >> 13)) >> 25;
  35.     tmp_2 = (((int64_t)CalibData.presureValue.dig_P8) * presureInt) >> 19;
  36.    
  37.     presureInt = ((presureInt + tmp_1 + tmp_2) >> 8) + ((int64_t)CalibData.presureValue.dig_P7 << 4);
  38.    
  39.     presUint = ((presureInt >> 8) * 1000) + (((presureInt & 0xff) * 390625) / 100000);
  40.     pressFloat = presUint / 100.0f;
  41.  
  42.     return pressFloat;
  43. }

Funkcja odczytu wilgotności:

  1. float BME280_ReadHumidity(void)
  2. {
  3.     float humidConverted = 0.0f;
  4.     int16_t humidRawValue = 0;
  5.     int32_t humidRaw32 = 0;
  6.     int32_t tmpValue = 0;
  7.  
  8.     BME280_ReadTemperature();
  9.     bme280_ReadSignedData16_Convert(BME280_REGISTER_HUMIDDATA, &humidRawValue);
  10.  
  11.     if(humidRawValue == 0x8000)
  12.     {
  13.         return 0xFFFF;
  14.     }
  15.    
  16.     humidRaw32 = ((int32_t)humidRawValue)&0x0000FFFF;
  17.  
  18.     tmpValue = (tFineValue - ((int32_t)76800));
  19.     tmpValue = (((((humidRaw32 << 14) - (((int32_t)CalibData.humidValue.dig_H4) << 20) -
  20.         (((int32_t)CalibData.humidValue.dig_H5) * tmpValue)) + ((int32_t)16384)) >> 15) *
  21.         (((((((tmpValue * ((int32_t)CalibData.humidValue.dig_H6)) >> 10) *
  22.         (((tmpValue * ((int32_t)CalibData.humidValue.dig_H3)) >> 11) + ((int32_t)32768))) >> 10) +
  23.         ((int32_t)2097152)) * ((int32_t)CalibData.humidValue.dig_H2) + 8192) >> 14));
  24.  
  25.     tmpValue = (tmpValue - (((((tmpValue >> 15) * (tmpValue >> 15)) >> 7) *
  26.         ((int32_t)CalibData.humidValue.dig_H1)) >> 4));
  27.     tmpValue = (tmpValue < 0) ? 0 : tmpValue;
  28.     tmpValue = (tmpValue > 419430400) ? 419430400 : tmpValue;
  29.  
  30.     humidConverted = (tmpValue>>12);
  31.     humidConverted /= 1024.0f;
  32.  
  33.     return humidConverted;
  34. }

Funkcja obliczająca wysokość:

  1. float BME280_ReadAltitude(float seaLevel)
  2. {
  3.     float altitude = 0.0f;
  4.     float presure = BME280_ReadPressure();
  5.  
  6.     altitude = 44330.0 * (1.0 - pow(presure/seaLevel, 0.1903));
  7.  
  8.     return altitude;
  9. }

Wzór zastosowany w powyższej funkcji wygląda następująco:

wysokość= 44330 *(1-(p/p0)^(1/5.255))

Aby zastosować ten wzór należy znać ciśnienie jakie panuje na poziomie morza (1013.25 hPa) oraz odczytaną aktualną wartość ciśnienia.

Zmiana ciśnienia o 1hPa będzie odpowiadała zmianie wysokości o 8.43m.

Taki sposób określania wysokości jest dużo dokładniejszy niż przy wykorzystywaniu odbiorników GPS. W nich błąd pomiaru może wynosić nawet 10%. Więcej szczegółów dotyczących tego sposobu pomiaru można znaleźć np. pod tym linkiem.

Przykładowy sposób użycia funkcji wygląda następująco:

  1. BME280_ReadedData_t BME280_Data;
  2. BME280_Initial(BME280_STANDBY_MS_1000, BME280_FILTER_X4,
  3.         BME280_TEMP_OVERSAMPLING_X4, BME280_PRES_OVERSAMPLING_X2,
  4.         BME280_HUM_OVERSAMPLING_X1, BME280_MODE_NORMAL);
  5. BME280_Data.temp = BME280_ReadTemperature();
  6. BME280_Data.pres= BME280_ReadPressure();
  7. BME280_Data.humi = BME280_ReadHumidity();
  8. BME280_Data.alti = BME280_ReadAltitude(kSEA_LEVEL_PRESURE_PA);

Wykaz pozostałych funkcji pomocniczych:

  1. static inline void I2Cx_WriteData(uint16_t Addr, uint8_t Reg, uint8_t Value);
  2. static inline uint8_t I2Cx_ReadData(uint16_t Addr, uint8_t Reg);
  3. static void I2Cx_ReadData16(uint16_t Addr, uint8_t Reg, uint16_t *Value);
  4. static void I2Cx_ReadData24(uint16_t Addr, uint8_t Reg, uint32_t *Value);
  5. static void bme280_WriteReg(uint8_t Reg, uint8_t Value);
  6. static uint8_t bme280_ReadReg(uint8_t Reg);
  7. static void bme280_ReadRegPtr(uint8_t readRegister, uint8_t *ptrReadedValue);
  8. static uint8_t bme280_ReadStatus(void);
  9. static void bme280_ReadData16(uint8_t readRegister, uint16_t *ptrReadedValue);
  10. static void bme280_ReadSignedData16(uint8_t readRegister, int16_t *ptrReadedValue);
  11. static void bme280_ReadSignedData16_Convert(uint8_t readRegister, int16_t *ptrReadedValue);
  12. static void bme280_ReadRegDataConvert24(uint8_t readRegister, uint32_t *ptrReadedValue);
  13. static void bme280_ReadCoefficients(void);
  14. static void bme280_SetStandby(BME280_standby_Time_E standByTime);
  15. static void bme280_SetFilter(BME280_filter_E filter);
  16. static void bme280_SetOversamplingTemper(BME280_overSamplingTemp_E tempOversampl);
  17. static void bme280_SetOversamplingPressure(BME280_overSamplingPres_E presOversampl);
  18. static void bme280_SetOversamplingHum(BME280_overSamplingHum_E humOversampl);
  19. static void bme280_SetMode(BME280_mode_E mode);

Całą bibliotekę można pobrać z dysku Google pod tym linkiem.