`armor/` 是它的 Rust 改写版,这一份留着当参考:ADC 阈值的 2000 就是从它标定出来的 (`.env.example` 里那句 "same as the reference firmware" 指的就是它),而且它的 README 记着实测设备与 MAC,换硬件时是有用的现场记录。 只收了 20 个源文件。它自带的 `.gitignore` 已经排掉 `build/`(211 MB 的 ESP-IDF 构建产物) 与生成的 `sdkconfig`,所以仓库里不会多出这些东西。 两处顺带处理: - `reference/armor/.git` 是一个**空的**仓库壳(`git init` 过,零提交、零 remote、零 ref), 而外层 git 正因为它的存在而拒绝索引 `reference/` 下的任何文件 (`error: 'reference/armor/' does not have a commit checked out`)。它被挪到 `desktop/.dev/backup/reference-armor-git`(可逆,且该目录已被忽略),不是删除。 - 它的 README 指着 `../armor-platformio-backup/`,那份备份并不存在。改成明说"没有进仓库", 免得以后有人照着找一个不存在的目录。 另外把 `/docs/` 加进 .gitignore:那是工作区里另起的一个 Astro 文档站,与本仓库无关。
216 lines
7.0 KiB
C++
216 lines
7.0 KiB
C++
/*
|
||
* 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 <cstdio>
|
||
#include <cstring>
|
||
|
||
#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<int>(Air::my_packet.color),
|
||
static_cast<int>(Air::peer_packet.hit_cnt),
|
||
static_cast<int>(Air::peer_packet.rssi),
|
||
static_cast<int>(Air::my_packet.rssi),
|
||
static_cast<int>(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);
|
||
}
|