#include "hit.hpp" #include #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(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((rgb >> 8) & 0xFF); // G s_pixels[i * 3 + 1] = static_cast((rgb >> 16) & 0xFF); // R s_pixels[i * 3 + 2] = static_cast((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(&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(); }