Продолжаем работу с энергонезависимым хранилищем данных (Non-volatile storage, NVS), с которым мы познакомились в уроке 33, и сегодня мы попробуем изменить в нём значение некоторых пар ключ-значение посредством интерфейса UART с компьютера. Так как на прошлом уроке мы уже поработали с UART на чтение, то сделать это нам сильного труда не составит.
Схема наша также будет состоять из платы с контроллером ESP32, подключенной к порту USB компьютера
Проект нашего урока будет сделан на основе проекта урока 33 с именем NVS01 и назван будет NVS01_WRITE_PC.
Откроем наш проект в Espressif IDE и в файле main.h подключим заголовочный файл для работы с UART и заголовочный файл для работы с логами
1 2 3 |
#include "driver/gpio.h" #include "driver/uart.h" #include "esp_log.h" |
Добавим ещё библиотеку для работы со строками
1 2 |
#include <stdio.h> #include <string.h> |
В файле main.c объявим размер буфера для UART и номера ножек портов
1 2 3 4 5 6 7 |
#include "main.h" //============================================================== #define TXD_PIN (GPIO_NUM_1) #define RXD_PIN (GPIO_NUM_3) //============================================================== static const int RX_BUF_SIZE = 1024; //============================================================== |
Также добавим функции для инициализации UART и для задачи приёма пакетов, взяв её вместе с телом из проекта прошлого урока с именем UART_RX
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
static const int RX_BUF_SIZE = 1024; //============================================================== 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_driver_install(UART_NUM_0, RX_BUF_SIZE * 2, 0, 0, NULL, 0); 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); } //============================================================== static void rx_task(void *arg) { static const char *RX_TASK_TAG = "RX_TASK"; esp_log_level_set(RX_TASK_TAG, ESP_LOG_INFO); 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); } } free(data); } //============================================================== |
Из тела функции app_main удалим код, отвечающий за запись данных в NVS
// Write
int8_t val01 = -108;
uint8_t val02 = 198;
int16_t val03 = -10135;
uint16_t val04 = 61145;
int32_t val05 = -101459;
uint32_t val06 = 3352582294;
int64_t val07 = -4047512760478312334;
uint64_t val08 = 7214561357951254753;
printf("Updating data in NVS ... ");
err = nvs_set_i8(my_handle, "dt01", val01);
printf((err != ESP_OK) ? "Failed!\n" : "Done\n");
err = nvs_set_u8(my_handle, "dt02", val02);
printf((err != ESP_OK) ? "Failed!\n" : "Done\n");
err = nvs_set_i16(my_handle, "dt03", val03);
printf((err != ESP_OK) ? "Failed!\n" : "Done\n");
err = nvs_set_u16(my_handle, "dt04", val04);
printf((err != ESP_OK) ? "Failed!\n" : "Done\n");
err = nvs_set_i32(my_handle, "dt05", val05);
printf((err != ESP_OK) ? "Failed!\n" : "Done\n");
err = nvs_set_u32(my_handle, "dt06", val06);
printf((err != ESP_OK) ? "Failed!\n" : "Done\n");
err = nvs_set_i64(my_handle, "dt07", val07);
printf((err != ESP_OK) ? "Failed!\n" : "Done\n");
err = nvs_set_u64(my_handle, "dt08", val08);
printf((err != ESP_OK) ? "Failed!\n" : "Done\n");
err = nvs_set_str(my_handle, "MySSID", "MyLogin");
printf((err != ESP_OK) ? "Failed!\n" : "Done\n");
err = nvs_set_str(my_handle, "MyPASS", "123abc7890");
printf((err != ESP_OK) ? "Failed!\n" : "Done\n");
printf("Committing updates in NVS ... ");
err = nvs_commit(my_handle);
printf((err != ESP_OK) ? "Failed!\n" : "Done\n");
Также часть кода, которая отвечает за чтение целых чисел, тоже удалим
int8_t res01 = 0;
uint8_t res02 = 0;
int16_t res03 = 0;
uint16_t res04 = 0;
int32_t res05 = 0;
uint32_t res06 = 0;
int64_t res07 = 0;
uint64_t res08 = 0;
nvs_get_i8(my_handle, "dt01", &res01);
printf("dt01: %d\n", res01);
nvs_get_u8(my_handle, "dt02", &res02);
printf("dt02: %d\n", res02);
nvs_get_i16(my_handle, "dt03", &res03);
printf("dt03: %d\n", res03);
nvs_get_u16(my_handle, "dt04", &res04);
printf("dt04: %d\n", res04);
nvs_get_i32(my_handle, "dt05", &res05);
printf("dt05: %d\n", res05);
nvs_get_u32(my_handle, "dt06", &res06);
printf("dt06: %u\n", res06);
nvs_get_i64(my_handle, "dt07", &res07);
printf("dt07: %lld\n", res07);
nvs_get_u64(my_handle, "dt08", &res08);
printf("dt08: %llu\n", res08);
Строку с объявлением хендла также удалим
nvs_handle_t my_handle;
и выполним объявление данной переменной в глобальной секции
1 2 |
static const int RX_BUF_SIZE = 1024; nvs_handle_t my_handle; |
Вернёмся в функцию app_main и после небольшой задержки произведём инициализацию UART, а также создадим задачу для чтения пакетов UART
1 2 3 4 5 6 |
printf("\n"); vTaskDelay(200 / portTICK_PERIOD_MS); uart_init(); xTaskCreate(rx_task, "uart_rx_task", 1024*2, NULL, configMAX_PRIORITIES, NULL); |
Если не будет задержки, то сразу пойдёт инициализация шины и мы не увидим часть сообщений в терминале из предыдущего кода.
Перейдём в функцию инициализации uart_init и в параметрах выделим буферную память также для передачи пакетов
uart_driver_install(UART_NUM_0, RX_BUF_SIZE * 2, RX_BUF_SIZE * 2, 0, NULL, 0);
В функции rx_task в бесконечном цикле из строки приёма пакета удалим объявление константы, а также уменьшим таймаут, так как от него зависит то время, когда мы дождёмся результата приёма ибо мы передаём в параметре максимально-возможное количество принятых байтов, а принимаем меньше
const int rxBytes = uart_read_bytes(UART_NUM_0, data, RX_BUF_SIZE, 10 / portTICK_RATE_MS);
Из условия ниже удалим вот эти строки
data[rxBytes] = 0;
printf((char*)data);
Вместо них добавим ещё одно условие, в котором отследим команду начала пакета (такое условие программы, через которую мы будем передавать данные
1 2 3 4 |
if (rxBytes > 0) { if(data[0] == 0xAA && data[1] == 0x55) { } |
Мы будем данные также и передавать на PC, поэтому в данном условии будут ещё два условия, которые будут разделять приём и передачу пакетов
1 2 3 4 5 6 7 8 |
if(data[0] == 0xAA && data[1] == 0x55) { if(data[2] == 0x31) //Read { } if(data[2] == 0x32) //Write { } |
Здесь всё наоборот. Read и Write относится не к пакетам, а к хранилищу, то есть Read — чтение из хранилища и передача на PC, а Write — приём из PC и запись в NVS.
При приёме и передаче пакетов мы будем менять уровень на ножке порта, к которой подключен синий светодиод. Для этого объявим локальную переменную
1 2 |
static const char *RX_TASK_TAG = "RX_TASK"; uint8_t lvl = 1; |
В условии, котором мы будем читать данные из NVS и передавать их на PC, применим уровень ножки и инвертируем состояние в переменной
1 2 3 4 |
if(data[2] == 0x31) //Read { gpio_set_level(CONFIG_BLINK_GPIO, lvl); lvl ^= 1; |
Подготовим команду, чтобы программа на PC поняла, что мы начинаем передавать пакет
1 2 3 4 |
lvl ^= 1; data[0] = 0xBB; data[1] = 0x66; data[2] = 0x06; |
Объявим переменные для размера строк, откроем хранилище и узнаем размер строки с ключом MySSID, которая у нас хранится с прошлого урока
1 2 3 4 5 |
data[2] = 0x06; size_t nvs_required_size_myssid; size_t nvs_required_size_mypass; nvs_open("storage", NVS_READWRITE, &my_handle); nvs_get_str(my_handle, "MySSID", NULL, &nvs_required_size_myssid); |
Запишем этот размер в массив для передачи
1 2 |
nvs_get_str(my_handle, "MySSID", NULL, &nvs_required_size_myssid); *(uint32_t*)(data+3) = nvs_required_size_myssid; |
Прочитаем строку из NVS
1 2 3 4 |
*(uint32_t*)(data+3) = nvs_required_size_myssid; char *nvs_ret_data_myssid; nvs_ret_data_myssid = malloc(nvs_required_size_myssid); nvs_get_str(my_handle, "MySSID", nvs_ret_data_myssid, &nvs_required_size_myssid); |
Узнаем размер ключа MyPASS и также сохраним его в массив
1 2 3 |
nvs_get_str(my_handle, "MySSID", nvs_ret_data_myssid, &nvs_required_size_myssid); nvs_get_str(my_handle, "MyPASS", NULL, &nvs_required_size_mypass); *(uint32_t*)(data+7) = nvs_required_size_mypass; |
Прочитаем строку и закроем хранилище
1 2 3 4 5 |
*(uint32_t*)(data+7) = nvs_required_size_mypass; char *nvs_ret_data_mypass; nvs_ret_data_mypass = malloc(nvs_required_size_mypass); nvs_get_str(my_handle, "MyPASS", nvs_ret_data_mypass, &nvs_required_size_mypass); nvs_close(my_handle); |
Передадим команду, размеры и строки в PC и очистим память, запрошенную под строки
1 2 3 4 5 6 |
nvs_close(my_handle); uart_write_bytes(UART_NUM_0, (void*)data, 11); uart_write_bytes(UART_NUM_0, (void*)nvs_ret_data_myssid, nvs_required_size_myssid); uart_write_bytes(UART_NUM_0, (void*)nvs_ret_data_mypass, nvs_required_size_mypass); free(nvs_ret_data_myssid); free(nvs_ret_data_mypass); |
Теперь работаем с приёмом данных строк из PC и записью их NVS.
Для этого в теле следующего условия объявим указатели на массивы для строк и узнаем их размер из пакета, который мы приняли из PC
1 2 3 4 5 6 |
if(data[2] == 0x32) //Write { char *nvs_data_myssid; char *nvs_data_mypass; uint32_t size_myssid = *(uint32_t*)(data+3); uint32_t size_mypass = *(uint32_t*)(data+7); |
Запросим память под наши массивы
1 2 3 |
uint32_t size_mypass = *(uint32_t*)(data+7); nvs_data_myssid = malloc(size_myssid); nvs_data_mypass = malloc(size_mypass); |
Заберём строки из пакета
1 2 3 |
nvs_data_mypass = malloc(size_mypass); strcpy(nvs_data_myssid,(char*)(data+11)); strcpy(nvs_data_mypass,(char*)(data+11 + size_myssid)); |
Запишем строки в NVS, освободим память, запрошенную под строки и переключим уровень ножки светодиода
1 2 3 4 5 6 7 8 9 10 11 12 13 |
strcpy(nvs_data_mypass,(char*)(data+11 + size_myssid)); nvs_open("storage", NVS_READWRITE, &my_handle); nvs_set_str(my_handle, "MySSID", nvs_data_myssid); nvs_set_str(my_handle, "MyPASS", nvs_data_mypass); nvs_commit(my_handle); nvs_close(my_handle); free(nvs_data_myssid); free(nvs_data_mypass); gpio_set_level(CONFIG_BLINK_GPIO, lvl); lvl ^= 1; |
Выйдем из всех наших условий (но не из цикла) и добавим задержку, иначе мы другим задачам с нашим огромным приоритетом вообще работать не дадим
1 2 3 4 5 |
lvl ^= 1; } } } vTaskDelay(1000 / portTICK_PERIOD_MS); |
Соберём код, прошьём контроллер, проверим в терминале наличие ключей, если у кого-то их нет, то прошейте проект прошлого урока
Запустим программ (ссылка внизу страницы) и настроим порт
Попробуем прочитать данные с помощью кнопки Read
Надеюсь, что многие поняли, почему не получилось. У меня был порт настроен заранее и данные настроек берутся из сохранённого конфига программы. А не получилось потому, что нужно остановить терминал, так как порт не может работать в несколько сессий. Если не остановить терминал, то порта даже не будет в выпадающем списке программы. После ошибки программа закроется. Остановим терминал, запустим программу и попробуем прочитать данные снова
Вот теперь всё получилось.
И теперь у нас кнопка чтения стала неактивной, а стала активной кнопка Write
Изменим данные строк и попробуем записать их в контроллер.
Нажмём кнопку Write и увидим запрос, так как вдруг мы нажали кнопку случайно
Увидим, что через некоторое время кнопка Write станет недоступной, а Read — доступной.
Можем сразу прочитать, но это неинтересно, так как ничего не изменится. Лучше закроем программу и прочитаем данные
Мы видим, что данные сохранились. Закроем программу, запустим терминал, контроллер перезагрузится и мы увидим наши данные также и в терминале.
Итак на данном уроке мы, используя шину UART, научились записывать и читать данные NVS, передавая их через UART на PC и обратно. Думаю, в дальнейшем нам это пригодится для изменения логина и пароля в целях подключения к точке доступа, а также других данных. Аналогичную программу вы можете и сами написать и не обязательно на чистом WinApi, как это делаю я.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP32 Следующий урок
Программа для чтения и записи данных NVS
Недорогие отладочные платы ESP32 можно купить здесь Недорогие отладочные платы ESP32
Недорогие отладочные платы ESP32/ESP32-C3/ESP32-S3 можно купить здесь Недорогие отладочные платы ESP32
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Добавить комментарий