26 KiB
Описание компонента ate0005
Общие сведения
ATE0005 — это прошивка ESP32 для ESP-IDF, реализующая универсальный оптический двухканальный угломер/тахометр.
Приложение измеряет угол поворота оси и/или скорость её вращения по двум независимым каналам A и B, подключённым к оптическим энкодерам, и выводит результат на LCD-экран 16x2. Управление приборами осуществляется поворотным энкодером MODE с кнопкой.
Основные возможности
- Два независимых канала — каналы A и B работают с собственными оптическими энкодерами.
- Четыре режима работы —
ANGLE/ANGLE,TACHO/TACHO,ANGLE/TACHO,REMOTE. - Измерение угла — текущий угол с разрешением 0.1°, сброс длительным нажатием кнопки.
- Измерение скорости — обороты в минуту (RPM) с обновлением каждые 100 мс.
- LCD 16x2 через PCF8574 — заставка, анимированный прогресс-бар загрузки, живой вывод значений.
- Меню на поворотном энкодере — выбор режима поворотом, вход/выход коротким нажатием.
- WiFi AP+STA — точка доступа для локального доступа и подключение к внешней сети.
- OTA-обновления — встроенный веб-сервер с самопроверкой образа и откатом (rollback).
- Удалённый syslog — периодическая отправка статистики задач и счётчиков ошибок драйверов.
- FreeRTOS-многозадачность — отдельные задачи на каждый канал, энкодер, расширитель и статистику.
Архитектура прошивки
Приложение состоит из единственного исходного файла main/ate0005.c и заголовка конфигурации main/ate0005.h. Вся прикладная логика построена вокруг компонентов проекта (zh_*), которые выполняют непосредственную работу с железом; ate0005.c отвечает за инициализацию, коммутацию режимов и вывод.
Точка входа app_main() выполняет последовательную инициализацию подсистем:
nvs_flash_init()с очисткой и повторной инициализацией при пустом разделе или несовпадении версии.esp_event_loop_create_default()— общая петля событий._zh_pcf8574_init()— шина I2C, расширитель PCF8574, драйвер LCD, мьютекс экрана._zh_system_load()— заставка и прогресс-бар загрузки._zh_encoder_init()— энкодер выбора режима с обработчиком событий._zh_timer_init()— программный таймер сброса угла._zh_a_inclinometer_init(),_zh_b_inclinometer_init()— угломеры каналов A и B._zh_wifi_init()— WiFi в режиме AP+STA._zh_webserver_init()— HTTP-сервер с OTA-страницей._zh_espnow_init()— заготовка под ESP-NOW (пока пуста).
После инициализации выполняется самопроверка OTA: если запущенный образ находится в состоянии ESP_OTA_IMG_PENDING_VERIFY, задача ждёт 60 секунд и помечает образ валидным через esp_ota_mark_app_valid_cancel_rollback().
Задачи FreeRTOS
| Задача | Приоритет | Стек, слов | Назначение |
|---|---|---|---|
_zh_a_inclinometer_processing_task |
tskIDLE_PRIORITY + 5 |
configMINIMAL_STACK_SIZE + 512 |
Вывод угла канала A |
_zh_b_inclinometer_processing_task |
tskIDLE_PRIORITY + 5 |
configMINIMAL_STACK_SIZE + 512 |
Вывод угла канала B |
_zh_a_tachometer_processing_task |
tskIDLE_PRIORITY + 5 |
configMINIMAL_STACK_SIZE + 512 |
Вывод RPM канала A |
_zh_b_tachometer_processing_task |
tskIDLE_PRIORITY + 5 |
configMINIMAL_STACK_SIZE + 512 |
Вывод RPM канала B |
_zh_encoder_event_handler |
обрабатывается в задаче zh_encoder |
— | События энкодера и кнопки |
_zh_pcf8574_* (внутри zh_pcf8574) |
tskIDLE_PRIORITY + 4 |
configMINIMAL_STACK_SIZE |
Обмен с расширителем LCD |
_zh_stats_processing_task |
tskIDLE_PRIORITY |
configMINIMAL_STACK_SIZE + 1280 |
Отправка статистики в syslog |
Все задачи создаются через xTaskCreatePinnedToCore() с tskNO_AFFINITY, то есть планировщик сам выбирает ядро.
Внутреннее устройство
Структура _task_stats_t
Хранит минимальный свободный объём стека (high water mark) каждой измерительной задачи. Статистическая задача читает эти значения для отправки в syslog.
| Поле | Тип | Описание |
|---|---|---|
a_inclinometer_task_min_stack_size |
uint32_t |
Минимальный свободный стек задачи угломера A |
b_inclinometer_task_min_stack_size |
uint32_t |
Минимальный свободный стек задачи угломера B |
a_tachometer_task_min_stack_size |
uint32_t |
Минимальный свободный стек задачи тахометра A |
b_tachometer_task_min_stack_size |
uint32_t |
Минимальный свободный стек задачи тахометра B |
Статический экземпляр _task_stats инициализируется полными размерами стеков, чтобы до первого замера статистика не давала ложных предупреждений.
Глобальные дескрипторы и флаги
| Переменная | Тип | Описание |
|---|---|---|
_http_server_handle |
httpd_handle_t |
Дескриптор HTTP-сервера |
_lcd_handle |
zh_pcf8574_handle_t * |
Расширитель PCF8574 |
_lcd_160x_handle |
zh_160x_i2c_handle_t * |
Драйвер LCD 16x2 |
_mode_encoder_handle |
zh_encoder_handle_t * |
Энкодер выбора режима |
_a_inclinometer_handle, _b_inclinometer_handle |
zh_inclinometer_handle_t * |
Угломеры каналов A/B |
_a_tachometer_handle, _b_tachometer_handle |
zh_tachometer_handle_t * |
Тахометры каналов A/B |
_inclinometer_reset_timer_handle |
esp_timer_handle_t |
Таймер сброса угла |
_lcd_mutex |
SemaphoreHandle_t |
Мьютекс доступа к LCD |
_is_menu_mode |
volatile bool |
Флаг нахождения в меню выбора режима |
_is_reset_mode |
volatile bool |
Флаг нажатия кнопки (идёт отсчёт долгого нажатия) |
_is_reset_occurred |
volatile bool |
Признак того, что сброс уже выполнен, чтобы отпустить кнопку «вхолостую» |
_current_menu_value |
volatile uint8_t |
Индекс выбранного режима |
_wifi_reconnect_retry_num |
volatile uint8_t |
Счётчик попыток переподключения STA |
_is_wifi_connected |
volatile bool |
Флаг подключения STA |
Все разделяемые между задачами и обработчиками прерываний флаги объявлены volatile, поскольку изменяются из разных контекстов без блокировок.
Константы конфигурации
Режимы работы заданы константами и подписями меню:
| Константа | Значение | Подпись меню |
|---|---|---|
MODE_ANGLE_ANGLE |
0 |
"ANGLE/ANGLE" |
MODE_TACHO_TACHO |
1 |
"TACHO/TACHO" |
MODE_ANGLE_TACHO |
2 |
"ANGLE/TACHO" |
MODE_REMOTE |
3 |
"REMOTE" |
Массив _main_menu[] хранит подписи в порядке их индексов; энкодер режима ограничен диапазоном 0 .. sizeof(_main_menu)/sizeof(_main_menu[0]) - 1.
Распиновка GPIO
| Периферия | Вывод A | Вывод B | Вывод S | Примечание |
|---|---|---|---|---|
| I2C-шина | SDA = GPIO_NUM_21 |
SCL = GPIO_NUM_22 |
— | Внутренние подтяжки включены |
| Расширитель LCD | адрес 0x20 |
— | — | PCF8574 (U2) |
| Энкодер MODE | GPIO_NUM_27 |
GPIO_NUM_26 |
GPIO_NUM_25 |
Разъём EN1, экземпляр 1 |
| Оптический энкодер A | GPIO_NUM_14 |
GPIO_NUM_13 |
— | Разъём EN2 |
| Оптический энкодер B | GPIO_NUM_18 |
GPIO_NUM_19 |
— | Разъём EN3 |
Оптическое устройство каждого канала общее для угломера и тахометра этого канала: они не могут работать одновременно на одном канале.
API (интерфейс)
Прикладной код ate0005.c не экспортирует публичных функций — это законченное приложение, а не библиотека. Все функции модуля объявлены static и вызываются только из app_main() и обработчиков событий. Ниже приведена их роль как внутренняя справка.
Инициализация подсистем
| Функция | Описание |
|---|---|
app_main() |
Точка входа: NVS, событийная петля, LCD, энкодер, таймер, угломеры, WiFi, веб-сервер, ESP-NOW, самопроверка OTA. |
_zh_pcf8574_init() |
Создаёт шину I2C, инициализирует PCF8574 и LCD 16x2, создаёт мьютекс экрана. |
_zh_encoder_init() |
Регистрирует обработчик энкодера, задаёт GPIO, диапазон и шаг, сбрасывает позицию в 0. |
_zh_timer_init() |
Создаёт одноразовый программный таймер сброса угла. |
_zh_a_inclinometer_init() |
Инициализирует угломер канала A и создаёт его задачу. |
_zh_b_inclinometer_init() |
Инициализирует угломер канала B и создаёт его задачу. |
_zh_a_tachometer_init() |
Инициализирует тахометр канала A и создаёт его задачу. |
_zh_b_tachometer_init() |
Инициализирует тахометр канала B и создаёт его задачу. |
_zh_wifi_init() |
Настраивает AP и STA, регистрирует обработчики событий WiFi/IP, запускает WiFi. |
_zh_webserver_init() |
Запускает HTTP-сервер и регистрирует OTA-страницу на "/". |
_zh_espnow_init() |
Заготовка под ESP-NOW, тело пусто. |
Обработчики событий
| Функция | Описание |
|---|---|
_zh_encoder_event_handler() |
Обрабатывает нажатие/отпускание кнопки и поворот энкодера: переключение меню, сброс угла. |
_zh_wifi_event_handler() |
Подключение STA, переподключение с ограничением попыток, инициализация syslog и создание задачи статистики при получении IP. |
_zh_espnow_event_handler() |
Заготовка под ESP-NOW, тело пусто. |
_zh_inclinometer_reset_timer_on_alarm_cb() |
Колбэк долгого нажатия: сбрасывает угол активных угломеров текущего режима. |
Задачи обработки и вывода
| Функция | Описание |
|---|---|
_zh_a_inclinometer_processing_task() |
Выводит угол канала A на 2-ю строку LCD с 1 знаком после запятой. |
_zh_b_inclinometer_processing_task() |
Выводит угол канала B на 2-ю строку LCD с 1 знаком после запятой. |
_zh_a_tachometer_processing_task() |
Выводит RPM канала A на 2-ю строку LCD. |
_zh_b_tachometer_processing_task() |
Выводит RPM канала B на 2-ю строку LCD. |
_zh_stats_processing_task() |
Периодически отправляет свободный стек задач и счётчики ошибок в syslog. |
Служебные функции вывода
| Функция | Описание |
|---|---|
_zh_system_load() |
Показывает заставку с названием и версией, затем прогресс-бар загрузки. |
Потокобезопасность
- Мьютекс
_lcd_mutex— защищает последовательности обращений к LCD (set_cursor→print_*) от одновременного доступа нескольких задач. Захватывается на время вывода значения и освобождается сразу после. volatileфлаги состояния —_is_menu_mode,_is_reset_mode,_is_reset_occurred,_current_menu_valueизменяются из обработчика событий энкодера и читаются из задач и колбэка таймера; прямой доступ без блокировки допустим для однобайтовых флагов._task_stats— каждая измерительная задача пишет только своё поле, читатель (статистическая задача) только читает; гонка исключена за счёт разделения полей.- Событийная модель
zh_encoder— прерывания энкодера постят события в очередь, обработка идёт в задаче компонента, а не в ISR.
Алгоритмы
Переключение режима работы
- Короткое нажатие кнопки энкодера инвертирует
_is_menu_mode. - При входе в меню текущие активные устройства режима останавливаются: удаляются соответствующие задачи через
vTaskDelete(), затем вызываютсяzh_inclinometer_deinit()/zh_tachometer_deinit(). - Включается курсор LCD, очищается 2-я строка, на 1-й отображается текущая позиция меню.
- При выходе из меню курсор выключается, инициализируются устройства выбранного режима и запускаются их задачи.
- Поворот энкодера в меню обновляет
_current_menu_valueи подпись режима; вне меню позиция энкодера принудительно возвращается к_current_menu_valueчерезzh_encoder_set().
Сброс угла долгим нажатием
- При нажатии кнопки запускается одноразовый таймер на 2 секунды и выставляется
_is_reset_mode. - Если кнопка отпущена раньше 2 секунд, таймер останавливается, событие считается коротким нажатием (переключение меню).
- Если таймер сработал, колбэк выставляет
_is_reset_occurredи вызываетzh_inclinometer_reset()для активных угломеров режима (вANGLE/ANGLE— оба, вANGLE/TACHO— только A). - Последующее отпускание кнопки игнорируется, если сброс уже произошёл (
_is_reset_occurred == true).
Обновление значения на LCD
- Задача кэширует предыдущее показание (
prev_angle/prev_rpm). - Каждые 100 мс читается новое значение через
zh_inclinometer_get()/zh_tachometer_get(). - Значение перерисовывается только при изменении: сначала область значения затишается пробелами, затем печатается новое число. Это снижает нагрузку на I2C и мигание экрана.
Статистика и syslog
- Задача статистики создаётся только после получения STA-адреса (
IP_EVENT_STA_GOT_IP). - Каждые ~60 секунд (с промежуточными задержками по 500 мс) отправляются сообщения о свободном стеке каждой задачи и о счётчиках ошибок
zh_encoderиzh_pcf8574. - Уровень
ZH_WARNINGставится, если свободный стек задачи меньше 100 слов или счётчик ошибок ненулевой; иначеZH_INFO. - При разрыве WiFi задача статистики удаляется, syslog деинициализируется.
Примеры использования
Инициализация угломера канала
#include "zh_inclinometer.h"
static zh_inclinometer_handle_t *_a_inclinometer_handle = NULL;
void app_main(void)
{
zh_inclinometer_init_config_t config = ZH_INCLINOMETER_INIT_CONFIG_DEFAULT();
config.a_gpio_number = A_OPTICAL_DEVICE_A_GPIO; // GPIO_NUM_14
config.b_gpio_number = A_OPTICAL_DEVICE_B_GPIO; // GPIO_NUM_13
config.encoder_pulses = 3600; // импульсов на оборот
zh_inclinometer_init(&config, &_a_inclinometer_handle);
// ... использование ...
zh_inclinometer_deinit(&_a_inclinometer_handle); // освобождение ресурсов
}
Вывод угла на LCD под мьютексом
#include "zh_160x_i2c.h"
static zh_160x_i2c_handle_t *_lcd_160x_handle = NULL;
static SemaphoreHandle_t _lcd_mutex = NULL;
void app_main(void)
{
_lcd_mutex = xSemaphoreCreateMutex();
float current_angle = 0.0;
for (;;)
{
// читаем угол и выводим только при изменении
zh_inclinometer_get(&_a_inclinometer_handle, ¤t_angle);
xSemaphoreTake(_lcd_mutex, portMAX_DELAY);
zh_160x_set_cursor(&_lcd_160x_handle, 1, 2);
zh_160x_print_float(&_lcd_160x_handle, current_angle, 1);
xSemaphoreGive(_lcd_mutex);
vTaskDelay(100 / portTICK_PERIOD_MS);
}
vSemaphoreDelete(_lcd_mutex);
}
Инициализация удалённого syslog
#include "zh_syslog.h"
void app_main(void)
{
zh_syslog_init_config_t config = ZH_SYSLOG_INIT_CONFIG_DEFAULT();
strcpy(config.syslog_ip, SYSLOG_IP); // адрес сервера из ate0005.h
esp_err_t err = zh_syslog_init(&config);
if (err != ESP_OK)
{
printf("Syslog initialization error %s\n", esp_err_to_name(err));
return;
}
zh_syslog_send(ZH_KERN, ZH_INFO, "ATE0005", "zh_syslog", "Stats task started.\n");
zh_syslog_deinit(); // освобождение ресурсов
}
Конфигурация сборки
Прошивка опирается на следующие параметры sdkconfig.defaults:
| Параметр | Значение | Назначение |
|---|---|---|
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE |
y |
Разрешает OTA-откат при непройденной самопроверке |
CONFIG_BOOTLOADER_LOG_LEVEL_NONE |
y |
Отключает лог загрузчика |
CONFIG_ESPTOOLPY_FLASHSIZE_8MB |
8MB |
Размер флеш под две OTA-партиции |
CONFIG_PARTITION_TABLE_CUSTOM |
y |
Используется partitions.csv |
CONFIG_COMPILER_OPTIMIZATION_SIZE |
y |
Оптимизация по размеру |
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240 |
240 |
Максимальная частота CPU |
CONFIG_FREERTOS_HZ |
1000 |
Тик 1 мс для точных задержек |
CONFIG_LOG_DEFAULT_LEVEL_NONE |
y |
Отключает ESP-IDF лог по умолчанию |
Партиции (partitions.csv)
| Имя | Тип | Подтип | Размер | Назначение |
|---|---|---|---|---|
nvs |
data | nvs | 0x6000 |
Хранилище настроек |
otadata |
data | ota | 0x2000 |
Состояние OTA |
phy_init |
data | phy | 0x1000 |
Калибровка PHY |
ota_0 |
app | ota_0 | 3500K | Образ прошивки A |
ota_1 |
app | ota_1 | 3500K | Образ прошивки B |
storage |
data | fat | 1M | Файловое хранилище |
Ошибки
Прикладной код не возвращает пользовательских кодов ошибок и полагается на коды ESP-IDF, транслируемые компонентами zh_*. Ниже типовые коды, встречающиеся в путях инициализации.
| Код | Описание |
|---|---|
ESP_OK |
Операция выполнена успешно |
ESP_ERR_INVALID_ARG |
Неверный аргумент функции (например, нулевой дескриптор) |
ESP_ERR_NO_MEM |
Недостаточно памяти |
ESP_ERR_NOT_FOUND |
Устройство или партиция не найдены |
ESP_ERR_TIMEOUT |
Таймаут операции (I2C, WiFi) |
ESP_ERR_NVS_NO_FREE_PAGES |
Раздел NVS переполнен (требует очистки) |
ESP_ERR_NVS_NEW_VERSION_FOUND |
Версия формата NVS не совпадает (требует очистки) |
Ограничения
- Режим
REMOTEне реализован — функции_zh_espnow_init()и_zh_espnow_event_handler()имеют пустое тело, поэтому в этом режиме на экране выводитсяNOT CONNECTED. - Общее оптическое устройство на канал — угломер и тахометр одного канала используют одни GPIO, поэтому не могут работать одновременно; при переключении режима предыдущие устройства деинициализируются.
- WiFi-кредиты зашиты в прошивку —
WIFI_STA_SSIDиWIFI_STA_PASSзаданы константами вate0005.hи требуют пересборки для смены сети. - Жёсткая распиновка GPIO — номера выводов соответствуют разъёмам платы ATE0005 и не должны меняться без аппаратной ревизии.
- Самопроверка OTA блокирует на 60 секунд — при первом запуске после OTA-обновления
app_main()выдерживает задержку перед подтверждением образа. - Порог предупреждения стека — 100 слов — статистика выдаёт
ZH_WARNINGпри свободном стеке ниже 100 слов; значение подобрано под конкретные размеры стеков задач.
Требования для сборки
- ESP-IDF версии 5.0 или выше.
- FreeRTOS (входит в ESP-IDF).
- Компоненты проекта:
zh_160x_i2c,zh_encoder,zh_inclinometer,zh_tachometer,zh_espnow,zh_ota_server,zh_syslog. - Встроенные компоненты ESP-IDF:
esp_wifi,nvs_flash,esp_timer,esp_app_format. - Флеш-память 8 МБ под две OTA-партиции по 3500K.
Установка
cd your_projects_folder
git clone https://github.com/aZholtikov/ate0005
cd ate0005
idf.py set-target esp32
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor
Зависимости-компоненты подтягиваются автоматически из main/idf_component.yml при первой сборке. Для подключения в собственном коде используется единый заголовок конфигурации:
#include "ate0005.h"