#include "Receiver.h" #include "Config.h" #include #if !OPTICAL_USE_RMT_DMA #include #include #endif uint32_t PulseReceiver::tickHz() const { #if OPTICAL_USE_RMT_DMA return captureResolutionHz_; #else return cpuTickHz_; #endif } uint32_t PulseReceiver::plannedTickHz(uint32_t expectedHz, float expectedDutyPct) const { #if OPTICAL_USE_RMT_DMA if (!expectedHz || expectedDutyPct <= 0.0f || expectedDutyPct >= 100.0f) return CAPTURE_RESOLUTION_OPTIONS_HZ[0]; uint32_t dutyX100 = static_cast(expectedDutyPct * 100.0f + 0.5f); if (dutyX100 < 5000U) dutyX100 = 10000U - dutyX100; for (int i = static_cast(countOf(CAPTURE_RESOLUTION_OPTIONS_HZ)) - 1; i >= 0; --i) { const uint32_t resolution = CAPTURE_RESOLUTION_OPTIONS_HZ[i]; const uint64_t levelTicksX100 = static_cast(resolution) * dutyX100; const uint64_t limitX100 = static_cast(expectedHz) * 10000ULL * RMT_MAX_LEVEL_TICKS; if (levelTicksX100 <= limitX100) return resolution; } return CAPTURE_RESOLUTION_OPTIONS_HZ[0]; #else (void)expectedHz; (void)expectedDutyPct; return cpuTickHz_; #endif } bool PulseReceiver::begin() { #if OPTICAL_USE_RMT_DMA queue_ = xQueueCreate(RMT_QUEUE_BLOCKS, sizeof(SymbolBlock)); return queue_ && configureRmt(CAPTURE_RESOLUTION_OPTIONS_HZ[0]); #else queue_ = xQueueCreate(256, sizeof(Edge)); if (!queue_) return false; pinMode(GPIO_RX, INPUT); cpuTickHz_ = getCpuFrequencyMhz() * 1000000UL; attachInterruptArg(GPIO_RX, onGpio, this, CHANGE); return cpuTickHz_ != 0; #endif } #if OPTICAL_USE_RMT_DMA bool PulseReceiver::configureRmt(uint32_t resolutionHz) { if (channel_ && captureResolutionHz_ == resolutionHz) return true; stop(); if (channel_) { if (rmt_del_channel(channel_) != ESP_OK) return false; channel_ = nullptr; } rmt_rx_channel_config_t cfg = {}; cfg.clk_src = RMT_CLK_SRC_DEFAULT; cfg.resolution_hz = resolutionHz; cfg.gpio_num = static_cast(GPIO_RX); cfg.flags.invert_in = RX_SIGNAL_INVERTED; #if CONFIG_IDF_TARGET_ESP32S3 cfg.mem_block_symbols = 512; cfg.flags.with_dma = true; #else // C3 has 48 RMT symbols per channel and no RMT DMA. A request for 512 // consumes all available blocks and fails with "no free rx channels". cfg.mem_block_symbols = RMT_MIN_RECEIVE_SYMBOLS; cfg.flags.with_dma = false; // C3 uses hardware RMT ping-pong partial reception #endif if (rmt_new_rx_channel(&cfg, &channel_) != ESP_OK) return false; rmt_rx_event_callbacks_t callbacks = {}; callbacks.on_recv_done = onRmt; if (rmt_rx_register_event_callbacks(channel_, &callbacks, this) != ESP_OK) { rmt_del_channel(channel_); channel_ = nullptr; return false; } captureResolutionHz_ = resolutionHz; return true; } #endif bool PulseReceiver::start(uint32_t expectedHz, float expectedDutyPct) { #if OPTICAL_USE_RMT_DMA const uint32_t resolutionHz = plannedTickHz(expectedHz, expectedDutyPct); if (!configureRmt(resolutionHz)) return false; #else (void)expectedHz; (void)expectedDutyPct; #endif resetStream(); #if OPTICAL_USE_RMT_DMA // In partial RX mode the callback is delivered when this user buffer fills. // Keep chunks near 5 ms so low-frequency input is reported before NO SIGNAL. uint64_t symbols = (static_cast(expectedHz) * RMT_TARGET_CHUNK_US + 999999ULL) / 1000000ULL; if (symbols < RMT_MIN_RECEIVE_SYMBOLS) symbols = RMT_MIN_RECEIVE_SYMBOLS; if (symbols > RMT_MAX_RECEIVE_SYMBOLS) symbols = RMT_MAX_RECEIVE_SYMBOLS; receiveChunkSymbols_ = static_cast(symbols); if (rmt_enable(channel_) != ESP_OK) return false; rmt_receive_config_t cfg = {}; cfg.signal_range_min_ns = 1000000000UL / captureResolutionHz_; const uint64_t maxNs = 4000000000ULL / (expectedHz ? expectedHz : 1); // A duration field is 15 bits. Keep the driver's end-of-signal threshold // strictly below that hardware limit (IDF rejects larger values). const uint64_t hardwareMaxNs = static_cast(RMT_MAX_LEVEL_TICKS) * 1000000000ULL / captureResolutionHz_; cfg.signal_range_max_ns = static_cast(maxNs > hardwareMaxNs ? hardwareMaxNs : maxNs); cfg.flags.en_partial_rx = true; if (rmt_receive(channel_, receiveBuffer_, receiveChunkSymbols_ * sizeof(receiveBuffer_[0]), &cfg) != ESP_OK) { rmt_disable(channel_); return false; } #endif running_ = true; return true; } void PulseReceiver::stop() { #if OPTICAL_USE_RMT_DMA if (running_) rmt_disable(channel_); #endif running_ = false; } void PulseReceiver::resetStream() { if (queue_) xQueueReset(queue_); overflow_ = false; droppedItems_ = 0; haveRise_ = haveFall_ = haveRawTick_ = false; lastRawTick_ = 0; tickEpoch_ = rise_ = fall_ = 0; #if OPTICAL_USE_RMT_DMA block_ = {}; blockIndex_ = 0; phase_ = 0; haveLevel_ = false; level_ = false; rmtTick_ = 0; #endif } bool PulseReceiver::consumeEdge(const Edge &e, PulsePeriod &out) { if (haveRawTick_ && e.tick < lastRawTick_ && lastRawTick_ - e.tick > 0x80000000UL) tickEpoch_ += 0x100000000ULL; haveRawTick_ = true; lastRawTick_ = e.tick; const uint64_t tick = tickEpoch_ + e.tick; if (e.rising) { if (!haveRise_) { rise_ = tick; haveRise_ = true; haveFall_ = false; return false; } if (!haveFall_) { overflow_ = true; rise_ = tick; return false; } const uint32_t period = static_cast(tick - rise_); const uint32_t active = fall_ - rise_; out = {rise_, period, active}; rise_ = tick; haveFall_ = false; return true; } // Reception can begin in the middle of a HIGH pulse. In that case the first // observable edge is falling and there is no complete period to validate. // Ignore only this leading partial pulse and synchronize on the next rise. if (!haveRise_) return false; if (haveFall_) { overflow_ = true; return false; } fall_ = tick; haveFall_ = true; return false; } bool PulseReceiver::overflowed() { const bool value = overflow_; overflow_ = false; return value; } uint32_t PulseReceiver::takeDroppedItems() { return __atomic_exchange_n(&droppedItems_, 0, __ATOMIC_RELAXED); } #if OPTICAL_USE_RMT_DMA bool IRAM_ATTR PulseReceiver::onRmt(rmt_channel_handle_t, const rmt_rx_done_event_data_t *data, void *ctx) { PulseReceiver *self = static_cast(ctx); BaseType_t wake = pdFALSE; size_t offset = 0; while (offset < data->num_symbols) { SymbolBlock &b = self->isrBlock_; b.count = static_cast((data->num_symbols - offset) > BLOCK_SYMBOLS ? BLOCK_SYMBOLS : (data->num_symbols - offset)); memcpy(b.symbols, data->received_symbols + offset, b.count * sizeof(rmt_symbol_word_t)); if (xQueueSendFromISR(self->queue_, &b, &wake) != pdTRUE) __atomic_fetch_add(&self->droppedItems_, b.count, __ATOMIC_RELAXED); offset += b.count; } return wake == pdTRUE; } bool PulseReceiver::nextRmtEdge(Edge &edge, TickType_t waitTicks) { for (;;) { if (blockIndex_ >= block_.count) { if (xQueueReceive(queue_, &block_, waitTicks) != pdTRUE) return false; blockIndex_ = 0; phase_ = 0; waitTicks = 0; } const rmt_symbol_word_t &s = block_.symbols[blockIndex_]; const bool nextLevel = phase_ == 0 ? s.level0 : s.level1; const uint32_t duration = phase_ == 0 ? s.duration0 : s.duration1; phase_ ^= 1; if (phase_ == 0) ++blockIndex_; if (!duration) continue; if (!haveLevel_) { haveLevel_ = true; level_ = nextLevel; rmtTick_ += duration; continue; } if (nextLevel != level_) { level_ = nextLevel; edge = {rmtTick_, static_cast(nextLevel)}; rmtTick_ += duration; return true; } rmtTick_ += duration; } } size_t PulseReceiver::readPeriods(PulsePeriod *periods, size_t capacity, TickType_t waitTicks) { size_t count = 0; Edge e; while (count < capacity && nextRmtEdge(e, count ? 0 : waitTicks)) if (consumeEdge(e, periods[count])) ++count; return count; } #else void IRAM_ATTR PulseReceiver::onGpio(void *ctx) { PulseReceiver *self = static_cast(ctx); bool level = gpio_get_level(static_cast(GPIO_RX)); if (RX_SIGNAL_INVERTED) level = !level; Edge e = {esp_cpu_get_cycle_count(), static_cast(level)}; BaseType_t wake = pdFALSE; if (xQueueSendFromISR(self->queue_, &e, &wake) != pdTRUE) __atomic_fetch_add(&self->droppedItems_, 1U, __ATOMIC_RELAXED); if (wake) portYIELD_FROM_ISR(); } size_t PulseReceiver::readPeriods(PulsePeriod *periods, size_t capacity, TickType_t waitTicks) { size_t count = 0; Edge e; while (count < capacity && xQueueReceive(queue_, &e, count ? 0 : waitTicks) == pdTRUE) if (consumeEdge(e, periods[count])) ++count; return count; } #endif