Initial project setup
This commit is contained in:
4
main/CMakeLists.txt
Normal file
4
main/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
idf_component_register(SRCS "main.c"
|
||||
"dcdc_controller.c"
|
||||
"usb_cdc_cli.c"
|
||||
INCLUDE_DIRS ".")
|
||||
106
main/dcdc_controller.c
Normal file
106
main/dcdc_controller.c
Normal file
@@ -0,0 +1,106 @@
|
||||
#include "dcdc_controller.h"
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "dcdc";
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
static bool s_initialized;
|
||||
static bool s_channel_state[DCDC_CHANNEL_COUNT];
|
||||
|
||||
static bool dcdc_is_channel_valid(dcdc_channel_t channel)
|
||||
{
|
||||
return channel >= DCDC_CHANNEL_0 && channel < DCDC_CHANNEL_COUNT;
|
||||
}
|
||||
|
||||
esp_err_t dcdc_init(void)
|
||||
{
|
||||
if (s_initialized) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
gpio_config_t cfg = {
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pin_bit_mask = 0,
|
||||
};
|
||||
|
||||
for (int i = 0; i < DCDC_CHANNEL_COUNT; ++i) {
|
||||
cfg.pin_bit_mask = 1ULL << s_dcdc_gpio_map[i];
|
||||
esp_err_t err = gpio_config(&cfg);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Не вдалося налаштувати GPIO %d (канал %d): %s",
|
||||
(int)s_dcdc_gpio_map[i], i, esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(gpio_set_level(s_dcdc_gpio_map[i], 0));
|
||||
s_channel_state[i] = false;
|
||||
}
|
||||
|
||||
s_initialized = true;
|
||||
ESP_LOGI(TAG, "DCDC контролер ініціалізовано, каналів: %d", DCDC_CHANNEL_COUNT);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dcdc_set_state(dcdc_channel_t channel, bool enabled)
|
||||
{
|
||||
if (!dcdc_is_channel_valid(channel)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
esp_err_t err = gpio_set_level(s_dcdc_gpio_map[channel], enabled ? 1 : 0);
|
||||
if (err == ESP_OK) {
|
||||
s_channel_state[channel] = enabled;
|
||||
ESP_LOGI(TAG, "Канал %d -> %s", channel, enabled ? "ON" : "OFF");
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t dcdc_enable(dcdc_channel_t channel)
|
||||
{
|
||||
return dcdc_set_state(channel, true);
|
||||
}
|
||||
|
||||
esp_err_t dcdc_disable(dcdc_channel_t channel)
|
||||
{
|
||||
return dcdc_set_state(channel, false);
|
||||
}
|
||||
|
||||
esp_err_t dcdc_toggle(dcdc_channel_t channel)
|
||||
{
|
||||
if (!dcdc_is_channel_valid(channel)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return dcdc_set_state(channel, !s_channel_state[channel]);
|
||||
}
|
||||
|
||||
bool dcdc_get_state(dcdc_channel_t channel)
|
||||
{
|
||||
if (!dcdc_is_channel_valid(channel)) {
|
||||
return false;
|
||||
}
|
||||
return s_channel_state[channel];
|
||||
}
|
||||
|
||||
size_t dcdc_channel_count(void)
|
||||
{
|
||||
return DCDC_CHANNEL_COUNT;
|
||||
}
|
||||
|
||||
gpio_num_t dcdc_get_gpio(dcdc_channel_t channel)
|
||||
{
|
||||
if (!dcdc_is_channel_valid(channel)) {
|
||||
return GPIO_NUM_NC;
|
||||
}
|
||||
return s_dcdc_gpio_map[channel];
|
||||
}
|
||||
25
main/dcdc_controller.h
Normal file
25
main/dcdc_controller.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#define DCDC_CHANNEL_COUNT 5
|
||||
|
||||
typedef enum {
|
||||
DCDC_CHANNEL_0 = 0,
|
||||
DCDC_CHANNEL_1,
|
||||
DCDC_CHANNEL_2,
|
||||
DCDC_CHANNEL_3,
|
||||
DCDC_CHANNEL_4,
|
||||
} dcdc_channel_t;
|
||||
|
||||
esp_err_t dcdc_init(void);
|
||||
esp_err_t dcdc_set_state(dcdc_channel_t channel, bool enabled);
|
||||
esp_err_t dcdc_enable(dcdc_channel_t channel);
|
||||
esp_err_t dcdc_disable(dcdc_channel_t channel);
|
||||
esp_err_t dcdc_toggle(dcdc_channel_t channel);
|
||||
bool dcdc_get_state(dcdc_channel_t channel);
|
||||
size_t dcdc_channel_count(void);
|
||||
gpio_num_t dcdc_get_gpio(dcdc_channel_t channel);
|
||||
3
main/idf_component.yml
Normal file
3
main/idf_component.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
espressif/esp_tinyusb: "^1"
|
||||
42
main/main.c
Normal file
42
main/main.c
Normal file
@@ -0,0 +1,42 @@
|
||||
#include <stddef.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "dcdc_controller.h"
|
||||
#include "usb_cdc_cli.h"
|
||||
|
||||
static const char *TAG = "watch-watch";
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
if (dcdc_init() != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Помилка ініціалізації DCDC контролера");
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
size_t prev_channel = DCDC_CHANNEL_COUNT - 1;
|
||||
while (true) {
|
||||
for (size_t ch = 0; ch < dcdc_channel_count(); ++ch) {
|
||||
if (prev_channel != ch) {
|
||||
dcdc_disable(prev_channel);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "-> Ввімкнення каналу %d", (int)ch);
|
||||
dcdc_enable(ch);
|
||||
vTaskDelay(on_time);
|
||||
|
||||
prev_channel = ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
296
main/usb_cdc_cli.c
Normal file
296
main/usb_cdc_cli.c
Normal file
@@ -0,0 +1,296 @@
|
||||
#include "usb_cdc_cli.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include "dcdc_controller.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "tinyusb.h"
|
||||
#include "tusb_cdc_acm.h"
|
||||
|
||||
#define CLI_LINE_MAX_LEN 128
|
||||
#define CLI_QUEUE_LEN 8
|
||||
|
||||
#ifndef CONFIG_TINYUSB_CDC_RX_BUFSIZE
|
||||
#define CONFIG_TINYUSB_CDC_RX_BUFSIZE 64
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
size_t length;
|
||||
uint8_t data[CONFIG_TINYUSB_CDC_RX_BUFSIZE];
|
||||
} usb_cli_msg_t;
|
||||
|
||||
static const char *TAG = "usb_cli";
|
||||
|
||||
static QueueHandle_t s_cli_queue;
|
||||
static bool s_cli_initialized;
|
||||
static bool s_host_ready;
|
||||
|
||||
static void usb_cli_task(void *arg);
|
||||
static void usb_cli_process_line(char *line);
|
||||
static void usb_cli_prompt(void);
|
||||
|
||||
static void usb_cli_write_raw(const char *data, size_t len)
|
||||
{
|
||||
if (!s_host_ready || len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tinyusb_cdcacm_write_queue(TINYUSB_CDC_ACM_0, (uint8_t *)data, len) == ESP_OK) {
|
||||
esp_err_t err = tinyusb_cdcacm_write_flush(TINYUSB_CDC_ACM_0, 0);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "CDC flush error: %s", esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void usb_cli_printf(const char *fmt, ...)
|
||||
{
|
||||
if (!s_host_ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
char buffer[192];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int len = vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
if (len > 0) {
|
||||
if (len >= (int)sizeof(buffer)) {
|
||||
len = (int)sizeof(buffer) - 1;
|
||||
}
|
||||
usb_cli_write_raw(buffer, len);
|
||||
}
|
||||
}
|
||||
|
||||
static const char *PROMPT = "\r\nwatch> ";
|
||||
|
||||
static char *usb_cli_trim(char *str)
|
||||
{
|
||||
while (*str && isspace((unsigned char)*str)) {
|
||||
str++;
|
||||
}
|
||||
char *end = str + strlen(str);
|
||||
while (end > str && isspace((unsigned char)*(end - 1))) {
|
||||
*(--end) = '\0';
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
static bool usb_cli_parse_channel(const char *token, dcdc_channel_t *channel)
|
||||
{
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
char *end = NULL;
|
||||
long val = strtol(token, &end, 10);
|
||||
if (end == token || val < 0 || val >= DCDC_CHANNEL_COUNT) {
|
||||
return false;
|
||||
}
|
||||
*channel = (dcdc_channel_t)val;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void usb_cli_print_status(void)
|
||||
{
|
||||
usb_cli_printf("\r\nСтан каналів:\r\n");
|
||||
for (size_t i = 0; i < dcdc_channel_count(); ++i) {
|
||||
dcdc_channel_t ch = (dcdc_channel_t)i;
|
||||
usb_cli_printf(" - CH%u: %s (GPIO %d)\r\n",
|
||||
(unsigned)i,
|
||||
dcdc_get_state(ch) ? "ON" : "OFF",
|
||||
(int)dcdc_get_gpio(ch));
|
||||
}
|
||||
}
|
||||
|
||||
static void usb_cli_prompt(void)
|
||||
{
|
||||
usb_cli_write_raw(PROMPT, strlen(PROMPT));
|
||||
}
|
||||
|
||||
static void usb_cli_handle_switch(const char *cmd, char *args)
|
||||
{
|
||||
dcdc_channel_t channel;
|
||||
char *ctx = NULL;
|
||||
char *channel_str = strtok_r(args, " ", &ctx);
|
||||
if (!usb_cli_parse_channel(channel_str, &channel)) {
|
||||
usb_cli_printf("\r\nНевірний номер каналу\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t err = ESP_OK;
|
||||
if (strcasecmp(cmd, "enable") == 0) {
|
||||
err = dcdc_enable(channel);
|
||||
} else if (strcasecmp(cmd, "disable") == 0) {
|
||||
err = dcdc_disable(channel);
|
||||
} else {
|
||||
err = dcdc_toggle(channel);
|
||||
}
|
||||
|
||||
if (err != ESP_OK) {
|
||||
usb_cli_printf("\r\nПомилка керування каналом: %s\r\n", esp_err_to_name(err));
|
||||
} else {
|
||||
usb_cli_printf("\r\nКанал %d -> %s\r\n", channel,
|
||||
dcdc_get_state(channel) ? "ON" : "OFF");
|
||||
}
|
||||
}
|
||||
|
||||
static void usb_cli_process_line(char *line)
|
||||
{
|
||||
char *trimmed = usb_cli_trim(line);
|
||||
if (*trimmed == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
char *save_ptr = NULL;
|
||||
char *cmd = strtok_r(trimmed, " ", &save_ptr);
|
||||
if (!cmd) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcasecmp(cmd, "help") == 0) {
|
||||
usb_cli_printf(
|
||||
"\r\nДоступні команди:\r\n"
|
||||
" help - показати довідку\r\n"
|
||||
" status - стан всіх каналів\r\n"
|
||||
" enable <n> - увімкнути канал n\r\n"
|
||||
" disable <n> - вимкнути канал n\r\n"
|
||||
" toggle <n> - перемкнути канал n\r\n");
|
||||
} else if (strcasecmp(cmd, "status") == 0) {
|
||||
usb_cli_print_status();
|
||||
} else if (strcasecmp(cmd, "enable") == 0 ||
|
||||
strcasecmp(cmd, "disable") == 0 ||
|
||||
strcasecmp(cmd, "toggle") == 0) {
|
||||
usb_cli_handle_switch(cmd, save_ptr);
|
||||
} else {
|
||||
usb_cli_printf("\r\nНевідома команда '%s'\r\n", cmd);
|
||||
}
|
||||
}
|
||||
|
||||
static void usb_cli_task(void *arg)
|
||||
{
|
||||
usb_cli_msg_t msg;
|
||||
char line[CLI_LINE_MAX_LEN];
|
||||
size_t line_len = 0;
|
||||
|
||||
while (xQueueReceive(s_cli_queue, &msg, portMAX_DELAY)) {
|
||||
for (size_t i = 0; i < msg.length; ++i) {
|
||||
char ch = (char)msg.data[i];
|
||||
|
||||
if (ch == '\r' || ch == '\n') {
|
||||
if (line_len > 0) {
|
||||
line[line_len] = '\0';
|
||||
usb_cli_process_line(line);
|
||||
line_len = 0;
|
||||
}
|
||||
usb_cli_prompt();
|
||||
} else if (ch == 0x7F || ch == '\b') {
|
||||
if (line_len > 0) {
|
||||
line_len--;
|
||||
}
|
||||
} else if (isprint((unsigned char)ch)) {
|
||||
if (line_len < CLI_LINE_MAX_LEN - 1) {
|
||||
line[line_len++] = ch;
|
||||
} else {
|
||||
usb_cli_printf("\r\nРядок занадто довгий\r\n");
|
||||
line_len = 0;
|
||||
usb_cli_prompt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void usb_cdc_rx_callback(int itf, cdcacm_event_t *event)
|
||||
{
|
||||
if (!s_cli_queue) {
|
||||
return;
|
||||
}
|
||||
|
||||
usb_cli_msg_t msg = { 0 };
|
||||
size_t rx_size = 0;
|
||||
esp_err_t ret = tinyusb_cdcacm_read(itf, msg.data, sizeof(msg.data), &rx_size);
|
||||
if (ret == ESP_OK && rx_size > 0) {
|
||||
msg.length = rx_size;
|
||||
if (xQueueSend(s_cli_queue, &msg, 0) != pdTRUE) {
|
||||
ESP_LOGW(TAG, "CLI queue overflow, втрачено %u байт", (unsigned)rx_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void usb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event)
|
||||
{
|
||||
const bool dtr = event->line_state_changed_data.dtr;
|
||||
const bool rts = event->line_state_changed_data.rts;
|
||||
ESP_LOGI(TAG, "CDC лінія %d змінилась: DTR=%d RTS=%d", itf, dtr, rts);
|
||||
s_host_ready = dtr;
|
||||
if (s_host_ready) {
|
||||
usb_cli_printf("\r\nwatch-watch CLI готовий. Надрукуйте 'help'.\r\n");
|
||||
usb_cli_prompt();
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t usb_cdc_cli_init(void)
|
||||
{
|
||||
if (s_cli_initialized) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
s_cli_queue = xQueueCreate(CLI_QUEUE_LEN, sizeof(usb_cli_msg_t));
|
||||
if (!s_cli_queue) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
if (xTaskCreate(usb_cli_task, "usb_cli", 4096, NULL, 5, NULL) != pdPASS) {
|
||||
vQueueDelete(s_cli_queue);
|
||||
s_cli_queue = NULL;
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
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 = CONFIG_TINYUSB_CDC_RX_BUFSIZE,
|
||||
.callback_rx = usb_cdc_rx_callback,
|
||||
.callback_rx_wanted_char = NULL,
|
||||
.callback_line_state_changed = NULL,
|
||||
.callback_line_coding_changed = NULL,
|
||||
};
|
||||
|
||||
ESP_RETURN_ON_ERROR(tusb_cdc_acm_init(&acm_cfg), TAG, "CDC init failed");
|
||||
ESP_RETURN_ON_ERROR(
|
||||
tinyusb_cdcacm_register_callback(TINYUSB_CDC_ACM_0,
|
||||
CDC_EVENT_LINE_STATE_CHANGED,
|
||||
usb_cdc_line_state_changed_callback),
|
||||
TAG,
|
||||
"Line state callback error");
|
||||
|
||||
s_cli_initialized = true;
|
||||
ESP_LOGI(TAG, "USB CDC CLI ініціалізовано");
|
||||
return ESP_OK;
|
||||
}
|
||||
11
main/usb_cdc_cli.h
Normal file
11
main/usb_cdc_cli.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
/**
|
||||
* @brief Ініціалізує USB CDC CLI, підключений до Raspberry Pi 5.
|
||||
*
|
||||
* Модуль запускає TinyUSB CDC ACM та фонове завдання, яке читає рядки з
|
||||
* USB-консолі та надає команди для керування DC/DC каналами.
|
||||
*/
|
||||
esp_err_t usb_cdc_cli_init(void);
|
||||
Reference in New Issue
Block a user