Compare commits

..
11 Commits
Author SHA1 Message Date
aZholtikov b9fc3a6f41 style: added docs 2026-11-04 09:12:31 +03:00
aZholtikov b8aa3a594d perf: updated components 2026-04-21 19:08:10 +03:00
aZholtikov 2a2d099504 doc: updated components 2026-04-21 19:07:08 +03:00
aZholtikov 32c5579fc9 perf: updated components 2026-03-15 22:12:36 +03:00
aZholtikov 69055cd185 doc: updated components 2026-03-15 07:39:56 +03:00
aZholtikov ee121e1e59 doc: updated manual 2026-03-12 16:18:09 +03:00
aZholtikov 9d82dbcbe9 doc: added invoice 2026-03-12 16:17:48 +03:00
aZholtikov d0ea38b750 doc: updated cad 2026-03-12 16:16:30 +03:00
aZholtikov 45df58a940 perf: changed starting order of functions 2026-03-11 10:58:31 +03:00
aZholtikov eaf66e38e5 fix: incorrect display of negative angle value 2026-03-11 10:44:18 +03:00
aZholtikov 67e207c111 fix: stats task small stack size 2026-03-11 10:42:35 +03:00
24 changed files with 357991 additions and 248 deletions

No files matched your search

+6 -2
View File
@@ -3,5 +3,9 @@ build
.DS_Store
sdkconfig.old
desktop.ini
.PVS-Studio
sdkconfig
sdkconfig
dependencies.lock
managed_components
commit-rules.md
readme-rules.md
wiki-rules.md
-27
View File
@@ -1,27 +0,0 @@
[submodule "components/zh_inclinometer"]
path = components/zh_inclinometer
url = https://github.com/aZholtikov/zh_inclinometer
[submodule "components/zh_encoder"]
path = components/zh_encoder
url = https://github.com/aZholtikov/zh_encoder
[submodule "components/zh_160x_i2c"]
path = components/zh_160x_i2c
url = https://github.com/aZholtikov/zh_160x_i2c
[submodule "components/zh_pcf8574"]
path = components/zh_pcf8574
url = https://github.com/aZholtikov/zh_pcf8574
[submodule "components/zh_vector"]
path = components/zh_vector
url = https://github.com/aZholtikov/zh_vector
[submodule "components/zh_ota_server"]
path = components/zh_ota_server
url = https://github.com/aZholtikov/zh_ota_server
[submodule "components/zh_espnow"]
path = components/zh_espnow
url = https://github.com/aZholtikov/zh_espnow
[submodule "components/zh_tachometer"]
path = components/zh_tachometer
url = https://github.com/aZholtikov/zh_tachometer
[submodule "components/zh_syslog"]
path = components/zh_syslog
url = https://github.com/aZholtikov/zh_syslog
+21 -11
View File
@@ -1,21 +1,31 @@
# Universal Optical 2-Channel Angle Gauge / Tachometer
## Tested on
## Features
1. [ESP32 ESP-IDF v5.5.3](https://docs.espressif.com/projects/esp-idf/en/v5.5.3/esp32/index.html)
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.
## SAST Tools
## Attention
[PVS-Studio](https://pvs-studio.com/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) - static analyzer for C, C++, C#, and Java code.
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.
## Build and flash
## Using
Run the following command to firmware build and flash module:
Clone and build the project:
```text
cd your_projects_folder
git clone --recurse-submodules https://github.com/aZholtikov/ate0005
```bash
git clone https://github.com/aZholtikov/ate0005
cd ate0005
idf.py build
idf.py flash
build
```
Binary file not shown.
Binary file not shown.
+3037
View File
File diff suppressed because it is too large. Load diff
+354126
View File
File diff suppressed because it is too large. Load diff
+375 -121
View File
@@ -1,46 +1,261 @@
#include "ate0005.h"
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};
/**
* @brief Internal structure holding the tasks minimum free stack sizes.
*
* 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;
static zh_pcf8574_handle_t _lcd_handle = {0};
static zh_encoder_handle_t _mode_encoder_handle = {0};
static zh_inclinometer_handle_t _a_inclinometer_handle = {0};
static zh_inclinometer_handle_t _b_inclinometer_handle = {0};
static zh_tachometer_handle_t _a_tachometer_handle = {0};
static zh_tachometer_handle_t _b_tachometer_handle = {0};
static esp_timer_handle_t _inclinometer_reset_timer_handle = {0};
static SemaphoreHandle_t _lcd_mutex = NULL;
static TaskHandle_t _a_inclinometer_task_handle = NULL;
static TaskHandle_t _b_inclinometer_task_handle = NULL;
static TaskHandle_t _a_tachometer_task_handle = NULL;
static TaskHandle_t _b_tachometer_task_handle = NULL;
static TaskHandle_t _zh_stats_task_handle = NULL;
static httpd_handle_t _http_server_handle = NULL; /*!< HTTP server handle */
static zh_pcf8574_handle_t *_lcd_handle = NULL; /*!< PCF8574 LCD expander handle */
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 */
static zh_inclinometer_handle_t *_a_inclinometer_handle = NULL; /*!< A channel inclinometer handle */
static zh_inclinometer_handle_t *_b_inclinometer_handle = NULL; /*!< B channel inclinometer handle */
static zh_tachometer_handle_t *_a_tachometer_handle = NULL; /*!< A channel tachometer handle */
static zh_tachometer_handle_t *_b_tachometer_handle = NULL; /*!< B channel tachometer handle */
static esp_timer_handle_t _inclinometer_reset_timer_handle = {0}; /*!< Inclinometer reset software timer handle */
static SemaphoreHandle_t _lcd_mutex = NULL; /*!< Mutex to protect the LCD display access */
static TaskHandle_t _a_inclinometer_task_handle = NULL; /*!< Handle of the A inclinometer processing task */
static TaskHandle_t _b_inclinometer_task_handle = NULL; /*!< Handle of the B inclinometer processing task */
static TaskHandle_t _a_tachometer_task_handle = NULL; /*!< Handle of the A tachometer processing task */
static TaskHandle_t _b_tachometer_task_handle = NULL; /*!< Handle of the B tachometer processing task */
static TaskHandle_t _zh_stats_task_handle = NULL; /*!< Handle of the statistics processing task */
/**
* @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);
/**
* @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);
/**
* @brief Initialize the ESP-NOW wireless protocol.
*
* Not implemented yet.
*/
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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @brief Main application entry point.
*
* Initializes NVS flash with erase and re-init on empty or version mismatch,
* the default event loop, the PCF8574 LCD expander, the system loading
* screen, the mode encoder, the inclinometer reset timer, the A and B
* inclinometers, WiFi in AP+STA mode, the web server and ESP-NOW.
*
* Then performs the OTA rollback self-check: if the running image is pending
* verification, waits 60 seconds and marks it as valid.
*/
void app_main(void);
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};
volatile static bool _is_menu_mode = false;
volatile static bool _is_reset_mode = false;
@@ -51,18 +266,22 @@ volatile static bool _is_wifi_connected = false;
void app_main(void)
{
_lcd_mutex = xSemaphoreCreateMutex();
nvs_flash_init();
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ESP_ERROR_CHECK(nvs_flash_init());
}
esp_event_loop_create_default();
_zh_wifi_init();
_zh_webserver_init();
_zh_pcf8574_init();
_zh_system_load();
_zh_encoder_init();
_zh_timer_init();
_zh_a_inclinometer_init();
_zh_b_inclinometer_init();
_zh_wifi_init();
_zh_webserver_init();
_zh_espnow_init();
_zh_timer_init();
const esp_partition_t *running = esp_ota_get_running_partition();
esp_ota_img_states_t ota_state = {0};
esp_ota_get_state_partition(running, &ota_state);
@@ -108,7 +327,6 @@ static void _zh_webserver_init(void)
static void _zh_espnow_init(void)
{
// To Do.
}
static void _zh_encoder_init(void)
@@ -121,7 +339,6 @@ static void _zh_encoder_init(void)
config.a_gpio_number = MODE_ENCODER_A_GPIO;
config.b_gpio_number = MODE_ENCODER_B_GPIO;
config.s_gpio_number = MODE_ENCODER_S_GPIO;
config.s_gpio_debounce_time = 1000;
config.encoder_min_value = 0;
config.encoder_max_value = (sizeof(_main_menu) / sizeof(_main_menu[0])) - 1;
config.encoder_step = 1;
@@ -137,8 +354,8 @@ static void _zh_a_inclinometer_init(void)
config.b_gpio_number = A_OPTICAL_DEVICE_B_GPIO;
config.encoder_pulses = 3600;
zh_inclinometer_init(&config, &_a_inclinometer_handle);
xTaskCreatePinnedToCore(&_zh_a_inclinometer_processing_task, "a_inclinometer_processing_task", A_INCLINOMETER_TASK_STACK_SIZE, NULL, A_INCLINOMETER_TASK_PRIORITY,
&_a_inclinometer_task_handle, tskNO_AFFINITY);
xTaskCreatePinnedToCore(&_zh_a_inclinometer_processing_task, "a_inclinometer_processing_task", A_INCLINOMETER_TASK_STACK_SIZE, NULL,
A_INCLINOMETER_TASK_PRIORITY, &_a_inclinometer_task_handle, tskNO_AFFINITY);
}
static void _zh_b_inclinometer_init(void)
@@ -148,8 +365,8 @@ static void _zh_b_inclinometer_init(void)
config.b_gpio_number = B_OPTICAL_DEVICE_B_GPIO;
config.encoder_pulses = 3600;
zh_inclinometer_init(&config, &_b_inclinometer_handle);
xTaskCreatePinnedToCore(&_zh_b_inclinometer_processing_task, "b_inclinometer_processing_task", B_INCLINOMETER_TASK_STACK_SIZE, NULL, B_INCLINOMETER_TASK_PRIORITY,
&_b_inclinometer_task_handle, tskNO_AFFINITY);
xTaskCreatePinnedToCore(&_zh_b_inclinometer_processing_task, "b_inclinometer_processing_task", B_INCLINOMETER_TASK_STACK_SIZE, NULL,
B_INCLINOMETER_TASK_PRIORITY, &_b_inclinometer_task_handle, tskNO_AFFINITY);
}
static void _zh_a_tachometer_init(void)
@@ -159,8 +376,8 @@ static void _zh_a_tachometer_init(void)
config.b_gpio_number = A_OPTICAL_DEVICE_B_GPIO;
config.encoder_pulses = 3600;
zh_tachometer_init(&config, &_a_tachometer_handle);
xTaskCreatePinnedToCore(&_zh_a_tachometer_processing_task, "a_tachometer_processing_task", A_TACHOMETER_TASK_STACK_SIZE, NULL, A_TACHOMETER_TASK_PRIORITY,
&_a_tachometer_task_handle, tskNO_AFFINITY);
xTaskCreatePinnedToCore(&_zh_a_tachometer_processing_task, "a_tachometer_processing_task", A_TACHOMETER_TASK_STACK_SIZE, NULL,
A_TACHOMETER_TASK_PRIORITY, &_a_tachometer_task_handle, tskNO_AFFINITY);
}
static void _zh_b_tachometer_init(void)
@@ -170,8 +387,8 @@ static void _zh_b_tachometer_init(void)
config.b_gpio_number = B_OPTICAL_DEVICE_B_GPIO;
config.encoder_pulses = 3600;
zh_tachometer_init(&config, &_b_tachometer_handle);
xTaskCreatePinnedToCore(&_zh_b_tachometer_processing_task, "b_tachometer_processing_task", B_TACHOMETER_TASK_STACK_SIZE, NULL, B_TACHOMETER_TASK_PRIORITY,
&_b_tachometer_task_handle, tskNO_AFFINITY);
xTaskCreatePinnedToCore(&_zh_b_tachometer_processing_task, "b_tachometer_processing_task", B_TACHOMETER_TASK_STACK_SIZE, NULL,
B_TACHOMETER_TASK_PRIORITY, &_b_tachometer_task_handle, tskNO_AFFINITY);
}
static void _zh_pcf8574_init(void)
@@ -191,34 +408,35 @@ static void _zh_pcf8574_init(void)
config.i2c_handle = _i2c_bus_handle;
config.i2c_address = LCD_I2C_ADDRESS;
zh_pcf8574_init(&config, &_lcd_handle);
zh_160x_init(&_lcd_handle, ZH_LCD_16X2);
zh_160x_init(&_lcd_handle, &_lcd_160x_handle, ZH_LCD_16X2);
_lcd_mutex = xSemaphoreCreateMutex();
}
static void _zh_system_load(void)
{
zh_160x_set_cursor(&_lcd_handle, 0, 0);
zh_160x_print_char(&_lcd_handle, "LLC AEROTECH");
zh_160x_set_cursor(&_lcd_handle, 1, 0);
zh_160x_set_cursor(&_lcd_160x_handle, 0, 0);
zh_160x_print_char(&_lcd_160x_handle, "LLC AEROTECH");
zh_160x_set_cursor(&_lcd_160x_handle, 1, 0);
const esp_app_desc_t *esp_app_desc = esp_app_get_description();
zh_160x_print_char(&_lcd_handle, esp_app_desc->project_name);
zh_160x_set_cursor(&_lcd_handle, 1, 8);
zh_160x_print_char(&_lcd_handle, "V");
zh_160x_print_char(&_lcd_handle, esp_app_desc->version);
zh_160x_print_char(&_lcd_160x_handle, esp_app_desc->project_name);
zh_160x_set_cursor(&_lcd_160x_handle, 1, 8);
zh_160x_print_char(&_lcd_160x_handle, "V");
zh_160x_print_char(&_lcd_160x_handle, esp_app_desc->version);
vTaskDelay(1000 / portTICK_PERIOD_MS);
zh_160x_lcd_clear(&_lcd_handle);
zh_160x_set_cursor(&_lcd_handle, 0, 0);
zh_160x_print_char(&_lcd_handle, "SYSTEM LOADING:");
zh_160x_lcd_clear(&_lcd_160x_handle);
zh_160x_set_cursor(&_lcd_160x_handle, 0, 0);
zh_160x_print_char(&_lcd_160x_handle, "SYSTEM LOADING:");
for (uint8_t i = 0; i <= 100; i += 10)
{
zh_160x_print_progress_bar(&_lcd_handle, 1, i);
zh_160x_print_progress_bar(&_lcd_160x_handle, 1, i);
vTaskDelay(100 / portTICK_PERIOD_MS);
}
vTaskDelay(500 / portTICK_PERIOD_MS);
zh_160x_lcd_clear(&_lcd_handle);
zh_160x_lcd_clear(&_lcd_160x_handle);
vTaskDelay(500 / portTICK_PERIOD_MS);
zh_160x_set_cursor(&_lcd_handle, 0, 0);
zh_160x_print_char(&_lcd_handle, "MODE:");
zh_160x_print_char(&_lcd_handle, _main_menu[0]);
zh_160x_set_cursor(&_lcd_160x_handle, 0, 0);
zh_160x_print_char(&_lcd_160x_handle, "MODE:");
zh_160x_print_char(&_lcd_160x_handle, _main_menu[0]);
}
static void _zh_timer_init(void)
@@ -231,11 +449,12 @@ static void _zh_timer_init(void)
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)
{
case ZH_BUTTON_EVENT:
zh_encoder_button_event_on_isr_t *button_event = event_data;
if (button_event->button_status == ENCODER_BUTTON_PRESSED)
zh_encoder_button_event_on_isr_t *button_event = (zh_encoder_button_event_on_isr_t *)event_data;
if (button_event->button_status == ZH_ENCODER_BUTTON_STATUS_PRESSED)
{
esp_timer_start_once(_inclinometer_reset_timer_handle, 2000000);
_is_reset_mode = true;
@@ -258,36 +477,53 @@ static void _zh_encoder_event_handler(void *arg, esp_event_base_t event_base, in
switch (_current_menu_value)
{
case MODE_ANGLE_ANGLE:
vTaskDelete(_a_inclinometer_task_handle);
vTaskDelete(_b_inclinometer_task_handle);
if (_a_inclinometer_task_handle != NULL)
{
vTaskDelete(_a_inclinometer_task_handle);
}
if (_b_inclinometer_task_handle != NULL)
{
vTaskDelete(_b_inclinometer_task_handle);
}
zh_inclinometer_deinit(&_a_inclinometer_handle);
zh_inclinometer_deinit(&_b_inclinometer_handle);
break;
case MODE_TACHO_TACHO:
vTaskDelete(_a_tachometer_task_handle);
vTaskDelete(_b_tachometer_task_handle);
if (_a_tachometer_task_handle != NULL)
{
vTaskDelete(_a_tachometer_task_handle);
}
if (_b_tachometer_task_handle != NULL)
{
vTaskDelete(_b_tachometer_task_handle);
}
zh_tachometer_deinit(&_a_tachometer_handle);
zh_tachometer_deinit(&_b_tachometer_handle);
break;
case MODE_ANGLE_TACHO:
vTaskDelete(_a_inclinometer_task_handle);
vTaskDelete(_b_tachometer_task_handle);
if (_a_inclinometer_task_handle != NULL)
{
vTaskDelete(_a_inclinometer_task_handle);
}
if (_b_tachometer_task_handle != NULL)
{
vTaskDelete(_b_tachometer_task_handle);
}
zh_inclinometer_deinit(&_a_inclinometer_handle);
zh_tachometer_deinit(&_b_tachometer_handle);
break;
case MODE_REMOTE:
// To Do.
break;
default:
break;
}
zh_160x_clear_row(&_lcd_handle, 1);
zh_160x_set_cursor(&_lcd_handle, 0, 0);
zh_160x_on_cursor(&_lcd_handle, true);
zh_160x_clear_row(&_lcd_160x_handle, 1);
zh_160x_set_cursor(&_lcd_160x_handle, 0, 0);
zh_160x_on_cursor(&_lcd_160x_handle, true);
}
else
{
zh_160x_off_cursor(&_lcd_handle);
zh_160x_off_cursor(&_lcd_160x_handle);
switch (_current_menu_value)
{
case MODE_ANGLE_ANGLE:
@@ -303,8 +539,8 @@ static void _zh_encoder_event_handler(void *arg, esp_event_base_t event_base, in
_zh_b_tachometer_init();
break;
case MODE_REMOTE:
zh_160x_set_cursor(&_lcd_handle, 1, 0);
zh_160x_print_char(&_lcd_handle, "NOT CONNECTED");
zh_160x_set_cursor(&_lcd_160x_handle, 1, 0);
zh_160x_print_char(&_lcd_160x_handle, "NOT CONNECTED");
break;
default:
break;
@@ -313,15 +549,15 @@ static void _zh_encoder_event_handler(void *arg, esp_event_base_t event_base, in
}
break;
case ZH_ENCODER_EVENT:
zh_encoder_event_on_isr_t *encoder_event = event_data;
zh_encoder_event_on_isr_t *encoder_event = (zh_encoder_event_on_isr_t *)event_data;
if (_is_menu_mode == true)
{
_current_menu_value = (uint8_t)encoder_event->encoder_position; // -V2004
zh_160x_clear_row(&_lcd_handle, 0);
zh_160x_set_cursor(&_lcd_handle, 0, 0);
zh_160x_print_char(&_lcd_handle, "MODE:");
zh_160x_print_char(&_lcd_handle, _main_menu[(uint8_t)encoder_event->encoder_position]); // -V2004
zh_160x_set_cursor(&_lcd_handle, 0, 0);
_current_menu_value = (uint8_t)encoder_event->encoder_position;
zh_160x_clear_row(&_lcd_160x_handle, 0);
zh_160x_set_cursor(&_lcd_160x_handle, 0, 0);
zh_160x_print_char(&_lcd_160x_handle, "MODE:");
zh_160x_print_char(&_lcd_160x_handle, _main_menu[(uint8_t)encoder_event->encoder_position]);
zh_160x_set_cursor(&_lcd_160x_handle, 0, 0);
}
else
{
@@ -335,11 +571,12 @@ static void _zh_encoder_event_handler(void *arg, esp_event_base_t event_base, in
static void _zh_espnow_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
// To Do.
}
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)
{
case WIFI_EVENT_STA_START:
@@ -348,7 +585,10 @@ static void _zh_wifi_event_handler(void *arg, esp_event_base_t event_base, int32
case WIFI_EVENT_STA_DISCONNECTED:
if (_is_wifi_connected == true)
{
vTaskDelete(_zh_stats_task_handle);
if (_zh_stats_task_handle != NULL)
{
vTaskDelete(_zh_stats_task_handle);
}
zh_syslog_deinit();
_is_wifi_connected = false;
}
@@ -364,7 +604,11 @@ 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();
strcpy(config.syslog_ip, SYSLOG_IP);
zh_syslog_init(&config);
xTaskCreatePinnedToCore(&_zh_stats_processing_task, "stats_processing_task", STATS_TASK_STACK_SIZE, NULL, STATS_TASK_PRIORITY, &_zh_stats_task_handle, tskNO_AFFINITY);
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);
}
break;
default:
break;
@@ -373,6 +617,7 @@ static void _zh_wifi_event_handler(void *arg, esp_event_base_t event_base, int32
static void _zh_inclinometer_reset_timer_on_alarm_cb(void *arg)
{
(void)arg;
_is_reset_mode = false;
_is_reset_occurred = true;
if (_is_menu_mode == false)
@@ -394,11 +639,13 @@ static void _zh_inclinometer_reset_timer_on_alarm_cb(void *arg)
static void _zh_a_inclinometer_processing_task(void *pvParameter)
{
float prev_angle = 0, current_angle = 0;
(void)pvParameter;
float prev_angle = 0.0;
float current_angle = 0.0;
xSemaphoreTake(_lcd_mutex, portMAX_DELAY);
zh_160x_set_cursor(&_lcd_handle, 1, 0);
zh_160x_print_char(&_lcd_handle, "A:");
zh_160x_print_float(&_lcd_handle, current_angle, 1);
zh_160x_set_cursor(&_lcd_160x_handle, 1, 0);
zh_160x_print_char(&_lcd_160x_handle, "A:");
zh_160x_print_float(&_lcd_160x_handle, current_angle, 1);
xSemaphoreGive(_lcd_mutex);
for (;;)
{
@@ -407,10 +654,10 @@ static void _zh_a_inclinometer_processing_task(void *pvParameter)
{
prev_angle = current_angle;
xSemaphoreTake(_lcd_mutex, portMAX_DELAY);
zh_160x_set_cursor(&_lcd_handle, 1, 5);
zh_160x_print_char(&_lcd_handle, " ");
zh_160x_set_cursor(&_lcd_handle, 1, 2);
zh_160x_print_float(&_lcd_handle, current_angle, 1);
zh_160x_set_cursor(&_lcd_160x_handle, 1, 5);
zh_160x_print_char(&_lcd_160x_handle, " ");
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);
@@ -421,11 +668,13 @@ static void _zh_a_inclinometer_processing_task(void *pvParameter)
static void _zh_b_inclinometer_processing_task(void *pvParameter)
{
float prev_angle = 0, current_angle = 0;
(void)pvParameter;
float prev_angle = 0.0;
float current_angle = 0.0;
xSemaphoreTake(_lcd_mutex, portMAX_DELAY);
zh_160x_set_cursor(&_lcd_handle, 1, 8);
zh_160x_print_char(&_lcd_handle, "B:");
zh_160x_print_float(&_lcd_handle, current_angle, 1);
zh_160x_set_cursor(&_lcd_160x_handle, 1, 8);
zh_160x_print_char(&_lcd_160x_handle, "B:");
zh_160x_print_float(&_lcd_160x_handle, current_angle, 1);
xSemaphoreGive(_lcd_mutex);
for (;;)
{
@@ -434,10 +683,10 @@ static void _zh_b_inclinometer_processing_task(void *pvParameter)
{
prev_angle = current_angle;
xSemaphoreTake(_lcd_mutex, portMAX_DELAY);
zh_160x_set_cursor(&_lcd_handle, 1, 13);
zh_160x_print_char(&_lcd_handle, " ");
zh_160x_set_cursor(&_lcd_handle, 1, 10);
zh_160x_print_float(&_lcd_handle, current_angle, 1);
zh_160x_set_cursor(&_lcd_160x_handle, 1, 13);
zh_160x_print_char(&_lcd_160x_handle, " ");
zh_160x_set_cursor(&_lcd_160x_handle, 1, 10);
zh_160x_print_float(&_lcd_160x_handle, current_angle, 1);
xSemaphoreGive(_lcd_mutex);
}
vTaskDelay(100 / portTICK_PERIOD_MS);
@@ -448,11 +697,13 @@ static void _zh_b_inclinometer_processing_task(void *pvParameter)
static void _zh_a_tachometer_processing_task(void *pvParameter)
{
uint16_t prev_rpm = 0, current_rpm = 0;
(void)pvParameter;
uint16_t prev_rpm = 0;
uint16_t current_rpm = 0;
xSemaphoreTake(_lcd_mutex, portMAX_DELAY);
zh_160x_set_cursor(&_lcd_handle, 1, 0);
zh_160x_print_char(&_lcd_handle, "A:");
zh_160x_print_int(&_lcd_handle, current_rpm);
zh_160x_set_cursor(&_lcd_160x_handle, 1, 0);
zh_160x_print_char(&_lcd_160x_handle, "A:");
zh_160x_print_int(&_lcd_160x_handle, current_rpm);
xSemaphoreGive(_lcd_mutex);
for (;;)
{
@@ -461,10 +712,10 @@ static void _zh_a_tachometer_processing_task(void *pvParameter)
{
prev_rpm = current_rpm;
xSemaphoreTake(_lcd_mutex, portMAX_DELAY);
zh_160x_set_cursor(&_lcd_handle, 1, 3);
zh_160x_print_char(&_lcd_handle, " ");
zh_160x_set_cursor(&_lcd_handle, 1, 2);
zh_160x_print_int(&_lcd_handle, current_rpm);
zh_160x_set_cursor(&_lcd_160x_handle, 1, 3);
zh_160x_print_char(&_lcd_160x_handle, " ");
zh_160x_set_cursor(&_lcd_160x_handle, 1, 2);
zh_160x_print_int(&_lcd_160x_handle, current_rpm);
xSemaphoreGive(_lcd_mutex);
}
vTaskDelay(100 / portTICK_PERIOD_MS);
@@ -475,11 +726,13 @@ static void _zh_a_tachometer_processing_task(void *pvParameter)
static void _zh_b_tachometer_processing_task(void *pvParameter)
{
uint16_t prev_rpm = 0, current_rpm = 0;
(void)pvParameter;
uint16_t prev_rpm = 0;
uint16_t current_rpm = 0;
xSemaphoreTake(_lcd_mutex, portMAX_DELAY);
zh_160x_set_cursor(&_lcd_handle, 1, 8);
zh_160x_print_char(&_lcd_handle, "B:");
zh_160x_print_int(&_lcd_handle, current_rpm);
zh_160x_set_cursor(&_lcd_160x_handle, 1, 8);
zh_160x_print_char(&_lcd_160x_handle, "B:");
zh_160x_print_int(&_lcd_160x_handle, current_rpm);
xSemaphoreGive(_lcd_mutex);
for (;;)
{
@@ -488,10 +741,10 @@ static void _zh_b_tachometer_processing_task(void *pvParameter)
{
prev_rpm = current_rpm;
xSemaphoreTake(_lcd_mutex, portMAX_DELAY);
zh_160x_set_cursor(&_lcd_handle, 1, 11);
zh_160x_print_char(&_lcd_handle, " ");
zh_160x_set_cursor(&_lcd_handle, 1, 10);
zh_160x_print_int(&_lcd_handle, current_rpm);
zh_160x_set_cursor(&_lcd_160x_handle, 1, 11);
zh_160x_print_char(&_lcd_160x_handle, " ");
zh_160x_set_cursor(&_lcd_160x_handle, 1, 10);
zh_160x_print_int(&_lcd_160x_handle, current_rpm);
xSemaphoreGive(_lcd_mutex);
}
vTaskDelay(100 / portTICK_PERIOD_MS);
@@ -502,51 +755,52 @@ static void _zh_b_tachometer_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();
for (;;)
{
char buffer[1024] = {0};
const zh_encoder_stats_t *encoder_stats = zh_encoder_get_stats();
const zh_pcf8574_stats_t *pcf8574_stats = zh_pcf8574_get_stats();
sprintf(buffer, "A inclinometer task minimum free stack size: %ld.\n", _task_stats.a_inclinometer_task_min_stack_size);
snprintf(buffer, sizeof(buffer), "A inclinometer task minimum free stack size: %lu.\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);
vTaskDelay(500 / portTICK_PERIOD_MS);
sprintf(buffer, "B inclinometer task minimum free stack size: %ld.\n", _task_stats.b_inclinometer_task_min_stack_size);
snprintf(buffer, sizeof(buffer), "B inclinometer task minimum free stack size: %lu.\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);
vTaskDelay(500 / portTICK_PERIOD_MS);
sprintf(buffer, "A tachometer task minimum free stack size: %ld.\n", _task_stats.b_tachometer_task_min_stack_size);
snprintf(buffer, sizeof(buffer), "A tachometer task minimum free stack size: %lu.\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);
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);
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);
sprintf(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);
vTaskDelay(500 / portTICK_PERIOD_MS);
sprintf(buffer, "Stats task minimum free stack size: %ld.\n", (uint32_t)uxTaskGetStackHighWaterMark(NULL));
snprintf(buffer, sizeof(buffer), "Stats task minimum free stack size: %lu.\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);
vTaskDelay(500 / portTICK_PERIOD_MS);
sprintf(buffer, "Encoder task minimum free stack size: %ld.\n", encoder_stats->min_stack_size);
snprintf(buffer, sizeof(buffer), "Encoder task minimum free stack size: %lu.\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);
vTaskDelay(500 / portTICK_PERIOD_MS);
sprintf(buffer, "Expander task minimum free stack size: %ld.\n", pcf8574_stats->min_stack_size);
snprintf(buffer, sizeof(buffer), "Expander task minimum free stack size: %lu.\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);
vTaskDelay(500 / portTICK_PERIOD_MS);
sprintf(buffer, "Number of event post error: %ld.\n", encoder_stats->event_post_error);
snprintf(buffer, sizeof(buffer), "Number of event post error: %lu.\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);
vTaskDelay(500 / portTICK_PERIOD_MS);
sprintf(buffer, "Number of queue overflow error: %ld.\n", encoder_stats->queue_overflow_error);
snprintf(buffer, sizeof(buffer), "Number of queue overflow error: %lu.\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);
vTaskDelay(500 / portTICK_PERIOD_MS);
sprintf(buffer, "Number of i2c driver error: %ld.\n", pcf8574_stats->i2c_driver_error);
snprintf(buffer, sizeof(buffer), "Number of i2c driver error: %lu.\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);
vTaskDelay(500 / portTICK_PERIOD_MS);
sprintf(buffer, "Number of event post error: %ld.\n", pcf8574_stats->event_post_error);
snprintf(buffer, sizeof(buffer), "Number of event post error: %lu.\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);
vTaskDelay(500 / portTICK_PERIOD_MS);
sprintf(buffer, "Number of vector error: %ld.\n", pcf8574_stats->vector_error);
snprintf(buffer, sizeof(buffer), "Number of vector error: %lu.\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);
vTaskDelay(500 / portTICK_PERIOD_MS);
sprintf(buffer, "Number of queue overflow error: %ld.\n", pcf8574_stats->queue_overflow_error);
snprintf(buffer, sizeof(buffer), "Number of queue overflow error: %lu.\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);
vTaskDelay(60000 / portTICK_PERIOD_MS);
}
vTaskDelete(NULL);
}
}
+64 -62
View File
@@ -1,5 +1,27 @@
/**
* @file ate0005.h
*
* @brief Configuration and interface definitions of the ATE0005 universal
* optical 2-channel angle gauge and tachometer.
*
* 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 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
@@ -15,82 +37,62 @@
#include "zh_espnow.h"
#include "zh_syslog.h"
#define STATS_TASK_PRIORITY tskIDLE_PRIORITY
#define STATS_TASK_STACK_SIZE configMINIMAL_STACK_SIZE + 1024
#define A_INCLINOMETER_TASK_PRIORITY tskIDLE_PRIORITY + 5
#define A_INCLINOMETER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE + 512
#define B_INCLINOMETER_TASK_PRIORITY tskIDLE_PRIORITY + 5
#define B_INCLINOMETER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE + 512
#define A_TACHOMETER_TASK_PRIORITY tskIDLE_PRIORITY + 5
#define A_TACHOMETER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE + 512
#define B_TACHOMETER_TASK_PRIORITY tskIDLE_PRIORITY + 5
#define B_TACHOMETER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE + 512
#define ENCODER_TASK_PRIORITY tskIDLE_PRIORITY + 4
#define ENCODER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE
#define EXPANDER_TASK_PRIORITY tskIDLE_PRIORITY + 4
#define EXPANDER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE
#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
#define TRIAC_OFF false
#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
#define ENCODER_BUTTON_PRESSED false
#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
#define LED_ON ZH_PCF8574_GPIO_HIGH
#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
#define RELAY_ON ZH_PCF8574_GPIO_HIGH
#define SYSLOG_IP "77.51.201.8" /*!< IP address of the remote syslog server */
#define MODE_ANGLE_ANGLE 0
#define MAIN_MENU_ANGLE_ANGLE_NAME "ANGLE/ANGLE"
#define MODE_TACHO_TACHO 1
#define MAIN_MENU_TACHO_TACHO_NAME "TACHO/TACHO"
#define MODE_ANGLE_TACHO 2
#define MAIN_MENU_ANGLE_TACHO_NAME "ANGLE/TACHO"
#define MODE_REMOTE 3
#define MAIN_MENU_REMOTE_NAME "REMOTE"
#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"
#define WIFI_AP_PASS "repairlab"
#define WIFI_AP_CHANNEL 1
#define MAX_AP_CONNECTION 4
#define LCD_I2C_ADDRESS 0x20 /*!< I2C address of the LCD PCF8574 expander (U2) */
#define WIFI_STA_SSID "rick_professor"
#define WIFI_STA_PASS "repairlab"
#define WIFI_STA_MAXIMUM_RETRY 10
#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"
#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
#define I2C_SDA_GPIO GPIO_NUM_21
#define LCD_I2C_ADDRESS 0x20 /*!< U2. */
#define MODE_ENCODER_NUMBER 0x01 /*!< Connector EN1. */
#define MODE_ENCODER_A_GPIO GPIO_NUM_27
#define MODE_ENCODER_B_GPIO GPIO_NUM_26
#define MODE_ENCODER_S_GPIO GPIO_NUM_25
#define A_OPTICAL_DEVICE_A_GPIO GPIO_NUM_14 /*!< Connector EN2. */
#define A_OPTICAL_DEVICE_B_GPIO GPIO_NUM_13
#define B_OPTICAL_DEVICE_A_GPIO GPIO_NUM_18 /*!< Connector EN3. */
#define B_OPTICAL_DEVICE_B_GPIO GPIO_NUM_19
#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
typedef struct
{
uint32_t a_inclinometer_task_min_stack_size;
uint32_t b_inclinometer_task_min_stack_size;
uint32_t a_tachometer_task_min_stack_size;
uint32_t b_tachometer_task_min_stack_size;
} task_stats_t;
#ifdef __cplusplus
}
#endif
#endif
+22
View File
@@ -0,0 +1,22 @@
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>"
dependencies:
idf: ">=5.0"
zh_160x_i2c:
git: "https://github.com/aZholtikov/zh_160x_i2c.git"
zh_syslog:
git: "https://github.com/aZholtikov/zh_syslog.git"
zh_ota_server:
git: "https://github.com/aZholtikov/zh_ota_server.git"
zh_encoder:
git: "https://github.com/aZholtikov/zh_encoder.git"
zh_inclinometer:
git: "https://github.com/aZholtikov/zh_inclinometer"
zh_tachometer:
git: "https://github.com/aZholtikov/zh_tachometer"
zh_espnow:
git: "https://github.com/aZholtikov/zh_espnow"
+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_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
+1 -1
View File
@@ -1 +1 @@
1.1.0
1.1.2
+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"
```