[DHT11] Szczypta gotowego kodu.

Tu możesz pisać o swoich problemach z pisaniem programów w języku C dla AVR.
Awatar użytkownika
Antystatyczny
Geek
Geek
Posty: 1123
Rejestracja: czwartek 03 wrz 2015, 22:02

[DHT11] Szczypta gotowego kodu.

Postautor: Antystatyczny » sobota 22 lip 2017, 23:39

Witam,

Po ostatnich bojach z HTS221 postanowiłem wygrzebać z szuflady niemal zapomniany już przez mnie DHT11, który zbierał kurz od dłuższego czasu. Nie bardzo miałem ochotę zajmować się nim, bo po przejrzeniu dokumentacji, a konkretnie protokołu wymiany danych, stwierdziłem, że ten czujnik będzie mi strasznie blokował resztę programu, bo najpierw należy na co najmniej 18 milisekund sprowadzić pin danych do poziomu logicznego zera, a następnie jednym ciurkiem odczytać bit startu oraz 40 bitów danych. Horror! No ale czego się nie robi dla sportu... Postanowiłem na początek wykonać jak najprostszą formę odczytu, ale przy pomocy przechwytywania zdarzeń na pinie wejściowym timera 1 (ICP). Można również zaprząc tradycyjny pin INTx oraz jakiś timer, można zaprząc pin obsługujący PCI (Pin Change Interrupt) oraz timer, a można cały odczyt zrobić na zwykłych delay'ach. Ja jednak nie chciałem brnąć w jakieś skomplikowane rozwiązania, dlatego wybrałem ICP. Kilka typów wyliczeniowych, trochę kodu w ISR(TIMER1_CAPT_vect) i zadziałało niemal od razu. Niestety nie obyło się bez delay'ów.

W pętli głównej:

Kod: Zaznacz cały

while(1)
   {
      _delay_ms(1000);

      result = DHT_GetData(&dht);
      ...


oraz w funkcji wymuszającej odczyt danych z czujnika:

Kod: Zaznacz cały

DHT_ResultType DHT_GetData(DHT_DataType *data)
{
   ...
   /* Start pulse from host. */
   DHT_OUT;
   _delay_ms(18);
   DHT_IN;
   ...


Nie wygląda to dobrze, ale póki co działa poprawnie. Spróbuję jednak coś z tym zrobić... Aha, najpierw kompletny kod:
ATmega32_DHT11_UART.zip


Jakaś podstawa jest, więc uruchomiłem sprzętowy timer, by móc odmierzać czas i pozbyć się delay'ów. Efektem pracy było skomplikowanie kodu. Co prawda delay'e zniknęły z programu, ale w zamian powstały dwie pętle, które tak samo skutecznie blokowały program.

Main:

Kod: Zaznacz cały

...
volatile uint8_t cnt10ms;

void TIMER2_Init(void);

int main(void)
{
   DHT_DataType dht;
   DHT_ResultType result;

   DHT_Init();
   TIMER2_Init();
   uart_init(0, 115200);
   sei();

   while(1)
   {
      /* ca. 1s delay */
      cnt10ms = 100;
      while(cnt10ms);

      result = DHT_GetData(&dht);
      ...


oraz funkcja wymuszająca odczyt danych:

Kod: Zaznacz cały

DHT_ResultType DHT_GetData(DHT_DataType *data)
{
   ...
   /* Start pulse from host. */
   DHT_OUT;

   /* ca. 20ms delay. */
   DHT_cnt10ms = 2;
   while(DHT_cnt10ms);
   ...


oraz kompletny kod:
ATmega32_DHT11_UART_NO_DELAY.zip


Skoro jednak mam uruchomiony timer do wyznaczania jakichś zadań w określonych odcinkach czasowych, nic nie stoi na przeszkodzie, by zlecić "szynie danych" rozpoczęcie komunikacji, a po 20 milisekundach przejść do odczytu. Właściwym odczytem nadal zajmuje się przerwanie, a funkcja cyklicznie wywoływana w pętli głównej sprawdza, na jakim ten odczyt jest etapie. Napisałem więc funkcję DHT_Task(), która te zadania wykonuje. Funkcja DHT_GetData(), która dotychczas miała na swych barkach sprawdzenie zajętości szyny, wygenerowanie startu, przekopiowanie oraz sprawdzenie danych, zajmuje się obecnie wyłącznie sprawdzeniem, czy trwa jakiś odczyt, a jeśli nie trwa, wymusza start całej procedury odczytu. Resztą zajmuje się DHT_Task(). Gdy zlecone zadanie dociera do końca, wywoływany jest callback, w którym mamy od razu wskaźnik na strukturę z danymi o temperaturze, wilgotności oraz statusie aktualnych danych. Wykrywane są następujące nieprawidłowości:
  • Zadanie zajęte poprzednim odczytem.
  • Zwarcie na szynie danych.
  • Przekroczenie czasu odczytu.
  • Błąd ramki danych.
  • Błąd spójności odczytanych danych.

Jeśli status == DHT_Ok, z danych można skorzystać.

Pętla główna wygląda następująco:

Kod: Zaznacz cały

while(1)
   {
      DHT_Task();

      if(!cnt10ms)
      {
         /* ca. 0.5s delay */
         cnt10ms = 50;
         DHT_GetData();
      }
   }


oraz funkcja "odczytująca":

Kod: Zaznacz cały

DHT_ResultType DHT_GetData(void)
{
   if(DHT_TaskState == DHT_TaskState_Idle)
   {
      DHT_TaskState = DHT_TaskState_Start;
      return DHT_Ok;
   }

   return DHT_Busy;
}


A całość jest tutaj:
ATmega32_DHT11_UART_NO_BLOCK.zip


Wszystkie trzy wersje działają całkiem nieźle, ale jestem pewien, że można je zoptymalizować, do czego gorąco zachęcam.
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.
"The true sign of intelligence is not knowledge but imagination" Albert Einstein.

Awatar użytkownika
Antystatyczny
Geek
Geek
Posty: 1123
Rejestracja: czwartek 03 wrz 2015, 22:02

Re: [DHT11] Szczypta gotowego kodu.

Postautor: Antystatyczny » niedziela 23 lip 2017, 12:39

Jest i kolejna wersja kodu "NO_BLOCK". Opis zmian:

  • Dodana osobna pozycja DHT_TimeoutError na liście błędów.
  • Dodane makro DHT_TRANSMISSION_TIMEOUT, by móc wygodnie zmieniać maksymalny czas przeznaczany na odczyt danych z czujnika.
  • Zaktualizowany callback użytkownika w pliku main.c, by unaocznić wykrywanie błędów z uwzględnieniem DHT_TimeoutError.
  • Dodane opisy funkcji.
  • Drobne zmiany w kodzie w celu poprawy czytelności.
Callback obecnie wygląda następująco:

Kod: Zaznacz cały

void DHT_Callback(DHT_DataType *msg)
{
   /* Led on. */
   PORTC &= ~(1 << PC7);

   if(msg->Status == DHT_Ok)
   {
      uart_puts_P(0, PSTR("DHT11 temperature: "));
      uart_put_int(0, msg->TemperatureMsb, 10);
      uart_putc(0, '.');
      uart_put_int(0, msg->TemperatureLsb, 10);
      uart_puts_P(0, PSTR("*C\r\n"));
      uart_puts_P(0, PSTR("DHT11 humidity: "));
      uart_put_int(0, msg->HumidityMsb, 10);
      uart_putc(0, '.');
      uart_put_int(0, msg->HumidityLsb, 10);
      uart_puts_P(0, PSTR("%rH\r\n\r\n"));
   }
   else if(msg->Status == DHT_Busy)
   {
      uart_puts_P(0, PSTR("DHT11: Busy.\r\n\r\n"));
   }
   else if(msg->Status == DHT_BusError)
   {
      uart_puts_P(0, PSTR("DHT11: Bus error.\r\n\r\n"));
   }
   else if(msg->Status == DHT_TimeoutError)
   {
      uart_puts_P(0, PSTR("DHT11: Transmission timeout.\r\n\r\n"));
   }
   else if(msg->Status == DHT_FrameError)
   {
      uart_puts_P(0, PSTR("DHT11: Frame error.\r\n\r\n"));
   }
   else if(msg->Status == DHT_ChecksumError)
   {
      uart_puts_P(0, PSTR("DHT11: Checksum error.\r\n\r\n"));
   }

   /* Led off. */
   PORTC |= (1 << PC7);
}


Wywołanie tej funkcji powoduje zaświecenie diody sygnalizacyjnej, a następnie sprawdzenie pola Status w strukturze, na którą wskaźnik został przekazany do tej funkcji. W zależności od statusu "przesyłki" wyświetlane są różne informacje w terminalu. Na koniec dioda jest wyłączana.

Zaktualizowany soft:
ATmega32_DHT11_UART_NO_BLOCK.zip
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.
"The true sign of intelligence is not knowledge but imagination" Albert Einstein.


Wróć do „Programowanie AVR w C”

Kto jest online

Użytkownicy przeglądający to forum: Obecnie na forum nie ma żadnego zarejestrowanego użytkownika i 1 gość