增加了语音抽签控制屏幕显示
增加了校准和财位控制电机旋转
This commit is contained in:
@@ -25,12 +25,15 @@ typedef enum {
|
|||||||
typedef enum {
|
typedef enum {
|
||||||
CONTROL_PLANE_MODEL_COMMAND_UNKNOWN = 0,
|
CONTROL_PLANE_MODEL_COMMAND_UNKNOWN = 0,
|
||||||
CONTROL_PLANE_MODEL_COMMAND_DIRECTION,
|
CONTROL_PLANE_MODEL_COMMAND_DIRECTION,
|
||||||
CONTROL_PLANE_MODEL_COMMAND_CALIBRATE
|
CONTROL_PLANE_MODEL_COMMAND_CALIBRATE,
|
||||||
|
CONTROL_PLANE_MODEL_COMMAND_CHOUQIAN
|
||||||
} control_plane_model_command_type_t;
|
} control_plane_model_command_type_t;
|
||||||
|
|
||||||
typedef void (*control_plane_model_command_callback_t)(
|
typedef void (*control_plane_model_command_callback_t)(
|
||||||
control_plane_model_command_type_t command_type,
|
control_plane_model_command_type_t command_type,
|
||||||
const char *value,
|
const char *value,
|
||||||
|
size_t value_index,
|
||||||
|
bool value_index_valid,
|
||||||
void *user_data);
|
void *user_data);
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ control_plane_direction_command_t control_plane_direction_parse_command_internal
|
|||||||
bool control_plane_extract_model_command(const char *text,
|
bool control_plane_extract_model_command(const char *text,
|
||||||
control_plane_model_command_type_t *out_command_type,
|
control_plane_model_command_type_t *out_command_type,
|
||||||
char *out_value,
|
char *out_value,
|
||||||
size_t out_value_capacity);
|
size_t out_value_capacity,
|
||||||
|
size_t *out_value_index,
|
||||||
|
bool *out_value_index_valid);
|
||||||
const char *control_plane_multimodal_resolve_upstream_mode(void);
|
const char *control_plane_multimodal_resolve_upstream_mode(void);
|
||||||
bool control_plane_multimodal_is_push2talk_mode(void);
|
bool control_plane_multimodal_is_push2talk_mode(void);
|
||||||
const char *control_plane_multimodal_resolve_voice_id(const char *auth_voice_id);
|
const char *control_plane_multimodal_resolve_voice_id(const char *auth_voice_id);
|
||||||
|
|||||||
@@ -51,10 +51,44 @@ static const char *find_command_prefix(const char *text)
|
|||||||
return strstr(cursor, "/*{");
|
return strstr(cursor, "/*{");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool text_contains_chouqian_result(const char *text)
|
||||||
|
{
|
||||||
|
return text != NULL &&
|
||||||
|
(strstr(text, "签文:") != NULL || strstr(text, "签文:") != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool resolve_chouqian_result_index(const char *value, size_t *out_index)
|
||||||
|
{
|
||||||
|
static const char *const s_chouqian_result_names[] = {
|
||||||
|
"卜运签",
|
||||||
|
"和平签",
|
||||||
|
"上上签",
|
||||||
|
"小吉签",
|
||||||
|
"中上签",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (value == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < sizeof(s_chouqian_result_names) / sizeof(s_chouqian_result_names[0]); ++i) {
|
||||||
|
if (strcmp(value, s_chouqian_result_names[i]) == 0) {
|
||||||
|
if (out_index != NULL) {
|
||||||
|
*out_index = i;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool control_plane_extract_model_command(const char *text,
|
bool control_plane_extract_model_command(const char *text,
|
||||||
control_plane_model_command_type_t *out_command_type,
|
control_plane_model_command_type_t *out_command_type,
|
||||||
char *out_value,
|
char *out_value,
|
||||||
size_t out_value_capacity)
|
size_t out_value_capacity,
|
||||||
|
size_t *out_value_index,
|
||||||
|
bool *out_value_index_valid)
|
||||||
{
|
{
|
||||||
if (text == NULL || out_command_type == NULL ||
|
if (text == NULL || out_command_type == NULL ||
|
||||||
out_value == NULL || out_value_capacity == 0U) {
|
out_value == NULL || out_value_capacity == 0U) {
|
||||||
@@ -63,9 +97,20 @@ bool control_plane_extract_model_command(const char *text,
|
|||||||
|
|
||||||
*out_command_type = CONTROL_PLANE_MODEL_COMMAND_UNKNOWN;
|
*out_command_type = CONTROL_PLANE_MODEL_COMMAND_UNKNOWN;
|
||||||
out_value[0] = '\0';
|
out_value[0] = '\0';
|
||||||
|
if (out_value_index != NULL) {
|
||||||
|
*out_value_index = 0U;
|
||||||
|
}
|
||||||
|
if (out_value_index_valid != NULL) {
|
||||||
|
*out_value_index_valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
const char *command_start = find_command_prefix(text);
|
const char *command_start = find_command_prefix(text);
|
||||||
if (command_start == NULL || command_start[0] != '/' || command_start[1] != '*') {
|
if (command_start == NULL || command_start[0] != '/' || command_start[1] != '*') {
|
||||||
|
if (text_contains_chouqian_result(text)) {
|
||||||
|
*out_command_type = CONTROL_PLANE_MODEL_COMMAND_CHOUQIAN;
|
||||||
|
strlcpy(out_value, "签文", out_value_capacity);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +146,7 @@ bool control_plane_extract_model_command(const char *text,
|
|||||||
bool found = false;
|
bool found = false;
|
||||||
cJSON *direction = cJSON_GetObjectItemCaseSensitive(cmd_json, "转向");
|
cJSON *direction = cJSON_GetObjectItemCaseSensitive(cmd_json, "转向");
|
||||||
cJSON *calibrate = cJSON_GetObjectItemCaseSensitive(cmd_json, "校准");
|
cJSON *calibrate = cJSON_GetObjectItemCaseSensitive(cmd_json, "校准");
|
||||||
|
cJSON *chouqian = cJSON_GetObjectItemCaseSensitive(cmd_json, "抽签");
|
||||||
|
|
||||||
if (cJSON_IsString(direction) && direction->valuestring != NULL) {
|
if (cJSON_IsString(direction) && direction->valuestring != NULL) {
|
||||||
if (is_supported_model_direction(direction->valuestring)) {
|
if (is_supported_model_direction(direction->valuestring)) {
|
||||||
@@ -114,6 +160,19 @@ bool control_plane_extract_model_command(const char *text,
|
|||||||
strlcpy(out_value, calibrate->valuestring, out_value_capacity);
|
strlcpy(out_value, calibrate->valuestring, out_value_capacity);
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
|
} else if (cJSON_IsString(chouqian) && chouqian->valuestring != NULL) {
|
||||||
|
size_t result_index = 0U;
|
||||||
|
if (resolve_chouqian_result_index(chouqian->valuestring, &result_index)) {
|
||||||
|
*out_command_type = CONTROL_PLANE_MODEL_COMMAND_CHOUQIAN;
|
||||||
|
strlcpy(out_value, chouqian->valuestring, out_value_capacity);
|
||||||
|
if (out_value_index != NULL) {
|
||||||
|
*out_value_index = result_index;
|
||||||
|
}
|
||||||
|
if (out_value_index_valid != NULL) {
|
||||||
|
*out_value_index_valid = true;
|
||||||
|
}
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cJSON_Delete(cmd_json);
|
cJSON_Delete(cmd_json);
|
||||||
|
|||||||
@@ -197,7 +197,14 @@ static void control_plane_responding_text_bridge(const char *text,
|
|||||||
|
|
||||||
control_plane_model_command_type_t command_type = CONTROL_PLANE_MODEL_COMMAND_UNKNOWN;
|
control_plane_model_command_type_t command_type = CONTROL_PLANE_MODEL_COMMAND_UNKNOWN;
|
||||||
char value[32] = {0};
|
char value[32] = {0};
|
||||||
if (!control_plane_extract_model_command(text, &command_type, value, sizeof(value))) {
|
size_t value_index = 0U;
|
||||||
|
bool value_index_valid = false;
|
||||||
|
if (!control_plane_extract_model_command(text,
|
||||||
|
&command_type,
|
||||||
|
value,
|
||||||
|
sizeof(value),
|
||||||
|
&value_index,
|
||||||
|
&value_index_valid)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,8 +220,13 @@ static void control_plane_responding_text_bridge(const char *text,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Device command: type=%d value=%s", (int)command_type, value);
|
ESP_LOGI(TAG,
|
||||||
cb(command_type, value, cb_user_data);
|
"Device command: type=%d value=%s index=%u valid=%d",
|
||||||
|
(int)command_type,
|
||||||
|
value,
|
||||||
|
(unsigned int)value_index,
|
||||||
|
value_index_valid ? 1 : 0);
|
||||||
|
cb(command_type, value, value_index, value_index_valid, cb_user_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static esp_err_t control_plane_prepare_for_multimodal_start(void)
|
static esp_err_t control_plane_prepare_for_multimodal_start(void)
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
/* Liveness safety bound for the fixed two-cycle wealth spin intro (not the
|
/* Liveness safety bound for the fixed two-cycle wealth spin intro (not the
|
||||||
* animation length). On timeout the worker proceeds to reveal + rotate anyway. */
|
* animation length). On timeout the worker proceeds to reveal + rotate anyway. */
|
||||||
#define CONTROL_PLANE_SPIN_INTRO_TIMEOUT_MS 6000
|
#define CONTROL_PLANE_SPIN_INTRO_TIMEOUT_MS 6000
|
||||||
|
#define CONTROL_PLANE_COMMAND_ROTATION_DEG 45.0f
|
||||||
|
|
||||||
#if (configSUPPORT_STATIC_ALLOCATION == 1) && \
|
#if (configSUPPORT_STATIC_ALLOCATION == 1) && \
|
||||||
((defined(CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM) && CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM) || \
|
((defined(CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM) && CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM) || \
|
||||||
@@ -128,6 +129,8 @@ typedef struct {
|
|||||||
control_plane_model_command_type_t command_type;
|
control_plane_model_command_type_t command_type;
|
||||||
bool stop_requested;
|
bool stop_requested;
|
||||||
char value[32];
|
char value[32];
|
||||||
|
size_t value_index;
|
||||||
|
bool value_index_valid;
|
||||||
} control_plane_model_command_t;
|
} control_plane_model_command_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@@ -654,6 +657,8 @@ static void control_plane_task_start_gate(void *arg)
|
|||||||
|
|
||||||
static void control_plane_on_model_command(control_plane_model_command_type_t command_type,
|
static void control_plane_on_model_command(control_plane_model_command_type_t command_type,
|
||||||
const char *value,
|
const char *value,
|
||||||
|
size_t value_index,
|
||||||
|
bool value_index_valid,
|
||||||
void *user_data)
|
void *user_data)
|
||||||
{
|
{
|
||||||
(void)user_data;
|
(void)user_data;
|
||||||
@@ -667,6 +672,8 @@ static void control_plane_on_model_command(control_plane_model_command_type_t co
|
|||||||
.command_type = command_type,
|
.command_type = command_type,
|
||||||
.stop_requested = false,
|
.stop_requested = false,
|
||||||
.value = {0},
|
.value = {0},
|
||||||
|
.value_index = value_index,
|
||||||
|
.value_index_valid = value_index_valid,
|
||||||
};
|
};
|
||||||
if (value != NULL) {
|
if (value != NULL) {
|
||||||
strlcpy(cmd.value, value, sizeof(cmd.value));
|
strlcpy(cmd.value, value, sizeof(cmd.value));
|
||||||
@@ -707,7 +714,33 @@ static esp_err_t control_plane_apply_wealth_direction(size_t direction_index,
|
|||||||
ESP_LOGW(TAG, "Wealth reveal request failed: %s", esp_err_to_name(reveal_err));
|
ESP_LOGW(TAG, "Wealth reveal request failed: %s", esp_err_to_name(reveal_err));
|
||||||
}
|
}
|
||||||
|
|
||||||
return control_plane_rotate_to_direction(direction_index);
|
(void)direction_index;
|
||||||
|
|
||||||
|
float current_angle = domain_get_current_angle();
|
||||||
|
return control_plane_rotate_to_angle(current_angle + CONTROL_PLANE_COMMAND_ROTATION_DEG);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t control_plane_run_model_calibration_motion(void)
|
||||||
|
{
|
||||||
|
float start_angle = domain_get_current_angle();
|
||||||
|
|
||||||
|
esp_err_t err = control_plane_rotate_to_angle(start_angle + CONTROL_PLANE_COMMAND_ROTATION_DEG);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return control_plane_rotate_to_angle(start_angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t control_plane_show_chouqian_expression(size_t result_index, bool result_index_valid)
|
||||||
|
{
|
||||||
|
const domain_expression_request_t request = {
|
||||||
|
.id = DOMAIN_EXPRESSION_CHOUQIAN,
|
||||||
|
.trigger = DOMAIN_EXPRESSION_TRIGGER_MODEL_DIRECTION,
|
||||||
|
.chouqian_result_index = result_index,
|
||||||
|
.chouqian_result_index_valid = result_index_valid,
|
||||||
|
};
|
||||||
|
return domain_runtime_request_expression(&request);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void control_plane_model_command_task(void *arg)
|
static void control_plane_model_command_task(void *arg)
|
||||||
@@ -750,15 +783,22 @@ static void control_plane_model_command_task(void *arg)
|
|||||||
ESP_LOGW(TAG, "Unknown model direction: %s", cmd.value);
|
ESP_LOGW(TAG, "Unknown model direction: %s", cmd.value);
|
||||||
}
|
}
|
||||||
} else if (cmd.command_type == CONTROL_PLANE_MODEL_COMMAND_CALIBRATE) {
|
} else if (cmd.command_type == CONTROL_PLANE_MODEL_COMMAND_CALIBRATE) {
|
||||||
ESP_LOGI(TAG, "Calibration triggered by model command");
|
ESP_LOGI(TAG, "Calibration motion triggered by model command");
|
||||||
esp_err_t err = control_plane_calibrate();
|
esp_err_t err = control_plane_run_model_calibration_motion();
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
if (err == ESP_ERR_TIMEOUT || err == ESP_ERR_INVALID_STATE) {
|
if (err == ESP_ERR_TIMEOUT || err == ESP_ERR_INVALID_STATE) {
|
||||||
ESP_LOGI(TAG, "Calibrate not completed: %s", esp_err_to_name(err));
|
ESP_LOGI(TAG, "Calibration motion not completed: %s", esp_err_to_name(err));
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "Calibrate failed: %s", esp_err_to_name(err));
|
ESP_LOGW(TAG, "Calibration motion failed: %s", esp_err_to_name(err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (cmd.command_type == CONTROL_PLANE_MODEL_COMMAND_CHOUQIAN) {
|
||||||
|
ESP_LOGI(TAG, "Chouqian triggered by model command: %s", cmd.value);
|
||||||
|
esp_err_t err = control_plane_show_chouqian_expression(cmd.value_index,
|
||||||
|
cmd.value_index_valid);
|
||||||
|
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
||||||
|
ESP_LOGW(TAG, "Chouqian expression failed: %s", esp_err_to_name(err));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -110,6 +110,8 @@ typedef struct {
|
|||||||
domain_expression_id_t id;
|
domain_expression_id_t id;
|
||||||
domain_expression_trigger_t trigger;
|
domain_expression_trigger_t trigger;
|
||||||
size_t direction_index; /* Only used by DOMAIN_EXPRESSION_WEALTH_DIRECTION */
|
size_t direction_index; /* Only used by DOMAIN_EXPRESSION_WEALTH_DIRECTION */
|
||||||
|
size_t chouqian_result_index; /* Optional for DOMAIN_EXPRESSION_CHOUQIAN */
|
||||||
|
bool chouqian_result_index_valid;
|
||||||
} domain_expression_request_t;
|
} domain_expression_request_t;
|
||||||
|
|
||||||
esp_err_t domain_init(const domain_direction_axis_config_t *config,
|
esp_err_t domain_init(const domain_direction_axis_config_t *config,
|
||||||
|
|||||||
@@ -1275,7 +1275,18 @@ static esp_err_t domain_resolve_expression_platform_request(
|
|||||||
static platform_lcd_presentation_step_t s_chouqian_steps[6];
|
static platform_lcd_presentation_step_t s_chouqian_steps[6];
|
||||||
const size_t result_count = sizeof(s_chouqian_result_asset_dirs) /
|
const size_t result_count = sizeof(s_chouqian_result_asset_dirs) /
|
||||||
sizeof(s_chouqian_result_asset_dirs[0]);
|
sizeof(s_chouqian_result_asset_dirs[0]);
|
||||||
const char *result_asset = s_chouqian_result_asset_dirs[esp_random() % result_count];
|
size_t result_index = esp_random() % result_count;
|
||||||
|
if (request->chouqian_result_index_valid) {
|
||||||
|
if (request->chouqian_result_index >= result_count) {
|
||||||
|
ESP_LOGW(TAG,
|
||||||
|
"Chouqian result index out of range: %u (max %u)",
|
||||||
|
(unsigned int)request->chouqian_result_index,
|
||||||
|
(unsigned int)result_count);
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
result_index = request->chouqian_result_index;
|
||||||
|
}
|
||||||
|
const char *result_asset = s_chouqian_result_asset_dirs[result_index];
|
||||||
|
|
||||||
s_chouqian_steps[0] = (platform_lcd_presentation_step_t) {
|
s_chouqian_steps[0] = (platform_lcd_presentation_step_t) {
|
||||||
.asset_dir = "chouqian_qianzhi",
|
.asset_dir = "chouqian_qianzhi",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ set(platform_srcs
|
|||||||
"src/runtime/platform_lcd_runtime.c"
|
"src/runtime/platform_lcd_runtime.c"
|
||||||
"src/platform_stepper_driver.c"
|
"src/platform_stepper_driver.c"
|
||||||
"src/platform_digital_input_driver.c"
|
"src/platform_digital_input_driver.c"
|
||||||
|
"src/platform_io_effects.c"
|
||||||
"src/lcd_direct.c"
|
"src/lcd_direct.c"
|
||||||
"src/lcd_direct_assets.c"
|
"src/lcd_direct_assets.c"
|
||||||
"src/multimodal/audio_pipeline.c"
|
"src/multimodal/audio_pipeline.c"
|
||||||
@@ -27,6 +28,7 @@ set(platform_priv_requires
|
|||||||
esp_driver_gpio
|
esp_driver_gpio
|
||||||
esp_driver_i2c
|
esp_driver_i2c
|
||||||
esp_driver_i2s
|
esp_driver_i2s
|
||||||
|
esp_driver_rmt
|
||||||
esp_driver_spi
|
esp_driver_spi
|
||||||
esp_event
|
esp_event
|
||||||
esp_app_format
|
esp_app_format
|
||||||
|
|||||||
@@ -16,5 +16,6 @@ esp_err_t platform_stepper_driver_rotate_angle_with_accel(float angle,
|
|||||||
int timeout_ms);
|
int timeout_ms);
|
||||||
void platform_stepper_driver_power_off(void);
|
void platform_stepper_driver_power_off(void);
|
||||||
void platform_power_hold_init(void);
|
void platform_power_hold_init(void);
|
||||||
|
esp_err_t platform_io_effects_init(void);
|
||||||
esp_err_t platform_digital_input_driver_configure(const platform_digital_input_config_t *config);
|
esp_err_t platform_digital_input_driver_configure(const platform_digital_input_config_t *config);
|
||||||
bool platform_digital_input_driver_is_active(const platform_digital_input_config_t *config);
|
bool platform_digital_input_driver_is_active(const platform_digital_input_config_t *config);
|
||||||
|
|||||||
@@ -0,0 +1,313 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "platform_internal.h"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "driver/rmt_tx.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#ifndef CONFIG_TQ_LOCAL_TASK_CORE
|
||||||
|
#define CONFIG_TQ_LOCAL_TASK_CORE 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CONFIG_TQ_LOCAL_TASK_CORE < 0
|
||||||
|
#define PLATFORM_IO_EFFECTS_TASK_CORE tskNO_AFFINITY
|
||||||
|
#else
|
||||||
|
#define PLATFORM_IO_EFFECTS_TASK_CORE ((BaseType_t)CONFIG_TQ_LOCAL_TASK_CORE)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define PLATFORM_RMT_GPIO GPIO_NUM_4
|
||||||
|
#define PLATFORM_PULSE_GPIO GPIO_NUM_5
|
||||||
|
|
||||||
|
#define PLATFORM_PULSE_HIGH_MS 500
|
||||||
|
#define PLATFORM_PULSE_LOW_MS 200
|
||||||
|
|
||||||
|
#define PLATFORM_RMT_LED_COUNT 22
|
||||||
|
#define PLATFORM_RMT_LED_BYTES_PER_PIXEL 3
|
||||||
|
#define PLATFORM_RMT_RESOLUTION_HZ 10000000
|
||||||
|
#define PLATFORM_RMT_FRAME_MS 30
|
||||||
|
#define PLATFORM_RMT_WAIT_DONE_MS 100
|
||||||
|
#define PLATFORM_RMT_BRIGHTNESS_MIN 8
|
||||||
|
#define PLATFORM_RMT_BRIGHTNESS_MAX 180
|
||||||
|
|
||||||
|
static const char *TAG = "platform_io_effects";
|
||||||
|
|
||||||
|
static TaskHandle_t s_pulse_task;
|
||||||
|
static TaskHandle_t s_rmt_task;
|
||||||
|
|
||||||
|
static const rmt_symbol_word_t s_ws2812_zero = {
|
||||||
|
.level0 = 1,
|
||||||
|
.duration0 = PLATFORM_RMT_RESOLUTION_HZ / 10000000 * 3,
|
||||||
|
.level1 = 0,
|
||||||
|
.duration1 = PLATFORM_RMT_RESOLUTION_HZ / 10000000 * 9,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const rmt_symbol_word_t s_ws2812_one = {
|
||||||
|
.level0 = 1,
|
||||||
|
.duration0 = PLATFORM_RMT_RESOLUTION_HZ / 10000000 * 9,
|
||||||
|
.level1 = 0,
|
||||||
|
.duration1 = PLATFORM_RMT_RESOLUTION_HZ / 10000000 * 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const rmt_symbol_word_t s_ws2812_reset = {
|
||||||
|
.level0 = 0,
|
||||||
|
.duration0 = PLATFORM_RMT_RESOLUTION_HZ / 1000000 * 25,
|
||||||
|
.level1 = 0,
|
||||||
|
.duration1 = PLATFORM_RMT_RESOLUTION_HZ / 1000000 * 25,
|
||||||
|
};
|
||||||
|
|
||||||
|
static size_t platform_ws2812_encoder_callback(const void *data,
|
||||||
|
size_t data_size,
|
||||||
|
size_t symbols_written,
|
||||||
|
size_t symbols_free,
|
||||||
|
rmt_symbol_word_t *symbols,
|
||||||
|
bool *done,
|
||||||
|
void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
|
||||||
|
if (symbols_free < 8U) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t data_pos = symbols_written / 8U;
|
||||||
|
const uint8_t *data_bytes = (const uint8_t *)data;
|
||||||
|
if (data_pos < data_size) {
|
||||||
|
size_t symbol_pos = 0U;
|
||||||
|
for (uint8_t bitmask = 0x80U; bitmask != 0U; bitmask >>= 1U) {
|
||||||
|
symbols[symbol_pos++] =
|
||||||
|
(data_bytes[data_pos] & bitmask) ? s_ws2812_one : s_ws2812_zero;
|
||||||
|
}
|
||||||
|
return symbol_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
symbols[0] = s_ws2812_reset;
|
||||||
|
*done = true;
|
||||||
|
return 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t scale_u8(uint8_t value, uint8_t scale)
|
||||||
|
{
|
||||||
|
return (uint8_t)(((uint16_t)value * (uint16_t)scale) / 255U);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hsv_to_rgb(uint8_t hue, uint8_t value, uint8_t *red, uint8_t *green, uint8_t *blue)
|
||||||
|
{
|
||||||
|
uint8_t region = hue / 43U;
|
||||||
|
uint8_t remainder = (uint8_t)((hue - (region * 43U)) * 6U);
|
||||||
|
uint8_t p = 0U;
|
||||||
|
uint8_t q = (uint8_t)(((uint16_t)value * (255U - remainder)) / 255U);
|
||||||
|
uint8_t t = (uint8_t)(((uint16_t)value * remainder) / 255U);
|
||||||
|
|
||||||
|
switch (region) {
|
||||||
|
case 0:
|
||||||
|
*red = value;
|
||||||
|
*green = t;
|
||||||
|
*blue = p;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
*red = q;
|
||||||
|
*green = value;
|
||||||
|
*blue = p;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
*red = p;
|
||||||
|
*green = value;
|
||||||
|
*blue = t;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
*red = p;
|
||||||
|
*green = q;
|
||||||
|
*blue = value;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
*red = t;
|
||||||
|
*green = p;
|
||||||
|
*blue = value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
*red = value;
|
||||||
|
*green = p;
|
||||||
|
*blue = q;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t breathing_brightness(uint16_t phase)
|
||||||
|
{
|
||||||
|
uint16_t wave = phase & 0x01FFU;
|
||||||
|
if (wave > 255U) {
|
||||||
|
wave = 511U - wave;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uint8_t)(PLATFORM_RMT_BRIGHTNESS_MIN +
|
||||||
|
((wave * (PLATFORM_RMT_BRIGHTNESS_MAX - PLATFORM_RMT_BRIGHTNESS_MIN)) / 255U));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fill_breathing_pixels(uint8_t *pixels, size_t pixel_len, uint8_t hue_base, uint8_t brightness)
|
||||||
|
{
|
||||||
|
if (pixel_len < (PLATFORM_RMT_LED_COUNT * PLATFORM_RMT_LED_BYTES_PER_PIXEL)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t led = 0; led < PLATFORM_RMT_LED_COUNT; ++led) {
|
||||||
|
uint8_t red = 0U;
|
||||||
|
uint8_t green = 0U;
|
||||||
|
uint8_t blue = 0U;
|
||||||
|
uint8_t hue = (uint8_t)(hue_base + ((led * 256U) / PLATFORM_RMT_LED_COUNT));
|
||||||
|
hsv_to_rgb(hue, 255U, &red, &green, &blue);
|
||||||
|
|
||||||
|
pixels[(led * 3U) + 0U] = scale_u8(green, brightness);
|
||||||
|
pixels[(led * 3U) + 1U] = scale_u8(red, brightness);
|
||||||
|
pixels[(led * 3U) + 2U] = scale_u8(blue, brightness);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void platform_pulse_task(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
gpio_set_level(PLATFORM_PULSE_GPIO, 1);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(PLATFORM_PULSE_HIGH_MS));
|
||||||
|
gpio_set_level(PLATFORM_PULSE_GPIO, 0);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(PLATFORM_PULSE_LOW_MS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void platform_rmt_led_task(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
|
||||||
|
uint8_t pixels[PLATFORM_RMT_LED_COUNT * PLATFORM_RMT_LED_BYTES_PER_PIXEL] = {0};
|
||||||
|
rmt_channel_handle_t led_channel = NULL;
|
||||||
|
rmt_encoder_handle_t led_encoder = NULL;
|
||||||
|
|
||||||
|
rmt_tx_channel_config_t tx_channel_config = {
|
||||||
|
.gpio_num = PLATFORM_RMT_GPIO,
|
||||||
|
.clk_src = RMT_CLK_SRC_DEFAULT,
|
||||||
|
.resolution_hz = PLATFORM_RMT_RESOLUTION_HZ,
|
||||||
|
.mem_block_symbols = 64,
|
||||||
|
.trans_queue_depth = 4,
|
||||||
|
.flags = {
|
||||||
|
.invert_out = false,
|
||||||
|
.with_dma = false,
|
||||||
|
.io_loop_back = false,
|
||||||
|
.io_od_mode = false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_err_t err = rmt_new_tx_channel(&tx_channel_config, &led_channel);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Create RMT TX channel failed: %s", esp_err_to_name(err));
|
||||||
|
s_rmt_task = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rmt_simple_encoder_config_t encoder_config = {
|
||||||
|
.callback = platform_ws2812_encoder_callback,
|
||||||
|
.min_chunk_size = 64,
|
||||||
|
};
|
||||||
|
err = rmt_new_simple_encoder(&encoder_config, &led_encoder);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Create RMT encoder failed: %s", esp_err_to_name(err));
|
||||||
|
rmt_del_channel(led_channel);
|
||||||
|
s_rmt_task = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rmt_enable(led_channel);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Enable RMT channel failed: %s", esp_err_to_name(err));
|
||||||
|
rmt_del_encoder(led_encoder);
|
||||||
|
rmt_del_channel(led_channel);
|
||||||
|
s_rmt_task = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "GPIO4 RMT breathing effect started for %u LEDs", (unsigned)PLATFORM_RMT_LED_COUNT);
|
||||||
|
|
||||||
|
rmt_transmit_config_t tx_config = {
|
||||||
|
.loop_count = 0,
|
||||||
|
};
|
||||||
|
uint8_t hue_base = 0U;
|
||||||
|
uint16_t phase = 0U;
|
||||||
|
while (true) {
|
||||||
|
fill_breathing_pixels(pixels, sizeof(pixels), hue_base, breathing_brightness(phase));
|
||||||
|
|
||||||
|
err = rmt_transmit(led_channel, led_encoder, pixels, sizeof(pixels), &tx_config);
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
err = rmt_tx_wait_all_done(led_channel, pdMS_TO_TICKS(PLATFORM_RMT_WAIT_DONE_MS));
|
||||||
|
}
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "RMT LED frame failed: %s", esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
hue_base++;
|
||||||
|
phase += 4U;
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(PLATFORM_RMT_FRAME_MS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t platform_io_effects_init(void)
|
||||||
|
{
|
||||||
|
gpio_config_t pulse_config = {
|
||||||
|
.pin_bit_mask = BIT64(PLATFORM_PULSE_GPIO),
|
||||||
|
.mode = GPIO_MODE_OUTPUT,
|
||||||
|
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||||
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||||
|
.intr_type = GPIO_INTR_DISABLE,
|
||||||
|
};
|
||||||
|
esp_err_t err = gpio_config(&pulse_config);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
gpio_set_level(PLATFORM_PULSE_GPIO, 0);
|
||||||
|
|
||||||
|
if (s_pulse_task == NULL) {
|
||||||
|
BaseType_t created = xTaskCreatePinnedToCore(platform_pulse_task,
|
||||||
|
"platform_pulse",
|
||||||
|
2048,
|
||||||
|
NULL,
|
||||||
|
2,
|
||||||
|
&s_pulse_task,
|
||||||
|
PLATFORM_IO_EFFECTS_TASK_CORE);
|
||||||
|
if (created != pdPASS) {
|
||||||
|
s_pulse_task = NULL;
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s_rmt_task == NULL) {
|
||||||
|
BaseType_t created = xTaskCreatePinnedToCore(platform_rmt_led_task,
|
||||||
|
"platform_rmt_led",
|
||||||
|
4096,
|
||||||
|
NULL,
|
||||||
|
2,
|
||||||
|
&s_rmt_task,
|
||||||
|
PLATFORM_IO_EFFECTS_TASK_CORE);
|
||||||
|
if (created != pdPASS) {
|
||||||
|
s_rmt_task = NULL;
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG,
|
||||||
|
"GPIO5 pulse output started (%ums high, %ums low); GPIO4 RMT task starting",
|
||||||
|
(unsigned)PLATFORM_PULSE_HIGH_MS,
|
||||||
|
(unsigned)PLATFORM_PULSE_LOW_MS);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
@@ -118,6 +118,11 @@ esp_err_t platform_init(void)
|
|||||||
|
|
||||||
platform_power_hold_init();
|
platform_power_hold_init();
|
||||||
|
|
||||||
|
esp_err_t io_effects_err = platform_io_effects_init();
|
||||||
|
if (io_effects_err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "IO effects init failed: %s", esp_err_to_name(io_effects_err));
|
||||||
|
}
|
||||||
|
|
||||||
#if CONFIG_TQ_STEPPER_ENABLE
|
#if CONFIG_TQ_STEPPER_ENABLE
|
||||||
esp_err_t err = platform_stepper_driver_init();
|
esp_err_t err = platform_stepper_driver_init();
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
## B. 抽签
|
## B. 抽签
|
||||||
|
|
||||||
1. **问:** 帮我抽个签
|
1. **问:** 帮我抽个签
|
||||||
**答:** 签文:稳中求进。当下不用急着冲,像猫揣手手晒太阳一样,先把眼前小事做好,好时机自然来。
|
**答:** `/*{"抽签":"上上签"}*/`当下不用急着冲,像猫揣手手晒太阳一样,先把眼前小事做好,好时机自然来。
|
||||||
|
|
||||||
2. **问:** 我想求个签
|
2. **问:** 我想求个签
|
||||||
**答:** 签文:小满即安。今天少跟人较劲,多夸自己两句,就像发现猫碗里多了条小鱼干,小小的快乐也值得开心。
|
**答:** 签文:小满即安。今天少跟人较劲,多夸自己两句,就像发现猫碗里多了条小鱼干,小小的快乐也值得开心。
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ is from the ESP32-S3 MCU perspective.
|
|||||||
| 1 | `CONFIG_MULTIMODAL_AUDIO_I2C_SDA_GPIO` | Bidirectional | Audio codec | ES8311 I2C SDA | I2C port 0, codec control bus |
|
| 1 | `CONFIG_MULTIMODAL_AUDIO_I2C_SDA_GPIO` | Bidirectional | Audio codec | ES8311 I2C SDA | I2C port 0, codec control bus |
|
||||||
| 2 | `CONFIG_MULTIMODAL_AUDIO_I2C_SCL_GPIO` | Output | Audio codec | ES8311 I2C SCL | I2C port 0, codec control bus |
|
| 2 | `CONFIG_MULTIMODAL_AUDIO_I2C_SCL_GPIO` | Output | Audio codec | ES8311 I2C SCL | I2C port 0, codec control bus |
|
||||||
| 3 | `CONFIG_TQ_SCREEN_RESET_PIN` | Output | LCD | ST7789 panel reset | Set high after GPIO init; `-1` disables hardware reset |
|
| 3 | `CONFIG_TQ_SCREEN_RESET_PIN` | Output | LCD | ST7789 panel reset | Set high after GPIO init; `-1` disables hardware reset |
|
||||||
|
| 4 | IO4 RMT control | Output | RMT control | RMT output control | Driven by platform IO effects RMT task |
|
||||||
|
| 5 | IO5 PWM control | Output | PWM control | PWM output control | Driven by platform IO effects pulse task |
|
||||||
| 6 | `CONFIG_TQ_SCREEN_SPI_CS_GPIO` | Output | LCD | ST7789 SPI chip select | SPI2 host |
|
| 6 | `CONFIG_TQ_SCREEN_SPI_CS_GPIO` | Output | LCD | ST7789 SPI chip select | SPI2 host |
|
||||||
| 7 | `CONFIG_TQ_SCREEN_DC_GPIO` | Output | LCD | ST7789 data/command select | Initialized low |
|
| 7 | `CONFIG_TQ_SCREEN_DC_GPIO` | Output | LCD | ST7789 data/command select | Initialized low |
|
||||||
| 9 | `CONFIG_TQ_SCREEN_BACKLIGHT_GPIO` | Output | LCD | LCD backlight enable | Active-high by default |
|
| 9 | `CONFIG_TQ_SCREEN_BACKLIGHT_GPIO` | Output | LCD | LCD backlight enable | Active-high by default |
|
||||||
@@ -73,8 +75,6 @@ them to an active project feature.
|
|||||||
| GPIO / Net | Board function | Firmware status |
|
| GPIO / Net | Board function | Firmware status |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| GPIO0 / K1 | Switch to GND, commonly boot/download related | Not consumed by application code |
|
| GPIO0 / K1 | Switch to GND, commonly boot/download related | Not consumed by application code |
|
||||||
| GPIO4 | Unassigned | Not used |
|
|
||||||
| GPIO5 | Unassigned | Not used |
|
|
||||||
| GPIO8 | Unassigned | Not used |
|
| GPIO8 | Unassigned | Not used |
|
||||||
| GPIO10 / `SWING_PWM` | Swing coil control interface | No current firmware driver/config found |
|
| GPIO10 / `SWING_PWM` | Swing coil control interface | No current firmware driver/config found |
|
||||||
| GPIO11 | Unassigned | Not used |
|
| GPIO11 | Unassigned | Not used |
|
||||||
|
|||||||
+2
-2
@@ -11,8 +11,8 @@ perspective.
|
|||||||
| 1 | GND | GND | N/A | Ground reference | Ground reference |
|
| 1 | GND | GND | N/A | Ground reference | Ground reference |
|
||||||
| 2 | 3V3 | 3V3 | Power | 3.3 V supply rail | Power rail |
|
| 2 | 3V3 | 3V3 | Power | 3.3 V supply rail | Power rail |
|
||||||
| 3 | EN/RST | EN | Input | Chip enable and reset input | Reset and enable |
|
| 3 | EN/RST | EN | Input | Chip enable and reset input | Reset and enable |
|
||||||
| 4 | GPIO4 | UNASSIGNED | N/A | No connection assigned | Reserved; no signal assigned |
|
| 4 | GPIO4 | IO4_RMT | Output | RMT control | RMT output control |
|
||||||
| 5 | GPIO5 | UNASSIGNED | N/A | No connection assigned | Reserved; no signal assigned |
|
| 5 | GPIO5 | IO5_PWM | Output | PWM control | PWM output control |
|
||||||
| 6 | GPIO6 | ESP_IO6 | Output | Display interface CS | U9 |
|
| 6 | GPIO6 | ESP_IO6 | Output | Display interface CS | U9 |
|
||||||
| 7 | GPIO7 | ESP_IO7 | Output | Display interface A0 (RS) | U9 |
|
| 7 | GPIO7 | ESP_IO7 | Output | Display interface A0 (RS) | U9 |
|
||||||
| 8 | GPIO15 | ESP_IO15 | Input | Function key K2 | 10 kOhm pull-up to 3V3; pulled to GND when pressed |
|
| 8 | GPIO15 | ESP_IO15 | Input | Function key K2 | 10 kOhm pull-up to 3V3; pulled to GND when pressed |
|
||||||
|
|||||||
Reference in New Issue
Block a user