Compare commits

..
2 Commits
Author SHA1 Message Date
aZholtikov 1ad6a80fce wip: 2026-09-10 19:01:04 +03:00
aZholtikov e987d4cafb wip: 2026-09-10 18:40:56 +03:00
7 changed files with 431 additions and 812 deletions

No files matched your search

+2 -3
View File
@@ -3,9 +3,8 @@ build
.DS_Store .DS_Store
sdkconfig.old sdkconfig.old
desktop.ini desktop.ini
.PVS-Studio
sdkconfig sdkconfig
dependencies.lock dependencies.lock
managed_components managed_components
commit-rules.md rules.md
readme-rules.md
wiki-rules.md
+14 -21
View File
@@ -2,30 +2,23 @@
## Features ## Features
1. Two independent measuring channels for optical encoders with 3600 pulses per revolution. 1. Two independent channels supporting 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. 2. Four operating modes: both inclinometers, both tachometers, mixed angle/tachometer, and remote via ESP-NOW.
3. Angle measurement with 0.1 degree resolution and resettable current angle value. 3. 16x2 LCD display (PCF8574 I2C expander) with boot animation, progress bar, and real-time value updates.
4. Rotational speed measurement in RPM with 100 ms refresh period. 4. Rotary encoder menu with push-button for mode switching and angle reset (long press, 2 seconds).
5. 16x2 LCD display on PCF8574 I2C expander with boot animation, progress bar and live value updates. 5. WiFi AP+STA mode with local access and STA connection for remote logging.
6. Rotary encoder menu for mode switching and push-button long press for angle reset. 6. Over-the-Air (OTA) firmware updates via built-in HTTP server.
7. WiFi access point and station modes running simultaneously. 7. Remote syslog with periodic logging of task stack usage and driver error counters.
8. Over-the-Air firmware updates through the built-in web server with rollback self-check. 8. FreeRTOS-based multitasking with separate tasks for each channel, encoder, LCD expander, and statistics monitoring.
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.
## Attention ## Build and flash
1. Remote mode via ESP-NOW is not implemented yet. Run the following command to firmware build and flash module:
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 ```bash
git clone https://github.com/aZholtikov/ate0005 cd your_projects_folder
git clone --recurse-submodules https://github.com/aZholtikov/ate0005
cd ate0005 cd ate0005
build idf.py build
idf.py flash
``` ```
+321 -387
View File
@@ -1,261 +1,77 @@
#include "ate0005.h" #include "ate0005.h"
/** /** @brief Global structure holding minimum free stack sizes for processing tasks. */
* @brief Internal structure holding the tasks minimum free stack sizes. static task_stats_t _task_stats = {A_INCLINOMETER_TASK_STACK_SIZE, B_INCLINOMETER_TASK_STACK_SIZE, A_TACHOMETER_TASK_STACK_SIZE, B_TACHOMETER_TASK_STACK_SIZE};
*
* Each field stores the high water mark of the corresponding processing
* task, reported by uxTaskGetStackHighWaterMark(). The statistics task reads
* these values to log stack usage and to raise a warning when a value falls
* below the threshold.
*/
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;
static httpd_handle_t _http_server_handle = NULL; /*!< HTTP server handle */ /** @brief HTTP server handle for OTA and web interface. */
static zh_pcf8574_handle_t *_lcd_handle = NULL; /*!< PCF8574 LCD expander handle */ static httpd_handle_t _http_server_handle = NULL;
static zh_160x_i2c_handle_t *_lcd_160x_handle = NULL; /*!< 16x2 LCD display handle */
static zh_encoder_handle_t *_mode_encoder_handle = NULL; /*!< Mode selector rotary encoder handle */ /** @brief PCF8574 handle for LCD I2C expander. */
static zh_inclinometer_handle_t *_a_inclinometer_handle = NULL; /*!< A channel inclinometer handle */ static zh_pcf8574_handle_t *_lcd_handle = NULL;
static zh_inclinometer_handle_t *_b_inclinometer_handle = NULL; /*!< B channel inclinometer handle */ /** @brief LCD 16x2 display handle wrapper. */
static zh_tachometer_handle_t *_a_tachometer_handle = NULL; /*!< A channel tachometer handle */ static zh_160x_i2c_handle_t *_lcd_160x_handle = NULL;
static zh_tachometer_handle_t *_b_tachometer_handle = NULL; /*!< B channel tachometer handle */ /** @brief Mode encoder handle for menu navigation. */
static esp_timer_handle_t _inclinometer_reset_timer_handle = {0}; /*!< Inclinometer reset software timer handle */ static zh_encoder_handle_t *_mode_encoder_handle = NULL;
static SemaphoreHandle_t _lcd_mutex = NULL; /*!< Mutex to protect the LCD display access */ /** @brief Inclinometer handle for channel A. */
static TaskHandle_t _a_inclinometer_task_handle = NULL; /*!< Handle of the A inclinometer processing task */ static zh_inclinometer_handle_t *_a_inclinometer_handle = NULL;
static TaskHandle_t _b_inclinometer_task_handle = NULL; /*!< Handle of the B inclinometer processing task */ /** @brief Inclinometer handle for channel B. */
static TaskHandle_t _a_tachometer_task_handle = NULL; /*!< Handle of the A tachometer processing task */ static zh_inclinometer_handle_t *_b_inclinometer_handle = NULL;
static TaskHandle_t _b_tachometer_task_handle = NULL; /*!< Handle of the B tachometer processing task */ /** @brief Tachometer handle for channel A. */
static TaskHandle_t _zh_stats_task_handle = NULL; /*!< Handle of the statistics processing task */ static zh_tachometer_handle_t *_a_tachometer_handle = NULL;
/** @brief Tachometer handle for channel B. */
static zh_tachometer_handle_t *_b_tachometer_handle = NULL;
/** @brief ESP timer handle for inclinometer reset timeout. */
static esp_timer_handle_t _inclinometer_reset_timer_handle = {0};
/** @brief Mutex for protecting LCD access from multiple tasks. */
static SemaphoreHandle_t _lcd_mutex = NULL;
/** @brief Task handle for the A inclinometer processing task. */
static TaskHandle_t _a_inclinometer_task_handle = NULL;
/** @brief Task handle for the B inclinometer processing task. */
static TaskHandle_t _b_inclinometer_task_handle = NULL;
/** @brief Task handle for the A tachometer processing task. */
static TaskHandle_t _a_tachometer_task_handle = NULL;
/** @brief Task handle for the B tachometer processing task. */
static TaskHandle_t _b_tachometer_task_handle = NULL;
/** @brief Task handle for the statistics logging task. */
static TaskHandle_t _zh_stats_task_handle = NULL;
/**
* @brief Initialize WiFi in AP+STA mode.
*
* Creates default AP and STA netifs, configures the AP with the SSID,
* password, channel and maximum number of connections and the STA with the
* SSID and password. Registers the Wi-Fi event handler and starts Wi-Fi.
*/
static void _zh_wifi_init(void); static void _zh_wifi_init(void);
/**
* @brief Initialize the web server with OTA support.
*
* Starts the HTTP server with default configuration and registers the OTA
* update page handler at the root URI.
*/
static void _zh_webserver_init(void); static void _zh_webserver_init(void);
/**
* @brief Initialize the ESP-NOW wireless protocol.
*
* Not implemented yet.
*/
static void _zh_espnow_init(void); static void _zh_espnow_init(void);
/**
* @brief Initialize the mode selector rotary encoder.
*
* Registers the encoder event handler and initializes the encoder with the
* task configuration, GPIO numbers, queue size and position limits
* corresponding to the number of main menu entries.
*/
static void _zh_encoder_init(void); static void _zh_encoder_init(void);
/**
* @brief Initialize the A channel inclinometer.
*
* Initializes the inclinometer with the A channel optical device GPIO numbers
* and the encoder pulse count, then creates the A inclinometer processing
* task.
*/
static void _zh_a_inclinometer_init(void); static void _zh_a_inclinometer_init(void);
/**
* @brief Initialize the B channel inclinometer.
*
* Initializes the inclinometer with the B channel optical device GPIO numbers
* and the encoder pulse count, then creates the B inclinometer processing
* task.
*/
static void _zh_b_inclinometer_init(void); static void _zh_b_inclinometer_init(void);
/**
* @brief Initialize the A channel tachometer.
*
* Initializes the tachometer with the A channel optical device GPIO numbers
* and the encoder pulse count, then creates the A tachometer processing
* task.
*/
static void _zh_a_tachometer_init(void); static void _zh_a_tachometer_init(void);
/**
* @brief Initialize the B channel tachometer.
*
* Initializes the tachometer with the B channel optical device GPIO numbers
* and the encoder pulse count, then creates the B tachometer processing
* task.
*/
static void _zh_b_tachometer_init(void); static void _zh_b_tachometer_init(void);
/**
* @brief Initialize the PCF8574 I2C expander of the LCD display.
*
* Creates the I2C master bus, initializes the PCF8574 expander with the task
* configuration and I2C address, initializes the 16x2 LCD driver on top of
* it and creates the LCD access mutex.
*/
static void _zh_pcf8574_init(void); static void _zh_pcf8574_init(void);
/**
* @brief Show the system loading screen on the LCD display.
*
* Displays the manufacturer name, project name and firmware version, then an
* animated progress bar. After loading, shows the current operating mode
* label.
*/
static void _zh_system_load(void); static void _zh_system_load(void);
/**
* @brief Create the inclinometer reset timer.
*
* Creates a one-shot software timer that resets the inclinometer angle on
* long press of the mode encoder button.
*/
static void _zh_timer_init(void); static void _zh_timer_init(void);
/**
* @brief Handle the mode encoder events.
*
* On button press starts the inclinometer reset timer and enters reset mode.
* On button release stops the timer and either ignores the event if the reset
* already occurred, or toggles the menu mode.
*
* Entering the menu mode stops the tasks and deinitializes the active devices
* of the current operating mode and enables the LCD cursor. Leaving the menu
* mode initializes the devices of the selected mode.
*
* On encoder rotation in the menu mode updates and displays the selected menu
* entry, otherwise restores the encoder position to the current menu value.
*
* @param arg User data registered with the event handler (unused)
* @param event_base Event base of the registered event
* @param event_id ID of the encoder or button event
* @param event_data Pointer to the event payload structure
*/
static void _zh_encoder_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); static void _zh_encoder_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
/**
* @brief Handle the ESP-NOW events.
*
* Not implemented yet.
*
* @param arg User data registered with the event handler (unused)
* @param event_base Event base of the registered event
* @param event_id ID of the ESP-NOW event (unused)
* @param event_data Pointer to the event payload (unused)
*/
static void _zh_espnow_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); static void _zh_espnow_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
/**
* @brief Handle the Wi-Fi station events.
*
* Initiates connection on station start, reconnects with a limited number of
* retries on disconnection and stops the syslog statistics task. On IP
* assignment resets the retry counter, initializes the syslog client and
* creates the statistics processing task.
*
* @param arg User data registered with the event handler (unused)
* @param event_base Event base of the registered event
* @param event_id ID of the Wi-Fi or IP event
* @param event_data Pointer to the event payload structure (unused)
*/
static void _zh_wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); static void _zh_wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
/**
* @brief Inclinometer reset timer callback.
*
* Called on long press of the mode encoder button. Resets the angle of the
* active inclinometers of the current operating mode when the device is not
* in the menu mode.
*
* @param arg User data registered with the timer (unused)
*/
static void _zh_inclinometer_reset_timer_on_alarm_cb(void *arg); static void _zh_inclinometer_reset_timer_on_alarm_cb(void *arg);
/**
* @brief A channel inclinometer processing task.
*
* Displays the A channel angle with 1 decimal place on the second LCD row.
* The value is refreshed only when it changes. Updates the task minimum free
* stack size for the statistics task.
*
* @param pvParameter Task parameter (unused)
*/
static void _zh_a_inclinometer_processing_task(void *pvParameter); static void _zh_a_inclinometer_processing_task(void *pvParameter);
/**
* @brief B channel inclinometer processing task.
*
* Displays the B channel angle with 1 decimal place on the second LCD row.
* The value is refreshed only when it changes. Updates the task minimum free
* stack size for the statistics task.
*
* @param pvParameter Task parameter (unused)
*/
static void _zh_b_inclinometer_processing_task(void *pvParameter); static void _zh_b_inclinometer_processing_task(void *pvParameter);
/**
* @brief A channel tachometer processing task.
*
* Displays the A channel RPM on the second LCD row. The value is refreshed
* only when it changes. Updates the task minimum free stack size for the
* statistics task.
*
* @param pvParameter Task parameter (unused)
*/
static void _zh_a_tachometer_processing_task(void *pvParameter); static void _zh_a_tachometer_processing_task(void *pvParameter);
/**
* @brief B channel tachometer processing task.
*
* Displays the B channel RPM on the second LCD row. The value is refreshed
* only when it changes. Updates the task minimum free stack size for the
* statistics task.
*
* @param pvParameter Task parameter (unused)
*/
static void _zh_b_tachometer_processing_task(void *pvParameter); static void _zh_b_tachometer_processing_task(void *pvParameter);
/**
* @brief Statistics processing task.
*
* Periodically sends the tasks minimum free stack sizes and the component
* error counters to the remote syslog server. Messages are logged with the
* warning severity when a stack size falls below the threshold or an error
* counter is not zero.
*
* @param pvParameter Task parameter (unused)
*/
static void _zh_stats_processing_task(void *pvParameter); static void _zh_stats_processing_task(void *pvParameter);
/** static const char *_main_menu[] = {MAIN_MENU_ANGLE_ANGLE_NAME, MAIN_MENU_TACHO_TACHO_NAME, MAIN_MENU_ANGLE_TACHO_NAME, MAIN_MENU_REMOTE_NAME};
* @brief Main application entry point. /** @brief Flag indicating whether the menu selection mode is active. */
* volatile static bool _is_menu_mode = false;
* Initializes NVS flash with erase and re-init on empty or version mismatch, /** @brief Flag indicating whether a long button press (reset mode) is detected. */
* the default event loop, the PCF8574 LCD expander, the system loading volatile static bool _is_reset_mode = false;
* screen, the mode encoder, the inclinometer reset timer, the A and B /** @brief Flag indicating that a reset event has occurred and must be cleared. */
* inclinometers, WiFi in AP+STA mode, the web server and ESP-NOW. volatile static bool _is_reset_occurred = false;
* /** @brief Index of the currently selected menu mode. */
* Then performs the OTA rollback self-check: if the running image is pending volatile static uint8_t _current_menu_value = 0;
* verification, waits 60 seconds and marks it as valid. /** @brief Counter for WiFi STA reconnection retry attempts. */
*/ volatile static uint8_t _wifi_reconnect_retry_num = 0;
void app_main(void); /** @brief Flag indicating whether WiFi STA is connected. */
volatile static bool _is_wifi_connected = false;
volatile static _task_stats_t _task_stats = {A_INCLINOMETER_TASK_STACK_SIZE, B_INCLINOMETER_TASK_STACK_SIZE, A_TACHOMETER_TASK_STACK_SIZE, B_TACHOMETER_TASK_STACK_SIZE};
static const char *_main_menu[] = {MAIN_MENU_ANGLE_ANGLE_NAME, MAIN_MENU_TACHO_TACHO_NAME, MAIN_MENU_ANGLE_TACHO_NAME, MAIN_MENU_REMOTE_NAME}; static const char *_main_menu[] = {MAIN_MENU_ANGLE_ANGLE_NAME, MAIN_MENU_TACHO_TACHO_NAME, MAIN_MENU_ANGLE_TACHO_NAME, MAIN_MENU_REMOTE_NAME};
volatile static bool _is_menu_mode = false; volatile static bool _is_menu_mode = false;
volatile static bool _is_reset_mode = false; volatile static bool _is_reset_mode = false;
@@ -264,41 +80,51 @@ volatile static uint8_t _current_menu_value = 0;
volatile static uint8_t _wifi_reconnect_retry_num = 0; volatile static uint8_t _wifi_reconnect_retry_num = 0;
volatile static bool _is_wifi_connected = false; volatile static bool _is_wifi_connected = false;
/**
* @brief Application entry point.
*
* Initializes all system components: LCD, encoder, timers, inclinometers,
* WiFi, web server, and ESP-NOW. Checks OTA state and marks firmware
* as valid if a pending verification is detected.
*/
void app_main(void) void app_main(void)
{ {
esp_err_t err = nvs_flash_init(); _lcd_mutex = xSemaphoreCreateMutex(); /*!< Create mutex for LCD access protection. */
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) nvs_flash_init(); /*!< Initialize NVS flash storage. */
{ esp_event_loop_create_default(); /*!< Create default event loop. */
ESP_ERROR_CHECK(nvs_flash_erase()); _zh_pcf8574_init(); /*!< Initialize I2C bus and PCF8574 expander for LCD. */
ESP_ERROR_CHECK(nvs_flash_init()); _zh_system_load(); /*!< Display boot animation on LCD. */
} _zh_encoder_init(); /*!< Initialize mode encoder for menu navigation. */
esp_event_loop_create_default(); _zh_timer_init(); /*!< Initialize inclinometer reset timer. */
_zh_pcf8574_init(); _zh_a_inclinometer_init(); /*!< Initialize inclinometer for channel A and its task. */
_zh_system_load(); _zh_b_inclinometer_init(); /*!< Initialize inclinometer for channel B and its task. */
_zh_encoder_init(); _zh_wifi_init(); /*!< Initialize WiFi in AP+STA mode. */
_zh_timer_init(); _zh_webserver_init(); /*!< Start HTTP server with OTA endpoint. */
_zh_a_inclinometer_init(); _zh_espnow_init(); /*!< Initialize ESP-NOW communication. */
_zh_b_inclinometer_init(); const esp_partition_t *running = esp_ota_get_running_partition(); /*!< Get current OTA partition. */
_zh_wifi_init();
_zh_webserver_init();
_zh_espnow_init();
const esp_partition_t *running = esp_ota_get_running_partition();
esp_ota_img_states_t ota_state = {0}; esp_ota_img_states_t ota_state = {0};
esp_ota_get_state_partition(running, &ota_state); esp_ota_get_state_partition(running, &ota_state); /*!< Read OTA image state. */
if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) /*!< Check if firmware update is pending verification. */
{ {
vTaskDelay(60000 / portTICK_PERIOD_MS); vTaskDelay(60000 / portTICK_PERIOD_MS); /*!< Wait 60 seconds before confirming. */
esp_ota_mark_app_valid_cancel_rollback(); esp_ota_mark_app_valid_cancel_rollback(); /*!< Mark firmware as valid and cancel rollback. */
} }
} }
/**
* @brief Initialize WiFi in AP+STA mode.
*
* Configures and starts WiFi with both AP and STA interfaces.
* AP provides local access for device configuration.
* STA connects to the configured network for remote logging.
*/
static void _zh_wifi_init(void) static void _zh_wifi_init(void)
{ {
esp_netif_init(); esp_netif_init(); /*!< Initialize networking stack. */
esp_netif_create_default_wifi_ap(); esp_netif_create_default_wifi_ap(); /*!< Create default network interface for AP. */
esp_netif_create_default_wifi_sta(); esp_netif_create_default_wifi_sta(); /*!< Create default network interface for STA. */
wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT(); wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&wifi_init_config); esp_wifi_init(&wifi_init_config); /*!< Initialize WiFi driver with default config. */
wifi_config_t wifi_ap_config = { wifi_config_t wifi_ap_config = {
.ap.ssid = WIFI_AP_SSID, .ap.ssid = WIFI_AP_SSID,
.ap.password = WIFI_AP_PASS, .ap.password = WIFI_AP_PASS,
@@ -310,125 +136,180 @@ static void _zh_wifi_init(void)
.sta.ssid = WIFI_STA_SSID, .sta.ssid = WIFI_STA_SSID,
.sta.password = WIFI_STA_PASS, .sta.password = WIFI_STA_PASS,
}; };
esp_wifi_set_mode(WIFI_MODE_APSTA); esp_wifi_set_mode(WIFI_MODE_APSTA); /*!< Set WiFi mode to AP+STA. */
esp_wifi_set_config(WIFI_IF_AP, &wifi_ap_config); esp_wifi_set_config(WIFI_IF_AP, &wifi_ap_config); /*!< Apply AP configuration. */
esp_wifi_set_config(WIFI_IF_STA, &wifi_sta_config); esp_wifi_set_config(WIFI_IF_STA, &wifi_sta_config); /*!< Apply STA configuration. */
esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &_zh_wifi_event_handler, NULL, NULL); esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &_zh_wifi_event_handler, NULL, NULL); /*!< Register WiFi event handler. */
esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &_zh_wifi_event_handler, NULL, NULL); esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &_zh_wifi_event_handler, NULL, NULL); /*!< Register IP event handler. */
esp_wifi_start(); esp_wifi_start(); /*!< Start WiFi. */
} }
/**
* @brief Initialize the HTTP server with OTA support.
*
* Starts the ESP HTTP server and registers the OTA server
* endpoint for over-the-air firmware updates.
*/
static void _zh_webserver_init(void) static void _zh_webserver_init(void)
{ {
httpd_config_t config = HTTPD_DEFAULT_CONFIG(); httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_start(&_http_server_handle, &config); httpd_start(&_http_server_handle, &config); /*!< Start HTTP server. */
zh_ota_server_init(_http_server_handle, "/"); zh_ota_server_init(_http_server_handle, "/"); /*!< Register OTA endpoint. */
} }
/**
* @brief Initialize ESP-NOW communication.
*
* Currently a placeholder. ESP-NOW initialization is not yet implemented.
*/
static void _zh_espnow_init(void) static void _zh_espnow_init(void)
{ {
// To Do.
} }
/**
* @brief Initialize the mode encoder for menu navigation.
*
* Configures the rotary encoder with GPIO pins, task parameters,
* and event handler. Sets encoder range to cover all menu modes.
*/
static void _zh_encoder_init(void) static void _zh_encoder_init(void)
{ {
esp_event_handler_instance_register(ZH_ENCODER, ESP_EVENT_ANY_ID, &_zh_encoder_event_handler, NULL, NULL); esp_event_handler_instance_register(ZH_ENCODER, ESP_EVENT_ANY_ID, &_zh_encoder_event_handler, NULL, NULL); /*!< Register encoder event handler. */
zh_encoder_init_config_t config = ZH_ENCODER_INIT_CONFIG_DEFAULT(); zh_encoder_init_config_t config = ZH_ENCODER_INIT_CONFIG_DEFAULT();
config.task_priority = ENCODER_TASK_PRIORITY; config.task_priority = ENCODER_TASK_PRIORITY; /*!< Set encoder task priority. */
config.stack_size = ENCODER_TASK_STACK_SIZE; config.stack_size = ENCODER_TASK_STACK_SIZE; /*!< Set encoder task stack size. */
config.queue_size = 5; config.queue_size = 5; /*!< Set event queue size. */
config.a_gpio_number = MODE_ENCODER_A_GPIO; config.a_gpio_number = MODE_ENCODER_A_GPIO; /*!< Set A-phase GPIO pin. */
config.b_gpio_number = MODE_ENCODER_B_GPIO; config.b_gpio_number = MODE_ENCODER_B_GPIO; /*!< Set B-phase GPIO pin. */
config.s_gpio_number = MODE_ENCODER_S_GPIO; config.s_gpio_number = MODE_ENCODER_S_GPIO; /*!< Set switch GPIO pin. */
config.encoder_min_value = 0; config.s_gpio_debounce_time = 1000; /*!< Set switch debounce time in microseconds. */
config.encoder_max_value = (sizeof(_main_menu) / sizeof(_main_menu[0])) - 1; config.encoder_min_value = 0; /*!< Set minimum encoder value (first menu item). */
config.encoder_step = 1; config.encoder_max_value = (sizeof(_main_menu) / sizeof(_main_menu[0])) - 1; /*!< Set maximum encoder value (last menu item). */
config.encoder_number = MODE_ENCODER_NUMBER; config.encoder_step = 1; /*!< Set encoder step size. */
zh_encoder_init(&config, &_mode_encoder_handle); config.encoder_number = MODE_ENCODER_NUMBER; /*!< Set encoder instance number. */
zh_encoder_set(&_mode_encoder_handle, 0); zh_encoder_init(&config, &_mode_encoder_handle); /*!< Initialize encoder driver. */
zh_encoder_set(&_mode_encoder_handle, 0); /*!< Reset encoder to initial position. */
} }
/**
* @brief Initialize inclinometer for channel A and create its processing task.
*
* Configures optical device GPIO pins and encoder pulses (3600).
* Creates a FreeRTOS task for processing inclinometer data.
*/
static void _zh_a_inclinometer_init(void) static void _zh_a_inclinometer_init(void)
{ {
zh_inclinometer_init_config_t config = ZH_INCLINOMETER_INIT_CONFIG_DEFAULT(); zh_inclinometer_init_config_t config = ZH_INCLINOMETER_INIT_CONFIG_DEFAULT();
config.a_gpio_number = A_OPTICAL_DEVICE_A_GPIO; config.a_gpio_number = A_OPTICAL_DEVICE_A_GPIO; /*!< Set A-phase GPIO pin. */
config.b_gpio_number = A_OPTICAL_DEVICE_B_GPIO; config.b_gpio_number = A_OPTICAL_DEVICE_B_GPIO; /*!< Set B-phase GPIO pin. */
config.encoder_pulses = 3600; config.encoder_pulses = 3600; /*!< Set encoder pulses per revolution. */
zh_inclinometer_init(&config, &_a_inclinometer_handle); zh_inclinometer_init(&config, &_a_inclinometer_handle); /*!< Initialize inclinometer driver. */
xTaskCreatePinnedToCore(&_zh_a_inclinometer_processing_task, "a_inclinometer_processing_task", A_INCLINOMETER_TASK_STACK_SIZE, NULL, xTaskCreatePinnedToCore(&_zh_a_inclinometer_processing_task, "a_inclinometer_processing_task", A_INCLINOMETER_TASK_STACK_SIZE, NULL, A_INCLINOMETER_TASK_PRIORITY,
A_INCLINOMETER_TASK_PRIORITY, &_a_inclinometer_task_handle, tskNO_AFFINITY); &_a_inclinometer_task_handle, tskNO_AFFINITY); /*!< Create processing task. */
} }
/**
* @brief Initialize inclinometer for channel B and create its processing task.
*
* Configures optical device GPIO pins and encoder pulses (3600).
* Creates a FreeRTOS task for processing inclinometer data.
*/
static void _zh_b_inclinometer_init(void) static void _zh_b_inclinometer_init(void)
{ {
zh_inclinometer_init_config_t config = ZH_INCLINOMETER_INIT_CONFIG_DEFAULT(); zh_inclinometer_init_config_t config = ZH_INCLINOMETER_INIT_CONFIG_DEFAULT();
config.a_gpio_number = B_OPTICAL_DEVICE_A_GPIO; config.a_gpio_number = B_OPTICAL_DEVICE_A_GPIO; /*!< Set A-phase GPIO pin. */
config.b_gpio_number = B_OPTICAL_DEVICE_B_GPIO; config.b_gpio_number = B_OPTICAL_DEVICE_B_GPIO; /*!< Set B-phase GPIO pin. */
config.encoder_pulses = 3600; config.encoder_pulses = 3600; /*!< Set encoder pulses per revolution. */
zh_inclinometer_init(&config, &_b_inclinometer_handle); zh_inclinometer_init(&config, &_b_inclinometer_handle); /*!< Initialize inclinometer driver. */
xTaskCreatePinnedToCore(&_zh_b_inclinometer_processing_task, "b_inclinometer_processing_task", B_INCLINOMETER_TASK_STACK_SIZE, NULL, xTaskCreatePinnedToCore(&_zh_b_inclinometer_processing_task, "b_inclinometer_processing_task", B_INCLINOMETER_TASK_STACK_SIZE, NULL, B_INCLINOMETER_TASK_PRIORITY,
B_INCLINOMETER_TASK_PRIORITY, &_b_inclinometer_task_handle, tskNO_AFFINITY); &_b_inclinometer_task_handle, tskNO_AFFINITY); /*!< Create processing task. */
} }
/**
* @brief Initialize tachometer for channel A and create its processing task.
*
* Configures optical device GPIO pins and encoder pulses (3600).
* Creates a FreeRTOS task for processing tachometer data.
*/
static void _zh_a_tachometer_init(void) static void _zh_a_tachometer_init(void)
{ {
zh_tachometer_init_config_t config = ZH_TACHOMETER_INIT_CONFIG_DEFAULT(); zh_tachometer_init_config_t config = ZH_TACHOMETER_INIT_CONFIG_DEFAULT();
config.a_gpio_number = A_OPTICAL_DEVICE_A_GPIO; config.a_gpio_number = A_OPTICAL_DEVICE_A_GPIO; /*!< Set A-phase GPIO pin. */
config.b_gpio_number = A_OPTICAL_DEVICE_B_GPIO; config.b_gpio_number = A_OPTICAL_DEVICE_B_GPIO; /*!< Set B-phase GPIO pin. */
config.encoder_pulses = 3600; config.encoder_pulses = 3600; /*!< Set encoder pulses per revolution. */
zh_tachometer_init(&config, &_a_tachometer_handle); zh_tachometer_init(&config, &_a_tachometer_handle); /*!< Initialize tachometer driver. */
xTaskCreatePinnedToCore(&_zh_a_tachometer_processing_task, "a_tachometer_processing_task", A_TACHOMETER_TASK_STACK_SIZE, NULL, xTaskCreatePinnedToCore(&_zh_a_tachometer_processing_task, "a_tachometer_processing_task", A_TACHOMETER_TASK_STACK_SIZE, NULL, A_TACHOMETER_TASK_PRIORITY,
A_TACHOMETER_TASK_PRIORITY, &_a_tachometer_task_handle, tskNO_AFFINITY); &_a_tachometer_task_handle, tskNO_AFFINITY); /*!< Create processing task. */
} }
/**
* @brief Initialize tachometer for channel B and create its processing task.
*
* Configures optical device GPIO pins and encoder pulses (3600).
* Creates a FreeRTOS task for processing tachometer data.
*/
static void _zh_b_tachometer_init(void) static void _zh_b_tachometer_init(void)
{ {
zh_tachometer_init_config_t config = ZH_TACHOMETER_INIT_CONFIG_DEFAULT(); zh_tachometer_init_config_t config = ZH_TACHOMETER_INIT_CONFIG_DEFAULT();
config.a_gpio_number = B_OPTICAL_DEVICE_A_GPIO; config.a_gpio_number = B_OPTICAL_DEVICE_A_GPIO; /*!< Set A-phase GPIO pin. */
config.b_gpio_number = B_OPTICAL_DEVICE_B_GPIO; config.b_gpio_number = B_OPTICAL_DEVICE_B_GPIO; /*!< Set B-phase GPIO pin. */
config.encoder_pulses = 3600; config.encoder_pulses = 3600; /*!< Set encoder pulses per revolution. */
zh_tachometer_init(&config, &_b_tachometer_handle); zh_tachometer_init(&config, &_b_tachometer_handle); /*!< Initialize tachometer driver. */
xTaskCreatePinnedToCore(&_zh_b_tachometer_processing_task, "b_tachometer_processing_task", B_TACHOMETER_TASK_STACK_SIZE, NULL, xTaskCreatePinnedToCore(&_zh_b_tachometer_processing_task, "b_tachometer_processing_task", B_TACHOMETER_TASK_STACK_SIZE, NULL, B_TACHOMETER_TASK_PRIORITY,
B_TACHOMETER_TASK_PRIORITY, &_b_tachometer_task_handle, tskNO_AFFINITY); &_b_tachometer_task_handle, tskNO_AFFINITY); /*!< Create processing task. */
} }
/**
* @brief Initialize I2C bus and PCF8574 expander for LCD display.
*
* Configures I2C with internal pull-ups and creates the master bus.
* Initializes the PCF8574 expander and 16x2 LCD wrapper.
*/
static void _zh_pcf8574_init(void) static void _zh_pcf8574_init(void)
{ {
i2c_master_bus_handle_t _i2c_bus_handle = NULL; i2c_master_bus_handle_t _i2c_bus_handle = NULL;
i2c_master_bus_config_t i2c_bus_config = { i2c_master_bus_config_t i2c_bus_config = {
.clk_source = I2C_CLK_SRC_DEFAULT, .clk_source = I2C_CLK_SRC_DEFAULT,
.scl_io_num = I2C_SCL_GPIO, .scl_io_num = I2C_SCL_GPIO, /*!< I2C clock GPIO. */
.sda_io_num = I2C_SDA_GPIO, .sda_io_num = I2C_SDA_GPIO, /*!< I2C data GPIO. */
.glitch_ignore_cnt = 7, .glitch_ignore_cnt = 7, /*!< Glitch filter count. */
.flags.enable_internal_pullup = true, .flags.enable_internal_pullup = true, /*!< Enable internal pull-up resistors. */
}; };
i2c_new_master_bus(&i2c_bus_config, &_i2c_bus_handle); i2c_new_master_bus(&i2c_bus_config, &_i2c_bus_handle); /*!< Create I2C master bus. */
zh_pcf8574_init_config_t config = ZH_PCF8574_INIT_CONFIG_DEFAULT(); zh_pcf8574_init_config_t config = ZH_PCF8574_INIT_CONFIG_DEFAULT();
config.task_priority = EXPANDER_TASK_PRIORITY; config.task_priority = EXPANDER_TASK_PRIORITY; /*!< Set expander task priority. */
config.stack_size = EXPANDER_TASK_STACK_SIZE; config.stack_size = EXPANDER_TASK_STACK_SIZE; /*!< Set expander task stack size. */
config.i2c_handle = _i2c_bus_handle; config.i2c_handle = _i2c_bus_handle; /*!< Assign I2C bus handle. */
config.i2c_address = LCD_I2C_ADDRESS; config.i2c_address = LCD_I2C_ADDRESS; /*!< Set I2C address of PCF8574. */
zh_pcf8574_init(&config, &_lcd_handle); zh_pcf8574_init(&config, &_lcd_handle); /*!< Initialize PCF8574 driver. */
zh_160x_init(&_lcd_handle, &_lcd_160x_handle, ZH_LCD_16X2); zh_160x_init(&_lcd_handle, &_lcd_160x_handle, ZH_LCD_16X2); /*!< Initialize 16x2 LCD wrapper. */
_lcd_mutex = xSemaphoreCreateMutex();
} }
/**
* @brief Display system boot animation on LCD.
*
* Shows company name, project name, and version.
* Then displays a progress bar during system loading.
* Finally, shows the initial operating mode.
*/
static void _zh_system_load(void) static void _zh_system_load(void)
{ {
zh_160x_set_cursor(&_lcd_160x_handle, 0, 0); zh_160x_set_cursor(&_lcd_160x_handle, 0, 0); /*!< Set cursor to first row, first column. */
zh_160x_print_char(&_lcd_160x_handle, "LLC AEROTECH"); zh_160x_print_char(&_lcd_160x_handle, "LLC AEROTECH"); /*!< Display company name. */
zh_160x_set_cursor(&_lcd_160x_handle, 1, 0); zh_160x_set_cursor(&_lcd_160x_handle, 1, 0); /*!< Set cursor to second row, first column. */
const esp_app_desc_t *esp_app_desc = esp_app_get_description(); const esp_app_desc_t *esp_app_desc = esp_app_get_description(); /*!< Get application description. */
zh_160x_print_char(&_lcd_160x_handle, esp_app_desc->project_name); zh_160x_print_char(&_lcd_160x_handle, esp_app_desc->project_name); /*!< Display project name. */
zh_160x_set_cursor(&_lcd_160x_handle, 1, 8); zh_160x_set_cursor(&_lcd_160x_handle, 1, 8); /*!< Set cursor to version position. */
zh_160x_print_char(&_lcd_160x_handle, "V"); zh_160x_print_char(&_lcd_160x_handle, "V"); /*!< Display version prefix. */
zh_160x_print_char(&_lcd_160x_handle, esp_app_desc->version); zh_160x_print_char(&_lcd_160x_handle, esp_app_desc->version); /*!< Display firmware version. */
vTaskDelay(1000 / portTICK_PERIOD_MS); vTaskDelay(1000 / portTICK_PERIOD_MS); /*!< Wait 1 second. */
zh_160x_lcd_clear(&_lcd_160x_handle); zh_160x_lcd_clear(&_lcd_160x_handle); /*!< Clear LCD. */
zh_160x_set_cursor(&_lcd_160x_handle, 0, 0); zh_160x_set_cursor(&_lcd_160x_handle, 0, 0);
zh_160x_print_char(&_lcd_160x_handle, "SYSTEM LOADING:"); zh_160x_print_char(&_lcd_160x_handle, "SYSTEM LOADING:");
for (uint8_t i = 0; i <= 100; i += 10) for (uint8_t i = 0; i <= 100; i += 10)
{ {
zh_160x_print_progress_bar(&_lcd_160x_handle, 1, i); zh_160x_print_progress_bar(&_lcd_160x_handle, 1, i); /*!< Draw progress bar at given percentage. */
vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelay(100 / portTICK_PERIOD_MS);
} }
vTaskDelay(500 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
@@ -439,24 +320,40 @@ static void _zh_system_load(void)
zh_160x_print_char(&_lcd_160x_handle, _main_menu[0]); zh_160x_print_char(&_lcd_160x_handle, _main_menu[0]);
} }
/**
* @brief Initialize the inclinometer reset timer.
*
* Creates a one-shot timer that triggers after a long button press.
* Used to reset inclinometer angles when the encoder button is held.
*/
static void _zh_timer_init(void) static void _zh_timer_init(void)
{ {
const esp_timer_create_args_t timer_args = { const esp_timer_create_args_t timer_args = {
.callback = &_zh_inclinometer_reset_timer_on_alarm_cb, .callback = &_zh_inclinometer_reset_timer_on_alarm_cb, /*!< Timer callback function. */
}; };
esp_timer_create(&timer_args, &_inclinometer_reset_timer_handle); esp_timer_create(&timer_args, &_inclinometer_reset_timer_handle); /*!< Create one-shot timer. */
} }
/**
* @brief Handle encoder events (button press/release and rotation).
*
* Processes button events for menu navigation and long-press reset.
* Processes rotation events for menu mode selection.
*
* @param arg Unused context argument.
* @param event_base Event base (ZH_ENCODER).
* @param event_id Event identifier (ZH_BUTTON_EVENT or ZH_ENCODER_EVENT).
* @param event_data Pointer to event data structure.
*/
static void _zh_encoder_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) static void _zh_encoder_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{ {
(void)arg;
switch (event_id) switch (event_id)
{ {
case ZH_BUTTON_EVENT: case ZH_BUTTON_EVENT:
zh_encoder_button_event_on_isr_t *button_event = (zh_encoder_button_event_on_isr_t *)event_data; zh_encoder_button_event_on_isr_t *button_event = event_data;
if (button_event->button_status == ZH_ENCODER_BUTTON_STATUS_PRESSED) if (button_event->button_status == ENCODER_BUTTON_PRESSED)
{ {
esp_timer_start_once(_inclinometer_reset_timer_handle, 2000000); esp_timer_start_once(_inclinometer_reset_timer_handle, 2000000); /*!< Start reset timer (2 seconds). */
_is_reset_mode = true; _is_reset_mode = true;
} }
else else
@@ -468,7 +365,7 @@ static void _zh_encoder_event_handler(void *arg, esp_event_base_t event_base, in
} }
if (_is_reset_mode == true) if (_is_reset_mode == true)
{ {
esp_timer_stop(_inclinometer_reset_timer_handle); esp_timer_stop(_inclinometer_reset_timer_handle); /*!< Stop reset timer on button release. */
_is_reset_mode = false; _is_reset_mode = false;
} }
_is_menu_mode = !_is_menu_mode; _is_menu_mode = !_is_menu_mode;
@@ -477,42 +374,25 @@ static void _zh_encoder_event_handler(void *arg, esp_event_base_t event_base, in
switch (_current_menu_value) switch (_current_menu_value)
{ {
case MODE_ANGLE_ANGLE: case MODE_ANGLE_ANGLE:
if (_a_inclinometer_task_handle != NULL)
{
vTaskDelete(_a_inclinometer_task_handle); vTaskDelete(_a_inclinometer_task_handle);
}
if (_b_inclinometer_task_handle != NULL)
{
vTaskDelete(_b_inclinometer_task_handle); vTaskDelete(_b_inclinometer_task_handle);
}
zh_inclinometer_deinit(&_a_inclinometer_handle); zh_inclinometer_deinit(&_a_inclinometer_handle);
zh_inclinometer_deinit(&_b_inclinometer_handle); zh_inclinometer_deinit(&_b_inclinometer_handle);
break; break;
case MODE_TACHO_TACHO: case MODE_TACHO_TACHO:
if (_a_tachometer_task_handle != NULL)
{
vTaskDelete(_a_tachometer_task_handle); vTaskDelete(_a_tachometer_task_handle);
}
if (_b_tachometer_task_handle != NULL)
{
vTaskDelete(_b_tachometer_task_handle); vTaskDelete(_b_tachometer_task_handle);
}
zh_tachometer_deinit(&_a_tachometer_handle); zh_tachometer_deinit(&_a_tachometer_handle);
zh_tachometer_deinit(&_b_tachometer_handle); zh_tachometer_deinit(&_b_tachometer_handle);
break; break;
case MODE_ANGLE_TACHO: case MODE_ANGLE_TACHO:
if (_a_inclinometer_task_handle != NULL)
{
vTaskDelete(_a_inclinometer_task_handle); vTaskDelete(_a_inclinometer_task_handle);
}
if (_b_tachometer_task_handle != NULL)
{
vTaskDelete(_b_tachometer_task_handle); vTaskDelete(_b_tachometer_task_handle);
}
zh_inclinometer_deinit(&_a_inclinometer_handle); zh_inclinometer_deinit(&_a_inclinometer_handle);
zh_tachometer_deinit(&_b_tachometer_handle); zh_tachometer_deinit(&_b_tachometer_handle);
break; break;
case MODE_REMOTE: case MODE_REMOTE:
// To Do.
break; break;
default: default:
break; break;
@@ -549,7 +429,7 @@ static void _zh_encoder_event_handler(void *arg, esp_event_base_t event_base, in
} }
break; break;
case ZH_ENCODER_EVENT: case ZH_ENCODER_EVENT:
zh_encoder_event_on_isr_t *encoder_event = (zh_encoder_event_on_isr_t *)event_data; zh_encoder_event_on_isr_t *encoder_event = event_data;
if (_is_menu_mode == true) if (_is_menu_mode == true)
{ {
_current_menu_value = (uint8_t)encoder_event->encoder_position; _current_menu_value = (uint8_t)encoder_event->encoder_position;
@@ -569,14 +449,35 @@ static void _zh_encoder_event_handler(void *arg, esp_event_base_t event_base, in
} }
} }
/**
* @brief Handle ESP-NOW events.
*
* Currently a placeholder. ESP-NOW event handling is not yet implemented.
*
* @param arg Unused context argument.
* @param event_base Event base.
* @param event_id Event identifier.
* @param event_data Pointer to event data structure.
*/
static void _zh_espnow_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) static void _zh_espnow_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{ {
// To Do.
} }
/**
* @brief Handle WiFi events (connection, disconnection, IP acquisition).
*
* Manages STA connection lifecycle: initiates connection on start,
* attempts reconnection on disconnect (with retry limit), and starts
* syslog + stats task upon successful IP acquisition.
*
* @param arg Unused context argument.
* @param event_base Event base (WIFI_EVENT or IP_EVENT).
* @param event_id Event identifier.
* @param event_data Pointer to event data structure.
*/
static void _zh_wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) static void _zh_wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{ {
(void)arg;
(void)event_data;
switch (event_id) switch (event_id)
{ {
case WIFI_EVENT_STA_START: case WIFI_EVENT_STA_START:
@@ -584,11 +485,8 @@ static void _zh_wifi_event_handler(void *arg, esp_event_base_t event_base, int32
break; break;
case WIFI_EVENT_STA_DISCONNECTED: case WIFI_EVENT_STA_DISCONNECTED:
if (_is_wifi_connected == true) if (_is_wifi_connected == true)
{
if (_zh_stats_task_handle != NULL)
{ {
vTaskDelete(_zh_stats_task_handle); vTaskDelete(_zh_stats_task_handle);
}
zh_syslog_deinit(); zh_syslog_deinit();
_is_wifi_connected = false; _is_wifi_connected = false;
} }
@@ -604,20 +502,23 @@ static void _zh_wifi_event_handler(void *arg, esp_event_base_t event_base, int32
zh_syslog_init_config_t config = ZH_SYSLOG_INIT_CONFIG_DEFAULT(); zh_syslog_init_config_t config = ZH_SYSLOG_INIT_CONFIG_DEFAULT();
strcpy(config.syslog_ip, SYSLOG_IP); strcpy(config.syslog_ip, SYSLOG_IP);
zh_syslog_init(&config); zh_syslog_init(&config);
if (_zh_stats_task_handle == NULL) xTaskCreatePinnedToCore(&_zh_stats_processing_task, "stats_processing_task", STATS_TASK_STACK_SIZE, NULL, STATS_TASK_PRIORITY, &_zh_stats_task_handle, tskNO_AFFINITY);
{
xTaskCreatePinnedToCore(&_zh_stats_processing_task, "stats_processing_task", STATS_TASK_STACK_SIZE, NULL, STATS_TASK_PRIORITY,
&_zh_stats_task_handle, tskNO_AFFINITY);
}
break; break;
default: default:
break; break;
} }
} }
/**
* @brief Timer callback for inclinometer reset on long button press.
*
* Called when the encoder button is held for 2 seconds.
* Resets inclinometer angles for the active mode.
*
* @param arg Unused context argument.
*/
static void _zh_inclinometer_reset_timer_on_alarm_cb(void *arg) static void _zh_inclinometer_reset_timer_on_alarm_cb(void *arg)
{ {
(void)arg;
_is_reset_mode = false; _is_reset_mode = false;
_is_reset_occurred = true; _is_reset_occurred = true;
if (_is_menu_mode == false) if (_is_menu_mode == false)
@@ -637,11 +538,17 @@ static void _zh_inclinometer_reset_timer_on_alarm_cb(void *arg)
} }
} }
/**
* @brief FreeRTOS task for processing inclinometer A data.
*
* Reads angle from inclinometer A and updates the LCD display
* on row 1, columns 2-5. Tracks minimum free stack size for monitoring.
*
* @param pvParameter Unused parameter.
*/
static void _zh_a_inclinometer_processing_task(void *pvParameter) static void _zh_a_inclinometer_processing_task(void *pvParameter)
{ {
(void)pvParameter; float prev_angle = 0, current_angle = 0;
float prev_angle = 0.0;
float current_angle = 0.0;
xSemaphoreTake(_lcd_mutex, portMAX_DELAY); xSemaphoreTake(_lcd_mutex, portMAX_DELAY);
zh_160x_set_cursor(&_lcd_160x_handle, 1, 0); zh_160x_set_cursor(&_lcd_160x_handle, 1, 0);
zh_160x_print_char(&_lcd_160x_handle, "A:"); zh_160x_print_char(&_lcd_160x_handle, "A:");
@@ -666,11 +573,17 @@ static void _zh_a_inclinometer_processing_task(void *pvParameter)
vTaskDelete(NULL); vTaskDelete(NULL);
} }
/**
* @brief FreeRTOS task for processing inclinometer B data.
*
* Reads angle from inclinometer B and updates the LCD display
* on row 1, columns 10-13. Tracks minimum free stack size for monitoring.
*
* @param pvParameter Unused parameter.
*/
static void _zh_b_inclinometer_processing_task(void *pvParameter) static void _zh_b_inclinometer_processing_task(void *pvParameter)
{ {
(void)pvParameter; float prev_angle = 0, current_angle = 0;
float prev_angle = 0.0;
float current_angle = 0.0;
xSemaphoreTake(_lcd_mutex, portMAX_DELAY); xSemaphoreTake(_lcd_mutex, portMAX_DELAY);
zh_160x_set_cursor(&_lcd_160x_handle, 1, 8); zh_160x_set_cursor(&_lcd_160x_handle, 1, 8);
zh_160x_print_char(&_lcd_160x_handle, "B:"); zh_160x_print_char(&_lcd_160x_handle, "B:");
@@ -695,11 +608,17 @@ static void _zh_b_inclinometer_processing_task(void *pvParameter)
vTaskDelete(NULL); vTaskDelete(NULL);
} }
/**
* @brief FreeRTOS task for processing tachometer A data.
*
* Reads RPM from tachometer A and updates the LCD display
* on row 1, columns 2-5. Tracks minimum free stack size for monitoring.
*
* @param pvParameter Unused parameter.
*/
static void _zh_a_tachometer_processing_task(void *pvParameter) static void _zh_a_tachometer_processing_task(void *pvParameter)
{ {
(void)pvParameter; uint16_t prev_rpm = 0, current_rpm = 0;
uint16_t prev_rpm = 0;
uint16_t current_rpm = 0;
xSemaphoreTake(_lcd_mutex, portMAX_DELAY); xSemaphoreTake(_lcd_mutex, portMAX_DELAY);
zh_160x_set_cursor(&_lcd_160x_handle, 1, 0); zh_160x_set_cursor(&_lcd_160x_handle, 1, 0);
zh_160x_print_char(&_lcd_160x_handle, "A:"); zh_160x_print_char(&_lcd_160x_handle, "A:");
@@ -724,11 +643,17 @@ static void _zh_a_tachometer_processing_task(void *pvParameter)
vTaskDelete(NULL); vTaskDelete(NULL);
} }
/**
* @brief FreeRTOS task for processing tachometer B data.
*
* Reads RPM from tachometer B and updates the LCD display
* on row 1, columns 10-13. Tracks minimum free stack size for monitoring.
*
* @param pvParameter Unused parameter.
*/
static void _zh_b_tachometer_processing_task(void *pvParameter) static void _zh_b_tachometer_processing_task(void *pvParameter)
{ {
(void)pvParameter; uint16_t prev_rpm = 0, current_rpm = 0;
uint16_t prev_rpm = 0;
uint16_t current_rpm = 0;
xSemaphoreTake(_lcd_mutex, portMAX_DELAY); xSemaphoreTake(_lcd_mutex, portMAX_DELAY);
zh_160x_set_cursor(&_lcd_160x_handle, 1, 8); zh_160x_set_cursor(&_lcd_160x_handle, 1, 8);
zh_160x_print_char(&_lcd_160x_handle, "B:"); zh_160x_print_char(&_lcd_160x_handle, "B:");
@@ -753,52 +678,61 @@ static void _zh_b_tachometer_processing_task(void *pvParameter)
vTaskDelete(NULL); vTaskDelete(NULL);
} }
/**
* @brief FreeRTOS task for periodic system statistics logging.
*
* Periodically sends syslog messages with minimum free stack sizes
* for all processing tasks, and error counters for encoder and
* PCF8574 drivers. Logs at INFO level if values are within limits,
* WARNING otherwise.
*
* @param pvParameter Unused parameter.
*/
static void _zh_stats_processing_task(void *pvParameter) static void _zh_stats_processing_task(void *pvParameter)
{ {
(void)pvParameter;
const esp_app_desc_t *esp_app_desc = esp_app_get_description(); const esp_app_desc_t *esp_app_desc = esp_app_get_description();
for (;;) for (;;)
{ {
char buffer[1024] = {0}; char buffer[1024] = {0};
const zh_encoder_stats_t *encoder_stats = zh_encoder_get_stats(); const zh_encoder_stats_t *encoder_stats = zh_encoder_get_stats();
const zh_pcf8574_stats_t *pcf8574_stats = zh_pcf8574_get_stats(); const zh_pcf8574_stats_t *pcf8574_stats = zh_pcf8574_get_stats();
snprintf(buffer, sizeof(buffer), "A inclinometer task minimum free stack size: %lu.\n", _task_stats.a_inclinometer_task_min_stack_size); snprintf(buffer, sizeof(buffer), "A inclinometer task minimum free stack size: %ld.\n", _task_stats.a_inclinometer_task_min_stack_size);
zh_syslog_send(ZH_KERN, _task_stats.a_inclinometer_task_min_stack_size > 100 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_inclinometer", buffer); zh_syslog_send(ZH_KERN, _task_stats.a_inclinometer_task_min_stack_size > 100 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_inclinometer", buffer);
vTaskDelay(500 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
snprintf(buffer, sizeof(buffer), "B inclinometer task minimum free stack size: %lu.\n", _task_stats.b_inclinometer_task_min_stack_size); snprintf(buffer, sizeof(buffer), "B inclinometer task minimum free stack size: %ld.\n", _task_stats.b_inclinometer_task_min_stack_size);
zh_syslog_send(ZH_KERN, _task_stats.b_inclinometer_task_min_stack_size > 100 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_inclinometer", buffer); zh_syslog_send(ZH_KERN, _task_stats.b_inclinometer_task_min_stack_size > 100 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_inclinometer", buffer);
vTaskDelay(500 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
snprintf(buffer, sizeof(buffer), "A tachometer task minimum free stack size: %lu.\n", _task_stats.a_tachometer_task_min_stack_size); snprintf(buffer, sizeof(buffer), "A tachometer task minimum free stack size: %ld.\n", _task_stats.a_tachometer_task_min_stack_size);
zh_syslog_send(ZH_KERN, _task_stats.a_tachometer_task_min_stack_size > 100 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_tachometer", buffer); zh_syslog_send(ZH_KERN, _task_stats.a_tachometer_task_min_stack_size > 100 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_tachometer", buffer);
vTaskDelay(500 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
snprintf(buffer, sizeof(buffer), "B tachometer task minimum free stack size: %lu.\n", _task_stats.b_tachometer_task_min_stack_size); snprintf(buffer, sizeof(buffer), "B tachometer task minimum free stack size: %ld.\n", _task_stats.b_tachometer_task_min_stack_size);
zh_syslog_send(ZH_KERN, _task_stats.b_tachometer_task_min_stack_size > 100 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_tachometer", buffer); zh_syslog_send(ZH_KERN, _task_stats.b_tachometer_task_min_stack_size > 100 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_tachometer", buffer);
vTaskDelay(500 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
snprintf(buffer, sizeof(buffer), "Stats task minimum free stack size: %lu.\n", (uint32_t)uxTaskGetStackHighWaterMark(NULL)); snprintf(buffer, sizeof(buffer), "Stats task minimum free stack size: %ld.\n", (uint32_t)uxTaskGetStackHighWaterMark(NULL));
zh_syslog_send(ZH_KERN, (uint32_t)uxTaskGetStackHighWaterMark(NULL) > 100 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_syslog", buffer); zh_syslog_send(ZH_KERN, (uint32_t)uxTaskGetStackHighWaterMark(NULL) > 100 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_syslog", buffer);
vTaskDelay(500 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
snprintf(buffer, sizeof(buffer), "Encoder task minimum free stack size: %lu.\n", encoder_stats->min_stack_size); snprintf(buffer, sizeof(buffer), "Encoder task minimum free stack size: %ld.\n", encoder_stats->min_stack_size);
zh_syslog_send(ZH_KERN, encoder_stats->min_stack_size > 100 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_encoder", buffer); zh_syslog_send(ZH_KERN, encoder_stats->min_stack_size > 100 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_encoder", buffer);
vTaskDelay(500 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
snprintf(buffer, sizeof(buffer), "Expander task minimum free stack size: %lu.\n", pcf8574_stats->min_stack_size); snprintf(buffer, sizeof(buffer), "Expander task minimum free stack size: %ld.\n", pcf8574_stats->min_stack_size);
zh_syslog_send(ZH_KERN, pcf8574_stats->min_stack_size > 100 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_pcf8574", buffer); zh_syslog_send(ZH_KERN, pcf8574_stats->min_stack_size > 100 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_pcf8574", buffer);
vTaskDelay(500 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
snprintf(buffer, sizeof(buffer), "Number of event post error: %lu.\n", encoder_stats->event_post_error); snprintf(buffer, sizeof(buffer), "Number of event post error: %ld.\n", encoder_stats->event_post_error);
zh_syslog_send(ZH_KERN, encoder_stats->event_post_error == 0 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_encoder", buffer); zh_syslog_send(ZH_KERN, encoder_stats->event_post_error == 0 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_encoder", buffer);
vTaskDelay(500 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
snprintf(buffer, sizeof(buffer), "Number of queue overflow error: %lu.\n", encoder_stats->queue_overflow_error); snprintf(buffer, sizeof(buffer), "Number of queue overflow error: %ld.\n", encoder_stats->queue_overflow_error);
zh_syslog_send(ZH_KERN, encoder_stats->queue_overflow_error == 0 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_encoder", buffer); zh_syslog_send(ZH_KERN, encoder_stats->queue_overflow_error == 0 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_encoder", buffer);
vTaskDelay(500 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
snprintf(buffer, sizeof(buffer), "Number of i2c driver error: %lu.\n", pcf8574_stats->i2c_driver_error); snprintf(buffer, sizeof(buffer), "Number of i2c driver error: %ld.\n", pcf8574_stats->i2c_driver_error);
zh_syslog_send(ZH_KERN, pcf8574_stats->i2c_driver_error == 0 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_pcf8574", buffer); zh_syslog_send(ZH_KERN, pcf8574_stats->i2c_driver_error == 0 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_pcf8574", buffer);
vTaskDelay(500 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
snprintf(buffer, sizeof(buffer), "Number of event post error: %lu.\n", pcf8574_stats->event_post_error); snprintf(buffer, sizeof(buffer), "Number of event post error: %ld.\n", pcf8574_stats->event_post_error);
zh_syslog_send(ZH_KERN, pcf8574_stats->event_post_error == 0 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_pcf8574", buffer); zh_syslog_send(ZH_KERN, pcf8574_stats->event_post_error == 0 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_pcf8574", buffer);
vTaskDelay(500 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
snprintf(buffer, sizeof(buffer), "Number of vector error: %lu.\n", pcf8574_stats->vector_error); snprintf(buffer, sizeof(buffer), "Number of vector error: %ld.\n", pcf8574_stats->vector_error);
zh_syslog_send(ZH_KERN, pcf8574_stats->vector_error == 0 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_pcf8574", buffer); zh_syslog_send(ZH_KERN, pcf8574_stats->vector_error == 0 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_pcf8574", buffer);
vTaskDelay(500 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
snprintf(buffer, sizeof(buffer), "Number of queue overflow error: %lu.\n", pcf8574_stats->queue_overflow_error); snprintf(buffer, sizeof(buffer), "Number of queue overflow error: %ld.\n", pcf8574_stats->queue_overflow_error);
zh_syslog_send(ZH_KERN, pcf8574_stats->queue_overflow_error == 0 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_pcf8574", buffer); zh_syslog_send(ZH_KERN, pcf8574_stats->queue_overflow_error == 0 ? ZH_INFO : ZH_WARNING, esp_app_desc->project_name, "zh_pcf8574", buffer);
vTaskDelay(60000 / portTICK_PERIOD_MS); vTaskDelay(60000 / portTICK_PERIOD_MS);
} }
+76 -59
View File
@@ -1,27 +1,18 @@
/** /**
* @file ate0005.h * @file ate0005.h
* *
* @brief Configuration and interface definitions of the ATE0005 universal * @brief Header file for ATE0005 — Universal Optical 2-Channel Angle Gauge / Tachometer
* optical 2-channel angle gauge and tachometer.
* *
* This header aggregates the includes required by the application and defines * This header defines task priorities, stack sizes, mode constants,
* the FreeRTOS task priorities and stack sizes, the operating mode constants * WiFi configuration, GPIO pin mappings, and data structures for the
* with the main menu labels, the WiFi access point and station parameters and * ATE0005 device running on ESP32 platform with FreeRTOS.
* the GPIO pin mapping of the peripherals of the ATE0005 device running on the
* ESP32 platform.
* *
* Key features: * Key features:
* - Task priorities and stack sizes for the inclinometer, tachometer, encoder, * - Task configuration for inclinometer, tachometer, encoder, and stats
* expander and statistics tasks * - WiFi AP/STA mode configuration
* - Operating mode constants and the matching main menu labels * - I2C and GPIO pin definitions for peripherals
* - WiFi access point and station configuration with the remote syslog server * - Mode selection constants for different operating modes
* address * - LCD display and PCF8574 expander macros
* - 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 #pragma once
@@ -37,62 +28,88 @@
#include "zh_espnow.h" #include "zh_espnow.h"
#include "zh_syslog.h" #include "zh_syslog.h"
#define STATS_TASK_PRIORITY tskIDLE_PRIORITY /*!< Priority of the statistics logging 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 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_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 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_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 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_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 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_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 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_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 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_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 EXPANDER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE /*!< Stack size for the PCF8574 expander task. */
#define MODE_ANGLE_ANGLE 0 /*!< Operating mode: both inclinometers active */ #define TRIAC_ON true /*!< Triac enabled state. */
#define MAIN_MENU_ANGLE_ANGLE_NAME "ANGLE/ANGLE" /*!< Menu label for angle/angle mode */ #define TRIAC_OFF false /*!< Triac disabled state. */
#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 WIFI_AP_SSID "ATE0005" /*!< WiFi AP SSID */ #define ENCODER_BUTTON_RELEASED true /*!< Encoder button released state. */
#define WIFI_AP_PASS "repairlab" /*!< WiFi AP password */ #define ENCODER_BUTTON_PRESSED false /*!< Encoder button pressed state. */
#define WIFI_AP_CHANNEL 1 /*!< WiFi AP channel */
#define MAX_AP_CONNECTION 4 /*!< Maximum number of stations connected to AP */
#define WIFI_STA_SSID "rick_professor" /*!< WiFi STA SSID */ #define LED_OFF ZH_PCF8574_GPIO_LOW /*!< LED turned off (low level). */
#define WIFI_STA_PASS "repairlab" /*!< WiFi STA password */ #define LED_ON ZH_PCF8574_GPIO_HIGH /*!< LED turned on (high level). */
#define WIFI_STA_MAXIMUM_RETRY 10 /*!< Maximum number of reconnection attempts for STA mode */
#define SYSLOG_IP "77.51.201.8" /*!< IP address of the remote syslog server */ #define RELAY_OFF ZH_PCF8574_GPIO_LOW /*!< Relay disconnected (low level). */
#define RELAY_ON ZH_PCF8574_GPIO_HIGH /*!< Relay connected (high level). */
#define I2C_SCL_GPIO GPIO_NUM_22 /*!< I2C clock line GPIO */ #define MODE_ANGLE_ANGLE 0 /*!< Operating mode: both inclinometers active. */
#define I2C_SDA_GPIO GPIO_NUM_21 /*!< I2C data line GPIO */ #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 LCD_I2C_ADDRESS 0x20 /*!< I2C address of the LCD PCF8574 expander (U2) */ #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 MODE_ENCODER_NUMBER 1 /*!< Encoder instance number (Connector EN1) */ #define WIFI_STA_SSID "rick_professor" /*!< WiFi STA SSID. */
#define MODE_ENCODER_A_GPIO GPIO_NUM_27 /*!< Mode encoder A-phase GPIO */ #define WIFI_STA_PASS "repairlab" /*!< WiFi STA password. */
#define MODE_ENCODER_B_GPIO GPIO_NUM_26 /*!< Mode encoder B-phase GPIO */ #define WIFI_STA_MAXIMUM_RETRY 10 /*!< Maximum number of reconnection attempts for STA mode. */
#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 SYSLOG_IP "77.51.201.8" /*!< IP address of the remote syslog server. */
#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 I2C_SCL_GPIO GPIO_NUM_22 /*!< I2C clock line GPIO. */
#define B_OPTICAL_DEVICE_B_GPIO GPIO_NUM_19 /*!< Optical device B-phase GPIO for channel B */ #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. */
#ifdef __cplusplus #ifdef __cplusplus
extern "C" extern "C"
{ {
#endif #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 #ifdef __cplusplus
} }
#endif #endif
+3 -3
View File
@@ -1,6 +1,6 @@
description: Universal Optical 2-Channel Angle Gauge / Tachometer 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/ate0005 url: https://github.com/aZholtikov/ate0002
version: "1.1.2" version: "1.2.0"
license: "Apache-2.0" license: "Apache-2.0"
maintainers: maintainers:
- "Alexey Zholtikov <alexey.zholtikov@yandex.ru>" - "Alexey Zholtikov <alexey.zholtikov@yandex.ru>"
+15 -2
View File
@@ -1,8 +1,21 @@
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y CONFIG_BOOTLOADER_LOG_LEVEL_ERROR=y
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_COMPILER_OPTIMIZATION_SIZE=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_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_FREERTOS_HZ=1000 CONFIG_FREERTOS_HZ=1000
CONFIG_LOG_DEFAULT_LEVEL_NONE=y CONFIG_LOG_DEFAULT_LEVEL_ERROR=y
-337
View File
@@ -1,337 +0,0 @@
# Описание компонента 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"
```