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:
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:
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:
Wszystkie trzy wersje działają całkiem nieźle, ale jestem pewien, że można je zoptymalizować, do czego gorąco zachęcam.