Продолжаем учиться писать код для микроконтроллера ESP32 и на данном занятии мы попробуем принять данные по шине UART.
До сих пор мы только передавали данные по этой шине, причём не вдаваясь в процесс происходящего ибо всё это реализовано в комплекте IDF.
А вот с приёмом, который нам пригодится в недалёком будущем, дела обстоят несколько посложнее.
Схема наша по-прежнему будет состоять из платы с контроллером ESP32, подключенной к порту USB компьютера
Проект мы возьмём за основу из урока 4, в котором мы работали с кнопкой, с именем BUTTON01 и на его основе создадим проект с именем UART_RX.
Откроем наш проект в ESPRESSIF IDE и из файла Kconfig.projbuild удалим пункт меню по работе с ножкой, к которой была подключена кнопка (BUTTON_GPIO). Также в данном файле ножку порта по умолчанию для светодиода выберем вторую
default 48 if IDF_TARGET_ESP32S3
default 2
В функции app_main файла main.c оставим только вот эти строки
1 2 3 4 5 6 7 |
//------------------------------------------------------------- void app_main(void) { gpio_reset_pin(CONFIG_BLINK_GPIO); gpio_set_direction(CONFIG_BLINK_GPIO, GPIO_MODE_OUTPUT); } //------------------------------------------------------------- |
Не забываем также в конфигураторе выбрать 4 мегабайта объёма памяти FLASH
Попробуем собрать наш проект. Если всё нормально собирается, то ещё создадим заголовочный файл main.h, в который соберём все подключенные заголовочные файлы из main.c, а заодно и подключим заголовочные файлы для работы с UART
1 2 3 4 5 6 7 8 9 10 11 12 |
#ifndef MAIN_MAIN_H_ #define MAIN_MAIN_H_ //--------------------------------------------------------------------- #include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gpio.h" #include "driver/uart.h" #include "esp_log.h" #include "sdkconfig.h" //--------------------------------------------------------------------- #endif /* MAIN_MAIN_H_ */ |
Вернёмся в main.c и удалим подключение всех заголовочных файлов, а подключим только один
#include "main.h"
Добавим функцию инициализации UART, чтобы не нагромождать код в функции точки входа
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//============================================================== void uart_init(void) { const uart_config_t uart_config = { .baud_rate = 115200, .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, }; } //============================================================== |
В теле функции мы объявили переменную типа структуры параметров конфигурации UART. Параметры все стандартные и объяснять тут нечего.
Объявим глобальную переменную с размером буфера для UART
1 2 3 4 |
#include "main.h" //============================================================== static const int RX_BUF_SIZE = 1024; //============================================================== |
В теле функции uart_init мы настроим драйвер для шины, используя для этого функцию из комплекта с параметрами номера шины, размера буфера, умноженного на 2. Остальные параметры будут нулевые.
1 2 3 |
.source_clk = UART_SCLK_APB, }; uart_driver_install(UART_NUM_0, RX_BUF_SIZE * 2, 0, 0, NULL, 0); |
Третий параметр, который у нас также нулевой, должен быть с размером буфера передачи. А если он нулевой, то драйвер не будет использовать буфер TX, функция TX заблокирует задачу, пока все данные не будут отправлены. Остальные параметры нам пока вообще не интересны.
Далее мы, используя, переменную структуры , применим нашу конфигурацию для интерфейса UART
1 2 |
uart_driver_install(UART_NUM_0, RX_BUF_SIZE * 2, 0, 0, NULL, 0); uart_param_config(UART_NUM_0, &uart_config); |
Объявим макросы с ножками порта, которые будут у нас задействованы под UART. Исходя из схемы нашей платы это GPIO1 и GPIO3
1 2 3 4 5 |
#include "main.h" //============================================================== #define TXD_PIN (GPIO_NUM_1) #define RXD_PIN (GPIO_NUM_3) //============================================================== |
Вернёмся в нашу функцию и применим наши ножки к шине
1 2 |
uart_param_config(UART_NUM_0, &uart_config); uart_set_pin(UART_NUM_0, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); |
4 и 5 параметры — это ножки для CTS и RTS. Они у нас не используются. Передаём здесь -1.
В функции app_main вызовем нашу функцию инициализации
1 2 3 |
gpio_set_direction(CONFIG_BLINK_GPIO, GPIO_MODE_OUTPUT); uart_init(); |
Так как передачу мы не используем, то и задачу мы для неё не создаём, создадим функцию для задачи приёма пакетов ниже функции инициализации uart_init
1 2 3 4 5 |
//============================================================== static void rx_task(void *arg) { } //============================================================== |
Тем не менее передавать данные в UART мы можем, поэтому объявим указатель на символьный массив для сообщения в лог и передадим данный тег при помощи специальной функции
1 2 3 4 |
static void rx_task(void *arg) { static const char *RX_TASK_TAG = "RX_TASK"; esp_log_level_set(RX_TASK_TAG, ESP_LOG_INFO); |
Объявим также указатель на 8-разрядный беззнаковый целочисленный тип и сразу выделим под него память с небольшим запасом
1 2 |
esp_log_level_set(RX_TASK_TAG, ESP_LOG_INFO); uint8_t* data = (uint8_t*) malloc(RX_BUF_SIZE+1); |
Ниже организуем цикл, в котором попытаемся прочитать пакет и, если он больше нуля, то закончим его нулём, так как у нас это будет строка и выведем её в терминал. Тем самым мы получим эхо нашего сообщения, которое мы пошлём из терминала в наш контроллер
1 2 3 4 5 6 7 8 |
uint8_t* data = (uint8_t*) malloc(RX_BUF_SIZE+1); while (1) { const int rxBytes = uart_read_bytes(UART_NUM_0, data, RX_BUF_SIZE, 1000 / portTICK_RATE_MS); if (rxBytes > 0) { data[rxBytes] = 0; printf((char*)data); } } |
Выйдя из цикла, не забываем про очистку памяти
1 2 3 4 |
printf((char*)data); } } free(data); |
Мы не используем логирование напрямую, тем не менее инициализация нужна.
В функции app_main создадим нашу задачу
1 2 3 |
uart_init(); xTaskCreate(rx_task, "uart_rx_task", 1024*2, NULL, configMAX_PRIORITIES, NULL); |
Вот и весь код. Вроде небольшой и несложный, но свои тонкости имеет.
Соберём код, прошьём контроллер и запустим отдельную терминальную программу, так как штатный терминал ESPRESSIF_IDE конфигурируется на приём не совсем привычным образом. Настроим порт
Соединимся с портом и попробуем что-нибудь передать
Через некоторое время мы сможем увидеть, что наша строка вернётся в терминальную программу
Значит у нас всё нормально работает.
При желании мы можем подключить логический анализатор вот к этим ножкам
И посмотреть в программе логического анализа, как происходит приём и передача по шине UART
Наличие лишнего символа возврата каретки нас не должно волновать, это мелочи. Возможно, мы немного не там завершаем строку в коде. Также то, что эхо идёт не сразу, это также решаемый вопрос, но это нас тоже сейчас никак не волнует.
Самое главное, что мы смогли принять пакет от UART в отдельном потоке и записать его в массив, что нам очень скоро пригодится.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP32 Следующий урок
Недорогие отладочные платы ESP32 можно купить здесь Недорогие отладочные платы ESP32
Недорогие отладочные платы ESP32/ESP32-C3/ESP32-S3 можно купить здесь Недорогие отладочные платы ESP32
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Добавить комментарий