niedziela, 5 lutego 2017

[IV] STM32F4 - Projekt - HC-SR04, DS18B20, USART, RTC

Ten post chciałbym poświęcić na projekt realizujący pomiar odległości czujnikiem HC-SR04, wyposażony w dwa czujniki temperatury DS18B20, które pozwolą na dostosowanie wyników pomiarów do warunków zewnętrznych, komunikację poprzez USART z komputerem.

Dodatkowe funkcje jakie chciałbym wprowadzić to pomiar wielkości pomieszczenia czy pudła, wraz z mierzeniem jej zawartości.

Elementy oraz podłączenie:


  • HC-SR04 domyślnie PD0;
  • DS18B20 domyślnie PB8. Można go dowolnie modyfikować ponieważ procedura obsługi transmisji OneWire została oparta o funkcje opóźniające.
  • Konwerter USART USB - RX PC10; TX PC11;
  • Wyświetlacz Nokia5110 DC - GPIOC_14 | CE - GPIOC_13 | RST - GPIOC_15 | DIN - GPIOC_3 | CLK - GPIOB_10 ;

Programowanie:


Do projektu należy skonfigurować takie elementy jak:

Czujnik odległości HC-SR04, który działa na dwóch timerach generujących odpowiednie czasy oraz zliczający czas wystąpienia stanu wysokiego na pinie Echo. Jest on następnie zamieniany na czas, który zostaje przeliczony na odległość układu od przeszkody.



Czujniki temperatury DS18B20, połączonych w jednej linii. Dane z nich są następnie uśredniane i ich wartość jest pobierana do obliczenia odległości.

Poprzez USART dane będą wysyłane do komputera. Głównie w celu debugowania, ale także z informacjami zebranymi z czujników.

Wyświetlacz Nokia, na którym będą prezentowane wyniki działania programu tzn, aktualny czas, odległość od przeszkody oraz temperatura.

HC-SR04 opis:


W tej części chciałbym trochę opisać czujnik HC-SR04. Podstawowe parametry:

  • Napięcie zasilania 5V (z 3.3V nie działa). Zgodnie z dokumentacją tylko 5V akceptuje. Do STMa najlepiej podłączyć przez rezystor, 500Ohm do 1kOhm spokojnie powinno wystarczyć.
  • Pobór prądu wynosi 15mA;
  • Zakres od 2 do 200cm. Daje rade do 230cm z przyzwoitą dokładnością i nie skomplikowanych celów pomiarowych. Większy zakres oferuje np US-015 bądź HC-SR04 od SparkFun (taki czerwony).
  • Częstotliwość pracy wynosi 40kHz
  • Kąt pracy pomiędzy 15 a 30 st.

Ja korzystam z czujnika zawierającego dwa piny przeznaczone do sterowania. Na jednym z nich generowany jest impuls 10us, drugi natomiast odczytuje odpowiedź. Występują też układy posiadające tylko jeden pin sterujący. Zasada działa jest taka sama. Takie same sygnały należy podać w celu inicjalizacji oraz odczytu.

Należy także mieć na uwadze, że mogą występować sytuacje w których dana przeszkoda nie zostanie poprawnie zinterpretowana. Może się to zdarzyć gdy odległość od przeszkody przekracza maksymalny dopuszczalny zakres pomiarowy. W takim przypadku, jeśli jest to niewielkie przekroczenie to powinna wystąpić tylko pogorszenie dokładności. Druga sytuacja dotyczy za małego kąta pomiarowego. Fala ultradźwiękowa trafi na przeszkodę natomiast nie zostanie zarejestrowana fala powrotna, która po odbiciu nie wróci do odbiornika. Ostatnim problemem jest wielkość obiektu mierzonego. Ten z kolei jeśli będzie za mały to czujnik nie zarejestruje jego obecności. Im dalej znajduje się on od przeszkody tym większe prawdopodobieństwo wystąpienia takiej sytuacji.

Dokładność HC-SR04:


Czujnik HC-SR04 posiada pewną rozbieżność, która w zależności od jego zastosowania może mieć, większy lub mniejszy wpływ na jego skuteczność działania. W celu jej maksymalnej poprawy zastosowałem uśrednianie dziesięciu wyników pomiarów, które wyeliminują występujące duże niedokładności. Następnie wyeliminowałem wyniki, znacząco odbiegające od pozostałych. Każdy z wyników zanim przejdzie do uśredniania zostaje obliczony poprzez zmodyfikowany wzór obliczania odległości, który pobiera wcześniej obliczoną prędkość dźwięku w powietrzu. Te wartości wahają się w następujący sposób:
  • -40 st. C - 306.5 m/s
  • -30 st. C - 312.9 m/s
  • -20 st. C - 319.3 m/s
  • -10 st. C - 325.6 m/s
  • 0 st. C - 331.8 m/s
  • 10 st. C - 337.8 m/s
  • 15 st. C - 340.3 m/s
  • 20 st. C - 343.8 m/s
  • 30 st. C - 349.6 m/s
  • 40 st. C - 355.3 m/s
Pozostałe parametry takie jak ciśnienie czy wilgotność nie wpływają znaczącą na tą wartość. Wartość tą oblicza się na podstawie wzoru:

v = 331.5*sqrt(1+(temperatura[st. C]/273,15) [m/s]

Taki wzór można stosować na dwa sposoby. Pierwszy z nich jest obliczanie każdorazowo tej prędkości dla zmierzonej temperatury. Drugi sposób dotyczy wykonania obliczeń wcześniej i przygotowania tablicy zawierającej wartości już obliczone. Jest on dosyć kłopotliwy jeśli zależy nam na dużej dokładności dla szerokiego zakresu temperatur. Ponieważ wtedy rozmiar tablicy byłby dosyć znaczący.

Program:


Poniżej przejdę przez ważniejsze funkcje dla każdego z modułów. Cały program będzie do pobrania pod linkiem na dole strony.

HC-SR04


Jak już wspomniałem wcześniej czujnik HC-SR04 został oprogramowany z użyciem dwóch timerów. 

W pierwszej kolejności należy zainicjalizować porty GPIO. Takie funkcje można wywołać jako funkcje wspólną razem z inicjalizacją timera oraz przerwań. Dzięki czemu te podstawowe elemen ty można zdefiniować jako prywatne:

  1. // Echo Pin HC-SR04 (PA0)
  2. #define  HCSR04_ECHO_PORT     GPIOA
  3. #define  HCSR04_ECHO_CLK      RCC_AHB1Periph_GPIOA
  4. #define  HCSR04_ECHO_PIN      GPIO_Pin_0
  5. #define  HCSR04_ECHO_SOURCE   GPIO_PinSource0
  6. // Trigger Pin (PD3)
  7. #define  HCSR04_TRIGGER_PORT  GPIOD
  8. #define  HCSR04_TRIGGER_CLK   RCC_AHB1Periph_GPIOD
  9. #define  HCSR04_TRIGGER_PIN   GPIO_Pin_3
  10. static void Priv_HCSR04_GPIO_Init(void)
  11. {
  12.   GPIO_InitTypeDef GPIO_InitStructure;
  13.   //Pin - Trigger
  14.   RCC_AHB1PeriphClockCmd(HCSR04_TRIGGER_CLK, ENABLE);
  15.   RCC_AHB1PeriphClockCmd(HCSR04_ECHO_CLK, ENABLE);
  16.   GPIO_InitStructure.GPIO_Pin = HCSR04_TRIGGER_PIN;
  17.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  18.   GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  19.   GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  20.   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  21.   GPIO_Init(HCSR04_TRIGGER_PORT, &GPIO_InitStructure);
  22.   //Ustawienie pinu na low
  23.   HCSR04_TRIGGER_PORT->BSRRH = HCSR04_TRIGGER_PIN;
  24.   //Echo - Pin
  25.   GPIO_InitStructure.GPIO_Pin = HCSR04_ECHO_PIN;
  26.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  27.   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  28.   GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  29.   GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  30.   GPIO_Init(HCSR04_ECHO_PORT, &GPIO_InitStructure);
  31.   GPIO_PinAFConfig(HCSR04_ECHO_PORT, HCSR04_ECHO_SOURCE, GPIO_AF_TIM2);
  32. }

Funkcja pozwalająca na włączenie wykorzystywanych timerów:

  1. static void Priv_HCSR04_Init_TIM2_TIM7(void)
  2. {
  3.   TIM_ICInitTypeDef  TIM_ICInitStructure;
  4.   TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
  5.   //==========================
  6.   // Timer-7 wlaczenie, odpowiada za opoznienie
  7.   RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);  
  8.   RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
  9.   TIM_Cmd(TIM7, DISABLE);
  10.   // Ustawienie timera
  11.   TIM_TimeBaseStructure.TIM_Period=9;       //10us
  12.   TIM_TimeBaseStructure.TIM_Prescaler=83;   //1Mhz
  13.   TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
  14.   TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
  15.   TIM_TimeBaseInit(TIM7, &TIM_TimeBaseStructure);
  16.   TIM_ARRPreloadConfig(TIM7, ENABLE);
  17.   // Timer -2 ustawienie trybu input capture
  18.   TIM_PrescalerConfig(TIM2, 83, TIM_PSCReloadMode_Immediate);
  19.   TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
  20.   TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;
  21.   TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
  22.   TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
  23.   TIM_ICInitStructure.TIM_ICFilter = 0x0;                  
  24.   TIM_PWMIConfig(TIM2, &TIM_ICInitStructure);
  25.   TIM_SelectInputTrigger(TIM2, TIM_TS_TI1FP1);          //TIM_Internal_Trigger_Selection
  26.                                                         //Triger input
  27.   // Tryb slave_mode reset
  28.   TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset);
  29.   TIM_SelectMasterSlaveMode(TIM2,TIM_MasterSlaveMode_Enable);
  30. }

Dodatkowo do układu należy jeszcze włączyć przerwania dla timerów 2 oraz 7. Wykonuje się to poprzez następującą funkcję:

  1. static void Priv_HCSR04_InitNVIC(void)
  2. {
  3.   NVIC_InitTypeDef NVIC_InitStructure;
  4.   //Przerwanie wewnetrzne od timera 2
  5.   NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
  6.   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  7.   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  8.   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  9.   NVIC_Init(&NVIC_InitStructure);
  10.   TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE);
  11.   //Przerwanie wewnetrzne od timera 7
  12.   NVIC_InitStructure.NVIC_IRQChannel = TIM7_IRQn;
  13.   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  14.   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  15.   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  16.   NVIC_Init(&NVIC_InitStructure);
  17.   TIM_ITConfig(TIM7,TIM_IT_Update,ENABLE);
  18. }

Dane jakie są potrzebne dla układu są przetrzymywane w strukturze:

  1. typedef struct {
  2.   uint32_t time_tim2;
  3.   uint32_t time_tim7;
  4.   uint32_t delay_u_sec;
  5. }HCSR04_Ultra_t;

Przedstawione powyżej funkcje są prywatne zainicjalizowane jako static. Ma to za zadanie ograniczyć ich zasięg tylko do plików należących do układu HC-SR04. Ich wywołanie następuje poprzez jedną publiczną funkcję przedstawioną poniżej.

  1. static void Priv_SetData_Struct(void)
  2. {
  3.     HCSR04.time_tim2= 0;
  4.     HCSR04.time_tim7=0;
  5.     HCSR04.delay_u_sec=0;
  6. }
  7. //===============================================================
  8. //Inicjalizacja HCSR04
  9. void HCSR04_Init(void)
  10. {
  11.   Priv_SetData_Struct();
  12.   Priv_HCSR04_GPIO_Init();
  13.   Priv_HCSR04_Init_TIM2_TIM7();
  14.   Priv_HCSR04_InitNVIC();
  15. }

W pierwszej kolejności ustawieniu ulegają parametry dla struktury. Następnie następuje przejście przez wszystkie procedury inicjalizacji.

Sygnał włączający pomiar musi trwać 10us. Za jego generację odpowiada timer 7:

  1. static void Priv_HCSR04_Trigger(void)
  2. {
  3.   HCSR04.time_tim7=0;
  4.   //Pin Trigger ustawiony w stan high
  5.   HCSR04_TRIGGER_PORT->BSRRL = HCSR04_TRIGGER_PIN;
  6.   TIM_Cmd(TIM7, ENABLE);        //Wlaczenie timera
  7.   while(HCSR04.time_tim7<10);   //Odliczanie od 0 do 9, aktualizacja w
  8.                                 //obsludze przerwania
  9.   TIM_Cmd(TIM7, DISABLE);       //Wylaczenie timera
  10.   //Pin triggera ustawiony na low
  11.   HCSR04_TRIGGER_PORT->BSRRH = HCSR04_TRIGGER_PIN;
  12. }

Pojedynczy pomiar bez kompensacji temperaturowej wygląda następująco:

  1. float HCSR04_Distance_cm(void)
  2. {
  3.   float return_value=0.0;
  4.   uint32_t check_ok=0;
  5.   HCSR04.time_tim2=0;             //czyszczenie danych w strukturze
  6.   TIM_SetCounter(TIM2, 0);      //zerowanie licznika
  7.   TIM_Cmd(TIM2, ENABLE);        //Wlaczenie timer2
  8.   Priv_HCSR04_Trigger_Send();   //Wyslanie sygnalu Send
  9.   check_ok=0;
  10.   HCSR04.time_tim7=0;
  11.   TIM_Cmd(TIM7, ENABLE); //Wlaczenie timera
  12.   do
  13.   {
  14.         if(HCSR04.time_tim2!=0)               { check_ok=1; }     // pomiar zakonczony powodzeniem
  15.         if(HCSR04.time_tim7>=HCSR04_TIMEOUT)  { check_ok=2; }     // niepowodzenie pomiaru
  16.   }while(check_ok==0);
  17.   TIM_Cmd(TIM7, DISABLE);
  18.   TIM_Cmd(TIM2, DISABLE);
  19.   HCSR04.time_tim7=0;
  20.   TIM_Cmd(TIM7, ENABLE);
  21.   while(HCSR04.time_tim7<HCSR04_DELAY);
  22.   TIM_Cmd(TIM7, DISABLE);
  23.   if(check_ok==1)
  24.   {
  25.         //konwersja czasu na centymetry(w us) na centymetry
  26.         return_value=(float)(HCSR04.delay_us)*(float)(0.017);
  27.   }
  28.   //Blad pomiaru
  29.   else { return_value=-1.0; }
  30.   return return_value;
  31. }

Pojedynczy pomiar z kompensacją temperaturową z dwóch czujników temperatury:

  1. float HCSR04_Single_With_Temp_cm(float temp)
  2. {
  3.     float return_value = 0.0;
  4.     float c = 0.0;
  5.    
  6.     HCSR04_Distance_cm();
  7.    
  8.     c = 331.5 + (0.6 * temp);
  9.     c = c * 0.0001;
  10.    
  11.     return return_value += ((float)(HCSR04.delay_u_sec*0.5) * c);
  12. }

Wykonywanie kilku pomiarów wraz z ich uśrednieniem poprzez zastosowanie podstawowej metody czyli średniej arytmetycznej:

  1. typedef enum {
  2.     Use_Temp_Value_False = 0, //Nie korzysta z kompensacji temperatury
  3.     Use_Temp_Value_True = 1   //Wykorzystuje kompensacje temperaturowa
  4. }Use_Temp_Value_t;
  5. float HC_SR04_Distance_Few_Data(uint8_t sample_number, Use_Temp_Value_t use_temp_value)
  6. {
  7.     float return_value = 0.0;
  8.     uint8_t i = 0;
  9.     //Bez kompensacji temperaturowej
  10.     if(use_temp_value == Use_Temp_Value_False)
  11.     {
  12.         for(i=0;i<sample_number;i++)
  13.         {
  14.             return_value += HCSR04_Distance_cm();
  15.         }
  16.     }
  17.     //Z kompensacją temperaturową
  18.     if(use_temp_value == Use_Temp_Value_True)
  19.     {
  20.         for(i=0;i<sample_number;i++)
  21.         {
  22.             return_value += HCSR04_Distance_With_Temp_cm();
  23.         }
  24.     }
  25.     return_value = (return_value/(float)sample_number);
  26.     return return_value;
  27. }

Ta funkcja pozwala na wykonanie zadanej ilości pomiarów, oraz zdecydowanie czy do pomiaru ma być brana wartość temperatury od czujnika DS18B20. W tym przypadku zdecydowałem się wynik odległości przechowywać w zmiennej float. Jeśli prób pomiarowych będzie bardzo dużo, chociaż uważam, że jest to mało potrzebne bo nie polepszy to aż tak dokładności natomiast znacząco zwiększy czas pomiaru, to należałoby zastosować np dwie zmienne typu float, i z nich na końcu wyciągnąć średnią.

Na samym końcu przedstawię funkcje odpowiedzialne za obsługę przerwania. Działanie jest dosyć proste w TIM7 następuje sprawdzenie wygenerowanego przerwania, po czym flaga od przerwania jest czyszczona. Jedno wywołanie i przejście przez funkcję pozwoli na odliczenie około 10us, potrzebnych do wygenerowania odpowiedniego sygnału inicjalizującego.

  1. void TIM7_IRQHandler(void)
  2. {
  3.   if (TIM_GetITStatus(TIM7, TIM_IT_Update) != RESET)
  4.   {
  5.     //Czyszczenie flagi
  6.     TIM_ClearITPendingBit(TIM7, TIM_IT_Update);
  7.     HCSR04.time_tim7+=10;
  8.   }
  9. }

Timer 2 podstawowe działanie ma takie samo. Główna część obsługi przerwania pobiera dane z rejestrów CCR1 oraz CCR2, Rejestry te przechowują wartości jakie mają być porównywane z rejestrem CNT. W kolejnym kroku wartości te są od siebie odejmowane i przekazywane do wartości opóźnienia.

  1. void TIM2_IRQHandler(void)
  2. {
  3.   uint32_t start,stop;
  4.   if(TIM_GetITStatus(TIM2, TIM_IT_CC1) == SET)
  5.   {
  6.     //Usuniecie flagi przerwania
  7.     TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
  8.     //Odczyt wskazan
  9.     start=TIM_GetCapture1(TIM2);
  10.     stop=TIM_GetCapture2(TIM2);
  11.     HCSR04.delay_u_sec=start-stop;
  12.     HCSR04.time_tim2++;
  13.   }
  14. }

Do pobrania danych z rejestrów CCR1 i 2 wykorzystuje funkcje zawarte w pliku stm32f4xx_tim.

DS18B20


Na samym początku opiszę trochę komunikację poprzez interfejs One Wire, po czym przejdę do krótkiego opisania komunikacji z czujnikiem DS18B20.

Zacznę od transmisji One Wire. Jest ona ściśle określona i zawiera poszczególne sekwencje działań jakie należy wykonać aby uzyskać komunikację z układem. Na początku wysyłana jest inicjalizacja, po czym przesyłana jest komenda ROM na końcu przesyłane są poszczególne funkcje. Wyjątek dla czujnika stanowią komendy Search ROM 0x0F oraz Alarm Search 0xEC. Dla nich po ich wysłaniu należy wrócić do inicjalizacji.

Polega ona na wysłaniu sygnału reset od mikrokontrolera po czym czujnik odsyła impuls zwany presence pulse. Dopiero po tej odpowiedzi można przejść do wysyłania komend ROM. DO wyboru są następujące działania:


  • Search ROM (0xF0) - powoduje identyfikację kodów ROM jakie są podłączone do linii. Kiedy jest podłączone tylko jeden układ podrzędny to tą komendę można pominąć.
  • Read ROM (0x33) - odczyt ROM z podrzędnego układu. Wykorzystywana tylko gdy podłączone jest na linii jedno urządzenie. 
  • Match ROM (0x55) - do zaadresowanie podrzędnego układu. Na tą komendę powinien odpowiedzieć jedynie układ o wysłanym kodzie ROM. 
  • Skip ROM (0xCC) - czyli pominięcie wysłania kodu ROM. Pozwala na równoczesne adresowanie urządzeń podłączonych na linii.
  • Alarm Search (0xEC) - na tą komendę odpowiadają układy na których została ustawiona flaga alarmu. Pozwala to na sprawdzenie czy wystąpił sygnał alarmu z czujników, Jak wspomniałem wcześniej po tej operacji 


Na samym początku zdefiniowanie funkcji opóźniających, odpowiedzialne za generowanie opóźnienia pomiędzy funkcjami.

  1. void Delay_Init(void)
  2. {
  3.     RCC_ClocksTypeDef RCC_Clocks;
  4.     RCC_GetClocksFreq(&RCC_Clocks); //Pobranie zegara systemowego
  5.     data_delay = RCC_Clocks.HCLK_Frequency / 5000000;   //1us Delay
  6. }
  7. void DelayMicros(uint32_t micros)
  8. {
  9.     micros = micros * data_delay - 10;
  10.     while (micros--);      
  11. }
  12. void DelayMillis(uint32_t millis)
  13. {
  14.     millis = 1000 * millis * data_delay - 10;
  15.     while (millis--);  
  16. }

  1. void OneWire_Init(OneWire_DS_t* OneWire_DS_St, GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
  2. {
  3.     GPIO_InitTypeDef GPIO_InitDef;
  4.     Delay_Init();
  5.    
  6.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);   //Wlaczenie zegara
  7.     GPIO_InitDef.GPIO_Pin = GPIO_Pin;                       //Wybranie pinów
  8.     GPIO_InitDef.GPIO_OType = GPIO_OType_PP;                //Wyjscie jako push-pull
  9.     GPIO_InitDef.GPIO_Mode = GPIO_Mode_OUT;                 //Ustawienie pinu jako wyjscie
  10.     GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_UP;                  //Bez rezystora podciagajacego
  11.     GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;             //Ustawienie czestotliwosci
  12.     GPIO_Init(GPIOx, &GPIO_InitDef);                        //Wlaczenie lini GPIOD
  13.     OneWire_DS_St->GPIOx = GPIOx;
  14.     OneWire_DS_St->GPIO_Pin = GPIO_Pin;
  15. }
  16. void OneWire_WriteBit(OneWire_DS_t* OneWireStruct, uint8_t bit)
  17. {
  18.     if (bit)
  19.     {
  20.         //Ustaw stan LOW
  21.         ONEWIRE_LOW(OneWireStruct);
  22.         ONEWIRE_OUTPUT(OneWireStruct);
  23.         DelayMicros(10);
  24.        
  25.         //Bit high
  26.         ONEWIRE_INPUT(OneWireStruct);
  27.        
  28.         DelayMicros(55);
  29.         ONEWIRE_INPUT(OneWireStruct);
  30.     }
  31.     else
  32.     {
  33.         //Ustaw linie na LOW
  34.         ONEWIRE_LOW(OneWireStruct);
  35.         ONEWIRE_OUTPUT(OneWireStruct);
  36.         DelayMicros(65);
  37.        
  38.         //Bit high
  39.         ONEWIRE_INPUT(OneWireStruct);
  40.        
  41.         //Odczekaj 5us i zwolnij linie
  42.         DelayMicros(5);
  43.         ONEWIRE_INPUT(OneWireStruct);
  44.     }
  45. }
  46. uint8_t OneWire_ReadBit(OneWire_DS_t* OneWireStruct)
  47. {
  48.     uint8_t bit = 0;
  49.    
  50.     //Linia na low
  51.     ONEWIRE_LOW(OneWireStruct);
  52.     ONEWIRE_OUTPUT(OneWireStruct);
  53.     DelayMicros(3);
  54.    
  55.     //Zwolnij linie
  56.     ONEWIRE_INPUT(OneWireStruct);
  57.     DelayMicros(10);
  58.    
  59.     //Odczytanie stanu linii
  60.     if ((((OneWireStruct->GPIOx)->IDR & (OneWireStruct->GPIO_Pin)) == 0 ? 0 : 1)) { bit = 1; }
  61.    
  62.     //Odczekaj 50us
  63.     DelayMicros(50);
  64.    
  65.     //Zwroc wartosc rejestru
  66.     return bit;
  67. }

Teraz przejdę do opisu komunikacji z czujnikiem DS18B20:

Dane od czujnika przechowywane są scratchpadzie, które zawierają 2 bajty rejestr przechowujący informacje dotyczącą temperatury dostarczoną z czujnika. Z niego uzyskuje się dostęp do jedno bajtowego rejestru porównań TH oraz TL. Dodatkowo pozwala na uzyskanie dostępu do rejestru konfiguracyjnego

W pierwszej kolejności wprowadzenie danych do rejestru konfiguracyjnego dotyczące dokładności czujnika. Do dyspozycji są 4 wartości, 9, 10, 11 lub 12 bitów. TH, TL oraz rejestr konfiguracyjny są typu EEPROM, wobec tego przechowują one dane w przypadku braku zasilania.

Adresowanie urządzenia odbywa się poprzez wykorzystywanie kodów 64-bit kod umieszczony w pamięci ROM. Osiem najmłodszych bitów układu to tzw. family code czyli 0x28. Kolejne 48 bitów zawierają unikalny numer urządzenia. Osiem najstarszych bitów przechowuje CRC, które zostały obliczone z pierwszych 56 bitów.

Wracając do rozdzielczości to każda z dostępnych wartości pozwala na uzyskanie przybliżenia z dokładnością wynoszącą 0,5 st. C, 0,25 st. C, 0,125 st.C, 0,0625 st. C. Im większa rozdzielczość tym czas konwersji zostaje wydłużony do maksymalnie 750ms przy 12 bitach. Jest ona domyślnie ustawiana po włączeniu urządzenia. Aby ustawić rozdzielczość trzeba się odnieść do bit 5 i 6 rejestru konfiguracyjnego.  Tam od 00 (9 bit) do 11(12 bit) można przechodzić po rozdzielczości.

Rozpoczęcie konwersji rozpoczyna się od wysłania wartości 0x44 do czujnika. Po konwersji układ przechodzi w stan bezczynności. Następnie mikrokontroler może wysłać zapytanie odnośnie stanu wykonywania operacji. Jeśli jeszcze konwersja trwa to w odpowiedzi zostaną uzyskane same zera. Jedynki natomiast będą oznaczać zakończenie transmisji.

Dane po konwersji są przechowywane na 16 bitach z rozszerzeniem obejmującym znak. W zależności od rozdzielczości zajęte są wszystkie bity początkowe dla 12 bitów. Dla pozostałych są powoli zwalniane np. dla 9 bitów bit 0, 1 i 2 zostają wolne. Bity od 15 do 11 pozwalają na określenie czy temperatura jest dodatnia czy ujemna.

Istnieje możliwość także zasilania pasożytniczego bezpośrednio z linii magistrali. Natomiast wtedy należy wprowadzać poprawki do kodu ponieważ pewne operacje trzeba wykonać inaczej.

Wracająć do scratchpada, to składa się on z 8 bytów. Bajt 0 oraz 1 przechowuje wynik pomiaru temperatury. Bajt 2 oraz 3 pozwala na uzyskanie dostępu do TH i TL przechowywanych w EEPROM. Bajt 4 pozwala na uzyskanie informacji odnośnie rejestrów konfiguracyjnych. Bajt 5, 6, 7 są zarezerwowane i niedostępne. Można je jedynie odczytać, powoduje to otrzymanie wartości 1. Bajt 8 zawiera CRC z bajtów od 0 do 7.

Wprowadzanie danych do scratchpada musi być poprzedzone komendą 0x4E. Dane muszą być wysłane od najmłodszego bitu od 2 bajtu. Aby odczytać poprawność danych odczytuje się dane za pomocą komendy 0xBE. Dane w odpowiedzi przesyłane są od pierwszego bitu bajtu 0. Aby dane przesłać ze scratchpada do pamięci EEPROM należy wykonać polecenie 0x48. Można je także pobrać ze scratchpada poleceniem 0xB8. Po ty można sprawdzić jej status, 0 jeśli procedura się wykonuje lub 1 jeśli została zakończona.

Poniżej przedstawiam zebrany zestaw komend wykorzystywanych przy komunikację z czujnikiem:


  • Convert T - 0x44 - pojedyncza konwersja temperatury. 
  • Write scratchpad - 0x4E - zapis trzech bajtów do układu (1: TH, 2: TL, 3: rejestr konfiguracyjny). Dane przesyłane od najmłodszego do najstarszego bajtu. Zapis musi nastąpić przed wystawieniem sygnału reset. 
  • Read scratchpad - 0xBE - odczytanie danych z pamięci scratchpada. Układ master zakończy odczyt sygnałem reset.
  • Copy scratchpad - 0x48 - skopiowanie danych do pamięci EEPROM. 
  • Recal E2 - 0xB8 - wywołuje alarm TH, TL oraz rejestry konfiguracyjne z pamięci EEPROM. 
  • Read power supply - 0xB4.


Teraz pokaże opis funkcji odpowiedzialnych za komunikację z czujnikiem:

Na samym początku sprawdzenie czy family code się zgadza:

  1. uint8_t DS18B20_Check_Family_Code(uint8_t *ROM)
  2. {
  3.     if (*ROM == DS18B20_FAMILY_CODE) {  return 1; }
  4.     return 0;
  5. }

Druga funckja odpowiada za zaadresowanie konkretnego czujnika:

  1. uint8_t DS18B20_Single_Sensor_Start(OneWire_DS_t* OneWire, uint8_t *ROM)
  2. {
  3.     if (!DS18B20_Check_Family_Code(ROM)) { return 0; }          //Sprawdzenie czy na linii podlaczone DS18B20
  4.    
  5.     OneWire_Reset(OneWire);                                     //Reset
  6.     OneWire_SelectWithPointer(OneWire, ROM);                    //Wybranie numeru ROM
  7.     OneWire_WriteByte(OneWire, DS18B20_CMD_CONVERTTEMP);        //Rozpoczecie konwersji temperatury
  8.    
  9.     return 1;
  10. }

Pozostałe funkcje będą opisane bezpośrednio w plikach od projektu.

NOKIA5110


Ten układ był juz przezemnie poruszany na forum dla tego mikrokontrolera wobec tego na szybko przedstawię tylko najważniejsze funkcje z których będę korzystał.

Funkcje włączające GPIO, SPI i sam wyświetlacz.

  1. void NOKIA_INIT(void)
  2. {
  3.   GPIO_ResetBits(LCD_RST_PORT, LCD_RST);
  4.   DelayMillis(2);
  5.   GPIO_SetBits(LCD_RST_PORT, LCD_RST);
  6.   NOKIA_CMD(0x21);
  7.   NOKIA_CMD(0xc8);
  8.   NOKIA_CMD(0x06);
  9.   NOKIA_CMD(0x13);
  10.   NOKIA_CMD(0x20);
  11.   NOKIA_CMD(0x0c);
  12.   NOKIA_CMD(0x40);
  13.   NOKIA_CMD(0x80);
  14. }
  15. void GPIO_NOKIA_INIT(void)
  16. {
  17.   GPIO_InitTypeDef GPIOInit;
  18.   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
  19.   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
  20.   GPIO_PinAFConfig(GPIOC, GPIO_PinSource3, GPIO_AF_SPI2);
  21.   GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_SPI2);
  22.   //Inicjalizacja pinów MOSI
  23.   GPIO_StructInit(&GPIOInit);
  24.   GPIOInit.GPIO_Pin = LCD_DIN;
  25.   GPIOInit.GPIO_Mode = GPIO_Mode_AF;
  26.   GPIOInit.GPIO_PuPd = GPIO_PuPd_NOPULL;
  27.   GPIOInit.GPIO_OType = GPIO_OType_PP;
  28.   GPIOInit.GPIO_Speed = GPIO_Speed_100MHz;
  29.   GPIO_Init(LCD_DIN_PORT, &GPIOInit);
  30.   //Inicjalizacja SCK
  31.   GPIO_StructInit(&GPIOInit);
  32.   GPIOInit.GPIO_Pin = LCD_CLK;
  33.   GPIOInit.GPIO_Mode = GPIO_Mode_AF;
  34.   GPIOInit.GPIO_PuPd = GPIO_PuPd_NOPULL;
  35.   GPIOInit.GPIO_OType = GPIO_OType_PP;
  36.   GPIOInit.GPIO_Speed = GPIO_Speed_100MHz;
  37.   GPIO_Init(LCD_CLK_LINE, &GPIOInit);
  38.   //Inicjalizacja MISO, niewykorzystywane
  39.   GPIOInit.GPIO_Pin = MISO_PIN;
  40.   GPIOInit.GPIO_Mode = GPIO_Mode_AF;
  41.   GPIOInit.GPIO_PuPd = GPIO_PuPd_NOPULL;
  42.   GPIOInit.GPIO_OType = GPIO_OType_PP;
  43.   GPIOInit.GPIO_Speed = GPIO_Speed_100MHz;
  44.   GPIO_Init(MISO_PORT, &GPIOInit);
  45.   //Wlaczenie pozostalych linii
  46.   GPIOInit.GPIO_Pin = LCD_DC|LCD_CE|LCD_RST;
  47.   GPIOInit.GPIO_Mode = GPIO_Mode_OUT;
  48.   GPIOInit.GPIO_PuPd = GPIO_PuPd_NOPULL;
  49.   GPIOInit.GPIO_OType = GPIO_OType_PP;
  50.   GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
  51.   GPIO_Init(LCD_DC_PORT, &GPIOInit);
  52.   GPIO_SetBits(LCD_CE_PORT, LCD_CE|LCD_RST);
  53. }
  54. //Inicjalizacja SPI
  55. void SPI_NOKIA_INIT(void)
  56. {
  57.   SPI_InitTypeDef SPIInit;
  58.   RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
  59.   SPI_StructInit(&SPIInit);
  60.   SPIInit.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
  61.   SPIInit.SPI_Mode = SPI_Mode_Master;
  62.   SPIInit.SPI_CPOL = SPI_CPOL_Low;
  63.   SPIInit.SPI_CPHA = SPI_CPHA_1Edge;
  64.   SPIInit.SPI_NSS = SPI_NSS_Soft;
  65.   SPIInit.SPI_DataSize = SPI_DataSize_8b;
  66.   SPIInit.SPI_FirstBit = SPI_FirstBit_MSB;
  67.   SPIInit.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32;
  68.   SPI_NOKIA->CR1 &= ~SPI_CR1_SPE;
  69.   SPI_Init(SPI_NOKIA, &SPIInit);
  70.   SPI_Cmd(SPI_NOKIA, ENABLE);
  71. }
  72. //Wyslanie komendy na wyswietlacz
  73. static uint8_t SPI_SEND_NOKIA(uint8_t byte)
  74. {
  75.   while (SPI_I2S_GetFlagStatus(SPI_NOKIA, SPI_I2S_FLAG_TXE) == RESET);
  76.   GPIO_ResetBits(LCD_CE_PORT, LCD_CE);
  77.   SPI_I2S_SendData(SPI_NOKIA, byte);
  78.   while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET);
  79.   GPIO_SetBits(LCD_CE_PORT, LCD_CE);
  80.   return SPI_I2S_ReceiveData(SPI_NOKIA);
  81. }
  82. //Funkcja pozwalajaca na przesylanie danych do ukladu
  83. static void NOKIA_CMD(uint8_t cmd)
  84. {
  85.   GPIO_ResetBits(LCD_CE_PORT, LCD_CE|LCD_DC);
  86.   SPI_SEND_NOKIA(cmd);
  87.   GPIO_SetBits(LCD_CE_PORT, LCD_CE);
  88. }
  89. void LCD_CLEAR(void)
  90. {
  91.   memset(lcd_buffer, 0, (84 * 48 / 8));
  92. }

Poniżej funkcja odpowiedzialna za wyświetlanie tekstu na wyświetlaczu. I to właściwie tylko z niej korzystam aby wyświetlić dane

  1. void LCD_DRAW(int row, int col, const char* text)
  2. {
  3.   //Deklaracja zmiennych
  4.   int i;
  5.   uint8_t* pbuf = &lcd_buffer[row * 84 + col];
  6.   const uint8_t* font = 0;
  7.   //Dopóki bedzie jakis tekst do wyswietlania oraz bedzie mozliwosc
  8.   //wpisania danych wtedy funkcja bedzie wykonywana
  9.   while ((*text) && (pbuf < &lcd_buffer[(84 * 48 / 8) - 6]))
  10.   {
  11.   //Wskaznik na wartosc, zostaje ona zwiekszona o 1
  12.   int ch = *text++;
  13.   //Deklaracja zmiennej w kodzie ASCII
  14.   //z wskaznikiem na jej adres.
  15.   //Liczenie od 0 odejmnowanie znaku spacji
  16.   font = &font_ASCII[ch - ' '][0];
  17.   //Przejscie w tablicy po poszczególnych
  18.   //wartosciach podanego znaku
  19.   for (= 0; i < 5; i++)
  20.   {
  21.    *pbuf++ = *font++;
  22.   }
  23.    *pbuf++ = 0;
  24.   }
  25.   i=0;
  26.   //Ustawienie lini DC na wysoki
  27.   GPIO_SetBits(GPIOC, LCD_DC);
  28.   //Wybranie urzadzenia, niski na CE
  29.   GPIO_ResetBits(GPIOC, LCD_CE);
  30.   for (= 0; i < (84 * 48 / 8); i++)
  31.   {
  32.       SPI_SEND_NOKIA(lcd_buffer[i]);
  33.   }
  34.   GPIO_SetBits(LCD_CE_PORT, LCD_CE);
  35. }

USART

Transmisja od USART'a jest właściwie standardowa. Poniżej potrzebne funkcje:

  1. void GPIO_Initialize(void)
  2. {
  3.      //konfigurowanie portow GPIO
  4.      GPIO_InitTypeDef  GPIO_In;
  5.      RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
  6.      //TX dla pinu PB6
  7.      GPIO_In.GPIO_Pin = GPIO_Pin_10;
  8.      GPIO_In.GPIO_PuPd = GPIO_PuPd_UP;
  9.      GPIO_In.GPIO_OType = GPIO_OType_PP;
  10.      GPIO_In.GPIO_Mode = GPIO_Mode_AF;
  11.      GPIO_In.GPIO_Speed = GPIO_Speed_100MHz;
  12.      GPIO_Init(GPIOC, &GPIO_In);
  13.      //RX dla pinu PB7
  14.      GPIO_In.GPIO_Pin = GPIO_Pin_11;
  15.      GPIO_In.GPIO_Mode = GPIO_Mode_AF;
  16.      GPIO_In.GPIO_PuPd = GPIO_PuPd_UP;
  17.      GPIO_In.GPIO_OType = GPIO_OType_PP;
  18.      GPIO_In.GPIO_Speed = GPIO_Speed_100MHz;
  19.      GPIO_Init(GPIOC, &GPIO_In);
  20.      //Wlaczenie transmisji na podanych pinach
  21.      GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_USART3);
  22.      GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_USART3);
  23. }
  24. void USART_Initialize(void)
  25. {
  26.      GPIO_Initialize();
  27.      USART_InitTypeDef USART_InitStructure;
  28.      RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
  29.      USART_InitStructure.USART_BaudRate = 115200;
  30.      USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  31.      USART_InitStructure.USART_StopBits = USART_StopBits_1;
  32.      USART_InitStructure.USART_Parity = USART_Parity_No;
  33.      USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  34.      USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  35.      USART_Init(USART3, &USART_InitStructure);
  36.      USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
  37.      USART_Cmd(USART3, ENABLE);
  38. }
  39. void USART_puts(volatile char *c)
  40. {
  41.       while (*!= 0)
  42.       {
  43.           SendChar(*c);
  44.           c++;
  45.       }
  46. }
  47. void SendChar(char c)
  48. {
  49.       while (USART_GetFlagStatus(USART3, USART_FLAG_TXE) == RESET);
  50.       USART_SendData(USART3, c);
  51. }

Obsługa przycisku:

Przycisk odpowiedzialny jest za włączenie pomiaru pojedynczego, ponowne włączenie inicjalizuje kolejny pomiar. Oba są brane do obliczenia powierzchni danego pudła czy np. pokoju. Po tym w przypadku pudła bądź innego pojemnika można obliczyć jaka jest wartość jego zapełnienia.

Ja wykorzystałem pin PA3 wraz z zewnętrznym przyciskiem. Nie wykorzystałem pinu PA0 z przyciskiem ponieważ do niego mam podłaczoną jedną z nóg od HCSR04. W przerwaniu zwiększana jest wartość flagi która następnie w pętli głównej w zależności od jej wartości wyświetla dane.

  1. void ButtonInit(void)
  2. {
  3.      EXTI_InitTypeDef EXTI_InitStruct;
  4.      NVIC_InitTypeDef NVIC_InitStruct;
  5.      GPIO_InitTypeDef GPIO_InitDef;
  6.      RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
  7.      RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  8.      GPIO_InitDef.GPIO_Pin = GPIO_Pin_3;
  9.      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;
  10.      //Ustawienie jako wejście
  11.      GPIO_InitDef.GPIO_Mode = GPIO_Mode_IN;
  12.      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;
  13.      GPIO_InitDef.GPIO_Speed = GPIO_Speed_100MHz;
  14.      GPIO_Init(GPIOA, &GPIO_InitDef);
  15.      //Wybranie EXTI dla konkretnego pinu
  16.      SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource3);
  17.      EXTI_InitStruct.EXTI_Line = EXTI_Line3;
  18.      EXTI_InitStruct.EXTI_LineCmd = ENABLE;
  19.      EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
  20.      EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
  21.      EXTI_Init(&EXTI_InitStruct);
  22.      NVIC_InitStruct.NVIC_IRQChannel = EXTI3_IRQn;
  23.      NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x00;
  24.      NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x00;
  25.      NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
  26.      NVIC_Init(&NVIC_InitStruct);
  27. }

Obsługa przerwania:

  1. void EXTI3_IRQHandler(void)
  2. {
  3.  if (EXTI_GetITStatus(EXTI_Line3) != RESET)
  4.  {
  5.      EXTI_ClearITPendingBit(EXTI_Line3);
  6.      
  7.         if (Button_flag == 0)
  8.         {
  9.             Wymiar_Dane.szerokosc1 = 0;
  10.             Wymiar_Dane.szerokosc2 = 0;
  11.             Wymiar_Dane.wysokosc = 0;
  12.             Wymiar_Dane.rozmiar = 0;
  13.             //Wykonanie pierwszego pomiaru
  14.             Wymiar_Dane.szerokosc1 = HC_SR04_Distance_Few_Data((float)10.0, 1, Srednia_temp);
  15.         }
  16.         else if(Button_flag == 1)
  17.         {
  18.             //Wykoanie drugiego pomiaru i wyswietlenie danych
  19.             Wymiar_Dane.szerokosc2 = HC_SR04_Distance_Few_Data((float)10.0, 1, Srednia_temp);
  20.         }
  21.         else if(Button_flag == 2)
  22.         {
  23.             //Wykoanie drugiego pomiaru i wyswietlenie danych
  24.             Wymiar_Dane.wysokosc = HC_SR04_Distance_Few_Data((float)10.0, 1, Srednia_temp);
  25.             Wymiar_Dane.rozmiar = Wymiar_Dane.szerokosc1 * Wymiar_Dane.szerokosc2 * Wymiar_Dane.wysokosc; //obliczona objetosc
  26.             sprintf(bufer, "Szero1: %f | Szero2: %f %\n\r", Wymiar_Dane.szerokosc1, Wymiar_Dane.szerokosc2);
  27.             USART_puts(bufer);
  28.             sprintf(bufer, "Wysokosc: %f | Rozmiar: %f %\n\r", Wymiar_Dane.wysokosc, Wymiar_Dane.rozmiar);
  29.             USART_puts(bufer);
  30.         }
  31.         else if(Button_flag == 3)
  32.         {
  33.             Wymiar_Dane.szerokosc1 = 0.0;
  34.             Wymiar_Dane.szerokosc2 = 0.0;
  35.             Wymiar_Dane.wysokosc = 0;
  36.         }
  37.      Button_flag++;
  38.      if(Button_flag == 4) { Button_flag = 0; }
  39.  }
  40. }

Dane dotyczące rozmiarów oraz zapełnienia przechowywane są w przygotowanej strukturze:

  1. typedef struct Wymiar{
  2.     float szerokosc1;
  3.     float szerokosc2;
  4.     float wysokosc;
  5.     float rozmiar;
  6.     float zapelnienie;
  7. }wymiar_t;
  8. wymiar_t Wymiar_Dane;

MAIN

W głównej części programu włączane są wszystkie potrzebne elementy, po nich sprawdzane są czujniki podłączone na linii onewire. W dalszej części następuje wykonanie pomiarów oraz wyświetlenie informacji.

  1. int main(void)
  2. {
  3.     float distance = 0.0;
  4.     float temps[SENSORS];
  5.     ErrorStatus check;
  6.     char buf[40];
  7.     uint8_t devices, i, count;
  8.     uint8_t device[SENSORS][8];
  9.     //---------------------------------------------------------------
  10.     SystemInit();
  11.     SYSTICK_ON(0);
  12.     //---------------------------------------------------------------
  13.     LedInit();
  14.     ButtonInit();
  15.     USART_Initialize();
  16.     USART_puts("LED USART INIT\n\r");
  17.     //---------------------------------------------------------------
  18.     OneWire_DS_t OneWire1_Sensors;
  19.     OneWire_Init(&OneWire1_Sensors, GPIOB, GPIO_Pin_8);
  20.     USART_puts("ONE_WIRE_INIT\n\r");
  21.     //---------------------------------------------------------------
  22.     HCSR04_Init();
  23.     USART_puts("HCSR04\n\r");
  24.     //---------------------------------------------------------------
  25.     count = 0;
  26.     devices = OneWire_First(&OneWire1_Sensors);
  27.     USART_puts("One wire init\n\r");
  28.     //---------------------------------------------------------------
  29.     GPIO_NOKIA_INIT();
  30.     SPI_NOKIA_INIT();
  31.     NOKIA_INIT();
  32.     LCD_CLEAR();
  33.     LCD_DRAW(1, 1, "Init Ok");
  34.     USART_puts("LCD INIT\n\r");
  35.     //---------------------------------------------------------------
  36.     while (devices)
  37.     {
  38.         count++;
  39.         OneWire_GetFullROM(&OneWire1_Sensors, device[count - 1]);
  40.         devices = OneWire_Next(&OneWire1_Sensors);
  41.     }
  42.     if (count > 0)
  43.     {
  44.         sprintf(buf, "Urzadzenia obecne na lini: %d\n", count);
  45.         USART_puts(buf);
  46.         for (= 0; i < count; i++)
  47.         {
  48.             DS18B20_Set_Resolution(&OneWire1_Sensors, device[i], DS18B20_Resolution_12bits);
  49.         }
  50.     }
  51.         while (1)
  52.         {
  53.             //Rozpoczecie konwersji na wszystkich urzadzeniach podlaczonych na linii
  54.             DS18B20_Start_All_Sensors(&OneWire1_Sensors);
  55.             //Odczekanie na zakonczenie pobierania temperatury
  56.             while (!DS18B20_Check_Conversion_Complete(&OneWire1_Sensors));
  57.             //Odczytaj temperaturę z każdego urządzenia oddzielnie
  58.             for (= 0; i < count; i++)
  59.             {
  60.                 // Read temperature from ROM address and store it to temps variable
  61.                 if (DS18B20_Read_Data(&OneWire1_Sensors, device[i], &temps[i]))
  62.                 {
  63.                     //Print temperature
  64.                     sprintf(buf, "Temp %d: %3.5f ", i, temps[i]);
  65.                     USART_puts(buf);
  66.                     sprintf(buf, "T %d:%3.5f", i, temps[i]);
  67.                     LCD_DRAW(i+2, 1, buf);
  68.                     Srednia_temp += temps[i];
  69.                 }
  70.                 else
  71.                 {
  72.                     //Blad odczytu
  73.                     USART_puts("Reading error;\n");
  74.                 }
  75.             }
  76.             //-------------------------------------------------------------------------
  77.             Srednia_temp = (Srednia_temp*0.5);              //Obliczenie sredniej temperatury
  78.             sprintf(buf, "Ts:%3.5f", Srednia_temp);         //Przygotowanie wartosci
  79.             LCD_DRAW(4, 1, buf);                            //Wypisanie bufora
  80.             //-------------------------------------------------------------------------
  81.             //Obliczenie średniej wartosci
  82.             distance = HC_SR04_Distance_Few_Data((float)10.0, 1, Srednia_temp);
  83.             sprintf(bufer, " Dis: %f\n\r", distance);
  84.             USART_puts(bufer);
  85.             sprintf(bufer, "Dis %.4f", distance);
  86.             LCD_DRAW(1, 1, bufer);
  87.             Srednia_temp = 0.0;
  88.             //---------------------------------------------------------------
  89.             //Ustawienie diod swiecacych
  90.             if(distance<10)
  91.             {
  92.                 GPIO_SetBits(GPIOD, GPIO_Pin_12);
  93.                 GPIO_ResetBits(GPIOD, GPIO_Pin_13);
  94.                 GPIO_ResetBits(GPIOD, GPIO_Pin_14);
  95.                 GPIO_ResetBits(GPIOD, GPIO_Pin_15);
  96.             }
  97.             else if(distance>50)
  98.             {
  99.                 GPIO_SetBits(GPIOD, GPIO_Pin_12);
  100.                 GPIO_SetBits(GPIOD, GPIO_Pin_13);
  101.                 GPIO_ResetBits(GPIOD, GPIO_Pin_14);
  102.                 GPIO_ResetBits(GPIOD, GPIO_Pin_15);
  103.             }
  104.             else if(distance>100)
  105.             {
  106.                 GPIO_SetBits(GPIOD, GPIO_Pin_12);
  107.                 GPIO_SetBits(GPIOD, GPIO_Pin_13);
  108.                 GPIO_SetBits(GPIOD, GPIO_Pin_14);
  109.                 GPIO_ResetBits(GPIOD, GPIO_Pin_15);
  110.             }
  111.             else if(distance>150)
  112.             {
  113.                 GPIO_SetBits(GPIOD, GPIO_Pin_12);
  114.                 GPIO_SetBits(GPIOD, GPIO_Pin_13);
  115.                 GPIO_SetBits(GPIOD, GPIO_Pin_14);
  116.                 GPIO_SetBits(GPIOD, GPIO_Pin_15);
  117.             }
  118.             //--------------------------------------------------------------
  119.             if(Button_flag == 2)
  120.             {
  121.               Wymiar_Dane.zapelnienie = distance * Wymiar_Dane.szerokosc1 * Wymiar_Dane.szerokosc2;
  122.                 Wymiar_Dane.zapelnienie = ((Wymiar_Dane.zapelnienie)*100)/Wymiar_Dane.rozmiar;
  123.                 sprintf(bufer, "Zapelnienie: %f %%\n\r", Wymiar_Dane.zapelnienie);
  124.                 USART_puts(bufer);
  125.                 sprintf(bufer, "Zap: %.4f %%", Wymiar_Dane.zapelnienie);
  126.                 LCD_DRAW(5, 1, bufer);
  127.             }
  128.         }
  129. }

Pliki z projektem można znaleźć na dysku Google pod tym linkiem.