Add KiCad files and USB CDC logging support

This commit is contained in:
2025-12-16 08:57:32 +02:00
parent de959b9a8b
commit f3d5e4018b
18 changed files with 7557 additions and 14 deletions

View File

@@ -1,6 +1,6 @@
idf_component_register(SRCS "main.c"
"dcdc_controller.c"
"usb_cdc_cli.c"
"usb_cdc_log.c"
"ws2812_status.c"
"ina226_monitor.c"
"uart_mux.c"

156
main/Kconfig.projbuild Normal file
View File

@@ -0,0 +1,156 @@
menu "Налаштування watch-watch"
menu "DC/DC контролер"
config WATCH_DCDC_EN_GPIO_0
int "GPIO EN каналу 0"
range 0 48
default 2
config WATCH_DCDC_EN_GPIO_1
int "GPIO EN каналу 1"
range 0 48
default 4
config WATCH_DCDC_EN_GPIO_2
int "GPIO EN каналу 2"
range 0 48
default 5
config WATCH_DCDC_EN_GPIO_3
int "GPIO EN каналу 3"
range 0 48
default 18
config WATCH_DCDC_EN_GPIO_4
int "GPIO EN каналу 4"
range 0 48
default 19
endmenu
config WATCH_WS2812_LED_COUNT
int "Кількість статусних світлодіодів WS2812"
range 1 30
default 5
help
Визначає кількість послідовно з’єднаних WS2812 індикаторів,
що показують стан каналів DC/DC.
config WATCH_WS2812_GPIO
int "GPIO для WS2812"
range 0 48
default 8
help
Встановіть номер GPIO, до якого підключено стрічку WS2812
(за замовчуванням GPIO8).
config WATCH_WS2812_RMT_RESOLUTION
int "RMT роздільна здатність (Гц)"
default 10000000
help
Тактова частота RMT драйвера для управління WS2812.
10 МГц = 0.1 мкс на імпульс.
endmenu
menu "INA226 моніторинг"
config WATCH_INA226_ENABLED
bool "Увімкнути моніторинг INA226"
default y
help
Якщо увімкнено, ESP32-S3 опитує датчики INA226 для вимірювання
напруги, струму та потужності кожного каналу.
config WATCH_INA226_I2C_PORT
int "I2C порт"
range 0 1
default 0
config WATCH_INA226_I2C_SDA
int "GPIO SDA"
range 0 48
default 6
config WATCH_INA226_I2C_SCL
int "GPIO SCL"
range 0 48
default 7
config WATCH_INA226_I2C_FREQ_HZ
int "Швидкість I2C (Гц)"
range 10000 1000000
default 400000
config WATCH_INA226_SHUNT_MILLIOHM
int "Опір шунта (мОм)"
range 1 500
default 10
config WATCH_INA226_CURRENT_LSB_uA
int "Крок струму (мкА/LSB)"
range 10 10000
default 100
config WATCH_INA226_SAMPLE_INTERVAL_MS
int "Інтервал опитування (мс)"
range 50 5000
default 500
config WATCH_INA226_ADDR
hex "Адреса INA226 (загальна шина)"
default 0x40
endmenu
menu "UART мультиплексор"
config WATCH_UART_MUX_ENABLED
bool "Увімкнути взаємодію з Raspberry Pi через UART"
default y
config WATCH_UART_MUX_CHANNELS
int "Кількість каналів (Raspberry Pi)"
range 1 8
default 5
config WATCH_UART_PORT
int "Номер UART"
range 0 2
default 1
config WATCH_UART_BAUD
int "Швидкість UART, біт/с"
default 115200
config WATCH_UART_TX_GPIO
int "GPIO TX"
default 17
config WATCH_UART_RX_GPIO
int "GPIO RX"
default 16
config WATCH_UART_MUX_SEL_A0
int "GPIO A0"
default 9
config WATCH_UART_MUX_SEL_A1
int "GPIO A1"
default 10
config WATCH_UART_MUX_SEL_A2
int "GPIO A2"
default 11
config WATCH_UART_MUX_DEFAULT_READ_LEN
int "Типова довжина читання (байт)"
default 128
config WATCH_UART_HEARTBEAT_TIMEOUT_SEC
int "Тайм-аут heartbeat (сек.)"
range 5 600
default 60
endmenu

View File

@@ -2,15 +2,32 @@
#include "driver/gpio.h"
#include "esp_log.h"
#include "sdkconfig.h"
static const char *TAG = "dcdc";
#ifndef CONFIG_WATCH_DCDC_EN_GPIO_0
#define CONFIG_WATCH_DCDC_EN_GPIO_0 2
#endif
#ifndef CONFIG_WATCH_DCDC_EN_GPIO_1
#define CONFIG_WATCH_DCDC_EN_GPIO_1 4
#endif
#ifndef CONFIG_WATCH_DCDC_EN_GPIO_2
#define CONFIG_WATCH_DCDC_EN_GPIO_2 5
#endif
#ifndef CONFIG_WATCH_DCDC_EN_GPIO_3
#define CONFIG_WATCH_DCDC_EN_GPIO_3 18
#endif
#ifndef CONFIG_WATCH_DCDC_EN_GPIO_4
#define CONFIG_WATCH_DCDC_EN_GPIO_4 19
#endif
static const gpio_num_t s_dcdc_gpio_map[DCDC_CHANNEL_COUNT] = {
GPIO_NUM_2,
GPIO_NUM_4,
GPIO_NUM_5,
GPIO_NUM_18,
GPIO_NUM_19,
(gpio_num_t)CONFIG_WATCH_DCDC_EN_GPIO_0,
(gpio_num_t)CONFIG_WATCH_DCDC_EN_GPIO_1,
(gpio_num_t)CONFIG_WATCH_DCDC_EN_GPIO_2,
(gpio_num_t)CONFIG_WATCH_DCDC_EN_GPIO_3,
(gpio_num_t)CONFIG_WATCH_DCDC_EN_GPIO_4,
};
static bool s_initialized;

View File

@@ -1,3 +1,9 @@
/*
* Developed by TComLab
* Version: v0.1
* Date: 2025-12-15
*/
#include "ina226_monitor.h"
#include <math.h>
@@ -50,6 +56,8 @@ static uint8_t s_address = CONFIG_WATCH_INA226_ADDR;
static ina226_reading_t s_last_reading;
#endif
// Ініціалізує контролер INA226: налаштовує I2C, записує конфігурацію та
// калібрувальні значення, а також зберігає початковий стан вимірювань.
esp_err_t ina226_monitor_init(void)
{
#if !CONFIG_WATCH_INA226_ENABLED
@@ -103,6 +111,7 @@ esp_err_t ina226_monitor_init(void)
#endif
}
// Дозволяє швидко перевірити, чи був модуль INA226 успішно ініціалізований.
bool ina226_monitor_ready(void)
{
#if CONFIG_WATCH_INA226_ENABLED
@@ -112,12 +121,14 @@ bool ina226_monitor_ready(void)
#endif
}
// INA226 вимірює єдиний канал живлення, тому повертаємо 1.
size_t ina226_monitor_channel_count(void)
{
return 1;
}
#if CONFIG_WATCH_INA226_ENABLED
// Зчитує 16-бітний регістр INA226, використовуючи транзакцію write-then-read.
static esp_err_t ina226_read_register(uint8_t reg, uint16_t *out_value)
{
uint8_t value[2];
@@ -132,6 +143,7 @@ static esp_err_t ina226_read_register(uint8_t reg, uint16_t *out_value)
}
#endif
// Виконує одне вимірювання напруги/струму через INA226 та кешує результат.
esp_err_t ina226_monitor_sample(ina226_reading_t *out_reading)
{
#if !CONFIG_WATCH_INA226_ENABLED
@@ -162,6 +174,7 @@ esp_err_t ina226_monitor_sample(ina226_reading_t *out_reading)
#endif
}
// Повертає останній виміряний набір даних без нового звернення до шини I2C.
const ina226_reading_t *ina226_monitor_get_last(void)
{
#if CONFIG_WATCH_INA226_ENABLED

View File

@@ -1,3 +1,9 @@
/*
* Developed by TComLab
* Version: v0.1
* Date: 2025-12-15
*/
#pragma once
#include <stdbool.h>
@@ -11,8 +17,13 @@ typedef struct {
float power_mw;
} ina226_reading_t;
// Ініціалізує INA226: конфігурує I2C та калібрує вимірювач.
esp_err_t ina226_monitor_init(void);
// true, якщо драйвер вже ініціалізований.
bool ina226_monitor_ready(void);
// INA226 підтримує один канал, але інтерфейс залишається узагальненим.
size_t ina226_monitor_channel_count(void);
// Зчитує нові дані; out_reading може бути NULL, якщо дані не потрібні.
esp_err_t ina226_monitor_sample(ina226_reading_t *out_reading);
// Повертає кеш останнього вимірювання або NULL, якщо модуль неактивний.
const ina226_reading_t *ina226_monitor_get_last(void);

View File

@@ -1,3 +1,9 @@
/*
* Developed by TComLab
* Version: v0.1
* Date: 2025-12-15
*/
#include <stddef.h>
#include "freertos/FreeRTOS.h"
@@ -7,13 +13,21 @@
#include "dcdc_controller.h"
#include "ina226_monitor.h"
#include "uart_mux.h"
#include "usb_cdc_cli.h"
#include "usb_cdc_log.h"
#include "ws2812_status.h"
static const char *TAG = "watch-watch";
void app_main(void)
{
if (usb_cdc_log_init() != ESP_OK) {
ESP_LOGW(TAG, "USB CDC лог недоступний");
} else {
ESP_LOGI(TAG, "USB CDC лог активовано");
}
vTaskDelay(pdMS_TO_TICKS(2000)); // Затримка для стабілізації живлення після перезавантаження
ESP_LOGI(TAG, "Запуск watch-watch systems");
if (dcdc_init() != ESP_OK) {
ESP_LOGE(TAG, "Помилка ініціалізації DCDC контролера");
ws2812_status_init();
@@ -23,6 +37,10 @@ void app_main(void)
if (ws2812_status_init() == ESP_OK) {
ws2812_status_refresh_from_dcdc();
esp_err_t anim_err = ws2812_status_play_bringup_animation(2, 120);
if (anim_err != ESP_OK) {
ESP_LOGW(TAG, "Анімація WS2812 недоступна: %s", esp_err_to_name(anim_err));
}
} else {
ESP_LOGW(TAG, "WS2812 статусний індикатор недоступний");
}
@@ -40,11 +58,7 @@ void app_main(void)
ESP_LOGW(TAG, "UART мультиплексор недоступний");
}
if (usb_cdc_cli_init() != ESP_OK) {
ESP_LOGW(TAG, "USB CDC CLI недоступний");
} else {
ESP_LOGI(TAG, "USB CDC CLI запущено");
}
ESP_LOGI(TAG, "Початок послідовного ввімкнення каналів з інтервалом 4 с");
const TickType_t on_time = pdMS_TO_TICKS(4000);

View File

@@ -1,3 +1,9 @@
/*
* Developed by TComLab
* Version: v0.1
* Date: 2025-12-15
*/
#include "uart_mux.h"
#include <string.h>
@@ -38,6 +44,8 @@ static bool s_initialized;
static int64_t s_last_heartbeat_us[UART_MUX_MAX_CHANNELS];
static TaskHandle_t s_watchdog_task;
// Перемикає апаратний мультиплексор на вказаний канал під захистом мьютекса,
// оновлюючи таймстемп останнього heartbeat для контролю watchdog.
static esp_err_t uart_mux_select_locked(size_t channel)
{
if (channel >= UART_MUX_MAX_CHANNELS) {
@@ -56,6 +64,8 @@ static esp_err_t uart_mux_select_locked(size_t channel)
return ESP_OK;
}
// Періодично опитує всі канали, щоб зчитати heartbeat та перезапускає DCDC,
// якщо канал «мовчить» довше за CONFIG_WATCH_UART_HEARTBEAT_TIMEOUT_SEC.
static void uart_mux_watchdog_task(void *arg)
{
const TickType_t poll_interval = pdMS_TO_TICKS(1000);
@@ -94,6 +104,8 @@ static void uart_mux_watchdog_task(void *arg)
#endif // CONFIG_WATCH_UART_MUX_ENABLED
// Налаштовує GPIO-вибірники, драйвер UART та створює watchdog-задачу для
// мультиплексора; повторний виклик просто повертає ESP_OK.
esp_err_t uart_mux_init(void)
{
#if !CONFIG_WATCH_UART_MUX_ENABLED
@@ -156,6 +168,7 @@ esp_err_t uart_mux_init(void)
#endif
}
// Повертає ознаку ініціалізації модулю UART мультиплексора.
bool uart_mux_ready(void)
{
#if CONFIG_WATCH_UART_MUX_ENABLED
@@ -165,11 +178,14 @@ bool uart_mux_ready(void)
#endif
}
// Кількість доступних каналів, визначених у конфігурації.
size_t uart_mux_channel_count(void)
{
return CONFIG_WATCH_UART_MUX_CHANNELS;
}
// Перемикається на канал, передає буфер даних через загальний UART та
// захищає доступ до шини мьютексом, щоб уникнути гонок між задачами.
esp_err_t uart_mux_write(size_t channel, const uint8_t *data, size_t length, TickType_t timeout)
{
#if !CONFIG_WATCH_UART_MUX_ENABLED
@@ -199,6 +215,8 @@ esp_err_t uart_mux_write(size_t channel, const uint8_t *data, size_t length, Tic
#endif
}
// Читає дані з вказаного каналу, оновлюючи heartbeat під час успішного
// зчитування, і повертає кількість байтів через out_length.
esp_err_t uart_mux_read(size_t channel, uint8_t *buffer, size_t buffer_size, size_t *out_length, TickType_t timeout)
{
#if !CONFIG_WATCH_UART_MUX_ENABLED

View File

@@ -1,3 +1,9 @@
/*
* Developed by TComLab
* Version: v0.1
* Date: 2025-12-15
*/
#pragma once
#include <stddef.h>
@@ -5,8 +11,13 @@
#include "freertos/FreeRTOS.h"
#include "esp_err.h"
// Ініціалізує апаратний мультиплексор UART: GPIO, UART драйвер та watchdog.
esp_err_t uart_mux_init(void);
// Повертає true, якщо драйвер готовий приймати виклики.
bool uart_mux_ready(void);
// Кількість доступних каналів мультиплексора.
size_t uart_mux_channel_count(void);
// Відправляє буфер даних на вказаний канал з тайм-аутом очікування мьютекса.
esp_err_t uart_mux_write(size_t channel, const uint8_t *data, size_t length, TickType_t timeout);
// Зчитує дані з каналу, повертаючи кількість байтів через out_length.
esp_err_t uart_mux_read(size_t channel, uint8_t *buffer, size_t buffer_size, size_t *out_length, TickType_t timeout);

98
main/usb_cdc_log.c Normal file
View File

@@ -0,0 +1,98 @@
#include "usb_cdc_log.h"
#include <stdarg.h>
#include <stdio.h>
#include "esp_check.h"
#include "esp_log.h"
#include "tinyusb.h"
#include "tusb_cdc_acm.h"
static const char *TAG = "usb_log";
static bool s_initialized;
static bool s_host_ready;
static vprintf_like_t s_prev_vprintf;
static int usb_cdc_vprintf(const char *fmt, va_list args)
{
if (s_host_ready) {
char buffer[256];
va_list args_copy;
va_copy(args_copy, args);
int len = vsnprintf(buffer, sizeof(buffer), fmt, args_copy);
va_end(args_copy);
if (len > 0) {
if (len >= (int)sizeof(buffer)) {
len = sizeof(buffer) - 1;
}
if (tinyusb_cdcacm_write_queue(TINYUSB_CDC_ACM_0, (const uint8_t *)buffer, len) == ESP_OK) {
tinyusb_cdcacm_write_flush(TINYUSB_CDC_ACM_0, 0);
}
}
return len;
}
if (s_prev_vprintf) {
va_list args_copy;
va_copy(args_copy, args);
int ret = s_prev_vprintf(fmt, args_copy);
va_end(args_copy);
return ret;
}
return 0;
}
static void usb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event)
{
(void)itf;
s_host_ready = event->line_state_changed_data.dtr;
if (s_host_ready) {
const char banner[] = "\r\nwatch-watch log over USB CDC\r\n";
tinyusb_cdcacm_write_queue(TINYUSB_CDC_ACM_0, (const uint8_t *)banner, sizeof(banner) - 1);
tinyusb_cdcacm_write_flush(TINYUSB_CDC_ACM_0, 0);
}
}
bool usb_cdc_log_ready(void)
{
return s_host_ready;
}
esp_err_t usb_cdc_log_init(void)
{
if (s_initialized) {
return ESP_OK;
}
const tinyusb_config_t tusb_cfg = {
.device_descriptor = NULL,
.string_descriptor = NULL,
.external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = NULL,
.hs_configuration_descriptor = NULL,
.qualifier_descriptor = NULL,
#else
.configuration_descriptor = NULL,
#endif
};
ESP_RETURN_ON_ERROR(tinyusb_driver_install(&tusb_cfg), TAG, "TinyUSB init failed");
const tinyusb_config_cdcacm_t acm_cfg = {
.usb_dev = TINYUSB_USBDEV_0,
.cdc_port = TINYUSB_CDC_ACM_0,
.rx_unread_buf_sz = 64,
.callback_rx = NULL,
.callback_rx_wanted_char = NULL,
.callback_line_state_changed = usb_cdc_line_state_changed_callback,
.callback_line_coding_changed = NULL,
};
ESP_RETURN_ON_ERROR(tusb_cdc_acm_init(&acm_cfg), TAG, "CDC init failed");
s_prev_vprintf = esp_log_set_vprintf(usb_cdc_vprintf);
s_initialized = true;
ESP_LOGI(TAG, "ESP_LOG перенаправлено в USB CDC");
return ESP_OK;
}

15
main/usb_cdc_log.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#include <stdbool.h>
#include "esp_err.h"
/**
* @brief Ініціалізує TinyUSB CDC та перенаправляє ESP_LOG у USB.
*/
esp_err_t usb_cdc_log_init(void);
/**
* @brief Чи встановив хост DTR і готовий приймати лог.
*/
bool usb_cdc_log_ready(void);

View File

@@ -4,6 +4,8 @@
#include "led_strip.h"
#include "esp_check.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#ifndef CONFIG_WATCH_WS2812_LED_COUNT
@@ -132,3 +134,62 @@ esp_err_t ws2812_status_refresh_from_dcdc(void)
}
return ws2812_status_apply();
}
esp_err_t ws2812_status_play_bringup_animation(size_t cycles, uint32_t step_delay_ms)
{
if (!s_strip) {
return ESP_ERR_INVALID_STATE;
}
if (cycles == 0) {
cycles = 1;
}
if (step_delay_ms == 0) {
step_delay_ms = 150;
}
bool saved_led_state[WS2812_STATUS_LED_COUNT];
for (size_t i = 0; i < WS2812_STATUS_LED_COUNT; ++i) {
saved_led_state[i] = s_led_state[i];
}
const bool saved_error = s_error_state;
const size_t saved_active = s_active_channel;
s_error_state = false;
s_active_channel = SIZE_MAX;
const TickType_t delay_ticks = pdMS_TO_TICKS(step_delay_ms);
esp_err_t err = ESP_OK;
for (size_t cycle = 0; cycle < cycles && err == ESP_OK; ++cycle) {
for (size_t led = 0; led < WS2812_STATUS_LED_COUNT; ++led) {
for (size_t idx = 0; idx < WS2812_STATUS_LED_COUNT; ++idx) {
const uint8_t g = (idx == led) ? 35 : 0;
err = led_strip_set_pixel(s_strip, idx, 0, g, 0);
if (err != ESP_OK) {
break;
}
}
if (err != ESP_OK) {
break;
}
err = led_strip_refresh(s_strip);
if (err != ESP_OK) {
break;
}
vTaskDelay(delay_ticks);
}
}
for (size_t i = 0; i < WS2812_STATUS_LED_COUNT; ++i) {
s_led_state[i] = saved_led_state[i];
}
s_error_state = saved_error;
s_active_channel = saved_active;
esp_err_t restore_err = ws2812_status_apply();
if (err != ESP_OK) {
return err;
}
return restore_err;
}

View File

@@ -2,6 +2,7 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "esp_err.h"
#include "sdkconfig.h"
@@ -18,3 +19,4 @@ esp_err_t ws2812_status_mark_active(size_t channel);
esp_err_t ws2812_status_clear_active(void);
esp_err_t ws2812_status_set_error(bool has_error);
esp_err_t ws2812_status_refresh_from_dcdc(void);
esp_err_t ws2812_status_play_bringup_animation(size_t cycles, uint32_t step_delay_ms);