feat(cpu): smoothed RP2350 CPU/Clock telemetry + 0xfc HID report

Add cpu_temp_raw_smoothed() (cmd.cpp/cmd.h): 256-sample average over a
slow EMA (alpha=0.15) so the RP2350 on-die temperature converges to the
true value instead of chasing per-sample 12-bit ADC noise (~1 LSB =
0.47 C). Single source of truth, ADC initialised in one place.

Also export CPU/Clock telemetry on HID feature report 0xfc (set_khz,
cached real_khz, vreg code, ADC ch4 raw). The handler is dormant: live
read over WebHID is not feasible (Chrome requires the report declared
in the HID descriptor, and declaring the OLED Edition vendor reports
breaks DualSense enumeration on Windows). It is harmless and left for a
possible future non-WebHID consumer. Link hardware_adc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Thierry Perraut
2026-05-18 07:31:13 -06:00
committed by Marceline
co-authored by Claude Opus 4.7
parent 4552fd2b41
commit 5425b21f00
3 changed files with 77 additions and 1 deletions
+1
View File
@@ -171,6 +171,7 @@ target_link_libraries(ds5-bridge
hardware_timer
hardware_flash
hardware_spi
hardware_adc
pico_btstack_classic
# pico_cyw43_arch_threadsafe_background
pico_cyw43_arch_poll
+69 -1
View File
@@ -14,6 +14,39 @@
#include "device/usbd.h"
#include "pico/time.h"
#include "slots.h"
#include "hardware/clocks.h"
#include "hardware/adc.h"
#include "hardware/vreg.h"
uint16_t cpu_temp_raw_smoothed() {
// One-time ADC bring-up. This is the only place the ADC is initialised
// now (oled.cpp's CPU screen calls through here too). Runs on core0
// under the cooperative main loop; adc_select_input(4) is set before
// every read, so the shared ADC needs no locking.
static bool adc_ready = false;
if (!adc_ready) {
adc_init();
adc_set_temp_sensor_enabled(true);
adc_ready = true;
}
adc_select_input(4);
// The temp sensor has a shallow slope (-1.721 mV/C) and ~1 LSB ≈ 0.47 C,
// so a lone 12-bit sample swings several tenths of a degree frame to
// frame. Average a big block to kill that...
constexpr int kSamples = 256;
uint32_t acc = 0;
for (int i = 0; i < kSamples; i++) acc += adc_read();
const float mean = (float)acc / (float)kSamples;
// ...then a slow EMA so the displayed value glides to the true die
// temperature rather than mirroring the latest block. Seeded on the
// first call so it doesn't ramp up from zero.
static float ema = -1.0f;
if (ema < 0.0f) ema = mean;
else ema += (mean - ema) * 0.15f;
return (uint16_t)(ema + 0.5f);
}
bool is_pico_cmd(uint8_t report_id) {
if (report_id == 0xf6 ||
@@ -21,7 +54,8 @@ bool is_pico_cmd(uint8_t report_id) {
report_id == 0xf8 ||
report_id == 0xf9 ||
report_id == 0xfa ||
report_id == 0xfb
report_id == 0xfb ||
report_id == 0xfc
) {
return true;
}
@@ -93,6 +127,40 @@ uint16_t pico_cmd_get(uint8_t report_id, uint8_t *buffer, uint16_t reqlen) {
memcpy(buffer + 14, &hci_errs, 4);
return want;
}
if (report_id == 0xfc) {
// OLED Edition: CPU / Clock telemetry for the web emulator. 11 bytes:
// [0..3] set_khz uint32 configured clk_sys (SYS_CLOCK_KHZ)
// [4..7] real_khz uint32 measured clk_sys (cached, see below)
// [8] vcode uint8 vreg_get_voltage() raw enum code
// [9..10] temp_raw uint16 ADC ch4 12-bit reading
// The web side does the volts/temperature math (same formulas as
// render_screen_cpu) so the firmware HID path stays float-free.
constexpr uint16_t want = 11;
if (reqlen < want) {
printf("[HID] 0xfc reqlen=%u too small for cpu payload (%u)\n", reqlen, want);
return 0;
}
const uint32_t set_khz = (uint32_t)SYS_CLOCK_KHZ;
// clk_sys is fixed at boot and frequency_count_khz() busy-waits a few
// ms — measure exactly once (lazily) and cache. Doing it here on the
// first poll keeps it off the boot path; one ~ms stall in a single
// GET_REPORT is acceptable.
static uint32_t cached_real_khz = 0;
if (cached_real_khz == 0) {
cached_real_khz = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_SYS);
}
const uint16_t temp_raw = cpu_temp_raw_smoothed();
const uint8_t vcode = (uint8_t)vreg_get_voltage();
memcpy(buffer + 0, &set_khz, 4);
memcpy(buffer + 4, &cached_real_khz, 4);
buffer[8] = vcode;
memcpy(buffer + 9, &temp_raw, 2);
return want;
}
return 0;
}
+7
View File
@@ -11,4 +11,11 @@ bool is_pico_cmd(uint8_t report_id);
uint16_t pico_cmd_get(uint8_t report_id, uint8_t *buffer,uint16_t reqlen);
void pico_cmd_set(uint8_t report_id, uint8_t const *buffer,uint16_t bufsize);
// Smoothed RP2350 on-die temperature sensor reading (ADC input 4, 12-bit
// raw). A single sample is very noisy; this averages a large block and runs
// a slow EMA so the value converges to the true die temperature instead of
// chasing per-sample noise. Single source of truth — the OLED CPU screen and
// the 0xfc web telemetry both call this so device and web always agree.
uint16_t cpu_temp_raw_smoothed();
#endif //DS5_BRIDGE_CMD_H