diff --git a/components/actuator/actuator.c b/components/actuator/actuator.c index 0d9ec20..0a7378b 100644 --- a/components/actuator/actuator.c +++ b/components/actuator/actuator.c @@ -506,6 +506,9 @@ void edc_proto_on_cycle(const edc_cycle_t *cycle, edc_channel_t ch) } s_last_exec_id = cycle->cycle_id; + /* 新一轮执行开始: 复位 RGB 状态 (执行期间 _wait_flash 显示红灯) */ + rgb_set_state(RGB_STATE_IDLE); + /* 执行电机运动 (阻塞, 含拾放全流程) */ ESP_LOGI(TAG, "开始执行 Cycle #%u ...", cycle->cycle_id); actuator_execute_cycle(cycle); diff --git a/components/buzzer/CMakeLists.txt b/components/buzzer/CMakeLists.txt new file mode 100644 index 0000000..582e112 --- /dev/null +++ b/components/buzzer/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "buzzer.c" + INCLUDE_DIRS "." + REQUIRES esp_driver_ledc esp_driver_gpio driver) diff --git a/components/buzzer/buzzer.c b/components/buzzer/buzzer.c new file mode 100644 index 0000000..259baf1 --- /dev/null +++ b/components/buzzer/buzzer.c @@ -0,0 +1,106 @@ +/** + * @file buzzer.c + * @brief 无源蜂鸣器控制实现 (LEDC PWM) + * + * 无源蜂鸣器需要外部提供音频频率的方波才能发声, 不能像有源蜂鸣器那样 + * 直接给高电平。这里用 LEDC 在独立定时器上产生可变频率的 50% 占空比方波。 + * + * 资源分配 (与 servo 错开): + * servo : LEDC_TIMER_0 / LEDC_CHANNEL_0 (50Hz 固定) + * buzzer : LEDC_TIMER_1 / LEDC_CHANNEL_2 (音频频率, 可动态改频) + */ + +#include "buzzer.h" +#include "driver/ledc.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#define BUZZER_TIMER LEDC_TIMER_1 +#define BUZZER_MODE LEDC_LOW_SPEED_MODE +#define BUZZER_CHANNEL LEDC_CHANNEL_2 +#define BUZZER_DUTY_RES LEDC_TIMER_10_BIT /* 0~1023, 允许更高频率 */ +#define BUZZER_MAX_DUTY 1023 +#define BUZZER_DEFAULT_FREQ_HZ 4000 + +static const char *TAG = "BUZZER"; +static bool s_initialized = false; +static uint32_t s_freq_hz = 0; + +void buzzer_init(int gpio) +{ + ledc_timer_config_t timer_cfg = { + .speed_mode = BUZZER_MODE, + .duty_resolution = BUZZER_DUTY_RES, + .timer_num = BUZZER_TIMER, + .freq_hz = BUZZER_DEFAULT_FREQ_HZ, + .clk_cfg = LEDC_AUTO_CLK, + }; + ESP_ERROR_CHECK(ledc_timer_config(&timer_cfg)); + + ledc_channel_config_t chan_cfg = { + .gpio_num = gpio, + .speed_mode = BUZZER_MODE, + .channel = BUZZER_CHANNEL, + .intr_type = LEDC_INTR_DISABLE, + .timer_sel = BUZZER_TIMER, + .duty = 0, /* 默认静音 */ + .hpoint = 0, + .flags.output_invert = 0, + }; + ESP_ERROR_CHECK(ledc_channel_config(&chan_cfg)); + + s_initialized = true; + s_freq_hz = BUZZER_DEFAULT_FREQ_HZ; + ESP_LOGI(TAG, "初始化完成 (GPIO%d, 通道%d)", gpio, BUZZER_CHANNEL); +} + +void buzzer_set_freq(uint32_t freq_hz) +{ + if (!s_initialized || freq_hz == 0) return; + if (freq_hz == s_freq_hz) return; + + ESP_ERROR_CHECK(ledc_set_freq(BUZZER_MODE, BUZZER_TIMER, freq_hz)); + s_freq_hz = freq_hz; + ESP_LOGD(TAG, "频率: %lu Hz", (unsigned long)freq_hz); +} + +void buzzer_on(uint32_t freq_hz) +{ + if (!s_initialized) return; + + buzzer_set_freq(freq_hz); + ESP_ERROR_CHECK(ledc_set_duty(BUZZER_MODE, BUZZER_CHANNEL, BUZZER_MAX_DUTY / 2)); + ESP_ERROR_CHECK(ledc_update_duty(BUZZER_MODE, BUZZER_CHANNEL)); +} + +void buzzer_off(void) +{ + if (!s_initialized) return; + + ESP_ERROR_CHECK(ledc_set_duty(BUZZER_MODE, BUZZER_CHANNEL, 0)); + ESP_ERROR_CHECK(ledc_update_duty(BUZZER_MODE, BUZZER_CHANNEL)); +} + +void buzzer_beep(uint32_t freq_hz, uint32_t duration_ms) +{ + buzzer_on(freq_hz); + vTaskDelay(pdMS_TO_TICKS(duration_ms)); + buzzer_off(); +} + +void buzzer_beep_twice(uint32_t freq_hz, uint32_t duration_ms) +{ + buzzer_beep(freq_hz, duration_ms); + vTaskDelay(pdMS_TO_TICKS(120)); + buzzer_beep(freq_hz, duration_ms); +} + +void buzzer_beep_triple(uint32_t freq_hz, uint32_t duration_ms) +{ + buzzer_beep(freq_hz, duration_ms); + vTaskDelay(pdMS_TO_TICKS(120)); + buzzer_beep(freq_hz, duration_ms); + vTaskDelay(pdMS_TO_TICKS(120)); + buzzer_beep(freq_hz, duration_ms); +} diff --git a/components/buzzer/buzzer.h b/components/buzzer/buzzer.h new file mode 100644 index 0000000..a00cb52 --- /dev/null +++ b/components/buzzer/buzzer.h @@ -0,0 +1,65 @@ +/** + * @file buzzer.h + * @brief 无源蜂鸣器控制 (LEDC PWM 音调) + * + * 硬件建议 (无源蜂鸣器电流较大, 建议用 NPN 三极管驱动): + * ESP32 GPIO ──[1kΩ]── S8050 基极 + * 蜂鸣器 + → 5V (或 3.3V) + * 蜂鸣器 − → 三极管集电极 + * 三极管发射极 → GND + * (磁式蜂鸣器可在两端并联 1N4148 续流二极管) + */ + +#ifndef BUZZER_H +#define BUZZER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief 初始化无源蜂鸣器 (LEDC 独立定时器/通道) + * @param gpio GPIO 引脚号, 如 GPIO_NUM_13 + */ +void buzzer_init(int gpio); + +/** + * @brief 开始发声 + * @param freq_hz 频率 (Hz), 常用 2000~4000 + */ +void buzzer_on(uint32_t freq_hz); + +/** + * @brief 停止发声 (静音) + */ +void buzzer_off(void); + +/** + * @brief 设置音调频率 (不改变发声状态) + */ +void buzzer_set_freq(uint32_t freq_hz); + +/** + * @brief 阻塞式响一声 (需在任务中调用) + * @param freq_hz 频率 (Hz) + * @param duration_ms 时长 (ms) + */ +void buzzer_beep(uint32_t freq_hz, uint32_t duration_ms); + +/** + * @brief 阻塞式响两声 (确认提示音) + */ +void buzzer_beep_twice(uint32_t freq_hz, uint32_t duration_ms); + +/** + * @brief 阻塞式急促响三声 (执行完成提示音) + */ +void buzzer_beep_triple(uint32_t freq_hz, uint32_t duration_ms); + +#ifdef __cplusplus +} +#endif + +#endif /* BUZZER_H */ diff --git a/components/rgb/rgb.c b/components/rgb/rgb.c index 66c2ada..5a760b1 100644 --- a/components/rgb/rgb.c +++ b/components/rgb/rgb.c @@ -14,6 +14,7 @@ #include "esp_log.h" #include "driver/rmt_tx.h" #include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" #include static const char *TAG = "RGB"; @@ -145,17 +146,24 @@ static esp_err_t rmt_new_led_strip_encoder(rmt_encoder_handle_t *ret_encoder) static rmt_channel_handle_t s_led_chan = NULL; static rmt_encoder_handle_t s_led_encoder = NULL; +static SemaphoreHandle_t s_lock = NULL; /* 串行化 RMT 访问 */ /* ── 内部: 发送 GRB 数据 ── */ static void rgb_transmit(uint8_t g, uint8_t r, uint8_t b) { + if (!s_led_chan || !s_lock) return; + uint8_t grb[3] = { g, r, b }; + /* main 循环与 Cycle 执行 (_wait_flash) 会从不同任务调用本函数, + 必须互斥, 否则并发操作同一 RMT 通道会导致绿灯丢失甚至复位 */ + xSemaphoreTake(s_lock, portMAX_DELAY); rmt_transmit_config_t tx_config = { .loop_count = 0 }; ESP_ERROR_CHECK(rmt_transmit(s_led_chan, s_led_encoder, grb, sizeof(grb), &tx_config)); ESP_ERROR_CHECK(rmt_tx_wait_all_done(s_led_chan, portMAX_DELAY)); + xSemaphoreGive(s_lock); } /* ── 公开接口 ── */ @@ -167,6 +175,8 @@ void rgb_init(int gpio) return; } + s_lock = xSemaphoreCreateMutex(); + rmt_tx_channel_config_t tx_chan_config = { .clk_src = RMT_CLK_SRC_DEFAULT, .gpio_num = gpio, diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 12d5f67..29c0bb4 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,6 +1,6 @@ idf_component_register(SRCS "main.c" INCLUDE_DIRS "." - PRIV_REQUIRES emm_v5 magnet servo key web_ctrl protocol rgb actuator) + PRIV_REQUIRES emm_v5 magnet servo key web_ctrl protocol rgb actuator buzzer) # 调试开关: 启用 WiFi + Web 服务器 (消除 IDE 未定义警告) target_compile_definitions(${COMPONENT_LIB} PRIVATE EDC_DEBUG_WEB) diff --git a/main/main.c b/main/main.c index b7bdc50..2296a2e 100644 --- a/main/main.c +++ b/main/main.c @@ -13,6 +13,7 @@ * ESP32 GPIO3 -> I2C SCL * ESP32 GPIO27 -> RGB LED (WS2812) * ESP32 GPIO8 -> 按钮 (低电平有效, 触发识别) + * ESP32 GPIO9 -> 无源蜂鸣器 (经三极管驱动) * ESP32 GND -> 电机 GND / MPU GND * * 注: 日志控制台已切换到 USB Serial/JTAG (COM 口监视), @@ -29,6 +30,7 @@ #include "rgb.h" #include "actuator.h" #include "key.h" +#include "buzzer.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" @@ -46,6 +48,7 @@ static void button_recognize_task(void *pvParam) while (1) { if (key_is_pressed()) { ESP_LOGI(TAG, "按钮按下, 归零后再发送 RECOGNIZE ..."); + buzzer_beep(2000, 100); /* 短音: 识别开始 */ /* ---- 归零: 舵机抬起 + Z 回 0° (低速, 最坏行程 270°) ---- */ actuator_servo_up(); @@ -86,6 +89,7 @@ static void button_recognize_task(void *pvParam) if (actuator_is_last_cycle_valid()) { got_cycle = true; ESP_LOGI(TAG, "RECOGNIZE 成功, 已收到 Cycle"); + buzzer_beep_twice(2000, 150); /* 双短音: 识别成功 */ break; } } @@ -93,6 +97,7 @@ static void button_recognize_task(void *pvParam) if (!got_cycle) { ESP_LOGW(TAG, "RECOGNIZE 超时 (%d ms), 发送 CYCLE_REQUEST", RECOGNIZE_TIMEOUT_MS); + buzzer_beep(880, 500); /* 长低音: 识别失败/超时 */ edc_proto_send_request(); } } @@ -115,6 +120,9 @@ void app_main(void) /* 初始化按钮 (GPIO8, 低电平有效, 触发识别) */ key_init(GPIO_NUM_8); + /* 初始化无源蜂鸣器 (GPIO9, 提示音) */ + buzzer_init(GPIO_NUM_9); + /* 初始化通讯协议 (I2C 从机 GPIO2/3 + UART0 GPIO11/12) */ edc_proto_init(NULL); @@ -134,16 +142,25 @@ void app_main(void) /* 系统就绪: 黄灯 (低亮) */ rgb_set(32, 32, 0); rgb_set_state(RGB_STATE_IDLE); + buzzer_beep(2000, 120); /* 启动/就绪: 短促一声 */ ESP_LOGI(TAG, "系统就绪, 等待网页指令..."); + /* 结束提示: 每轮只响一次, 防止慢闪循环重复响 */ + bool done_beeped = false; + while (1) { /* 根据系统状态选择闪烁颜色 */ rgb_state_t st = rgb_get_state(); if (st == RGB_STATE_DONE) { /* 绿灯慢闪: Cycle 执行完毕 */ + if (!done_beeped) { + buzzer_beep_triple(2000, 100); /* 执行结束: 急促三响 */ + done_beeped = true; + } rgb_set(0, 32, 0); } else { /* 黄灯慢闪: 待机 */ + done_beeped = false; rgb_set(32, 32, 0); } vTaskDelay(pdMS_TO_TICKS(500));