Programowanie modułów ESP32 w środowisku ESP-IDF (2). Podstawy obsługi portów I/O, ADC, PWM, UART

Programowanie modułów ESP32 w środowisku ESP-IDF (2). Podstawy obsługi portów I/O, ADC, PWM, UART

W tym artykule omówione zostaną podstawy programowania układów ESP32 za pomocą środowiska ESP-IDF. Nasza uwaga skupiona będzie na procedurach, od których zazwyczaj rozpoczyna się pracę z nowym mikrokontrolerem czy środowiskiem programistycznym: dostępie do portów wejścia/wyjścia, obsłudze przetworników ADC oraz wyjść PWM czy też komunikacji ze światem zewnętrznym poprzez interfejs UART.

Obsługa portów I/O

Porty I/O, czyli porty wejścia/wyjścia, są najwdzięczniejszym interfejsem, ponieważ pozwalają w prosty sposób przetestować związany z nimi fragment oprogramowania. Wystarczy do wybranego portu podłączyć poprzez opornik zwykłą diodę LED, a jej świecenie będzie sygnalizować stan danego wyprowadzenia. Na niektórych płytach rozwojowych z ESP32 producenci zamontowali już tzw. LED-y użytkownika, doskonale nadające się do eksperymentów. W takim przypadku wystarczy jedynie odszukać w dokumentacji płytki numer portu I/O, do którego taka dioda jest podłączona.

Stworzenie programu testowego rozpoczynamy od uruchomienia edytora ze środowiskiem ESP-IDF. W opisie domyślnie będzie to edytor oparty na Eclipse, ESP-IDF v.4.4.

Klikamy File → New → Espressif IDF Project. Jeżeli zaznaczymy „Create a project using one of the templates”, będzie można otworzyć któryś z gotowych przykładów. Tym razem jednak stworzymy program od podstaw. W polu „Project name” należy wpisać nazwę tworzonego programu. W przykładzie użyję nazwy test-io-esp32-s3. Na liście „Select project target:” trzeba wybrać wariant procesora zamontowanego na używanej płycie rozwojowej, w omawianym przykładzie będzie to ESP32-S3. Po kliknięciu Finish zostanie utworzony projekt z plikiem main.c i pozostałymi plikami potrzebnymi w strukturze projektu.

Rysunek 1. Widok okna głównego środowiska Espressif-IDE

Ewentualne komunikaty o błędach w wygenerowanym projekcie znikną po przeprowadzeniu kompilacji w tym celu należy wybrać Project → Build Project lub nacisnąć ikonę młotka na pasku skrótów (patrz rysunek 1, oznaczenie strzałką nr 1). Teraz w pliku main.c – na miejsce automatycznie utworzonego kodu – proszę wpisać ten z listingu 1. W linii #define BLINK_GPIO podajemy numer portu I/O, do którego podłączona jest dioda LED. Należy wykonać powtórną kompilację i usunąć ewentualne błędy powstałe przy wpisywaniu kodu z listingu 1. Następnie podłączamy płytkę ewaluacyjną kablem do gniazda USB. W kolejnym kroku za pomocą narzędzi systemowych odczytujemy przydzielony podłączonej płycie numer portu. W edytorze należy kliknąć edycję listy Launch Target i w polu listy rozwijanej Serial Port: (rysunek 1, strzałka nr 3) ustawić numer portu. Po naciśnięciu przycisku Launch (rysunek 1, strzałka nr 2) płyta powinna zostać zaprogramowana, a dioda zacznie migotać z częstotliwością 1 Hz.

#include <unistd.h>

#include <stdio.h>
#include "driver/gpio.h"
#include "esp_log.h"
#include "sdkconfig.h"

static const char *TAG = "example";

/* wstaw w tej linii numer GPIO do którego
* jest podłączona dioda LED
*/
#define BLINK_GPIO 21

static uint8_t led_stan = 0;

static void configure_led(void)
{
ESP_LOGI(TAG, "Przykład konfiguracji wyjścia GPIO LED!");
gpio_reset_pin(BLINK_GPIO);
/* Ustaw tryb pracy GPIO jako push/pull output */
gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
}

static void blink_led(void)
{
/* Set the GPIO level according to the state (LOW or HIGH)*/
gpio_set_level(BLINK_GPIO, led_stan);
led_stan =!led_stan;
}


void app_main(void)
{
ESP_LOGI(TAG, "Cześć tu program testowy GPIO LED!");
configure_led();

while (true) {
ESP_LOGI(TAG, "Stan LED=%s!", led_stan == true ? "ON" : "OFF");
blink_led();

sleep(1);
}
}

Listing 1. Program umożliwiający najprostsze miganie diodą LED

Działanie programu oparto na najprostszym sposobie sterowania portem I/O. W funkcji configure_led(void) port został ustawiony jako wyjściowy, a funkcja blink_led(void) zmienia poziom na wyjściu, do którego podłączona została dioda LED.

W celu przetestowania I/O pracującego w trybie wejścia należy wybrać drugi dostępny port na płycie rozwojowej. Ponieważ w czasie testu będzie on zwierany do masy, najlepiej wybrać taki, który nie ma przypisanej dodatkowej ważnej funkcji systemowej – czyli np. port o numeracji wyższej niż 17. Ponadto trzeba wprowadzić zmiany widoczne na listingu 2. Polegają one na zdefiniowaniu wybranego drugiego portu jako SW_GPIO, dodaniu funkcji ustawienia go jako wejściowego configure_sw(void) i modyfikacji funkcji main(). Po przesłaniu nowej wersji oprogramowania na płytę rozwojową i zwarciu do masy portu SW_GPIO, częstotliwość migotania diody LED zwiększa się do około 5 Hz.

(…)
#define SW_GPIO 47

(…)
static void configure_sw(void)
{
gpio_reset_pin(SW_GPIO);
if (gpio_set_pull_mode ( SW_GPIO , GPIO_PULLUP_ONLY) !=ESP_OK)
{
ESP_LOGE(TAG, "SW LED nie został prawidłowo skonfigurowany!");
}ESP_LOGI(TAG, "Przykład konfiguracji wejścia SW_GPIO");
gpio_set_direction(SW_GPIO, GPIO_MODE_INPUT);
}

(…)
void app_main(void)
{
uint8_t status_SW=true;

ESP_LOGI(TAG, "Cześć tu program testowy GPIO LED!");
configure_led();
configure_sw();

while (true) {
blink_led();
status_SW =gpio_get_level(SW_GPIO);
ESP_LOGI(TAG, "Stan LED=%s, f=%s", led_stan == true ? "ON" : "OFF", status_SW ==true ? "1Hz" : "5Hz");
if (status_SW!=0){
sleep(1);
}else usleep(200000);
led_stan =!led_stan;
}
}

Listing 2. Rozszerzenie programu z listingu 1 o odczyt stanu wejścia cyfrowego

Makra ESP_LOGI i ESP_LOGE powodują wysłanie odpowiednich komunikatów do terminalu wywoływanego z pulpitu edytora (rysunek 1, strzałka nr 4). Po naciśnięciu ikony terminalu wyświetla się tablica, na której w polu „Project Name:” trzeba ustawić nazwę projektu, a w polu „Serial port:” wybrać numer przydzielonego płycie portu. Po zatwierdzeniu powinna otworzyć się zakładka terminalu odbierającego komunikaty wysyłane przez nasze oprogramowanie. Takie zastosowanie terminalu pozwala na proste debugowanie programu.

W przykładzie pokazano najprostszy sposób sterowania portami I/O. API środowiska ESP-IDF umożliwia bardziej zaawansowane manipulacje, np. dołączanie wewnętrznych oporników podciągających, współpracę z systemem przerwań, zatrzaskiwanie ustawień portu nawet na czas resetu czy grupowe ustawianie parametrów wielu portów jednocześnie. Szczegółowy opis odpowiedni dla wersji IDF-4.4 można znaleźć w [1].

Obsługa przetworników ADC

W większości wersji ESP32 do dyspozycji użytkownika są dwa niezależne przetworniki analogowo-cyfrowe, każdy z kilkoma multipleksowanymi wejściami. Jako wejścia używa się portów I/O pracujących w trybie analogowym. W dokumentacji technicznej dotyczącej używanej wersji układu ESP32 opisane zostało przyporządkowanie dostępnych wejść przetwornika do portów I/O. Także w dokumentacji podane będą informacje o rozdzielczości bitowej, nieliniowości przetwarzania i ograniczeniach w użyciu przetworników ADC.

Na listingu 3 pokazany został prosty program, używający przetwornika ADC do odczytu bezpośredniego w trybie próbkowania. Listing oparto na przykładzie esp-idf-v4.4/examples/peripherals/adc/single_read/single_read. Klikamy File → New → Espressif IDF Project, zaznaczamy „Create a project using one of the templates” i na wyświetlonej liście klikamy przykład single_read.

#include <stdio.h>
#include <stdlib.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/adc.h"
#include "esp_adc_cal.h"

//ADC Channels
#if CONFIG_IDF_TARGET_ESP32
#define ADC1_EXAMPLE_CHAN0 ADC_CHANNEL_6//ADC1_CHANNEL_6
#define ADC2_EXAMPLE_CHAN0 ADC2_CHANNEL_0
static const char *TAG_CH[2][10] = {{"ADC1_CH6"}, {"ADC2_CH0"}};
#else
#define ADC1_EXAMPLE_CHAN0 ADC1_CHANNEL_2
//#define ADC2_EXAMPLE_CHAN0 ADC2_CHANNEL_0
static const char *TAG_CH[2][10] = {{"ADC1_CH2"}, {"ADC2_CH0"}};
#endif

//ADC Attenuation
#define ADC_EXAMPLE_ATTEN ADC_ATTEN_DB_11

//ADC Calibration
#if CONFIG_IDF_TARGET_ESP32
#define ADC_EXAMPLE_CALI_SCHEME ESP_ADC_CAL_VAL_EFUSE_VREF
#elif CONFIG_IDF_TARGET_ESP32S2
#define ADC_EXAMPLE_CALI_SCHEME ESP_ADC_CAL_VAL_EFUSE_TP
#elif CONFIG_IDF_TARGET_ESP32C3
#define ADC_EXAMPLE_CALI_SCHEME ESP_ADC_CAL_VAL_EFUSE_TP
#elif CONFIG_IDF_TARGET_ESP32S3
#define ADC_EXAMPLE_CALI_SCHEME ESP_ADC_CAL_VAL_EFUSE_TP_FIT
#endif


static int adc_raw[2][10];
//static const char *TAG = "ADC SINGLE";

static esp_adc_cal_characteristics_t adc1_chars;
static esp_adc_cal_characteristics_t adc2_chars;

static bool adc_calibration_init(void)
{
esp_err_t ret;
bool cali_enable = false;

ret = esp_adc_cal_check_efuse(ADC_EXAMPLE_CALI_SCHEME);
if (ret == ESP_ERR_NOT_SUPPORTED) {
ESP_LOGW(TAG, "Calibration scheme not supported, skip software calibration");
} else if (ret == ESP_ERR_INVALID_VERSION) {
ESP_LOGW(TAG, "eFuse not burnt, skip software calibration");
} else if (ret == ESP_OK) {
cali_enable = true;
esp_adc_cal_characterize(ADC_UNIT_1, ADC_EXAMPLE_ATTEN, ADC_WIDTH_BIT_DEFAULT, 0, &adc1_chars);
// esp_adc_cal_characterize(ADC_UNIT_2, ADC_EXAMPLE_ATTEN, ADC_WIDTH_BIT_DEFAULT, 0, &adc2_chars);
} else {
ESP_LOGE(TAG, "Invalid arg");
}

return cali_enable;
}

void app_main(void)
{
// esp_err_t ret = ESP_OK;
uint32_t voltage = 0;
bool cali_enable = adc_calibration_init();

//ADC1 config
ESP_ERROR_CHECK(adc1_config_width(ADC_WIDTH_BIT_DEFAULT));
ESP_ERROR_CHECK(adc1_config_channel_atten(ADC1_EXAMPLE_CHAN0, ADC_EXAMPLE_ATTEN));
//ADC2 config
// ESP_ERROR_CHECK(adc2_config_channel_atten(ADC2_EXAMPLE_CHAN0, ADC_EXAMPLE_ATTEN));
vTaskDelay(pdMS_TO_TICKS(5000));
while (1) {
adc_raw[0][0] = adc1_get_raw(ADC1_EXAMPLE_CHAN0);
ESP_LOGI(TAG_CH[0][0], "raw data: %d", adc_raw[0][0]);
if (cali_enable) {
voltage = esp_adc_cal_raw_to_voltage(adc_raw[0][0], &adc1_chars);
ESP_LOGI(TAG_CH[0][0], "cali data: %d mV", voltage);
}
vTaskDelay(pdMS_TO_TICKS(1000));
/*
do {
ret = adc2_get_raw(ADC2_EXAMPLE_CHAN0, ADC_WIDTH_BIT_DEFAULT, &adc_raw[1][0]);
} while (ret == ESP_ERR_INVALID_STATE);
ESP_ERROR_CHECK(ret);

ESP_LOGI(TAG_CH[1][0], "raw data: %d", adc_raw[1][0]);
if (cali_enable) {
voltage = esp_adc_cal_raw_to_voltage(adc_raw[1][0], &adc2_chars);
ESP_LOGI(TAG_CH[1][0], "cali data: %d mV", voltage);
}
vTaskDelay(pdMS_TO_TICKS(1000));
*/
}
}

Listing 3. Prosty przykład obsługi przetwornika ADC

Zmiany w programie polegają na uproszczeniu kodu i odczycie tylko z kanału 6 przetwornika nr 1. Na listingu 3 niepotrzebne fragmenty kodu zostały ujęte w komentarze.

Jako pierwsza wywoływana jest funkcja adc_calibration_init(), której procedury służą do kalibracji przetwornika ADC. W ESP32 niektóre stałe kalibracyjne mogą być zapisywane na etapie produkcji układu w specjalnym obszarze pamięci „eFuse bits”. Jeżeli procedura esp_adc_cal_check_efuse nie odnajdzie poszukiwanej wartości kalibracyjnej, do korekty odczytów z przetwornika zostaną użyte wartości domyślne. Funkcja esp_adc_cal_characterize() mierzy napięcie Vref i temperaturę, czyli parametry używane do korekty obliczeń. Kolejne wywoływane funkcje: adc1_config_width() i adc1_config_channel_atten(), ustawiają parametry pracy wybranego kanału przetwornika 1 zgodnie z definicjami umieszczonymi na początku listingu 3. Potem w nieskończonej pętli następuje odczyt surowych danych z przetwornika i ich konwersja na wartość napięcia w mV, z zastosowaniem obliczonej wcześniej korekcji (funkcje adc1_get_raw(), esp_adc_cal_raw_to_voltage()). Obie wartości – opakowane w stosowny komunikat – po każdym odczycie wysyłane są do terminalu.

Opis bardziej rozbudowanych funkcji API do obsługi przetwornika ADC można znaleźć w dokumentacji [2].

LEDC – sterowanie jasnością LED w trybie PWM

Funkcje z grupy API LEDC przeznaczone są do sterowania jasnością diod LED przez zmianę wypełnienia impulsu PWM. Można je także zastosować ogólnie – jako sposób generowania impulsów o zmiennym współczynniku wypełnienia. Sposób użycia funkcji pokazany zostanie na przykładzie, w którym jasność świecenia diody LED będzie zmieniana proporcjonalnie do wartości napięcia podawanego na wejście przetwornika ADC. W tym celu zmodyfikujemy przykład z listingu 3, dodając funkcje LEDC. Dodatkowe linie programu zawiera listing 4.

(…)
//LEDC PWM
#include "driver/ledc.h"

(…)
//LEDC PWM
#define LEDC_TIMER LEDC_TIMER_0
#define LEDC_MODE LEDC_LOW_SPEED_MODE
#define LEDC_OUTPUT_IO (2) // Define the output GPIO
#define LEDC_CHANNEL LEDC_CHANNEL_0
#define LEDC_DUTY_RES LEDC_TIMER_13_BIT // Set duty resolution to 13 bits
#define LEDC_FREQUENCY (5000) // Frequency in Hertz. Set frequency at 5 kHz

(…)
//LEDC PWM
static void example_ledc_init(void)
{
// Prepare and then apply the LEDC PWM timer configuration
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_MODE,
.timer_num = LEDC_TIMER,
.duty_resolution = LEDC_DUTY_RES,
.freq_hz = LEDC_FREQUENCY, // Set output frequency at 5 kHz
.clk_cfg = LEDC_AUTO_CLK
};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));

// Prepare and then apply the LEDC PWM channel configuration
ledc_channel_config_t ledc_channel = {
.speed_mode = LEDC_MODE,
.channel = LEDC_CHANNEL,
.timer_sel = LEDC_TIMER,
.intr_type = LEDC_INTR_DISABLE,
.gpio_num = LEDC_OUTPUT_IO,
.duty = 0, // Set duty to 0%
.hpoint = 0
};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
}

void app_main(void)
{
(…)
example_ledc_init();

while(1)
{
(…)
//LEDC PWM
// Set duty to 50%
uint32_t ledc_duty=(8192)-1;
ledc_duty *=voltage;
ledc_duty /=3300;
ESP_LOGI(TAG_CH[0][0],"LEDC PWM %d",ledc_duty);
ESP_ERROR_CHECK(ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, ledc_duty/*LEDC_DUTY*/));
// Update duty to apply the new value
ESP_ERROR_CHECK(ledc_update_duty(LEDC_MODE, LEDC_CHANNEL));

}
}
Listing 4. Rozbudowa przykładu z listingu 3 o obsługę wyjścia PWM

Najpierw należy dodać plik nagłówkowy biblioteki ledc.h. Następnie w sekcji definicji dodajemy nowe definicje: LEDC_TIMER – numer timera (0...3), LEDC_MODE – tryb pracy (wolny lub szybki), LEDC_OUTPUT_IO – numer portu I/O, na który ma być wyprowadzony przebieg PWM i do którego będzie podłączona dioda LED, LEDC_CHANNEL – numer kanału (0...7), LEDC_DUTY_RES – rozdzielczość (w bitach) generowanego przebiegu PWM, LEDC_FREQUENCY – częstotliwość generowanego przebiegu PWM. Należy także dodać funkcję example_ledc_init(), która inicjuje generowanie przebiegu PWM, używając do tego zdefiniowanych powyżej parametrów. Funkcje inicjujące mogą wygenerować komunikat błędu, jeżeli podane parametry przekraczają dopuszczalne zakresy rozdzielczości i częstotliwości. I tak dla niskich częstotliwości można ustawić wysoką rozdzielczość (do 20 bitów), natomiast dla najwyższej częstotliwości (40 MHz) rozdzielczość może być tylko 1-bitowa.

Wywołanie funkcji example_ledc_init() powinno zostać umieszczone na początku funkcji main(). Dalej – w pętli while – odczytana z przetwornika wartość napięcia voltage stosowana jest do obliczenia proporcjonalnej do niej wartości wypełnienia, umieszczanej w zmiennej ledc_duty. W każdym przebiegu pętli while funkcje ledc_set_duty() i ledc_update_duty() aktualizują wypełnienie przebiegu PWM. Opis funkcji i parametrów interfejsu LEDC można znaleźć w [3]. Na rysunku 2 pokazano schemat podłączenia do płyty rozwojowej dodatkowych elementów, takich jak dioda LED i potencjometr.

Rysunek 2. Schemat podłączenia dodatkowych elementów zewnętrznych do płyty rozwojowej

UART – obsługa interfejsu transmisji szeregowej

Protokół UART zapewnia obsługę asynchronicznych interfejsów komunikacji szeregowej, takich jak RS232, RS422 czy RS485. Układ ESP32 ma trzy identyczne kontrolery UART (UART0, UART1 i UART2). W przypadku płyt rozwojowych jeden z nich, zazwyczaj UART0, używany jest do przesyłania danych w czasie zapisu programu do pamięci FLASH, pozostałe mogą zostać użyte do komunikacji z urządzeniami zewnętrznymi.

Pokazany na listingu 5 program jest przykładem dostępnym w katalogu esp-idf-v4.4/examples/peripherals/uart/uart_echo. Program działa na zasadzie odpytywania (pollingu) danych wejściowych interfejsu UART. Po odebraniu jakichkolwiek danych zostaną one natychmiast odesłane tym samym kanałem UART. Procedura odpytywania działa w osobnym wątku echo_task(). Na początku wywoływane są procedury konfigurujące interfejs UART. Do konfiguracji używa się parametrów zdefiniowanych w pliku sdkonfig, znajdującym się w drzewie projektu. Plik sdkonfig można edytować po zaznaczeniu jego pozycji w zakładce Project Explorer i dwukrotnym kliknięciu lewym przyciskiem myszy. Po otwarciu okna konfiguratora i wybraniu pozycji Echo Example Configuration można ustawić takie parametry, jak: numer używanego interfejsu UART, wstępnie ustawiana szybkość transmisji, przypisanie wybranych portów I/O jako wyprowadzeń RXD i TXD. Procedura uart_driver_install() instaluje sterownik wybranego interfejsu UART, w przykładzie określa ona rozmiar pierścieniowego bufora odbiorczego na BUF_SIZE * 2 i wyłącza bufor nadawczy. Procedura uart_param_config() ustawia parametry komunikacji UART-a przekazywane strukturą uart_config. Z kolei procedura uart_set_pin() konfiguruje fizyczne piny GPIO, do których będzie podłączony interfejs UART. Jeżeli jakieś wyprowadzenia nie będą używane (tak jak w przykładzie RTS i CTS), należy zamiast numeru użyć makra UART_PIN_NO_CHANGE.

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "sdkconfig.h"

/**
* This is an example which echos any data it receives on configured UART back to the sender,
* with hardware flow control turned off. It does not use UART driver event queue.
*
* – Port: configured UART
* – Receive (Rx) buffer: on
* – Transmit (Tx) buffer: off
* – Flow control: off
* – Event queue: off
* – Pin assignment: see defines below (See Kconfig)
*/

#define ECHO_TEST_TXD (CONFIG_EXAMPLE_UART_TXD)
#define ECHO_TEST_RXD (CONFIG_EXAMPLE_UART_RXD)
#define ECHO_TEST_RTS (UART_PIN_NO_CHANGE)
#define ECHO_TEST_CTS (UART_PIN_NO_CHANGE)

#define ECHO_UART_PORT_NUM (CONFIG_EXAMPLE_UART_PORT_NUM)
#define ECHO_UART_BAUD_RATE (CONFIG_EXAMPLE_UART_BAUD_RATE)
#define ECHO_TASK_STACK_SIZE (CONFIG_EXAMPLE_TASK_STACK_SIZE)

#define BUF_SIZE (1024)

static void echo_task(void *arg)
{
/* Configure parameters of an UART driver,
* communication pins and install the driver */
uart_config_t uart_config = {
.baud_rate = ECHO_UART_BAUD_RATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_APB,
};
int intr_alloc_flags = 0;

#if CONFIG_UART_ISR_IN_IRAM
intr_alloc_flags = ESP_INTR_FLAG_IRAM;
#endif

ESP_ERROR_CHECK(uart_driver_install(ECHO_UART_PORT_NUM, BUF_SIZE * 2, 0, 0, NULL, intr_alloc_flags));
ESP_ERROR_CHECK(uart_param_config(ECHO_UART_PORT_NUM, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(ECHO_UART_PORT_NUM, ECHO_TEST_TXD, ECHO_TEST_RXD, ECHO_TEST_RTS, ECHO_TEST_CTS));

// Configure a temporary buffer for the incoming data
uint8_t *data = (uint8_t *) malloc(BUF_SIZE);

while (1) {
// Read data from the UART
int len = uart_read_bytes(ECHO_UART_PORT_NUM, data, BUF_SIZE, 20 / portTICK_RATE_MS);
// Write data back to the UART
uart_write_bytes(ECHO_UART_PORT_NUM, (const char *) data, len);
if (len !=0)uart_write_bytes(ECHO_UART_PORT_NUM, (const char *) "echo ok\r\n", 9);
}
}

void app_main(void)
{
xTaskCreate(echo_task, "uart_echo_task", ECHO_TASK_STACK_SIZE, NULL, 10, NULL);
}

Listing 5. Program do przetestowania interfejsu UART w trybie echa

Od tej chwili możliwy jest odbiór przesyłanych portem UART danych, które można odczytywać cyklicznie wywoływaną funkcją uart_read_bytes(). Funkcja przepisuje odebrane dane z pierścieniowego bufora odbiorczego – do utworzonego w programie bufora data[], do którego wskaźnik podany został jako parametr w wywołaniu funkcji. Funkcja zwraca także liczbę odebranych bajtów danych, wartość 0 oznacza brak nowych danych. W programie przykładowym odebrane bajty zapisane w buforze data[] odsyłane są tym samym UART-em jako echo, przy użyciu funkcji uart_write_bytes(). Następnie realizowany jest kolejny cykl odpytywania w pętli while(1).

W przykładzie pokazano najprostszy sposób korzystania z interfejsu UART. Informacje o bardziej wyrafinowanych metodach, opartych między innymi na przerwaniach, można znaleźć w dokumentacji [4] oraz w innych przykładach.

Ryszard Szymaniak, EP

Literatura:

  1. https://docs.espressif.com/projects/esp-idf/en/release-v4.4/esp32/api-reference/peripherals/gpio.html
  2. https://docs.espressif.com/projects/esp-idf/en/release-v4.4/esp32/api-reference/peripherals/adc.html
  3. https://docs.espressif.com/projects/esp-idf/en/release-v4.4/esp32/api-reference/peripherals/ledc.html
  4. https://docs.espressif.com/projects/esp-idf/en/release-v4.4/esp32/api-reference/peripherals/uart.html
Artykuł ukazał się w
Elektronika Praktyczna
lipiec 2024
Elektronika Praktyczna Plus lipiec - grudzień 2012

Elektronika Praktyczna Plus

Monograficzne wydania specjalne

Elektronik grudzień 2024

Elektronik

Magazyn elektroniki profesjonalnej

Raspberry Pi 2015

Raspberry Pi

Wykorzystaj wszystkie możliwości wyjątkowego minikomputera

Świat Radio listopad - grudzień 2024

Świat Radio

Magazyn krótkofalowców i amatorów CB

Automatyka, Podzespoły, Aplikacje listopad - grudzień 2024

Automatyka, Podzespoły, Aplikacje

Technika i rynek systemów automatyki

Elektronika Praktyczna grudzień 2024

Elektronika Praktyczna

Międzynarodowy magazyn elektroników konstruktorów

Elektronika dla Wszystkich grudzień 2024

Elektronika dla Wszystkich

Interesująca elektronika dla pasjonatów