Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ AllowShortFunctionsOnASingleLine: Empty
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: OnePerLine
PenaltyReturnTypeOnItsOwnLine: 1000
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
DerivePointerAlignment: false
Expand Down
24 changes: 24 additions & 0 deletions include/v2/events.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <tempo/bus/event.h>

#include "v2/pocketpd.h"
#include "v2/util/filter.h"

namespace pocketpd {

Expand Down Expand Up @@ -68,19 +69,42 @@ namespace pocketpd {
* @brief Load-side reading from INA226.
*/
struct LoadReading {
static constexpr uint32_t LOAD_EMA_DEN = 4;
static constexpr uint32_t SNAP_MV = 200;
static constexpr uint32_t SNAP_MA = 20;

uint32_t timestamp_ms = 0;
uint32_t vbus_mv = 0;
uint32_t current_ma = 0;

LoadReading ema(const LoadReading& sample ) const {
return {
.timestamp_ms = sample.timestamp_ms,
.vbus_mv = Filter::ema(vbus_mv, sample.vbus_mv, LOAD_EMA_DEN, SNAP_MV),
.current_ma = Filter::ema(current_ma, sample.current_ma, LOAD_EMA_DEN, SNAP_MA),
};
}
};

/**
* @brief Supply-side reading Sourced from the V_SENSE ADC divider on HW1.3+ or the AP33772
* VOLTAGE register on earlier board.
*/
struct SupplyReading {
static constexpr uint32_t SUPPLY_EMA_DEN = 8;
static constexpr uint32_t SNAP_MV = 200;

uint32_t timestamp_ms = 0;
uint32_t mv = 0;
bool valid = false;

SupplyReading ema(const SupplyReading& sample) const {
return {
.timestamp_ms = sample.timestamp_ms,
.mv = Filter::ema(mv, sample.mv, SUPPLY_EMA_DEN, SNAP_MV),
.valid = sample.valid,
};
}
};

/**
Expand Down
19 changes: 14 additions & 5 deletions include/v2/hal/adc_supply_voltage_source.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
*/
#pragma once

#include <Arduino.h>

#include <cstdint>

#include <Arduino.h>

#include "v2/hal/supply_voltage_source.h"

namespace pocketpd {
Expand All @@ -22,6 +22,9 @@ namespace pocketpd {
static constexpr uint32_t DIVIDER_NUM = 13;
static constexpr uint32_t DIVIDER_DEN = 2;

// Oversample N raw samples per read(); sqrt(N) noise reduction on uncorrelated ADC noise.
static constexpr uint16_t OVERSAMPLE_N = 64; // tested ~300us loop time at 64 samples

public:
explicit AdcSupplyVoltageSource(uint8_t pin) : m_pin(pin) {}

Expand All @@ -30,9 +33,15 @@ namespace pocketpd {
}

SupplyVoltageReading read() override {
const uint32_t raw = analogRead(m_pin);
const uint32_t adc_mv = (raw * ADC_REF_MV) / ADC_MAX;
const uint32_t vbus_mv = (adc_mv * DIVIDER_NUM) / DIVIDER_DEN;
uint32_t sum = 0;
for (int i = 0; i < OVERSAMPLE_N; i++) {
sum += analogRead(m_pin);
}

const uint64_t numerator = (uint64_t) sum * ADC_REF_MV * DIVIDER_NUM;
const uint64_t denominator = (uint64_t) OVERSAMPLE_N * ADC_MAX * DIVIDER_DEN;
const uint32_t vbus_mv = numerator / denominator;

return SupplyVoltageReading{vbus_mv, true};
}
};
Expand Down
13 changes: 13 additions & 0 deletions include/v2/pocketpd.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,19 @@ namespace pocketpd {
// PPS RDO stepping per USB-PD 3.0 spec.
constexpr int32_t PPS_VOLTAGE_STEP_MV = 20;
constexpr int32_t PPS_CURRENT_STEP_MA = 50;

constexpr uint8_t pin_encoder_SW = 18;
constexpr uint8_t pin_encoder_A = 19; // CLK
constexpr uint8_t pin_encoder_B = 20; // DATA

constexpr uint8_t pin_output_Enable = 1;
constexpr uint8_t pin_button_outputSW = 10;
constexpr uint8_t pin_button_selectVI = 11;

constexpr uint8_t pin_SDA = 4;
constexpr uint8_t pin_SCL = 5;

constexpr uint8_t pin_VSENSE = 29; // ADC3 — V_SENSE divider (2/13 of VBUS)

// —— I2C addresses

Expand Down
27 changes: 13 additions & 14 deletions include/v2/stages/energy_stage.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
#include "v2/hal/output_gate.h"
#include "v2/images.h"
#include "v2/stages/energy/energy_view.h"
#include "v2/pocketpd.h"
#include "v2/util/filter.h"

namespace pocketpd {

Expand All @@ -41,8 +39,6 @@ namespace pocketpd {
uint8_t m_arrow_frame = 0;
bool m_locked = false;

static constexpr uint32_t SENSOR_EMA_DEN = 4;

public:
static constexpr const char* LOG_TAG = "EnergyStage";

Expand Down Expand Up @@ -79,29 +75,32 @@ namespace pocketpd {

void on_event(Conductor& conductor, const Event& event, uint32_t) override {
auto handler = tempo::overloaded{
[&](const ButtonEvent& evt) {
// L+R combo always reachable; must precede lock guard so a locked screen can unlock.
if (evt.lr_long()) {
[&](const ButtonEvent& event) {
// L+R combo always reachable; must precede lock guard so a locked screen can
// unlock.
if (event.lr_long()) {
m_locked = !m_locked;
return;
}
if (m_locked) {
return;
}

if (evt.r_short()) {
if (event.r_short()) {
m_output_gate.toggle();
return;
}
if (evt.r_long()) {
if (event.r_long()) {
conductor.request<NormalStage>(m_active_pdo_index);
}
},
[&](const SensorEvent& evt) {
m_load_reading = m_load_init
? Filter::ema(m_load_reading, evt.load, SENSOR_EMA_DEN)
: evt.load;
m_load_init = true;
[&](const SensorEvent& event) {
if (m_load_init) {
m_load_reading = m_load_reading.ema(event.load);
} else {
m_load_reading = event.load;
m_load_init = true;
}
},
[&](const EnergyEvent& evt) { m_energy = evt; },
[](const auto&) {},
Expand Down
64 changes: 42 additions & 22 deletions include/v2/stages/normal_stage.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
#include "v2/stages/normal/fixed_mode.h"
#include "v2/stages/normal/normal_view.h"
#include "v2/stages/normal/pps_mode.h"
#include "v2/util/filter.h"

namespace pocketpd {

Expand Down Expand Up @@ -48,8 +47,12 @@ namespace pocketpd {
uint32_t m_last_draw_ms = 0;
bool m_blink_visible = true;

static constexpr uint32_t SENSOR_EMA_DEN = 4;
static constexpr uint32_t SUPPLY_EMA_DEN = 8;
// After OFF->ON, the first INA226 read returns a stale conversion (latched while FET
// was off; observed ~200 mV instead of true VBUS). Discard N load samples and hold the
// seeded supply value until the sensor has a fresh conversion in hand.
uint8_t m_postenable_discard_left = 0;
static constexpr uint8_t POSTENABLE_DISCARD_SAMPLES = 2;

static constexpr uint32_t READOUT_BLINK_ON_MS = 1200;
static constexpr uint32_t READOUT_BLINK_OFF_MS = 400;
static constexpr uint32_t READOUT_BLINK_CYCLE_MS =
Expand Down Expand Up @@ -132,7 +135,7 @@ namespace pocketpd {
if (m_last_draw_ms != 0) {
const uint32_t period = now_ms - m_last_draw_ms;
const uint32_t hz = period == 0 ? 0 : 1000 / period;
log.debug("draw period={}ms (~{}Hz)", period, hz);
// log.debug("draw period={}ms (~{}Hz)", period, hz);
}
m_last_draw_ms = now_ms;
m_blink_visible = m_output_gate.is_enabled() ||
Expand All @@ -143,38 +146,48 @@ namespace pocketpd {

void on_event(Conductor& conductor, const Event& event, uint32_t) override {
auto handler = tempo::overloaded{
[&](const ButtonEvent& evt) {
[&](const ButtonEvent& event) {
// L+R combo always reachable; must precede lock guard so a locked screen can
// unlock.
if (evt.lr_long()) {
if (event.lr_long()) {
m_locked = !m_locked;
return;
}

if (m_locked) {
return;
}

if (evt.r_short()) {
if (event.r_short()) {
const bool was_enabled = m_output_gate.is_enabled();
m_output_gate.toggle();
// Display source flips from supply-side to load-side on enable. Seed the
// load EMA with the current supply value so the first frame after toggle
// shows the known VBUS rather than a stale or zero load_reading.
if (!was_enabled && m_output_gate.is_enabled() && m_supply_init) {
m_load_reading.vbus_mv = m_supply_reading.mv;
m_load_init = true;
m_postenable_discard_left = POSTENABLE_DISCARD_SAMPLES;
}
return;
}

if (evt.r_long()) {
if (event.r_long()) {
conductor.request<EnergyStage>(m_active_pdo_index);
return;
}

if (evt.l_long()) {
if (event.l_long()) {
conductor.request<ProfilePickerStage>();
return;
}

// V/I are adjustable in PPS mode
if (auto* pps = std::get_if<PPSMode>(&m_mode)) {
pps->on_button(evt);
pps->on_button(event);
}
},
[&](const EncoderEvent& evt) {
[&](const EncoderEvent& event) {
if (m_locked) {
return;
}
Expand All @@ -183,21 +196,28 @@ namespace pocketpd {
return;
}

if (!pps->on_encoder(evt)) {
if (!pps->on_encoder(event)) {
const auto msg = "set_pps_pdo({}, {}, {}) failed";
log.error(msg, m_active_pdo_index, pps->target_mv, pps->target_ma);
}
},
[&](const SensorEvent& evt) {
m_load_reading = m_load_init
? Filter::ema(m_load_reading, evt.load, SENSOR_EMA_DEN)
: evt.load;
m_load_init = true;
if (evt.supply.valid) {
m_supply_reading = m_supply_init
? Filter::ema(m_supply_reading, evt.supply, SUPPLY_EMA_DEN)
: evt.supply;
m_supply_init = true;
[&](const SensorEvent& event) {
if (m_postenable_discard_left > 0) {
--m_postenable_discard_left;
} else if (m_load_init) {
m_load_reading = m_load_reading.ema(event.load);
} else {
m_load_reading = event.load;
m_load_init = true;
}

if (event.supply.valid) {
if (m_supply_init) {
m_supply_reading = m_supply_reading.ema(event.supply);
} else {
m_supply_reading = event.supply;
m_supply_init = true;
}
}
},
[](const auto&) {},
Expand Down
34 changes: 14 additions & 20 deletions include/v2/util/filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#pragma once

#include <cstdint>
#include <cstdlib>

#include "v2/events.h"

Expand All @@ -15,29 +16,22 @@ namespace pocketpd {
/**
* @brief One EMA step. `new = ((den-1)*prev + sample) / den`.
* For `den = 4`: weights 0.75 prev / 0.25 sample.
*
* @param prev The previous value.
* @param sample The new value.
* @param den The denominator of the EMA.
* @param snap The snap threshold.
* @return The EMA value.
*/
static uint32_t ema(uint32_t prev, uint32_t sample, uint32_t den) {
static uint32_t ema(
uint32_t prev, uint32_t sample, uint32_t den, uint32_t snap = UINT32_MAX
) {
uint32_t diff = sample > prev ? sample - prev : prev - sample;
if (diff >= snap) {
return sample;
}
return (prev * (den - 1) + sample) / den;
}

/** @brief Per-field EMA across a `LoadReading`. `timestamp_ms` tracks the latest. */
static LoadReading ema(const LoadReading& prev, const LoadReading& sample, uint32_t den) {
return LoadReading{
.timestamp_ms = sample.timestamp_ms,
.vbus_mv = ema(prev.vbus_mv, sample.vbus_mv, den),
.current_ma = ema(prev.current_ma, sample.current_ma, den),
};
}

/** @brief Per-field EMA across a `SupplyReading`. Caller filters invalid samples. */
static SupplyReading
ema(const SupplyReading& prev, const SupplyReading& sample, uint32_t den) {
return {
.timestamp_ms = sample.timestamp_ms,
.mv = ema(prev.mv, sample.mv, den),
.valid = sample.valid,
};
}
};

} // namespace pocketpd
Loading
Loading