Files
ESP32S3_vt/reference/armor/main/hit.cpp
T
Horldsence fd71b83e3a reference: 早先那版 C 写的装甲板固件(ESP-IDF 工程)
`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 文档站,与本仓库无关。
2026-10-14 19:45:15 +08:00

193 lines
7.2 KiB
C++

#include "hit.hpp"
#include <cstring>
#include "armor_time.hpp"
#include "driver/gpio.h"
#include "driver/rmt_tx.h"
#include "esp_adc/adc_continuous.h"
#include "esp_log.h"
#include "hal/adc_types.h"
#include "led_strip_encoder.h"
#include "soc/soc_caps.h"
#include "storage.hpp"
namespace {
constexpr const char *TAG = "hit";
// 这是总轮询采样率,每个通道的采样率只有 1/4
constexpr uint32_t SAMPLE_FREQ_HZ = 40000;
// 10ms 的采样数据(字节数,每个采样点 4 字节)
constexpr uint32_t SAMPLE_LEN = static_cast<uint32_t>(10e-3 * SAMPLE_FREQ_HZ * 4);
// 压电片 ADC 输入:front, back, left, right -> GPIO0 / GPIO4 / GPIO3 / GPIO1
constexpr int ADC_CHANNEL_COUNT = 4;
constexpr adc_channel_t ADC_CHANNELS[ADC_CHANNEL_COUNT] = {ADC_CHANNEL_0, ADC_CHANNEL_4, ADC_CHANNEL_3,
ADC_CHANNEL_1};
// WS2812 数据脚:front, back, left, right
constexpr int STRIP_COUNT = 4;
constexpr int STRIP_LEDS = 3;
constexpr gpio_num_t STRIP_GPIOS[STRIP_COUNT] = {GPIO_NUM_10, GPIO_NUM_5, GPIO_NUM_6, GPIO_NUM_7};
// WS2812 bit timing needs a fine resolution: 10MHz -> 1 tick = 0.1us
constexpr uint32_t RMT_RESOLUTION_HZ = 10000000;
// The ESP32-C3 RMT block owns 96 symbols of channel memory in total, shared by
// its two TX channels (each channel's memory is allocated in multiples of
// SOC_RMT_MEM_WORDS_PER_CHANNEL = 48 symbols). A 64-symbol block size would
// round up to 96 and therefore consume the whole block for a single channel,
// making the second `rmt_new_tx_channel()` fail with
// "rmt_tx_register_to_group: no free tx channels".
//
// All four strips always display the same colour, so a single channel that owns
// the full 96-symbol memory is enough: it holds a complete 3-LED frame
// (3 * 24 bits + reset = 73 symbols) in one shot and is parked on a different
// GPIO for each strip.
constexpr int RMT_CHANNEL_COUNT = 1;
constexpr uint32_t RMT_MEM_BLOCK_SYMBOLS = 96;
adc_continuous_handle_t s_adc = nullptr;
rmt_channel_handle_t s_rmt_channels[RMT_CHANNEL_COUNT] = {};
rmt_encoder_handle_t s_rmt_encoders[RMT_CHANNEL_COUNT] = {};
// Raw ADC DMA frame (10 ms worth of samples). 4-byte aligned so the result
// records can be read through `adc_digi_output_data_t`.
alignas(4) uint8_t s_adc_buf[SAMPLE_LEN];
// GRB pixel buffer; all four strips always display the same colour, so a single
// buffer is reused for each of them.
uint8_t s_pixels[STRIP_LEDS * 3];
void continuous_adc_init() {
// ADC 配置
adc_continuous_handle_cfg_t adc_dma_config = {};
adc_dma_config.max_store_buf_size = SAMPLE_LEN * 2; // 两倍缓冲区
adc_dma_config.conv_frame_size = SAMPLE_LEN;
ESP_ERROR_CHECK(adc_continuous_new_handle(&adc_dma_config, &s_adc));
// 通道配置
adc_digi_pattern_config_t adc_pattern[ADC_CHANNEL_COUNT] = {};
for (int i = 0; i < ADC_CHANNEL_COUNT; i++) {
adc_pattern[i].atten = ADC_ATTEN_DB_0;
adc_pattern[i].channel = ADC_CHANNELS[i] & 0x7;
adc_pattern[i].unit = 0;
adc_pattern[i].bit_width = SOC_ADC_DIGI_MAX_BITWIDTH;
}
adc_continuous_config_t dig_cfg = {};
dig_cfg.pattern_num = ADC_CHANNEL_COUNT;
dig_cfg.adc_pattern = adc_pattern;
dig_cfg.sample_freq_hz = SAMPLE_FREQ_HZ;
dig_cfg.conv_mode = ADC_CONV_SINGLE_UNIT_1;
ESP_ERROR_CHECK(adc_continuous_config(s_adc, &dig_cfg));
}
void rmt_led_init() {
for (int i = 0; i < RMT_CHANNEL_COUNT; i++) {
rmt_tx_channel_config_t tx_chan_config = {};
tx_chan_config.gpio_num = STRIP_GPIOS[i];
tx_chan_config.clk_src = RMT_CLK_SRC_DEFAULT;
tx_chan_config.resolution_hz = RMT_RESOLUTION_HZ;
tx_chan_config.mem_block_symbols = RMT_MEM_BLOCK_SYMBOLS;
tx_chan_config.trans_queue_depth = 4;
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &s_rmt_channels[i]));
led_strip_encoder_config_t encoder_config = {};
encoder_config.resolution = RMT_RESOLUTION_HZ;
ESP_ERROR_CHECK(rmt_new_led_strip_encoder(&encoder_config, &s_rmt_encoders[i]));
ESP_ERROR_CHECK(rmt_enable(s_rmt_channels[i]));
}
}
// Push the shared pixel buffer to one strip. A single RMT TX channel can only
// drive one pin at a time, and rmt_tx_switch_gpio() only works on a disabled
// channel, so the channel is briefly parked on the target GPIO before each frame.
void strip_show(int strip) {
const int index = strip % RMT_CHANNEL_COUNT;
rmt_channel_handle_t chan = s_rmt_channels[index];
esp_err_t err = rmt_disable(chan);
if (err == ESP_OK) {
err = rmt_tx_switch_gpio(chan, STRIP_GPIOS[strip], false);
}
if (err == ESP_OK) {
err = rmt_enable(chan);
}
rmt_transmit_config_t tx_config = {};
tx_config.loop_count = 0; // no transfer loop
if (err == ESP_OK) {
err = rmt_transmit(chan, s_rmt_encoders[index], s_pixels, sizeof(s_pixels), &tx_config);
}
if (err == ESP_OK) {
err = rmt_tx_wait_all_done(chan, 1000);
}
if (err != ESP_OK) {
ESP_LOGW(TAG, "strip %d refresh failed: %s", strip, esp_err_to_name(err));
}
}
void refresh_strips() {
const bool blink = Hit::detect_hit && (millis() - Hit::last_hit_ms < Hit::HIT_BLINK_MS);
if (blink) {
memset(s_pixels, 0, sizeof(s_pixels));
} else {
// WS2812 wants GRB byte order, MSB first, matching Adafruit_NeoPixel's
// NEO_GRB setting.
const uint32_t rgb = Hit::color;
for (int i = 0; i < STRIP_LEDS; i++) {
s_pixels[i * 3 + 0] = static_cast<uint8_t>((rgb >> 8) & 0xFF); // G
s_pixels[i * 3 + 1] = static_cast<uint8_t>((rgb >> 16) & 0xFF); // R
s_pixels[i * 3 + 2] = static_cast<uint8_t>((rgb >> 0) & 0xFF); // B
}
}
for (int i = 0; i < STRIP_COUNT; i++) {
strip_show(i);
}
}
} // namespace
// public
volatile uint32_t Hit::color = 0;
volatile bool Hit::detect_hit = true;
volatile uint32_t Hit::hit_cnt = 0;
volatile uint32_t Hit::last_hit_ms = 0;
void Hit::begin() {
// 从 flash 读取颜色
color = Storage::getUInt("color", RED);
rmt_led_init();
continuous_adc_init();
ESP_ERROR_CHECK(adc_continuous_start(s_adc));
}
void Hit::onLoop() {
// 击打检测
uint32_t out_length = 0;
esp_err_t err = adc_continuous_read(s_adc, s_adc_buf, SAMPLE_LEN, &out_length, 1000);
if (err == ESP_OK) {
for (uint32_t i = 0; i + SOC_ADC_DIGI_RESULT_BYTES <= out_length; i += SOC_ADC_DIGI_RESULT_BYTES) {
const auto *p = reinterpret_cast<const adc_digi_output_data_t *>(&s_adc_buf[i]);
const uint32_t data = p->type2.data;
if (data > HIT_THRESHOLD) { // 检测到击打
if (detect_hit && (millis() - last_hit_ms) > HIT_BLINK_MS) { // 不在上一次击打冷却期内,新的击打
// Note: '++' on a volatile is deprecated since C++20 (and IDF
// builds with -Werror), so use an explicit read-modify-write.
hit_cnt = hit_cnt + 1;
last_hit_ms = millis();
}
}
}
} else if (err != ESP_ERR_TIMEOUT) {
ESP_LOGW(TAG, "adc_continuous_read failed: %s", esp_err_to_name(err));
}
refresh_strips();
}