/* * ESP-IDF entry point for the "armor" hit-detection node (ESP32-C3). * * This is the ESP-IDF counterpart of the original Arduino `main.cpp`: * setup() -> app_main() * loop() -> hit_task() (only ran Hit::onLoop()) * loop2() -> loop2_task() (button, ESP-NOW and serial bridging) * Serial -> USB Serial/JTAG console + driver (CDC protocol) * Serial0 -> UART0 on GPIO20/21 (DBUS bridge) */ #include #include #include "driver/uart.h" #include "driver/usb_serial_jtag.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "nvs_flash.h" #include "air.hpp" #include "armor_time.hpp" #include "button.hpp" #include "hit.hpp" #include "storage.hpp" namespace { constexpr const char *TAG = "armor"; constexpr uint32_t TIMEOUT_MS = 100; // GPIO9 is the on-board BOOT button of the ESP32-C3 DevKitM-1, active low. // Original: `OneButton button(9, true);` Button button(9, true); volatile bool g_click = false; volatile bool g_long_click = false; volatile uint32_t g_last_cdc_receive_ms = 0; void handleClick() { g_click = true; } void handleLongPressStart() { g_long_click = true; } // --------------------------------------------------------------- UART0 ----- // Original: `Serial0.begin(115200, SERIAL_8N1, 20, 21);` (rx=20, tx=21) constexpr uart_port_t DBUS_UART = UART_NUM_0; constexpr int DBUS_UART_RX = 20; constexpr int DBUS_UART_TX = 21; void serial0_begin() { uart_config_t cfg = {}; cfg.baud_rate = 115200; cfg.data_bits = UART_DATA_8_BITS; cfg.parity = UART_PARITY_DISABLE; cfg.stop_bits = UART_STOP_BITS_1; cfg.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; cfg.source_clk = UART_SCLK_DEFAULT; ESP_ERROR_CHECK(uart_driver_install(DBUS_UART, 256, 256, 0, nullptr, 0)); ESP_ERROR_CHECK(uart_param_config(DBUS_UART, &cfg)); ESP_ERROR_CHECK(uart_set_pin(DBUS_UART, DBUS_UART_TX, DBUS_UART_RX, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); } // ----------------------------------------------------------- USB CDC ------- // Replaces Arduino's `serialEvent()`: 10-byte DBUS frames arrive over the USB // CDC port, and the original truncated/committed them in 10-byte units. void cdc_task(void *) { uint8_t chunk[32]; uint8_t frame[10]; size_t frame_len = 0; while (true) { const int n = usb_serial_jtag_read_bytes(chunk, sizeof(chunk), pdMS_TO_TICKS(20)); for (int i = 0; i < n; i++) { frame[frame_len++] = chunk[i]; if (frame_len == sizeof(frame)) { Air::dbusSet(frame, sizeof(frame)); g_last_cdc_receive_ms = millis(); frame_len = 0; } } } } // ------------------------------------------------------------ business ----- void handle_air() { static Air::packet_t peer_packet_last; // 处理收到的数据包 // 颜色变化 if (Air::peer_packet.color != peer_packet_last.color && Air::peer_packet.color != 0) { Hit::color = Air::peer_packet.color; Storage::putUInt("color", Hit::color); } // 击打次数变化 if (Air::peer_packet.hit_cnt != peer_packet_last.hit_cnt) { Hit::last_hit_ms = millis(); } peer_packet_last = Air::peer_packet; // 发送自己的数据包 Air::my_packet.color = Hit::color; Air::my_packet.hit_cnt = Hit::hit_cnt; if (millis() - g_last_cdc_receive_ms > TIMEOUT_MS) { // 超时数据清零 Air::dbusClear(); } } void handle_serial() { // 串口发送 DBUS 数据 if (millis() - Air::last_air_ms > TIMEOUT_MS) { // 超时数据清零 memset(Air::peer_packet.dbus, 0, sizeof(Air::peer_packet.dbus)); } uart_write_bytes(DBUS_UART, Air::peer_packet.dbus, sizeof(Air::peer_packet.dbus)); uart_wait_tx_done(DBUS_UART, pdMS_TO_TICKS(20)); // Serial0.flush() // CDC发送:颜色、击打次数、信号强度(tx, rx)、距离最后一次收到消息的毫秒数 const uint32_t latency_ms = millis() - Air::last_air_ms; printf("%d,%d,%d,%d,%d\n", static_cast(Air::my_packet.color), static_cast(Air::peer_packet.hit_cnt), static_cast(Air::peer_packet.rssi), static_cast(Air::my_packet.rssi), static_cast(latency_ms)); } // ------------------------------------------------------------- tasks ------- // Equivalent of the original Arduino `loop2()`. void loop2_task(void *) { while (true) { button.tick(); Air::onLoop(); if (!Air::is_pairing) { // 不在配对中 if (g_long_click) { // 长按配对 Air::entryPairing(); Hit::detect_hit = false; } else if (g_click) { // 短按切换颜色 Hit::color = (Hit::color == Hit::RED) ? Hit::BLUE : Hit::RED; Storage::putUInt("color", Hit::color); } else { // 正常运行业务逻辑 handle_air(); handle_serial(); } } else { // 在配对中 if (g_click) { // 短按保存配对 Air::savePairing(); Hit::color = Hit::RED; Storage::putUInt("color", Hit::color); Hit::detect_hit = true; } else { Hit::color = Air::pairing_found ? Hit::GREEN : Hit::YELLOW; } } g_click = false; g_long_click = false; vTaskDelay(pdMS_TO_TICKS(10)); } } // Equivalent of the original Arduino `loop()`. void hit_task(void *) { while (true) { Hit::onLoop(); } } } // namespace extern "C" void app_main(void) { // 初始化 NVS(原 Preferences 底层就是 NVS) 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()); err = nvs_flash_init(); } ESP_ERROR_CHECK(err); ESP_ERROR_CHECK(Storage::begin("armor")); // With CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG the console initialisation has // already installed the USB Serial/JTAG driver, so only install it when // that did not happen; reading raw CDC bytes then works either way. if (!usb_serial_jtag_is_driver_installed()) { usb_serial_jtag_driver_config_t usb_cfg = USB_SERIAL_JTAG_DRIVER_CONFIG_DEFAULT(); usb_cfg.rx_buffer_size = 256; if (usb_serial_jtag_driver_install(&usb_cfg) != ESP_OK) { ESP_LOGW(TAG, "USB Serial/JTAG driver unavailable, CDC protocol disabled"); } } serial0_begin(); // 绑定回调函数 button.attachClick(handleClick); button.attachLongPressStart(handleLongPressStart); button.setClickMs(50); // 短按时间(毫秒) button.setPressMs(500); // 长按时间(毫秒) Hit::begin(); Air::begin(); ESP_LOGI(TAG, "armor node started"); xTaskCreate(cdc_task, "cdc", 3072, nullptr, 5, nullptr); xTaskCreate(loop2_task, "loop2", 8192, nullptr, 5, nullptr); xTaskCreate(hit_task, "hit", 4096, nullptr, 5, nullptr); }