Files
ate0005/wiki_ru.md
T
2026-11-04 09:12:31 +03:00

338 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Описание компонента ate0005
## Общие сведения
**ATE0005** — это прошивка ESP32 для ESP-IDF, реализующая универсальный оптический двухканальный угломер/тахометр.
Приложение измеряет угол поворота оси и/или скорость её вращения по двум независимым каналам A и B, подключённым к оптическим энкодерам, и выводит результат на LCD-экран 16x2. Управление приборами осуществляется поворотным энкодером MODE с кнопкой.
### Основные возможности
1. **Два независимых канала** — каналы A и B работают с собственными оптическими энкодерами.
2. **Четыре режима работы** — `ANGLE/ANGLE`, `TACHO/TACHO`, `ANGLE/TACHO`, `REMOTE`.
3. **Измерение угла** — текущий угол с разрешением 0.1°, сброс длительным нажатием кнопки.
4. **Измерение скорости** — обороты в минуту (RPM) с обновлением каждые 100 мс.
5. **LCD 16x2 через PCF8574** — заставка, анимированный прогресс-бар загрузки, живой вывод значений.
6. **Меню на поворотном энкодере** — выбор режима поворотом, вход/выход коротким нажатием.
7. **WiFi AP+STA** — точка доступа для локального доступа и подключение к внешней сети.
8. **OTA-обновления** — встроенный веб-сервер с самопроверкой образа и откатом (rollback).
9. **Удалённый syslog** — периодическая отправка статистики задач и счётчиков ошибок драйверов.
10. **FreeRTOS-многозадачность** — отдельные задачи на каждый канал, энкодер, расширитель и статистику.
## Архитектура прошивки
Приложение состоит из единственного исходного файла `main/ate0005.c` и заголовка конфигурации `main/ate0005.h`. Вся прикладная логика построена вокруг компонентов проекта (`zh_*`), которые выполняют непосредственную работу с железом; `ate0005.c` отвечает за инициализацию, коммутацию режимов и вывод.
Точка входа `app_main()` выполняет последовательную инициализацию подсистем:
1. `nvs_flash_init()` с очисткой и повторной инициализацией при пустом разделе или несовпадении версии.
2. `esp_event_loop_create_default()` — общая петля событий.
3. `_zh_pcf8574_init()` — шина I2C, расширитель PCF8574, драйвер LCD, мьютекс экрана.
4. `_zh_system_load()` — заставка и прогресс-бар загрузки.
5. `_zh_encoder_init()` — энкодер выбора режима с обработчиком событий.
6. `_zh_timer_init()` — программный таймер сброса угла.
7. `_zh_a_inclinometer_init()`, `_zh_b_inclinometer_init()` — угломеры каналов A и B.
8. `_zh_wifi_init()` — WiFi в режиме AP+STA.
9. `_zh_webserver_init()` — HTTP-сервер с OTA-страницей.
10. `_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()` | Показывает заставку с названием и версией, затем прогресс-бар загрузки. |
## Потокобезопасность
1. **Мьютекс `_lcd_mutex`** — защищает последовательности обращений к LCD (`set_cursor` → `print_*`) от одновременного доступа нескольких задач. Захватывается на время вывода значения и освобождается сразу после.
2. **`volatile` флаги состояния** — `_is_menu_mode`, `_is_reset_mode`, `_is_reset_occurred`, `_current_menu_value` изменяются из обработчика событий энкодера и читаются из задач и колбэка таймера; прямой доступ без блокировки допустим для однобайтовых флагов.
3. **`_task_stats`** — каждая измерительная задача пишет только своё поле, читатель (статистическая задача) только читает; гонка исключена за счёт разделения полей.
4. **Событийная модель `zh_encoder`** — прерывания энкодера постят события в очередь, обработка идёт в задаче компонента, а не в ISR.
## Алгоритмы
### Переключение режима работы
1. Короткое нажатие кнопки энкодера инвертирует `_is_menu_mode`.
2. При входе в меню текущие активные устройства режима останавливаются: удаляются соответствующие задачи через `vTaskDelete()`, затем вызываются `zh_inclinometer_deinit()` / `zh_tachometer_deinit()`.
3. Включается курсор LCD, очищается 2-я строка, на 1-й отображается текущая позиция меню.
4. При выходе из меню курсор выключается, инициализируются устройства выбранного режима и запускаются их задачи.
5. Поворот энкодера в меню обновляет `_current_menu_value` и подпись режима; вне меню позиция энкодера принудительно возвращается к `_current_menu_value` через `zh_encoder_set()`.
### Сброс угла долгим нажатием
1. При нажатии кнопки запускается одноразовый таймер на 2 секунды и выставляется `_is_reset_mode`.
2. Если кнопка отпущена раньше 2 секунд, таймер останавливается, событие считается коротким нажатием (переключение меню).
3. Если таймер сработал, колбэк выставляет `_is_reset_occurred` и вызывает `zh_inclinometer_reset()` для активных угломеров режима (в `ANGLE/ANGLE` — оба, в `ANGLE/TACHO` — только A).
4. Последующее отпускание кнопки игнорируется, если сброс уже произошёл (`_is_reset_occurred == true`).
### Обновление значения на LCD
1. Задача кэширует предыдущее показание (`prev_angle` / `prev_rpm`).
2. Каждые 100 мс читается новое значение через `zh_inclinometer_get()` / `zh_tachometer_get()`.
3. Значение перерисовывается только при изменении: сначала область значения затишается пробелами, затем печатается новое число. Это снижает нагрузку на I2C и мигание экрана.
### Статистика и syslog
1. Задача статистики создаётся только после получения STA-адреса (`IP_EVENT_STA_GOT_IP`).
2. Каждые ~60 секунд (с промежуточными задержками по 500 мс) отправляются сообщения о свободном стеке каждой задачи и о счётчиках ошибок `zh_encoder` и `zh_pcf8574`.
3. Уровень `ZH_WARNING` ставится, если свободный стек задачи меньше 100 слов или счётчик ошибок ненулевой; иначе `ZH_INFO`.
4. При разрыве WiFi задача статистики удаляется, syslog деинициализируется.
## Примеры использования
### Инициализация угломера канала
```c
#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 под мьютексом
```c
#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, &current_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
```c
#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 не совпадает (требует очистки) |
## Ограничения
1. **Режим `REMOTE` не реализован** — функции `_zh_espnow_init()` и `_zh_espnow_event_handler()` имеют пустое тело, поэтому в этом режиме на экране выводится `NOT CONNECTED`.
2. **Общее оптическое устройство на канал** — угломер и тахометр одного канала используют одни GPIO, поэтому не могут работать одновременно; при переключении режима предыдущие устройства деинициализируются.
3. **WiFi-кредиты зашиты в прошивку** — `WIFI_STA_SSID` и `WIFI_STA_PASS` заданы константами в `ate0005.h` и требуют пересборки для смены сети.
4. **Жёсткая распиновка GPIO** — номера выводов соответствуют разъёмам платы ATE0005 и не должны меняться без аппаратной ревизии.
5. **Самопроверка OTA блокирует на 60 секунд** — при первом запуске после OTA-обновления `app_main()` выдерживает задержку перед подтверждением образа.
6. **Порог предупреждения стека — 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.
## Установка
```bash
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` при первой сборке. Для подключения в собственном коде используется единый заголовок конфигурации:
```c
#include "ate0005.h"
```