LinkedIn YouTube Facebook
Szukaj

Wstecz
Artykuły

BME280 – czujnik temperatury, wilgotności oraz ciśnienia (część 2)

Funkcje drivera I2C

Po konfiguracji i wygenerowaniu przez konfigurator odpowiednich plików po kliknięciu na Generate Project Content, można używać funkcji drivera magistrali I2C.

Otwarcie drivera

Pierwszą rzeczą jaką należy zrobić jest otwarcie drivera za pomocą funkcji open. Pokazane to zostało na listingu 1.

Listing 1. Otwarcie interfejsu I2C

//otwarcie magistrali I2C BME280
 ssp_err_t BME280_BUS_Open()
 {
     ssp_err_t status;
     status=BME280.p_api -> open(BME280.p_ctrl, BME280.p_cfg);

     return status;

 } 

Argumentami funkcji open są dwie struktury: p_ctrl i  p_cfg. Struktura p_ctrl zawiera informację opisującą interfejs i flagę określającą czy interfejs został prawidłowo otwarty. Struktura p_cfg zawiera wszystkie konfiguracje pokazane na rysunku 23.

Funkcja open zwraca status:

  • SSP_SUCCESS – interfejs został poprawnie otwarty i może być używany,
  • SSP_ERR_IN_USE – interfejs jest już otwarty i nie można go powtórnie otworzyć,
  • SSP_ERR_INVALID_RATE – nie można wyliczyć wybranej prędkości transmisji.

Do odczytywania i zapisywania danych z czujnika BME280 zostały napisane dwie funkcje: BME280_I2C_Write i BME280_I2C_Read.

Funkcja Callback

Ponieważ w konfiguracji określiliśmy nazwę Callback, to funkcje przesyłania danych po magistrali nie są blokujące. Użytkownik musi zdefiniować funkcję callback wywoływaną przez zdarzenia zgłaszane przez funkcje zapisywania i odczytywania danych z magistrali. Dla drivera magistrali I2C zdefiniowano w SSP zdarzenia I2C_EVENT_ABORTED (transfer niedokończony), I2C_EVENT_RX_COMPLETE (operacja odczytywania zakończona sukcesem), a także I2C_EVENT_TX_COMPLETE (operacja zapisywania zakończona sukcesem). Dla naszych potrzeb zdefiniowałem dwie zmienne globalne i funkcję BME280_Callback – listing 2.

Listing 2. Funkcja BME280_Callback

//***********************************************************
//prototyp funkcji callback dla kontroli transmisji I2C
//**********************************************************
volatile void BME280_Callback(i2c_callback_args_t * p_args)

{

   if(I2C_EVENT_TX_COMPLETE == p_args->event)

   {
	//zapis danych na magistralę zakończony sukcesem 
       BME280_data_tx = 1;
   }

   if(I2C_EVENT_RX_COMPLETE == p_args->event)

      {
	 //odczyt danych z magistrali zakończony sukcesem 
          BME280_data_rx = 1;
      }
   if(I2C_EVENT_ABORTED == p_args->event)

         {
		//transfer danych zakończony niepowodzeniem 
             BME280_data_rx = 10; BME280_data_tx = 10;
         }
   
}

Poprawne zakończenie odczytania lub zapisania danych jest sygnalizowane wpisaniem jedynki do odpowiednich zmiennych. Wpisanie wartości 10 sygnalizuje transfer danych, który nie zakończył się sukcesem. Użytkownik musi przed wywołaniem funkcji zapisu i odczytu wyzerować odpowiednią zmienną i po wykonaniu funkcji write lub read czekać na wpisanie do tej zmiennej wartości 1. Funkcje przykładowe czekają na wpisanie 1 w nieskończonych pętlach, czyli blokują cały program. Jednak łatwo jest je zmodyfikować tak, by oczekiwanie trwało przez określony czas i jeżeli potwierdzenie w tym czasie nie nadejdzie nastąpi wyjście z pętli i będzie zgłaszany błąd.

Funkcja przesyłania danych

Funkcja przesłania danych przez magistralę I2C BME280_I2C_Write została pokazana na listingu 3. Jej argumentami są: wskaźnik na bufor z danymi do wysłania (*buff), ilość danych do wysłania (size) i flaga stop określająca czy sekwencja zapisu ma się zakończyć sekwencją STOP. Samo zapisanie danych wykonuje funkcja write wykorzystująca funkcję warstwy HAL z argumentami: struktura p_ctrl opisująca czy interfejs został poprawnie otwarty, wskaźnik na bufor z danymi, ilość danych i flaga stop.

Listing 3. Funkcja zapisywania danych do czujnika BME280

ssp_err_t BME280_I2C_Write(uint8_t *buff,uint8_t size, bool stop )
{
    ssp_err_t status;
    BME280_data_tx=0;
    status=BME280.p_api->write(BME280.p_ctrl, buff, size,stop);
    if(status != SSP_SUCCESS)
           return(status);
           while (BME280_data_tx != 1)
                    ;
           return(status);

}

Poprawne zapisanie danych do wysłania przez interfejs I2C powoduje zwrócenie przez funkcję write wartości SSP_SUCCESS. Kiedy operacja zapisu nie powiedzie się, zwracana jest wartość SPP_ERR_ABORTED. W naszym przypadku zapisanie danych do wysłania nie czeka na zakończenie transferu i program musi czekać na wpisanie jedynki do  zmiennej BME280_data_tx po wystąpieniu zdarzenia I2C_EVENT_TX_COMPLETE.

Funkcja odczytywania danych

Funkcja BME280_I2C_Read jest przeznaczona do odczytania danych z sensora BME280 przez układ Master i ma trzy argumenty: wskaźnik na bufor z odczytanymi danymi, ilość odczytywanych danych i flaga stop. Odczytywanie realizuje funkcja read. Mechanizmy rozpoznawania zakończenia sekwencji odczytywania są takie same jak w przypadku poprzedniej funkcji.

Listing 4. Funkcja odczytywania danych z czujnika BME280

ssp_err_t BME280_I2C_Read(uint8_t *buff, uint8_t size, bool stop)
{
    ssp_err_t status;
    BME280_data_rx = 0;
    status=BME280.p_api->read(BME280.p_ctrl, buff, size,stop);
        if(status != SSP_SUCCESS)
         return status;
       while (BME280_data_rx != 1)
                    ;
       return(status);

}

Implementacja obsługi sensora BME280 na innym mikrokontrolerze (innego producenta) wymaga napisania na nowo obsługi I2C. Pozostałe funkcje opisywane poniżej mogą pozostać bez zmian.

Odczytywanie danych z sensora

Dane pomiarowe z trzech czujników są dostępne w rejestrach od adresu 0xF7 do 0xFE – rysunek 25.

Rysunek 25. Rejestry wyniku pomiarów z czujników BME280

Producent w danych technicznych zaleca, żeby odczytywać wszystkie dane pomiarowe z rejestrów w jednym cyklu odczytu. Zapobiega to możliwości mieszania się danych w rejestrach z kolejnych pomiarów. Jeżeli nie odczytujemy wilgotności, to w takim razie w jednym cyklu trzeba odczytać rejestry od 0xF7 do 0xFC. Odczytywanie danych ze wszystkich czujników wymaga odczytu rejestrów od adresu 0xF7 do 0xFE. Odczytywanie bloku rejestrów ma szczególne znaczenie w trybie normalnym. Układy logiczne sensora mają podwójne buforowanie danych. Pomiary są najpierw zapisywane do bufora cienia i potem przepisywane do bufora danych wyjściowych. Blokowanie zapisu do bufora danych z bufora cienia działa jedynie w momencie odczytywania danych przez magistralę. Dla interfejsu SPI koniec sesji odczytania danych jest wykrywane za pomocą narastającego zbocza sygnału CSB, natomiast dla I2C przez wykrycie sekwencji STOP.

Sekwencja odczytywania danych z rejestrów sensora przez magistralę I2C została pokazana na rysunku 19. W pierwszym etapie master wysyła sekwencje START, adres Slave z bitem RW=0 i adres rejestru. Nie jest wysyłana sekwencja STOP. W drugim etapie wysyłamy powtórnie sekwencję START, adres slave z bitem RW=1, odczytujemy zadaną ilość bajtów i na koniec sekwencję STOP. Na listingu 5 pokazano funkcję, która odczytuje blok danych od zadanego adresu rejestru i umieszcza go w buforze. Wskaźnik na bufor jest argumentem tej funkcji.

Listing 5. Odczytanie zadanej ilości danych od rejestru z argumentu reg

/***************************************************************************
 *
 * @param reg adres rejestru
 * @param data_size ilosc danych do odczytania
 * @param p_data wskaznik na bufor danych
 * @return kod bledu operacji na magistrali I2C
 * odczytanie rejestrow wszsytkich pomiarow
 * 8 bajtow od adresu 0xB7
 **************************************************************************/
uint8_t BME280_read_PTH(uint8_t reg, uint8_t data_size, uint8_t *p_data)
{

    ssp_err_t status;
    uint8_t temp[2];
    temp[0] = reg;

	//zapisz adres rejestru, bez sekwencji STOP
    status = BME280_I2C_Write(temp, 1, true);
    if(status != SSP_SUCCESS)
        return (status);

	//odczytaj zadana ilość bajtow i zapisz w buforze p_data
    status=BME280_I2C_Read(p_data, data_size, false);
    if(status != SSP_SUCCESS)
         return(status);

         return status;

}

Z tych danych trzeba „złożyć” słowa o długości odpowiadającej rozdzielczości pomiarów. Pomiar ciśnienia i temperatury ma rozdzielczość 20-bitową, natomiast pomiar wilgotności 16-bitową.

Dane kalibracyjne w pamięci BME280

Sensor BME280 ma zapisane w pamięci nieulotnej dane kalibracyjne potrzebne do uzyskania dużej dokładności w szerokim zakresie pomiarowym. Są tam umieszczane w procesie produkcyjnym, a więc użytkownik nie ma możliwości modyfikacji. Może je tylko odczytać i wykorzystać do obliczenia kompensacji pomiarów. Te dane są dostępne w rejestrach od adresu 0x88 do adresu 0xA1 oraz od adresu 0xE1 do 0xE7 (kompensacja wilgotności) – rysunek 26.

Rysunek 26. Umieszczenie danych kompensacyjnych w rejestrach

Funkcje odczytywania danych z rejestrów

Do odczytania danych kompensacji przygotowane zostały 3 funkcje. Pierwsza z nich to BME280_read_reg16 odczytująca 2 kolejne bajty od adresu z argumentu i zwracająca 16-bitową wartość. Natomiast, druga funkcja odczytuje i zwraca jeden bajt spod adresu z jej argumentu.

Listing 6. Odczytanie rejestru 16-bitowego

//odczytanie rejestru 16-bitowego
 uint16_t BME280_read_reg16(uint8_t reg)
 {
    ssp_err_t status;

   uint8_t data[4];
   uint16_t value = 0;
   data[0] = reg;
   Err = 0; //OK!


   status = BME280_I2C_Write(data,1,true);
   if(status != SSP_SUCCESS)
       {Err = 0xFF;
         return(0);
       }
   status = BME280_I2C_Read(data,2,false);
   if(status != SSP_SUCCESS)
         {
            Err = 0xFF;
             return(0);
         }

   //"składanie" 16-bitowej wartosci
   value = data[1];
   value = (value<<8) | data[0];
   return value;
 }

Listing 7. Odczytanie rejestru 8-bitowego

uint8_t readUint8(uint8_t reg)
 {
   ssp_err_t status;
   uint8_t data[3];
   data[0]=reg;
   status = BME280_I2C_Write(data,1,true);
     if(status != SSP_SUCCESS)
         {Err = 0xFF;
           return(0);
         }
     status = BME280_I2C_Read(data,1,false);
     if(status != SSP_SUCCESS)
           {
              Err = 0xFF;
               return(0);
           }


   return data[0];
 }

Dane kalibracyjne będą umieszczone w zmiennych globalnych zadeklarowanych w listingu 8.

Listing 8. Deklaracja zmiennych dla danych kalibracyjnych

// Calibration data.
 uint16_t _dig_T1;
 int16_t _dig_T2;
 int16_t _dig_T3;
 uint16_t _dig_P1;
 int16_t _dig_P2;
 int16_t _dig_P3;
 int16_t _dig_P4;
 int16_t _dig_P5;
 int16_t _dig_P6;
 int16_t _dig_P7;
 int16_t _dig_P8;
 int16_t _dig_P9;
 uint8_t _dig_H1;
 int16_t _dig_H2;
 uint8_t _dig_H3;
 int16_t _dig_H4;
 int16_t _dig_H5;
 int8_t _dig_H6;

Trzecia funkcja BME280_readCalibrationData odczytuje rejestry kalibracyjne, a także umieszcza dane kalibracyjne w zmiennych z listingu 8. Wykorzystuje się je później w prcedurach kompensacji pomiarów.

Listing 9. Odczytanie danych kalibracyjnych

{
    Err=0;
  _dig_T1 = BME280_read_reg16(BME280_CAL_T1);

  _dig_T2 = (int16_t) BME280_read_reg16(BME280_CAL_T2);

  _dig_T3 = (int16_t) BME280_read_reg16(BME280_CAL_T3);
  _dig_P1 = BME280_read_reg16(BME280_CAL_P1);

  _dig_P2 = (int16_t) BME280_read_reg16(BME280_CAL_P2);
  _dig_P3 = (int16_t) BME280_read_reg16(BME280_CAL_P3);
  _dig_P4 = (int16_t) BME280_read_reg16(BME280_CAL_P4);
  _dig_P5 = (int16_t) BME280_read_reg16(BME280_CAL_P5);
  _dig_P6 = (int16_t) BME280_read_reg16(BME280_CAL_P6);
  _dig_P7 = (int16_t) BME280_read_reg16(BME280_CAL_P7);
  _dig_P8 = (int16_t) BME280_read_reg16(BME280_CAL_P8);
  _dig_P9 = (int16_t) BME280_read_reg16(BME280_CAL_P9);
  _dig_H1 = readUint8(BME280_CAL_H1);
  _dig_H2 = (int16_t) BME280_read_reg16(BME280_CAL_H2);
  _dig_H3 = readUint8(BME280_CAL_H3);
  // H4 & H5 wspoldziela bajty .
  uint8_t temp1 = readUint8(BME280_CAL_H4);
  uint8_t temp2 = readUint8(BME280_CAL_H45);
  uint8_t temp3 = readUint8(BME280_CAL_H5);
  _dig_H4 = (temp1<<4) | (temp2&0x0f);
  _dig_H5 = (temp3<<4) | (temp2>>4);
  _dig_H6 = (int8_t) readUint8(BME280_CAL_H6);
}
Czujnik BME280 można znaleźć w sklepie Kamami.pl. Oferta obejmuje również moduły z zamontowanym sensorem.
Absolwent Wydziału Elektroniki Politechniki Wrocławskiej, współpracownik miesięcznika Elektronika Praktyczna, autor książek o mikrokontrolerach Microchip i wyświetlaczach graficznych, wydanych nakładem Wydawnictwa BTC. Zawodowo zajmuje się projektowaniem zaawansowanych systemów mikroprocesorowych.