howto - Paweł Szabaciuk

Paweł Szabaciuk

Software Developer

Tag: howto (page 1 of 2)

SystemAIR Modbus RTU

W pracy trafił się temat gadania po protokole Modbus. Przy użyciu standardowego Modbusa RTU. W dość znacznej prędkości 9600 🙂 A co najlepsze, to wyjście z urządzenia jest RS232, do tego przejście na RS485 i w takiej postaci mam się podłączyć 🙂

Poczytałem, poszukałem i co nieco się dowiedziałem od strony praktycznej.

Na początek mamy zdefiniowanych kilka bytów, określających rodzaje danych.
Rodzaje rejestrów zdefiniowanych przez protokół Modbus:

  • Coil – jest to 1 bitowy rejestr, wykorzystywany do kontroli wyjścia. Może być do odczytu i/lub do zapisu
  • Discrete Input – jest to 1 bitowy rejestr, używany jako wejście. Może być tylko do odczytu
  • Input Register – 16 bitowy rejestry, używany jako wejście. Może być tylko do odczytu
  • Holding registers – najbardziej uniwersalne 16 bitowe rejestry. Mogą być do odczytu i do zapisu. Może być używany np. do wejść, wyjść, danych konfiguracyjnych lub dowolnych danych wymagających przechowania

Jak widać, wielkiej filozofii nie ma, o ile mamy dokumentację techniczną producenta co gdzie pod jakim adresem siedzi.

Ponieważ nie mogę nic mówić o projekcie z pracy, postanowiłem podłączyć się do centrali wentylacyjnej, która to gada sobie po RS485 i Modbus z dowolnym urządzeniem master.
Ogólno dostępna dokumentacja do central wentylacyjnych firmy SystemAir.

Mamy tutaj rozpisane dokładnie jakie są rejestry, pod jakimi adresami, czy można je zapisywać czy odczytywać i jakiego są rodzaju.
Drugą istotną informacją jest adres i prędkość transmisji. Każde urządzenie slave w Modbus musi mieć swój unikalny adres. I tak na przykładzie centrali, mamy domyślnie adres 1. Pod taki adres się dobijamy, żeby np. odczytać informację o prędkości wentylacji, która to jest dostępna pod adresem 101 (czyli 101 – 1, zgodnie z dokumentacją).

W dokumentacji widzimy też, że rejestr ten może być zapisany nową wartością, i tak możemy odczytać i zmienić szybkość działania wentylatorów w centrali.

Na tej podstawie powstał prosty sterownik, oparty na starej płytce, pół-uniwersalnej na ESP8266.

Płytka jest identyczna z postem: Sterowanie pompą obiegową od cyrkulacji. Wlutowany został tylko transceiver MAX3485 do komunikacji po RS485 z centralą.

W wyniku pół dnia siedzenia nad kodem, mamy prostą aplikację do monitorowania danych i konfiguracji centrali przez stronę www.

W planach połączenie centrali z czujnikami środowiskowymi i sterowanie bardziej automatyczne siłą wentylacji z automatu.

Sterowanie pompą obiegową od cyrkulacji

Dzisiaj temat dość prozaiczny. Recyrkulacja wody w instalacji domowej, żeby po odkręceniu kranu leciał z niego Johny Walk… wróć! ciepła woda. I żeby nie trzeba było na tę ciepłą wodę czekać. Tylko żeby od razu była ciepła.

Problem jest w tym, że zimą woda jest ogrzewana przez piec, a piec ma możliwość sterowania pompą. Nawet potrafi ją włączać cyklicznie, co by ciepło w zasobniku oszczędzać. Tylko trzeba było sobie instrukcję doczytać. Bo pan serwisant, który uruchamiał instalację o takiej rzeczy nie wiedział. Albo nie pomyślał.

Inaczej jest latem, kiedy to woda ciepła jest pozyskiwana z innych źródeł. Na przykład kolektory słoneczne, pompa ciepła etc. Wtedy nie za bardzo ma co sterować pompką.

Samo sterowanie jest banalnie proste, raz na jakiś czas, mieszamy wodę w instalacji przez załączenie napięcia na pompie. W piecu można ustawić to mieszanie od 1x na godzine na 5 minut do 6x na godzinę na 5 minut. Lektura elektrody zaś głosi, że trzeba to robić raz na 5 minut przez minutę. Zimą miałem 5×5, latem mam 10×1 i nie widzę różnicy.

Do tej pory miałem prosty układ na ATMega328. Nie posiadał on jednak RTC, wobec czego, całość działała całą dobę.
Całą dobę ciepłej wody jednak nie używamy. W nocy zazwyczaj śpimy, więc żeby oszczędzić trochę ciepła w zasobniku, nie ma sensu odpalać pompki w godzinach nocnych.

Powstał więc układ na ESP8266. Pilnuje on sobie czasu na liczniku, natomiast aktualny czas pozyskuje sobie z internetu. Przez NTP. Jak zabraknie prądu i nie będzie internetu, to do czasu pozyskania aktualnego czasu i tak będzie mieszać wodę, tyle, że całą dobę.

Używam WebSocketów do prezentacji real-time czy pompka aktualnie jest włączona, czy też nie. No i można sobie skonfigurować godziny pracy oraz zakresy czasowe dla stanów ON i OFF.

Schemat jest banalny. Typowa implementacja ESP12E z automatycznym programowaniem przy użyciu RTS i DTR. Pamiętajcie tylko o wyprowadzeniu masy do konwertera USB< ->UART. Ja zapomniałem (schemat już poprawiony). Przez co straciłem trochę czasu na walkę z komunikacją.
Brakuje przekaźnika, ponieważ mam już płytkę z przekaźnikiem, do której jest podłączona pompa obiegowa, potrzebuję tylko podać sygnał z zakresu 3 – 5V żeby przekaźnik załączył pompę.

Sterownik wygląda tak:

Płytka jest „uniwersalna”, to znaczy ma obsłużyć jeszcze dwie inne sytuacje, dlatego ma dodatkowe elementy, i miejsce na kolejne. Może kiedyś to też opiszę 🙂

Tradycyjnie też, kilka screenów z aplikacji:

WiringPi (like) obsługa przerwań

Dzisiaj zajmę się poprawną obsługą oczekiwania na przerwanie, jeśli przerwania oparte są na poll (czyli biblioteka WiringPi i klony).
W czym leży problem?
Otóż poll chodzi sobie w drugim wątku. Jest to metoda nieobciążająca procesor. Samo poll nie zużywa mocy obliczeniowej. Natomiast większość przykładów, które widziałem, opartych jest na pętli nieskończonej, oczekującej na wystąpienie przerwania. Co powoduje, że ta pętla obciąża nam procesor w 100%.
Przykład takiego użycia:

 C++ | 
 
 copy code |
?

01
bool packetAvailable = false;         
02
 
03
void signalsInterrupt(void) 
04
{
05
	packetAvailable = true;
06
}
07
 
08
int main(int argc, char *argv[])
09
{
10
	wiringPiSetup();
11
 
12
	pinMode(GDO2, INPUT);
13
	pullUpDnControl(GDO2, PUD_UP);
14
 
15
	wiringPiISR(GDO2, INT_EDGE_FALLING, &signalsInterrupt);
16
 
17
	while (1)
18
	{
19
		if (packetAvailable) 
20
		{
21
			(...)
22
		}
23
	}
24
 
25
	return 0;
26
}

Mało eleganckie, nieprawdaż?
Z pomocą przychodzą nam mechanizmy do obsługi wątków, jak np. semafory.
A oto poprawny przykład, zużywający ~0% procesora:

 C++ | 
 
 copy code |
?

01
static sem_t interrupt;
02
 
03
void signalsInterrupt(void) 
04
{
05
	sem_post(&interrupt);
06
}
07
 
08
int main(int argc, char *argv[])
09
{
10
	wiringPiSetup();
11
 
12
	pinMode(GDO2, INPUT);
13
	pullUpDnControl(GDO2, PUD_UP);
14
 
15
	sem_init(&interrupt, 0, 0);
16
	wiringPiISR(GDO2, INT_EDGE_FALLING, &signalsInterrupt);
17
 
18
	while (1)
19
	{
20
		sem_wait(&interrupt);
21
 
22
		(...)
23
	}
24
 
25
	return 0;
26
}

Kod działa następująco. sem_init definiuje nam semafor obsługujący przerwanie. Callback signalsInterrupt wywoływany jest przez wątek obsługujący przerwanie (ten z poll). Tutaj ustawiany jest semafor. W pętli głównej while(1) oczekujemy na ustawienie semafora. Aplikacja działa teraz bez obciążania procesora.

Specjalne podziękowania dla anonimowego darczyńcy za zredagowanie tekstu 🙂

ESP32 – mała zajawka

Pracuję ostatnio prywatnie nam małym projektem, który niedługo opiszę.

(jakość słaba bo zdjęcie robione pralką):

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 – FreeRTOS – pierwsze starcie

Każdy kto programował jakikolwiek mikroprocesor (AVR, 8051, PIC) wie, że prędzej czy później, będzie potrzebował jakiegoś schedulera. Zawsze trafi się jakieś zadanie, które będzie musiało się z większą lub mniejszą dokładnością wykonywać w określonym czasie. O ile w przypadku małych procesorów AVR (głównie z takimi miałem do tej pory do czynienia) może być problem z pamięcią, o tyle w przypadku dużych jest to mniejszy problem. Jeśli mamy mało pamięci musimy sobie radzić bez bibliotek zewnętrznych. Jeśli mamy dość pamięci możemy użyć FreeRTOS.

Czym zatem jest FreeRTOS (i RTOS w ogólności)? Jest to system operacyjny, który zajmuje się rozdzielaniem czasu procesora pomiędzy zadania. Każdy procesor jedno rdzeniowy (wielordzeniowość pomaga, ale nie załatwia problemu) przy wykonywaniu kilku zadań, musi podzielić swój czas pomiędzy te zadania. Wyobraźmy sobie, że korzystamy z naszego PC, czytamy sobie ten wpis w jednym oknie, w tle ściąga się nam film na wieczór (oczywiście zakupiony i legalny ;)) a z głośników sączy się delikatnie kojąca, wprowadzająca prawie w medytacyjny stan nasz umysł, utwór pod wdzięcznym tytułem Highway to Hell. Spójrzmy zatem co się dzieje od strony systemu operacyjnego. Patrząc tylko na te trzy zadania, bo oczywiście wykonuje ich dziesiątki.

System operacyjny,mając do dyspozycji jeden rdzeń i jeden procesor, aby wykonać te wszystkie zadania jednocześnie, musi się roztroić. I może to zrobić. Każde z tych zadań jest dostatecznie mało zajmujące współczesne maszyny, aby nie było potrzeby poświęcania im 100% czasu. Co więc się dzieje? System operacyjny uruchamia wątek pierwszy na określoną ilość czasu, potem go wstrzymuje, uruchamia drugi, wstrzymuje, uruchamia trzeci. To tak jak z telewizją, nie mamy ciągłego obrazu, tylko 50 klatek na sekundę (lub 25 interpolowanych… nie ważne :)).

Powiecie, no dobra dobra, ale ja mam super hiper nowoczesny sprzęt 30 rdzeni, każdy po 4 wątki. OK. System operacyjny (jeśli potrafi) wykorzysta to tak, że na każdy z rdzeni wrzuci tyle wątków ile się da. A jak mu zabraknie rdzeni? To znowu zacznie dzielić po kilka zadań na jednym.

Każdy, kto próbował pisać aplikację wielowątkową wie że nie jest to proste. Napisanie systemu który obsługuje wątki na jednym rdzeniu jest dość proste. Natomiast na kilku już nie koniecznie (synchronizacja wątków, brrrrrr…). Z pomocą przychodzi nam tutaj oczywiście Internet. Jest wiele projektów RTOSów które możemy wykorzystać. Nie wszystkie bezpłatnie komercyjnie, niektóre w ogóle nie bezpłatnie, inne całkowicie bezpłatnie. Ja skupię się na implementacji FreeRTOS. SDK ESP32 jest oparte na FreeRTOS. Jak wygląda to licencyjnie, możemy sobie podejrzeć tutaj.

Generalnie warto by było wrzucić trochę dokumentacji:

Ok, porozmawiajmy sobie trochę o samych RTOSach. Systemy czasu rzeczywistego dzielą się generalnie na dwa rodzaje:

  • miękkie, czyli takie w których wykonanie zadania musi być w określonym czasie, ale jeśli spóźni się kilka mikro sekund to świat się nie skończy, bomba nie wybuchnie, imigranci nas nie zaleją etc.
  • twarde, jak się domyślacie, odwrotnie do miękkich – wykonanie zadania musi być idealnie przewidywalne w czasie. Tutaj spóźnienie nie wchodzi w grę. Wyobraźcie sobie np. sterowanie rakiety. Rakieta sobie startuje, leci prosto. Czujniki przesyłają cały czas dane do mikroprocesora sterującego. Na podstawie tych danych, mikroprocesor steruje lotkami, żeby rakieta leciała nam prosto do celu. Nie trudno sobie wyobrazić co się stanie, jeśli zastosujemy system miękki. Nie wykonanie od razu korekcji lotu, bo np. programista jest miłośnikiem kosmitów i podpiął naszą rakietę do programu SETI (3000 rakiet, większość leży nie używana, całkiem dużo mocy obliczeniowej się marnuje, nieprawdaż?). Wątek do SETI w dodatku ustawił na wysokim priorytecie, co powoduje, że korekcja lotu odbywa się 1ms za późno. Niby nic prawda? 1ms. Kto by tam liczył. Jak jeden grosz 🙂 Tylko zwłoka o 1ms powoduje, że kolejna poprawka musi być dużo większa. Kolejna (również spóźniona) jeszcze większa. Dochodzimy do momentu kiedy nasza rakieta już nie jest sterowalna. A to wszystko przez kosmitów…

Wywłaszczanie

Nie, nie chodzi o wyrzucanie ludzi z mieszkań. Wróćmy do naszej rakiety. Przez 99% czasu swojego życia nie robi nic. 1% potrzebuje na dolecenie do celu. Wobec tego, przez 99% czasu, zadanie sterowania rakietą nie robi nic. Wyobraźmy sobie zatem następującą sytuację. Zadanie sterowania jest uruchomione, sprawdza stan rakiety i wie, że rakieta leży sobie w silosie i nic nie robi. Oczekuje zatem, na sygnał startu. W tym czasie procesor i tak nic nie robi, więc zadanie oddaje sterowanie do OS, który czasie oczekiwania na start uruchamia zadanie drugie, które zostanie przerwane natychmiastowo w momencie startu i odda wtedy cały czas procesora do zadania sterowania rakietą. Takie działanie (przełączenie na inne zadanie, w czasie, kiedy pierwsze i tak nic nie robi) nazywa się wywłaszczaniem. Inny przykład? mikroprocesor sterujący silnikiem rolety. Zadanie obsługi silnika jest priorytetowe, natomiast UI możemy odświeżyć w dowolnym pozostałym czasie. Jest to więc generalnie prioryteryzacja zadań, zadanie mniej ważne jest przerywane na rzecz bardziej ważnego, które nie może mieć żadnych opóźnień.

Jeśli mamy procesor na tyle silny, że potrafi uporać się ze wszystkimi zadaniami przed czasem, przełącza się na proces Idle. Jest to proces,który sprząta pamieć, a poza tym jeśli nie ma nic więcej do roboty może np. uśpić procesor w celu bycia zielonym.

Scheduler

Czym jest scheduler, jest już chyba jasne. Jest to część systemu operacyjnego, zajmująca się rozdzielaniem czasu procesora pomiędzy poszczególne zadania. Uwzględnia priorytety i wymagania czasowe zadań. Jest kilka rodzajów implementacji, ale my nie piszemy RTOSa tylko go wykorzystujemy, więc na tę (tą?) chwilę nie ma potrzeby zaśmiecać się wiedzą na ten temat.

FreeRTOS

System FreeRTOS jest wywłaszczalnym systemem operacyjnym czasu rzeczywistego. Jest to system open source, może być wykorzystywany komercyjnie. Jeśli zależy nam na wsparciu technicznym, oczywiście jest taka opcja, możemy kupić licencję na OpenRTOS. Licencja to zmodyfikowane GNU, z wyłączeniem wirusowego otwarcia kodu aplikacji, która używa FreeRTOS.

Podział zadań

W systemie FreeRTOS wyróżniamy dwa typu procesów:

  • Task – zadanie, czyli niezależny proces. Posiada własny kontekst (czyli widzi rejestrt i stos, jak by był jedynym zadaniem procesora). Czyli z dużych systemów operacyjnych jest to po prostu proces.
  • Co-routine – współprogram. Jest podobny do zadania, natomiast współdzieli stos z innymi współprogramami. Czyli jest to wątek.

Co ważne, zadania i współprogramy nie mogą się komunikować przy użyciu semaforów i kolejek. Współprogramy też mają niższy priorytet niż zadania.

Każde zadanie może przyjąć jeden ze stanów:

  • Running – aktualnie wykonywane zadanie
  • Ready – oczekuje na zasoby
  • Blocked – oczekuje na przerwanie lub upływ czasu
  • Suspended – wstrzymane, nie jest brane pod uwagę przez schedulera

Współprogramy posiadają następujące stany:

  • Ready
  • Running
  • Blocked

Definicja zadania

Zadanie jest po prostu funkcją, nie zwracająca żadnej wartości, przyjmującą parametr typu void*. Konstrukcja funkcji musi być nieskończoną pętlą. Zadanie dodajemy do schedulera przy pomocy xTaskCreate() a usuwamy przy użyciu vTaskDelete().

Zwyczajowo w RTOS x w nazwie oznacza funkcję zwracającą wartość, v nie zwracającą.

Zajrzyjmy więc do naszej aplikacji Hello, World poprzedniego wpisu:

 C++ | 
 
 copy code |
?

01
#include "stdio.h"
02
#include "freertos/FreeRTOS.h"
03
#include "freertos/task.h"
04
#include "esp_system.h"
05
#include "nvs_flash.h"
06
 
07
void hello_task(void *pvParameter)
08
{
09
    printf("Hello world!\n");
10
    for (int i = 10; i >= 0; i--) {
11
        printf("Restarting in %d seconds...\n", i);
12
        vTaskDelay(1000 / portTICK_RATE_MS);
13
    }
14
    printf("Restarting now.\n");
15
    fflush(stdout);
16
    system_restart();
17
}
18
 
19
#ifdef __cplusplus
20
extern "C"
21
#endif
22
void app_main()
23
{
24
     nvs_flash_init();
25
    xTaskCreate(&hello_task, "hello_task"2048NULL5NULL);
26
}

Od linijki 7 widzimy definicję funkcji  hello_task , która jest naszym jedynym (poza  Idle ) zadaniem. W ciele funkcji widzimy co dane zadanie robi.

Linijka 25 tworzy nam zadanie. Pierwszy parametr to wskaźnik na funkcję, drugi to nazwa zadania, trzeci to rozmiar stosu przydzielony do zadania, czwarty parametr (czyli u nas nic), piąty to priorytet i wreszcie ostatni to wskaźnik do uchwytu zadania (używany np. do usunięcia zadania).

Wyjaśnienia wymagają linijki 19-21. Jest to eksport funkcji C do kompilatora C++. W VisualGDB standardowo piszemy w C++, czyli obiektowo. Natomiast wywołanie funkcji app_main jest w kodzie kompilowanym przez C, zatem musimy wyeksportować tą funkcję do kompilatora C++, żeby potrafił jej użyć oraz cały proces kompilacji poprawnie zlinkował nam kod C z C++.

Dobra, lecimy dalej. Linia 12 jest wstrzymaniem wykonania zadania na określony czas. W naszym przypadku jest to 1000ms. portTICK_RATE_MS jest to stała, określająca czas 1 ms w oparciu o taktowanie zegara mikroprocesora. Do sterowania przebiegiem mamy też funkcję vTaskDelayUntil , która różni się tym, że jest dokładna. To znaczy, że wywołanie vTaskDelay nie zawsze będzie precyzyjne. Wpływ na to mają instrukcje warunkowe w samym zadaniu, przerwania i wywłaszczenia. Druga funkcja natomiast dostaje w parametrze liczbę taktów, które upłynęły od momentu uruchomienia schedulera ( xTaskGetTickCount ) oraz ilość na jaką ma być wstrzymane zadanie. To pozwala precyzyjnie określić moment wznowienia zadania.

Ustawianie i odczyt priorytetów zapewniają nam funkcje uxTaskPriorityGet oraz vTaskPrioritySet .

Wstrzymywanie i wznawianie zadań to odpowiednio: vTaskSuspend i vTaskResume.

Komunikacja między zadaniami

Huh. Dużo o tym czytałem, zawsze była to czarna magia. Do momentu, kiedy w jednym z projektów Embedded Linux musiałem obsługiwać z kilku programów dostęp do jednej magistrali RS485. Zasada komunikacji między procesami czy różnymi aplikacjami jest podobna. Musimy mieć jakiś pojemnik na dane, które chcemy współdzielić oraz miejsce na oznaczanie blokowania danego zasobu, który jest wpółdzielony.

We FreeRTOS do wymiany danych służą Queues. Są to zwykłe kolejki FIFO. Działanie jest proste, zadanie A zapisuje dane do kolejki, zadanie B odczytuje. Kolejka, jak to kolejka, po odczycie przesuwa wskaźnik na kolejny element. Rozmiar kolejki jest definiowalny, zawsze można się podeprzeć zapisem wskaźników jako wartości do kolejki 🙂

Tworzenie kolejki to polecenie xQueueCreate. Podajemy w parametrach oczywiście wielkość i rozmiar elementu. Zwraca nam uchwyt do kolejki.

Semafory. Podobnie jak w kolejnictwie (kto się nie bawił kolejkami elektrycznymi w dzieciństwie ten traci), służą do synchronizacji oraz wykluczania dostępu. Zasada działania jest taka, że jeśli semafor jest nieaktywny, to blokuje on wykonanie zadania, natomiast aktywacja semafora wznawia dane zadanie. Przykładowo, mamy jedną magistralę RS485 i kilka zadań. Każde z zadań chce rozmawiać z innym urządzeniem peryferyjnym, podłączonym do magistrali. W momencie rozpoczynania transmisji, ustawiany jest semafor. W trakcie transmisji tylko dane zadanie może rozmawiać po magistrali. Wszystkie pozostałe zadania oczekują na zwolnienie semafora. Po zwolnieniu kolejne zadanie rozmawia ze swoim odbiorą itd. To zapewnia nam brak zakłóceń, gdyby więcej niż jedno zadanie rozmawiało w tym samym czasie.

To generalnie całość wiedzy, potrzebnej do rozpoczęcia programowania przy użyciu FreeRTOS. Oczywiście jak znajdę chwile to omówię dokładniej kolejki oraz semafory.

W kolejnym odcinku napiszemy sobie jakąś prostą aplikację łączącą się z domowym WiFi i hostującą prostą stronę z pomiarem temperatury/wilgotności. W między czasie czekam na ESP WROVER KIT jako platformę do podstawowych testów oraz programator JTAG.

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ść 🙂

ESP32 – przygotowanie środowiska

No to zaczynamy zabawę.

Posiedziałem trochę w VS i da się programować układ póki co tylko przez zewnętrzne polecenie esptool.py

Aplikacje przykładowe z VisualGDB działają bez problemu.

Podczas generowania projektu odznaczamy budowanie obrazu .bin (to zrobi nam esptool).

W opcjach projektu w C/C++ -> Optimization ustawiamy Optimization na Optimize for GDB (-Og)

Teraz warto by było skonfigurować sobie środowisko, żeby łatwo i przyjemnie programować z niego jednym kliknięciem. Ściągamy sobie zatem esptool.py i instalujemy zgodnie z instrukcją.

Tworzymy sobie następnie skrypt, który nam skonwertuje .elf na .bin i wrzuci go na ESP32:

 DOS | 
 
 copy code |
?

1
echo off
2
set arg1=%1
3
C:\Python27\python.exe c:\data\work\esp32\esptool-master\esptool.py --chip esp32 elf2image %arg1%
4
C:\Python27\python.exe c:\data\work\esp32\esptool-master\esptool.py --port COM15 write_flash 0x10000 %arg1%.bin

Gdzie:

 DOS | 
 
 copy code |
?

1
C:\Python27\python.exe
– ścieżka do pythona

 DOS | 
 
 copy code |
?

1
c:\data\work\esp32\esptool-master\esptool.py
– ścieżka do esptool.py

 DOS | 
 
 copy code |
?

1
COM15
– port COM pod którym jest nasz ESP

Teraz odpalamy Visual Studio, wchodzimy w Tools -> External Tools, dodajemy nowy (np. Program ESP32), podajemy w Command ścieżkę do skryptu .cmd a w Arguments $(TargetPath). Zaznaczamy Use Output window i klikamy OK.

Opcja ta pojawi nam się w menu Tools:

Teraz już możemy napisać jakiś testowy program i w prosty sposób zaprogramować ESP32 przez UART.

ESP32 – pierwsze wrażenia

Dotarła do mnie płytka DevKitC. Tak to wygląda:


Pierwsze wrażenia są bardzo pozytywne. Po odpaleniu modułu, stworzył sieć WiFi, do której można się podłączyć.

Pierwszy test, aplikacja z przykładów również działa, podpięcie konsoli pod port COM wyświetla wszystkie informacje.

Niestety nie udało mi się w prosty sposób programować bezpośrednio z VisualGDB, muszę nad tym popracować. Mam dwa pomysły, jeden to OTA, drugi to wykonywanie własnego polecenia do programowania (wywołanie po prostu esptool.py z VisualGDB).

Mam nadzieję doposażyć się w JTAG, co powinno usprawnić pracę. Póki co test na Atmel ICE się nie powiodł.

W najbliższych dniach skrobnę coś o podstawach pisania aplikacji. Pewnie zacznę od wpięcia się do domowego WiFi.

ESP32 – krótkie omówienie interfejsów zewnętrznych

Omówię poniżej czym się charakteryzują poszczególne interfejsy zewnętrzne.

GPIO

ESP 32 posiada 48 pinów dostępnych jako GPIO. Programistycznie definiujemy co która linia ma obsługiwać. Wyróżniamy następujące typy linii:

  • cyfrowe
  • analogowe (mogą działać jako cyfrowe)
  • czujniki dotykowe (mogą działać jako cyfrowe)

Każdy z cyfrowych pinów może mieć jeden ze stanów:

  • pull-up
  • pull-down
  • wysoka impedancja

Każdy z pinów cyfrowych może być ustawiony jako wejście lub wyjście. W przypadku wejścia, mamy do dyspozycji przerwania wyzwalane po zboczu lub poziomie sygnału.

Każdy z pinów może byc zmulipleksowany do obsługi innych funkcji, np. SDIO, UART, CAN, etc.

ADC

ESP 32 zawiera 18 kanałowy 12 bit przetwornik analogowo-cyfrowy. Jest on wystawiony na pinach analogowych. Niektóre z pinów mają możliwość użycia wbudowanego wzmacniacza.

Koprocesor ULP potrafi mierzyć napięcie podczas uśpienia i obudzić CPU kiedy napięcie przekroczy określony programistycznie próg.

Niskoszumowy przedwzmacniacz audio

Mamy w ESP 32 wbudowany niskoszumowy przedwzmacniacz, który może być podłączony do ADC. Wzmocnienie jest ustawiane przez zewnętrzne kondensatory. Wartość wzmocnienia dochodzi do 60 dB.

Czujnik Halla

Zintegrowany czujnik Halla oparty jest na rezystorze N-carrier. Kiedy układ znajduje się w polu magnetycznym, sensor Halla wytwarza małe napięcie na rezystorze, które może być zmierzone przez ADC lub wcześniej wzmocnione przez wzmacniacz.

DAC

ESP 32 zawiera dwa 8 bit kanały przetwornika cyfrowo – analogowego. Wewnętrzna budowa opiera się na drabinkach rezystorowych i buforze.

Czujnik temperatury

Wewnętrzny czujnik temperatury jest czujnikiem napięciowym. Wewnętrznie przetwarzany jest na postać cyfrową. Zakres badanych temperatur wacha się pomiędzy -40* a 125*C. Nie służy do pomiarów temperatury otoczenia, tylko do pomiaru wewnętrznej temperatury układu SoC.

Czujniki dotyku

ESP 32 posiada 10 GPIO obsługujących pojemnościowe czujniki dotyku. Piny te możemy zobaczyć w poniższej tabelce:

Ultra nisko prądowy Koprocesor

ULP i pamięć RTC zostają zasilone w trybie Deep-sleep. Dzięki temu, programista może używać ULP i pamięci RTC do dostępu do interfejsów zewnętrznych, wewnętrznych timerów oraz wewnętrznych czujników w trybie Deep-sleep.

Interfejs Ethernet

Wbudowany kontroler MAC zgodny z IEEE 802.3-2208 zapewnia komunikacje Ethernet. Wymaga to jednak zastosowania zewnętrznego interfejsu (PHY) podłączonego do linii LAN. PHY używa 17 sygnałów MII i 9ciu RMII.

Dzięki temu mamy dostęp do następujących funkcji:

  • Szybkość transmisji 10Mbps i 100Mbps
  • Kontroler DMA do komunikacji SRAM <-> Ethernet
  • Obsługa VLAN
  • Komunikacja Half i Full-duplex
  • Kontrola ramek MAC
  • Obsługa CRC 32
  • Wewnętrzne FIFO do transmisji ramek
  • Sprzętowe PTP (IEEE 1588 2008)

Kontroler SD/SDIO/MMC

Kontroler do pamięci zewnętrznych posiada obsługuje kilka standardów:

  • SD wersja 3.0 i 3.01
  • SDIO wersja 3.0
  • CE-ATA wersja 1.1
  • MMC wersja 4.41, eMMC wersja 4.5i 4.51

Kontroler pozwala na taktowanie zewnętrzne danych do 80MHz w trzech trybach: 1 bit, 4 bit oraz 8 bit. Obsługuje jednocześnie do 2 kart w trybie 4bit. Zgodny jest też z kartami 1.8V.

UART

ESP 32 posiada trzy interfejsy UART do komunikacji asynchronicznej (RS232 i RS485), IrDA. Komunikacja odbywa się z prędkością do 5Mbps. Posiada sprzętowe zarządzanie sygnałami CTS i RTS oraz kontrolę przepływu XON i XOFF. Całość ma dedykowany kontroler DMA lub komunikuje się bezpośrednio z CPU.

I2C

Dwa interfejsy I2C mogą działać w trybie master i slave. Obsługują:

  • Standard mode (100kbit/s)
  • Fast mode (400kbit/s)
  • Adresowanie 7bit i 10 bit

I2S

Dwa interfejsy I2S mogą działać w trybie master i slave, w trybie full i half-duplex i mogą operować z rozdzielczością 8, 16, 32, 40 lub 48bit. Częstotliwość zegara BCK jest obsługiwana w zakresie 10kHz – 40MHz. Jeśli jeden lub oba interfejsy są w trybie master, ich wyjście może być podłączone do przetwornika DAC. Oba mają kontrolery DMA.

Kontroler podczerwieni

Obsługuje 8 kanałów transmisji podczerwonej. Dzielą one 512×32 bit blok pamięci do zapisu wejścia/wyjścia.

PWM

Kontroler PWM może być użyty do zasilania silników lub świateł. Kontroler PWM LED potrafi wygenerować 16 niezależnych kanałów. Każdy z kanałów posiada 20 bit timer. Posiada też funkcję automatycznego zwiększania/zmniejszania wypełnienia.

SPI

ESP 32 posiada 3 interfejsy SPI (SPI, HSPI i VSPI) w trybie master i slave. Obsługują transmisję do 80MHz i każdą podzielną z 80MHz. Posiadają FIFO wielkości 64 bit. Każdy z nich posiada też DMA.

Kryptografia

Algorytmy AES, SHA, RSA i ECC posiadają sprzętowe wsparcie. Maksymalna długość kluczy to 4096 bit.

Dodatkowo pamięć Flash może być sprzętowo kodowana.

Older posts

© 2018 Paweł Szabaciuk

Theme by Anders NorenUp ↑