RTOS - Paweł Szabaciuk

Paweł Szabaciuk

Software Developer

Tag: RTOS

ESP32 – Monitoring klimatu pomieszczenia

W poprzedniej części omówiliśmy sobie po krótce jak działa FreeRTOS. Czas zatem zacząć pisanie prostego programu.

Na początek zrobimy sobie proste ćwiczenie polegające na obsłudze domowego WiFi. Oczywiście jako klient.

Drugie ćwiczenie będzie polegało na odczycie danych z czujnika dht11.

Trzecie ćwiczenie wyświetli nam wynik odczytu na hostowanej przez nas stronie http.

Całość działania widzę następująco:

  • Po włączeniu sprzętu do prądu ESP32 próbuje połączyć się z siecią WiFi przy użyciu konfiguracji z kodu
  • Następnie hostowana jest aplikacja jako jedni zadanie
  • Jako drugie zadanie cyklicznie co 5s odczytywane są dane z czujnika
  • Dane z czujnika wpadają do cache z danymi
  • Strona zawsze wyświetla dane z cache

Sterownik do obsługi DHTx

W SDK ESP32 niestety nie mamy obsługi DHTx. Posłużyłem się zatem kodem z githuba. Lekko zmodyfikowanym.

dht.h:

 C | 
 
 copy code |
?

01
#pragma once
02
 
03
#include "ets_sys.h"
04
#include <stdio .h>
05
#include "driver/gpio.h"
06
 
07
#ifdef __cplusplus
08
extern "C" {
09
#endif
10
 
11
	typedef enum {
12
		DHT11,
13
		DHT22
14
	} dhtx_type_t;
15
 
16
	typedef struct {
17
		float temperature;
18
		float humidity;
19
	} dhtx_sensor_data_t;
20
 
21
	typedef struct {
22
		gpio_num_t pin;
23
		dhtx_type_t type;
24
	} dhtx_sensor_t;
25
 
26
	bool dhtx_init(dhtx_sensor_t *sensor);
27
	bool dhtx_read(dhtx_sensor_t *sensor, dhtx_sensor_data_t* output);
28
 
29
#ifdef __cplusplus
30
}
31
#endif
32
</stdio>

dht.c:

 C | 
 
 copy code |
?

001
#include "ets_sys.h"
002
#include "dht.h"
003
#include "freertos/FreeRTOS.h"
004
#include "freertos/task.h"
005
 
006
#define DHT_MAXTIMINGS	10000
007
#define DHT_BREAKTIME	20
008
#define DHT_MAXCOUNT	32000
009
 
010
static inline float scale_humidity(dhtx_type_t sensor_type, int *data)
011
{
012
	if (sensor_type == DHT11) 
013
	{
014
		return (float) data[0];
015
	}
016
	else 
017
	{
018
		float humidity = data[0] * 256 + data[1];
019
		return humidity /= 10;
020
	}
021
}
022
 
023
static inline float scale_temperature(dhtx_type_t sensor_type, int *data)
024
{
025
	if (sensor_type == DHT11) 
026
	{
027
		return (float) data[2];
028
	}
029
	else 
030
	{
031
		float temperature = data[2] & 0x7f;
032
		temperature *= 256;
033
		temperature += data[3];
034
		temperature /= 10;
035
		if (data[2] & 0x80)
036
			temperature *= -1;
037
		return temperature;
038
	}
039
}
040
 
041
bool dhtx_read(dhtx_sensor_t *sensor, dhtx_sensor_data_t* output)
042
{
043
	taskDISABLE_INTERRUPTS();
044
 
045
	bool result = false;
046
 
047
	int counter = 0;
048
	int laststate = 1;
049
	int i = 0;
050
	int j = 0;
051
	int checksum = 0;
052
	int data[100];
053
	data[0] = data[1] = data[2] = data[3] = data[4] = 0;
054
	gpio_num_t pin = sensor->pin;
055
 
056
	// Wake up device, 250ms of high
057
	GPIO_OUTPUT_SET(pin, 1);
058
	ets_delay_us(250 * 1000);
059
	// Hold low for 20ms
060
	GPIO_OUTPUT_SET(pin, 0);
061
	ets_delay_us(20 * 1000);
062
	// High for 40ns
063
	GPIO_OUTPUT_SET(pin, 1);
064
	ets_delay_us(40);
065
	// Set DHT_PIN pin as an input
066
	GPIO_DIS_OUTPUT(pin);
067
 
068
	// wait for pin to drop?
069
	while (GPIO_INPUT_GET(pin) == 1 && i < DHT_MAXCOUNT) 
070
	{
071
		ets_delay_us(1);
072
		i++;
073
	}
074
 
075
	if (i == DHT_MAXCOUNT)
076
	{
077
		printf("DHT: Failed to get reading, dying\r\n");
078
	}
079
	else
080
	{
081
		// read data
082
		for (i = 0; i < DHT_MAXTIMINGS; i++)
083
		{
084
			counter = 0;
085
			// Count high time (in approx us)
086
			while (GPIO_INPUT_GET(pin) == laststate)
087
			{
088
				counter++;
089
				ets_delay_us(1);
090
				if (counter == 1000)
091
					break;
092
			}
093
			laststate = GPIO_INPUT_GET(pin);
094
			if (counter == 1000)
095
				break;
096
				// store data after 3 reads
097
 
098
			if ((i > 3) && (i % 2 == 0)) 
099
			{
100
				// shove each bit into the storage bytes
101
				data[j / 8] < <= 1;
102
				if (counter > DHT_BREAKTIME)
103
					data[j / 8] |= 1;
104
				j++;
105
			}
106
		}
107
 
108
		if (j >= 39) 
109
		{
110
			checksum = (data[0] + data[1] + data[2] + data[3]) & 0xFF;
111
			if (data[4] == checksum) {
112
				// checksum is valid
113
				output->temperature = scale_temperature(sensor->type, data);
114
				output->humidity = scale_humidity(sensor->type, data);
115
 
116
				result = true;
117
			}
118
			else 
119
			{
120
				printf("DHT: Checksum was incorrect after %d bits. Expected %d but got %d\r\n", j, data[4], checksum);
121
				return false;
122
			}
123
		}
124
		else 
125
		{
126
			printf("DHT: Got too few bits: %d should be at least 40\r\n", j);
127
		}
128
	}
129
 
130
	taskENABLE_INTERRUPTS();
131
 
132
	return result;
133
}
134
 
135
 
136
bool dhtx_init(dhtx_sensor_t *sensor)
137
{
138
	if (gpio_set_direction(sensor->pin, GPIO_MODE_INPUT_OUTPUT) == ESP_OK) 
139
	{
140
		gpio_set_pull_mode(sensor->pin, GPIO_PULLUP_ONLY);
141
 
142
		return true;
143
	}
144
	else 
145
	{
146
		printf("DHT: Error in function set_gpio_mode for type %s\n", sensor->type == DHT11 ? "DHT11" : "DHT22");
147
		return false;
148
	}
149
}

Nie za bardzo jest co wyjaśniać, standardowy kod zgodny z dokumentacją producenta.

Jedyne co warte uwagi, to wyłączenie przerwań na czas komunikacji z czujnikiem. Czujnik wymaga dość precyzyjnych czasów komunikacji, stąd konieczność wyłączenia przerwań(taskDISABLE_INTERRUPTS i taskENABLE_INTERRUPTS). Do generowania opóźnień wykorzystujemy też systemową funkcję ets_delay_us. Pozwala ona wygenerować precyzyjne opóźnienie bez oddawania zadania do schedulera.

Cały kod do obsługi aplikacji prezentuje się następująco:

 C | 
 
 copy code |
?

001
#include "stdio.h"
002
#include "string.h"
003
#include "freertos/FreeRTOS.h"
004
#include "freertos/task.h"
005
#include "freertos/event_groups.h"
006
#include "esp_wifi.h"
007
#include "esp_event_loop.h"
008
#include "esp_system.h"
009
#include "nvs_flash.h"
010
#include "lwip/err.h"
011
#include "lwip/sockets.h"
012
#include "lwip/sys.h"
013
#include "lwip/netdb.h"
014
#include "lwip/dns.h"
015
#include "dht.h"
016
 
017
#define WIFI_SSID "MOJE_SSID"
018
#define WIFI_PASS "tajne_haslo_do_wifi"
019
 
020
static EventGroupHandle_t wifi_event_group;
021
static dhtx_sensor_t sensor;
022
static TaskHandle_t wifiTask;
023
static dhtx_sensor_data_t cache_data;
024
 
025
const int CONNECTED_BIT = BIT0;
026
static SemaphoreHandle_t cacheSemaphore = NULL;
027
 
028
static const char szHeader[] = "HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n<html><body><h1>Hello, world</h1>Greetings from ESP32! <br />";
029
static const char szFooter[] = "</body></html>";
030
 
031
void dht_task(void *pvParameter);
032
void wifi_task(void *pvParameter);
033
void http_task(void *pvParameters);
034
 
035
// obsługa zdarzeń WiFi
036
static esp_err_t event_handler(void *ctx, system_event_t *event)
037
{
038
	switch (event->event_id) {
039
 
040
	case SYSTEM_EVENT_STA_START:
041
		esp_wifi_connect();
042
		break;
043
 
044
	case SYSTEM_EVENT_STA_GOT_IP:
045
		xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
046
		break;
047
 
048
	case SYSTEM_EVENT_STA_DISCONNECTED:
049
		xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
050
		break;
051
 
052
	default:
053
		break;
054
	}
055
 
056
	return ESP_OK;
057
}
058
 
059
void wifi_task(void *pvParameter)
060
{
061
	// oczekujemy na zestawienie połączenia
062
	printf("WiFi task: waiting for connection to the wifi network... \n");
063
	xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY);
064
	printf("connected!\n");
065
 
066
	// zrzucamy dane o połączeniu
067
	tcpip_adapter_ip_info_t ip_info;
068
	ESP_ERROR_CHECK(tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info));
069
	printf("IP Address:  %s\n", ip4addr_ntoa(&ip_info.ip));
070
	printf("Subnet mask: %s\n", ip4addr_ntoa(&ip_info.netmask));
071
	printf("Gateway:     %s\n", ip4addr_ntoa(&ip_info.gw));
072
 
073
	// tworzymy zadanie dla http
074
	xTaskCreate(&http_task, "http_task", 2048, NULL, 5, NULL);
075
 
076
	// usuwamy zadanie do wifi
077
	vTaskDelete(wifiTask);
078
}
079
 
080
void dht_task(void *pvParameter)
081
{
082
	printf("DHT11 task... ");
083
 
084
	// nieskończona pętla zadania
085
	for (;;)
086
	{
087
		// struktura na dane odczytane z czujnika
088
		dhtx_sensor_data_t data;
089
 
090
		printf("Measuring...\n");
091
		// czytamy dane z czujnika 
092
		if (dhtx_read(&sensor, &data))
093
		{
094
			// mutex na przepisanie danych
095
			if (xSemaphoreTake(cacheSemaphore, 1000 / portTICK_PERIOD_MS) == pdTRUE) // oczekiwanie 1s
096
			{
097
				cache_data.temperature = data.temperature;
098
				cache_data.humidity = data.humidity;
099
				xSemaphoreGive(cacheSemaphore);
100
			}
101
			else
102
			{
103
				printf("Could not obtain cache semaphore for rewrite data.\n");
104
			}
105
 
106
			printf("Temperature: %.2f *C, Humidity: %.2f %%\n", data.temperature, data.humidity);
107
		}
108
		// usypiamy wątek na 5s
109
		vTaskDelay(5000 / portTICK_RATE_MS);
110
	}
111
}
112
 
113
void http_task(void *pvParameters)
114
{
115
	struct sockaddr_in server_addr, client_addr;
116
	int server_sock;
117
	socklen_t sin_size = sizeof(client_addr);
118
	bzero(&server_addr, sizeof(struct sockaddr_in));
119
	server_addr.sin_family = AF_INET;
120
	server_addr.sin_addr.s_addr = INADDR_ANY;
121
	server_addr.sin_port = htons(80);
122
 
123
	server_sock = socket(AF_INET, SOCK_STREAM, 0);
124
	if (server_sock == -1)
125
	{
126
		asm("break 1,1");
127
	}
128
 
129
	if (bind(server_sock, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)))
130
	{
131
		asm("break 1,1");
132
	}
133
 
134
	if (listen(server_sock, 5)) 
135
	{
136
		asm("break 1,1");
137
	}
138
 
139
	for (;;) 
140
	{
141
		int client_sock = accept(server_sock, (struct sockaddr *) &client_addr, &sin_size);
142
		static char szBuf[4096];
143
		int readPos = 0;
144
		char *pURL;
145
 
146
		if (client_sock < 0) 
147
		{
148
			asm("break 1,1");
149
			continue;
150
		}
151
 
152
        //Read the entire HTTP request
153
		for (;;)
154
		{
155
			int done = read(client_sock, szBuf + readPos, sizeof(szBuf) - readPos - 1);
156
			if (done < 0 || done >= (sizeof(szBuf) - readPos))
157
				done = 0;
158
 
159
			readPos += done;
160
			szBuf[readPos] = 0;
161
			if (strstr(szBuf, "\r\n\r\n"))
162
				break;
163
			if (!done)
164
				break;
165
		}
166
 
167
		pURL = strchr(szBuf, ' ');
168
		if (pURL)
169
		{
170
			char *pURLEnd = strchr(pURL + 1, ' ');
171
			if (pURLEnd)
172
			{
173
				char buffer[128] = { 0 };
174
				if (cacheSemaphore != NULL && xSemaphoreTake(cacheSemaphore, 1000 / portTICK_PERIOD_MS) == pdTRUE) // oczekiwanie 1s
175
				{
176
					sprintf(buffer, "Temperature: %.2f *C, humidity: %.2f %%", cache_data.temperature, cache_data.humidity);
177
 
178
					xSemaphoreGive(cacheSemaphore);
179
				}
180
 
181
				pURL++;
182
				pURLEnd[0] = 0;
183
				write(client_sock, szHeader, sizeof(szHeader) - 1);
184
				write(client_sock, buffer, strlen(buffer));
185
				write(client_sock, szFooter, sizeof(szFooter) - 1);
186
			}
187
		}
188
 
189
		close(client_sock);
190
	}
191
}
192
 
193
#ifdef __cplusplus
194
extern "C" 
195
#endif
196
void app_main()
197
{
198
	// inicjalizuj system
199
    nvs_flash_init();
200
 
201
	// tworzymy grupę eventów dla obsługi WiFi
202
	wifi_event_group = xEventGroupCreate();
203
 
204
	// inicjalizujemy stos TCP
205
	tcpip_adapter_init();
206
 
207
	// inicjalizujemy zdarzenia WiFi
208
	esp_event_loop_init(event_handler, NULL);
209
 
210
	// Inicjalizujemy stos WiFi w trybie Station, z konfiguracją WiFi w pamięci RAM
211
	wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT();
212
	esp_wifi_init(&wifi_init_config);
213
	esp_wifi_set_storage(WIFI_STORAGE_RAM);
214
	esp_wifi_set_mode(WIFI_MODE_STA);
215
 
216
	// configure the wifi connection and start the interface
217
	wifi_config_t wifi_config = {
218
		.sta = {
219
			WIFI_SSID,
220
			WIFI_PASS,
221
		},
222
	};
223
	esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config);
224
	esp_wifi_start();
225
	printf("Connecting to %s\n", WIFI_SSID);
226
 
227
	// start the main task
228
	xTaskCreate(&wifi_task, "wifi_task", 2048, NULL, 5, &wifiTask);
229
 
230
	// ustawienia dla DHT11
231
	sensor.pin = GPIO_NUM_23;
232
	sensor.type = DHT11;
233
	// inicjalizacja, i jeśli poprawna to stworzenie zadania odczytującego cyklicznie dane z czujnika
234
	if (dhtx_init(&sensor))
235
	{
236
		cacheSemaphore = xSemaphoreCreateMutex();
237
 
238
		xTaskCreate(&dht_task, "dht_task", 2048, NULL, 5, NULL);
239
	}
240
}
241

Po kolei:

  • linia 17 WIFI_SSID – SSID sieci z którą się łączymy
  • linia 18 WIFI_PASS – hasło do sieci WiFi
  • linia 22 wifiTask – uchwyt do zadania obsługującego połączenie z siecią. Po poprawnym połączeniu zadanie zostanie usunięte z schedulera
  • linia 23 cache_data – cache dla wyników pomiaru – wyświetlany na stronie www
  • linia 26 cacheSemaphore – semafor blokujący dostęp do cache w czasie zapisu i odczytu danych
  • linia 28-29 – treść strony www do wyświetlenia
  • linia 36-57 – obsługa zdarzeń dla połączenia z WiFi
  • linia 59-78 – zadanie obsługujące połączenie z WiFi. Uruchamia próbę połączenia. Po poprawnym połączeniu wyrzuca na UART informacje o sieci. Tworzy zadanie dla obsługi strony www (task_http) oraz usuwa z schedulera obsługę WiFi
  • linia 80-111 – obsługa pomiaru. Pętla nieskończona, wykonująca pomiar co 5s. Po poprawnym odczycie danych z czujnika, zakładamy blokadę na czas przepisania danych do cache.
  • linia 113-191 – standardowa obsługa strony www. Wyświetla prosty komunikat i przy użyciu semafora odczytuje dane z cache, wyrzucając je na stronę www.
  • linia 202-228 – przygotowanie do połączenia z WiFi oraz utworzenie zadania dla obsługi WiFi

Powinniśmy w wyniku tego otrzymać podobny log z UART:

Zauważyć możemy, że samo zadanie pobierania danych z czujnika ruszyło sobie niezależnie od połączenia z WiFi i zapisuje dane do cache. Podłączając się przeglądarką do adresu 192.168.1.164, dostajemy stronę z wartościami:

Jak widać, hostowanie strony jest bajecznie proste. Podobnie jak samo podłączenie do WiFi czy obsługa zewnętrznych czujników.

ESP32 – pierwsza aplikacja

Zaczynamy więc z pierwszą aplikacją. Ja zacznę od prostego przykładu, opartego na szablonie z VisualGDB.

Projekt rozpoczynamy od wybrania New -> Project -> Embedded Project Wizard. Wypełniamy ścieżki do katalogów, podajemy nazwę i idziemy dalej.

Odznaczamy budowanie dodatkowego pliku .bin (co u mnie nie działa, muszę później w ustawieniach projektu wyłączyć). Ja wybieram MSBuild, Makefile się u mnie nie kompiluje a i tak nie ma standardowych poleceń z ESP32 IDF (make menuconfig etc.).

Wybieramy urządzenie (ESP32) i idziemy dalej.

Po drodze projekt nam się wstępnie skompiluje (jest to tylko środowisko do budowania, jeszcze bez RTOS).

Na kolejnym kroku możemy wybrać jeden z czterech template przygotowanych przez SysProg. Wybierzmy sobie podstawowy Hello, World, jako że to nasz pierwszy program na ESP32 a tradycja bardzo dobre imię dal dziewczynki 🙂

Ostatni krok to wybranie debuggera sprzętowego jakiego będziemy używać. W tej chwili nie ma znaczenia, jako że żadnego (jeszcze) nie posiadam. Kliknięcie Finish stworzy nam projekt, który następnie zbudujemy.

Efektem naszego budowania powinien być wyświetlony Memory utilization report.

Co nasza pierwsza aplikacja robi? Zajrzyjmy w kod:

Jak widać nie za dużo. No dobra, może nie widać. Nie każdy miał do czynienia z FreeRTOS wcześniej 🙂

Ale jak miał do czynienia z jakimkolwiek programem w C pochodnych, to domyśli się co może robić:

  1. Wyświetla Hello world!
  2. Wchodzi w pętlę 10 iteracji, przy każdym jej obrocie:
    1. wyświetla za ile system zostanie zrestarowany
    2. usypia wątek na sekundę
  3. Wyświetla Restarting now.
  4. Restartuje procesor.

Całość operacji, oczywiście możemy podejrzeć w terminalu. Ustawienia terminala:

  • Port COM – port pod którym mamy naszą płytkę
  • Szybkość transmisji – 115200

Ja używam do tego celu PuTTY.

Wynik jaki powinniśmy otrzymać w terminalu powinien być zbliżony do poniższego:

W następnym wpisie zajmę się podstawami FreeRTOS.

 

UWAGA

Pamiętajmy o poprzednim wpisie, trzeba przestawić dwie flagi w ustawieniach projektu. Najlepiej przed pierwszą kompilacją, po przestawieniu i tak nam przebuduje całość 🙂

© 2018 Paweł Szabaciuk

Theme by Anders NorenUp ↑