mirror of
https://github.com/ruvnet/RuView
synced 2026-08-08 20:11:43 +00:00
feat(firmware): v0.6.7-esp32 — real LP-core program + C6 soft-AP HE/TWT helper
ADR-110 P9 — software-only unblocks for the WITNESS-LOG-110 §B
hardware-blocked items. Two new modules, both default-off so v0.6.6 fleets
see no behavior change.
LP-core (B4 path):
- New firmware/esp32-csi-node/main/lp_core/main.c: real RISC-V LP-core
motion-gate program with debounce + monotonic motion_count counter.
- c6_lp_core.c rewritten to load + run the LP binary via ulp_lp_core_run
when CONFIG_C6_LP_CORE_ENABLE=y; falls back to the v0.6.6 ext1 GPIO-wake
path otherwise (keeps regression surface small).
- ulp_embed_binary() wired in main/CMakeLists.txt, gated on the Kconfig.
- New Kconfig knobs: C6_LP_POLL_PERIOD_US (default 10 ms),
C6_LP_DEBOUNCE_SAMPLES (default 3).
- Exposes c6_lp_core_motion_count() / c6_lp_core_poll_count() for the
witness harness — once an INA/Joulescope is on the bench, B4 is one
capture away from a measured number.
Soft-AP HE (B1/B2 unblock):
- New c6_softap_he.{h,c}: brings up the C6 in AP+STA mode with WPA2-PSK
+ HE advertisement, so a second C6 in STA mode can negotiate real
iTWT against a known-cooperative AP without buying an 11ax router.
- main.c calls c6_softap_he_start() right before esp_wifi_start() when
CONFIG_C6_SOFTAP_HE_ENABLE=y.
- New Kconfig knobs: C6_SOFTAP_HE_{SSID,PSK,CHANNEL} with NVS overrides
via softap_ssid / softap_psk / softap_chan in the ruview namespace.
Build artifacts (IDF v5.4, both green, RC=0):
- S3 8 MB: 1093 KB (47% partition slack)
- C6 4 MB: 1019 KB (45% partition slack)
- SHA-256 sums in dist/firmware-v0.6.7/SHA256SUMS.txt
Doc updates: CHANGELOG wave-3 entry, ADR-110 phase table gets P5
upgrade note + new P9 row, WITNESS-LOG-110 gets new A0 section
recording the v0.6.7 build evidence.
Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -1,14 +1,24 @@
|
||||
/**
|
||||
* @file c6_lp_core.c
|
||||
* @brief LP-core wake-on-motion hibernation — ADR-110 Phase 5 skeleton.
|
||||
* @brief LP-core wake-on-motion hibernation — ADR-110 Phase 5 (full).
|
||||
*
|
||||
* The actual LP-core binary lives in a separate component subproject
|
||||
* compiled with the LP RISC-V toolchain (`riscv32-esp-elf` with LP-core
|
||||
* memory layout). For the P5 skeleton we ship just the HP-side arming
|
||||
* + deep-sleep entry, using esp_sleep_enable_ext1_wakeup() as the wake
|
||||
* source. A follow-up turn will replace ext1 with a true LP-core
|
||||
* polling program that can debounce / threshold the accelerometer
|
||||
* signal in software, dropping standby current from ~10 µA to ~5 µA.
|
||||
* Two operating modes, controlled by CONFIG_C6_LP_CORE_ENABLE:
|
||||
*
|
||||
* 1. ENABLED — real LP-core RISC-V program polls the wake GPIO at
|
||||
* LP_TIMER cadence (default 10 ms), debounces N matching samples,
|
||||
* and triggers an HP wake via `ulp_lp_core_wakeup_main_processor()`.
|
||||
* HP enters deep sleep with `ESP_SLEEP_WAKEUP_ULP` as the source.
|
||||
* Targets ~5 µA average current (datasheet figure for LP-core +
|
||||
* RTC peripherals powered down). The LP binary is built by
|
||||
* `ulp_embed_binary(...)` in main/CMakeLists.txt from lp_core/main.c.
|
||||
*
|
||||
* 2. DISABLED — falls back to plain deep-sleep + GPIO wake-up
|
||||
* (`esp_deep_sleep_enable_gpio_wakeup`). No debounce, no
|
||||
* sub-10 µA floor, but no LP toolchain dependency either.
|
||||
* This is the path the v0.6.6 firmware shipped with.
|
||||
*
|
||||
* Both paths share `c6_lp_core_arm()` / `c6_lp_core_hibernate_and_wait()`
|
||||
* so call sites in main.c don't change between modes.
|
||||
*/
|
||||
|
||||
#include "sdkconfig.h"
|
||||
@@ -20,6 +30,18 @@
|
||||
#include "esp_sleep.h"
|
||||
#include "driver/rtc_io.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include <string.h>
|
||||
|
||||
#if defined(CONFIG_C6_LP_CORE_ENABLE)
|
||||
#include "ulp_lp_core.h"
|
||||
/* ulp_main.h is auto-generated by `ulp_embed_binary(ulp_main, ...)` and
|
||||
* exports every `volatile` global from lp_core/main.c with the `ulp_`
|
||||
* prefix. Include is guarded so disabled builds don't try to find a
|
||||
* file the build system hasn't generated. */
|
||||
#include "ulp_main.h"
|
||||
extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_main_bin_start");
|
||||
extern const uint8_t ulp_main_bin_end[] asm("_binary_ulp_main_bin_end");
|
||||
#endif
|
||||
|
||||
static const char *TAG = "c6_lp";
|
||||
|
||||
@@ -27,6 +49,14 @@ static int s_wake_gpio = -1;
|
||||
static bool s_active_high = true;
|
||||
static bool s_armed = false;
|
||||
|
||||
#ifndef CONFIG_C6_LP_POLL_PERIOD_US
|
||||
#define CONFIG_C6_LP_POLL_PERIOD_US 10000 /* 100 Hz default poll cadence */
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_C6_LP_DEBOUNCE_SAMPLES
|
||||
#define CONFIG_C6_LP_DEBOUNCE_SAMPLES 3
|
||||
#endif
|
||||
|
||||
esp_err_t c6_lp_core_arm(int wake_gpio, bool active_high)
|
||||
{
|
||||
if (wake_gpio < 0) {
|
||||
@@ -36,29 +66,87 @@ esp_err_t c6_lp_core_arm(int wake_gpio, bool active_high)
|
||||
s_wake_gpio = wake_gpio;
|
||||
s_active_high = active_high;
|
||||
|
||||
/* GPIO must be in the LP/RTC domain for deep-sleep wake. */
|
||||
/* GPIO must be in the LP/RTC domain for either wake path. */
|
||||
esp_err_t ret = rtc_gpio_init(wake_gpio);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "rtc_gpio_init(%d) failed: %s", wake_gpio, esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
rtc_gpio_set_direction(wake_gpio, RTC_GPIO_MODE_INPUT_ONLY);
|
||||
/* Floating inputs in deep sleep are an antenna — disable internal pulls
|
||||
* only if the user has an external pull on the motion line; we leave
|
||||
* default pulls so a disconnected pin doesn't toggle randomly. */
|
||||
|
||||
/* On the C6, deep-sleep GPIO wake is esp_deep_sleep_enable_gpio_wakeup. */
|
||||
#if defined(CONFIG_C6_LP_CORE_ENABLE)
|
||||
/* --- Real LP-core path --- */
|
||||
|
||||
/* On C6, LP-IO maps 1:1 to GPIO for indices 0..7. Validate. */
|
||||
if (wake_gpio > 7) {
|
||||
ESP_LOGE(TAG, "LP-core path requires LP-IO 0..7, got GPIO %d", wake_gpio);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Load the LP-core binary blob. */
|
||||
esp_err_t err = ulp_lp_core_load_binary(
|
||||
ulp_main_bin_start,
|
||||
(size_t)(ulp_main_bin_end - ulp_main_bin_start));
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "ulp_lp_core_load_binary failed: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Hand the GPIO parameters to the LP program via shared symbols.
|
||||
* These are declared `volatile` in lp_core/main.c so the HP write
|
||||
* is observed by LP on the next iteration. */
|
||||
ulp_wake_gpio_num = (uint32_t)wake_gpio;
|
||||
ulp_wake_active_high = active_high ? 1u : 0u;
|
||||
ulp_debounce_samples = CONFIG_C6_LP_DEBOUNCE_SAMPLES;
|
||||
ulp_motion_count = 0;
|
||||
ulp_poll_count = 0;
|
||||
ulp_last_gpio_level = 0;
|
||||
|
||||
/* Configure LP-timer wakeup at the configured poll period and start the
|
||||
* LP-core. `ulp_lp_core_run` is non-blocking; the LP core begins running
|
||||
* the program immediately and the HP core can proceed to deep sleep. */
|
||||
ulp_lp_core_cfg_t cfg = {
|
||||
.wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER,
|
||||
.lp_timer_sleep_duration_us = CONFIG_C6_LP_POLL_PERIOD_US,
|
||||
};
|
||||
err = ulp_lp_core_run(&cfg);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "ulp_lp_core_run failed: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Tell deep-sleep that the LP-core is our wake source. */
|
||||
err = esp_sleep_enable_ulp_wakeup();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_sleep_enable_ulp_wakeup failed: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
s_armed = true;
|
||||
ESP_LOGI(TAG, "LP-core armed: gpio=%d active_%s debounce=%d poll=%d µs",
|
||||
wake_gpio, active_high ? "high" : "low",
|
||||
CONFIG_C6_LP_DEBOUNCE_SAMPLES, CONFIG_C6_LP_POLL_PERIOD_US);
|
||||
return ESP_OK;
|
||||
|
||||
#else
|
||||
/* --- Fallback path: plain deep-sleep GPIO wakeup (~10 µA floor) --- */
|
||||
uint64_t mask = 1ULL << wake_gpio;
|
||||
esp_deepsleep_gpio_wake_up_mode_t mode = active_high
|
||||
? ESP_GPIO_WAKEUP_GPIO_HIGH
|
||||
: ESP_GPIO_WAKEUP_GPIO_LOW;
|
||||
ret = esp_deep_sleep_enable_gpio_wakeup(mask, mode);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "enable_gpio_wakeup failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
esp_err_t err = esp_deep_sleep_enable_gpio_wakeup(mask, mode);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "enable_gpio_wakeup failed: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
s_armed = true;
|
||||
ESP_LOGI(TAG, "armed: wake_gpio=%d active_%s",
|
||||
ESP_LOGI(TAG, "GPIO-wakeup armed (no LP-core): gpio=%d active_%s",
|
||||
wake_gpio, active_high ? "high" : "low");
|
||||
return ESP_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
void c6_lp_core_hibernate_and_wait(void)
|
||||
@@ -66,13 +154,15 @@ void c6_lp_core_hibernate_and_wait(void)
|
||||
if (!s_armed) {
|
||||
ESP_LOGW(TAG, "hibernate called without arm — sleeping with no wake source");
|
||||
}
|
||||
/* Configure for hibernation: power down everything except what's needed
|
||||
* to retain the wake source. On C6 the RTC peripheral domain is the
|
||||
* only one we need to gate explicitly — RTC_SLOW_MEM / RTC_FAST_MEM
|
||||
* aren't separate power domains on the C6 SoC. */
|
||||
/* Power down the RTC peripheral domain — the LP-core itself stays
|
||||
* powered on the LP power domain so it can keep polling. */
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
|
||||
|
||||
ESP_LOGI(TAG, "entering deep sleep — target ≤5 µA");
|
||||
#if defined(CONFIG_C6_LP_CORE_ENABLE)
|
||||
ESP_LOGI(TAG, "entering deep sleep — LP-core polling, target ≤5 µA");
|
||||
#else
|
||||
ESP_LOGI(TAG, "entering deep sleep — GPIO wakeup, target ~10 µA");
|
||||
#endif
|
||||
esp_deep_sleep_start();
|
||||
/* Never returns. */
|
||||
}
|
||||
@@ -80,7 +170,27 @@ void c6_lp_core_hibernate_and_wait(void)
|
||||
bool c6_lp_core_was_motion_wake(void)
|
||||
{
|
||||
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
|
||||
#if defined(CONFIG_C6_LP_CORE_ENABLE)
|
||||
/* Real LP-core path: wakeup cause is ULP (LP-core triggered HP). */
|
||||
if (cause == ESP_SLEEP_WAKEUP_ULP) return true;
|
||||
#endif
|
||||
/* Fallback path or alternate GPIO wakeup. */
|
||||
return cause == ESP_SLEEP_WAKEUP_GPIO || cause == ESP_SLEEP_WAKEUP_EXT1;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_C6_LP_CORE_ENABLE)
|
||||
uint32_t c6_lp_core_motion_count(void)
|
||||
{
|
||||
return (uint32_t)ulp_motion_count;
|
||||
}
|
||||
|
||||
uint32_t c6_lp_core_poll_count(void)
|
||||
{
|
||||
return (uint32_t)ulp_poll_count;
|
||||
}
|
||||
#else
|
||||
uint32_t c6_lp_core_motion_count(void) { return 0; }
|
||||
uint32_t c6_lp_core_poll_count(void) { return 0; }
|
||||
#endif
|
||||
|
||||
#endif /* CONFIG_IDF_TARGET_ESP32C6 && CONFIG_ULP_COPROC_TYPE_LP_CORE */
|
||||
|
||||
Reference in New Issue
Block a user