Compare commits

..
1 Commits
Author SHA1 Message Date
aZholtikov b9fc3a6f41 style: added docs 2026-11-04 09:12:31 +03:00
7 changed files with 843 additions and 462 deletions

No files matched your search

+3 -2
View File
@@ -3,8 +3,9 @@ build
.DS_Store
sdkconfig.old
desktop.ini
.PVS-Studio
sdkconfig
dependencies.lock
managed_components
rules.md
commit-rules.md
readme-rules.md
wiki-rules.md
+21 -14
View File
@@ -2,23 +2,30 @@
## Features
1. Two independent channels supporting optical encoders with 3600 pulses per revolution.
2. Four operating modes: both inclinometers, both tachometers, mixed angle/tachometer, and remote via ESP-NOW.
3. 16x2 LCD display (PCF8574 I2C expander) with boot animation, progress bar, and real-time value updates.
4. Rotary encoder menu with push-button for mode switching and angle reset (long press, 2 seconds).
5. WiFi AP+STA mode with local access and STA connection for remote logging.
6. Over-the-Air (OTA) firmware updates via built-in HTTP server.
7. Remote syslog with periodic logging of task stack usage and driver error counters.
8. FreeRTOS-based multitasking with separate tasks for each channel, encoder, LCD expander, and statistics monitoring.
1. Two independent measuring channels for optical encoders with 3600 pulses per revolution.
2. Four operating modes selected through the on-device menu: angle/angle, tacho/tacho, angle/tacho and remote.
3. Angle measurement with 0.1 degree resolution and resettable current angle value.
4. Rotational speed measurement in RPM with 100 ms refresh period.
5. 16x2 LCD display on PCF8574 I2C expander with boot animation, progress bar and live value updates.
6. Rotary encoder menu for mode switching and push-button long press for angle reset.
7. WiFi access point and station modes running simultaneously.
8. Over-the-Air firmware updates through the built-in web server with rollback self-check.
9. Remote syslog logging of task stack usage and driver error counters after station connection.
10. FreeRTOS multitasking with separate tasks for each channel, encoder, LCD expander and statistics.
## Build and flash
## Attention
Run the following command to firmware build and flash module:
1. Remote mode via ESP-NOW is not implemented yet.
2. WiFi station credentials are hardcoded in the firmware configuration.
3. Long press of the encoder button resets the angle value, short press switches the menu mode.
4. The GPIO pin mapping matches the ATE0005 board connectors and must not be changed without a hardware revision.
## Using
Clone and build the project:
```bash
cd your_projects_folder
git clone --recurse-submodules https://github.com/aZholtikov/ate0005
git clone https://github.com/aZholtikov/ate0005
cd ate0005
idf.py build
idf.py flash
build
```
+418 -352
View File
File diff suppressed because it is too large. Load diff
+59 -76
View File
@@ -1,18 +1,27 @@
/**
* @file ate0005.h
*
* @brief Header file for ATE0005 — Universal Optical 2-Channel Angle Gauge / Tachometer
* @brief Configuration and interface definitions of the ATE0005 universal
* optical 2-channel angle gauge and tachometer.
*
* This header defines task priorities, stack sizes, mode constants,
* WiFi configuration, GPIO pin mappings, and data structures for the
* ATE0005 device running on ESP32 platform with FreeRTOS.
* This header aggregates the includes required by the application and defines
* the FreeRTOS task priorities and stack sizes, the operating mode constants
* with the main menu labels, the WiFi access point and station parameters and
* the GPIO pin mapping of the peripherals of the ATE0005 device running on the
* ESP32 platform.
*
* Key features:
* - Task configuration for inclinometer, tachometer, encoder, and stats
* - WiFi AP/STA mode configuration
* - I2C and GPIO pin definitions for peripherals
* - Mode selection constants for different operating modes
* - LCD display and PCF8574 expander macros
* - Task priorities and stack sizes for the inclinometer, tachometer, encoder,
* expander and statistics tasks
* - Operating mode constants and the matching main menu labels
* - WiFi access point and station configuration with the remote syslog server
* address
* - I2C bus, LCD expander and optical device GPIO pin definitions
*
* @note The GPIO definitions match the connectors of the ATE0005 board and
* must not be changed without a hardware revision.
* @note The optical device of each channel is shared by the inclinometer and
* the tachometer of the same channel.
*/
#pragma once
@@ -28,88 +37,62 @@
#include "zh_espnow.h"
#include "zh_syslog.h"
#define STATS_TASK_PRIORITY tskIDLE_PRIORITY /*!< Priority of the statistics logging task. */
#define STATS_TASK_STACK_SIZE configMINIMAL_STACK_SIZE + 1280 /*!< Stack size for the statistics logging task. */
#define A_INCLINOMETER_TASK_PRIORITY tskIDLE_PRIORITY + 5 /*!< Priority of the A inclinometer processing task. */
#define A_INCLINOMETER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE + 512 /*!< Stack size for the A inclinometer processing task. */
#define B_INCLINOMETER_TASK_PRIORITY tskIDLE_PRIORITY + 5 /*!< Priority of the B inclinometer processing task. */
#define B_INCLINOMETER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE + 512 /*!< Stack size for the B inclinometer processing task. */
#define A_TACHOMETER_TASK_PRIORITY tskIDLE_PRIORITY + 5 /*!< Priority of the A tachometer processing task. */
#define A_TACHOMETER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE + 512 /*!< Stack size for the A tachometer processing task. */
#define B_TACHOMETER_TASK_PRIORITY tskIDLE_PRIORITY + 5 /*!< Priority of the B tachometer processing task. */
#define B_TACHOMETER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE + 512 /*!< Stack size for the B tachometer processing task. */
#define ENCODER_TASK_PRIORITY tskIDLE_PRIORITY + 4 /*!< Priority of the mode encoder task. */
#define ENCODER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE /*!< Stack size for the mode encoder task. */
#define EXPANDER_TASK_PRIORITY tskIDLE_PRIORITY + 4 /*!< Priority of the PCF8574 expander task. */
#define EXPANDER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE /*!< Stack size for the PCF8574 expander task. */
#define STATS_TASK_PRIORITY tskIDLE_PRIORITY /*!< Priority of the statistics logging task */
#define STATS_TASK_STACK_SIZE (configMINIMAL_STACK_SIZE + 1280) /*!< Stack size for the statistics logging task */
#define A_INCLINOMETER_TASK_PRIORITY tskIDLE_PRIORITY + 5 /*!< Priority of the A inclinometer processing task */
#define A_INCLINOMETER_TASK_STACK_SIZE (configMINIMAL_STACK_SIZE + 512) /*!< Stack size for the A inclinometer processing task */
#define B_INCLINOMETER_TASK_PRIORITY tskIDLE_PRIORITY + 5 /*!< Priority of the B inclinometer processing task */
#define B_INCLINOMETER_TASK_STACK_SIZE (configMINIMAL_STACK_SIZE + 512) /*!< Stack size for the B inclinometer processing task */
#define A_TACHOMETER_TASK_PRIORITY tskIDLE_PRIORITY + 5 /*!< Priority of the A tachometer processing task */
#define A_TACHOMETER_TASK_STACK_SIZE (configMINIMAL_STACK_SIZE + 512) /*!< Stack size for the A tachometer processing task */
#define B_TACHOMETER_TASK_PRIORITY tskIDLE_PRIORITY + 5 /*!< Priority of the B tachometer processing task */
#define B_TACHOMETER_TASK_STACK_SIZE (configMINIMAL_STACK_SIZE + 512) /*!< Stack size for the B tachometer processing task */
#define ENCODER_TASK_PRIORITY tskIDLE_PRIORITY + 4 /*!< Priority of the mode encoder task */
#define ENCODER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE /*!< Stack size for the mode encoder task */
#define EXPANDER_TASK_PRIORITY tskIDLE_PRIORITY + 4 /*!< Priority of the PCF8574 expander task */
#define EXPANDER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE /*!< Stack size for the PCF8574 expander task */
#define TRIAC_ON true /*!< Triac enabled state. */
#define TRIAC_OFF false /*!< Triac disabled state. */
#define MODE_ANGLE_ANGLE 0 /*!< Operating mode: both inclinometers active */
#define MAIN_MENU_ANGLE_ANGLE_NAME "ANGLE/ANGLE" /*!< Menu label for angle/angle mode */
#define MODE_TACHO_TACHO 1 /*!< Operating mode: both tachometers active */
#define MAIN_MENU_TACHO_TACHO_NAME "TACHO/TACHO" /*!< Menu label for tachometer/tachometer mode */
#define MODE_ANGLE_TACHO 2 /*!< Operating mode: A inclinometer and B tachometer active */
#define MAIN_MENU_ANGLE_TACHO_NAME "ANGLE/TACHO" /*!< Menu label for angle/tachometer mode */
#define MODE_REMOTE 3 /*!< Operating mode: remote control via ESP-NOW */
#define MAIN_MENU_REMOTE_NAME "REMOTE" /*!< Menu label for remote mode */
#define ENCODER_BUTTON_RELEASED true /*!< Encoder button released state. */
#define ENCODER_BUTTON_PRESSED false /*!< Encoder button pressed state. */
#define WIFI_AP_SSID "ATE0005" /*!< WiFi AP SSID */
#define WIFI_AP_PASS "repairlab" /*!< WiFi AP password */
#define WIFI_AP_CHANNEL 1 /*!< WiFi AP channel */
#define MAX_AP_CONNECTION 4 /*!< Maximum number of stations connected to AP */
#define LED_OFF ZH_PCF8574_GPIO_LOW /*!< LED turned off (low level). */
#define LED_ON ZH_PCF8574_GPIO_HIGH /*!< LED turned on (high level). */
#define WIFI_STA_SSID "rick_professor" /*!< WiFi STA SSID */
#define WIFI_STA_PASS "repairlab" /*!< WiFi STA password */
#define WIFI_STA_MAXIMUM_RETRY 10 /*!< Maximum number of reconnection attempts for STA mode */
#define RELAY_OFF ZH_PCF8574_GPIO_LOW /*!< Relay disconnected (low level). */
#define RELAY_ON ZH_PCF8574_GPIO_HIGH /*!< Relay connected (high level). */
#define SYSLOG_IP "77.51.201.8" /*!< IP address of the remote syslog server */
#define MODE_ANGLE_ANGLE 0 /*!< Operating mode: both inclinometers active. */
#define MAIN_MENU_ANGLE_ANGLE_NAME "ANGLE/ANGLE" /*!< Menu label for angle/angle mode. */
#define MODE_TACHO_TACHO 1 /*!< Operating mode: both tachometers active. */
#define MAIN_MENU_TACHO_TACHO_NAME "TACHO/TACHO" /*!< Menu label for tachometer/tachometer mode. */
#define MODE_ANGLE_TACHO 2 /*!< Operating mode: A inclinometer and B tachometer active. */
#define MAIN_MENU_ANGLE_TACHO_NAME "ANGLE/TACHO" /*!< Menu label for angle/tachometer mode. */
#define MODE_REMOTE 3 /*!< Operating mode: remote control via ESP-NOW. */
#define MAIN_MENU_REMOTE_NAME "REMOTE" /*!< Menu label for remote mode. */
#define I2C_SCL_GPIO GPIO_NUM_22 /*!< I2C clock line GPIO */
#define I2C_SDA_GPIO GPIO_NUM_21 /*!< I2C data line GPIO */
#define WIFI_AP_SSID "ATE0005" /*!< WiFi AP SSID. */
#define WIFI_AP_PASS "repairlab" /*!< WiFi AP password. */
#define WIFI_AP_CHANNEL 1 /*!< WiFi AP channel. */
#define MAX_AP_CONNECTION 4 /*!< Maximum number of stations connected to AP. */
#define LCD_I2C_ADDRESS 0x20 /*!< I2C address of the LCD PCF8574 expander (U2) */
#define WIFI_STA_SSID "rick_professor" /*!< WiFi STA SSID. */
#define WIFI_STA_PASS "repairlab" /*!< WiFi STA password. */
#define WIFI_STA_MAXIMUM_RETRY 10 /*!< Maximum number of reconnection attempts for STA mode. */
#define MODE_ENCODER_NUMBER 1 /*!< Encoder instance number (Connector EN1) */
#define MODE_ENCODER_A_GPIO GPIO_NUM_27 /*!< Mode encoder A-phase GPIO */
#define MODE_ENCODER_B_GPIO GPIO_NUM_26 /*!< Mode encoder B-phase GPIO */
#define MODE_ENCODER_S_GPIO GPIO_NUM_25 /*!< Mode encoder switch GPIO */
#define SYSLOG_IP "77.51.201.8" /*!< IP address of the remote syslog server. */
#define A_OPTICAL_DEVICE_A_GPIO GPIO_NUM_14 /*!< Optical device A-phase GPIO for channel A (Connector EN2) */
#define A_OPTICAL_DEVICE_B_GPIO GPIO_NUM_13 /*!< Optical device B-phase GPIO for channel A */
#define I2C_SCL_GPIO GPIO_NUM_22 /*!< I2C clock line GPIO. */
#define I2C_SDA_GPIO GPIO_NUM_21 /*!< I2C data line GPIO. */
#define LCD_I2C_ADDRESS 0x20 /*!< I2C address of the LCD PCF8574 expander (U2). */
#define MODE_ENCODER_NUMBER 0x01 /*!< Encoder instance number (Connector EN1). */
#define MODE_ENCODER_A_GPIO GPIO_NUM_27 /*!< Mode encoder A-phase GPIO. */
#define MODE_ENCODER_B_GPIO GPIO_NUM_26 /*!< Mode encoder B-phase GPIO. */
#define MODE_ENCODER_S_GPIO GPIO_NUM_25 /*!< Mode encoder switch GPIO. */
#define A_OPTICAL_DEVICE_A_GPIO GPIO_NUM_14 /*!< Optical device A-phase GPIO for channel A (Connector EN2). */
#define A_OPTICAL_DEVICE_B_GPIO GPIO_NUM_13 /*!< Optical device B-phase GPIO for channel A. */
#define B_OPTICAL_DEVICE_A_GPIO GPIO_NUM_18 /*!< Optical device A-phase GPIO for channel B (Connector EN3). */
#define B_OPTICAL_DEVICE_B_GPIO GPIO_NUM_19 /*!< Optical device B-phase GPIO for channel B. */
#define B_OPTICAL_DEVICE_A_GPIO GPIO_NUM_18 /*!< Optical device A-phase GPIO for channel B (Connector EN3) */
#define B_OPTICAL_DEVICE_B_GPIO GPIO_NUM_19 /*!< Optical device B-phase GPIO for channel B */
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief Structure holding minimum free stack sizes for processing tasks.
*
* Tracks the minimum free stack size observed at runtime for each
* processing task, used for syslog monitoring and stack overflow detection.
*/
typedef struct
{
uint32_t a_inclinometer_task_min_stack_size; /*!< Minimum free stack size of the A inclinometer task. */
uint32_t b_inclinometer_task_min_stack_size; /*!< Minimum free stack size of the B inclinometer task. */
uint32_t a_tachometer_task_min_stack_size; /*!< Minimum free stack size of the A tachometer task. */
uint32_t b_tachometer_task_min_stack_size; /*!< Minimum free stack size of the B tachometer task. */
} task_stats_t;
#ifdef __cplusplus
}
#endif
+3 -3
View File
@@ -1,6 +1,6 @@
description: Honeywell Ram Air Modulation Linear Electromechanical Actuator CMM 21-20-36 P/N 541674-X Linear Actuator Interface Test Box
url: https://github.com/aZholtikov/ate0002
version: "1.2.0"
description: Universal Optical 2-Channel Angle Gauge / Tachometer
url: https://github.com/aZholtikov/ate0005
version: "1.1.2"
license: "Apache-2.0"
maintainers:
- "Alexey Zholtikov <alexey.zholtikov@yandex.ru>"
+2 -15
View File
@@ -1,21 +1,8 @@
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
CONFIG_BOOTLOADER_LOG_LEVEL_ERROR=y
CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_GPIO_CTRL_FUNC_IN_IRAM=y
CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM=y
CONFIG_GPTIMER_ISR_CACHE_SAFE=y
CONFIG_I2C_ISR_IRAM_SAFE=y
CONFIG_LEDC_CTRL_FUNC_IN_IRAM=y
CONFIG_PCNT_CTRL_FUNC_IN_IRAM=y
CONFIG_PCNT_ISR_IRAM_SAFE=y
CONFIG_RMT_RECV_FUNC_IN_IRAM=y
CONFIG_RMT_TX_ISR_CACHE_SAFE=y
CONFIG_RMT_RX_ISR_CACHE_SAFE=y
CONFIG_SPI_MASTER_ISR_IN_IRAM=y
CONFIG_UART_ISR_IN_IRAM=y
CONFIG_HTTPD_WS_SUPPORT=y
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_FREERTOS_HZ=1000
CONFIG_LOG_DEFAULT_LEVEL_ERROR=y
CONFIG_LOG_DEFAULT_LEVEL_NONE=y
+337
View File
@@ -0,0 +1,337 @@
# Описание компонента 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"
```