AnasSarkiz/usb-c-pd-brushed-motor
USB-C PD-powered motor controller with protected input, selectable 5/9/12 V buck supply, MCU qualification, PWM H-bridge drive, overvoltage sensing, regenerative energy-dump resistors, and safety interlocks.
- Version
- 0.0.1-a48-cloud-pipeline9-unrouted-c41d79a-305b1e7a
- License
- unset
- Stars
- 0
firmware/motor_measurement.c
#include "motor_measurement.h"
#define PPM UINT64_C(1000000)
static uint64_t ceil_div(uint64_t numerator, uint64_t denominator) {
return numerator / denominator + (numerator % denominator != 0);
}
static bool profile_valid(const struct motor_measurement_profile *p) {
/* Reject nonsensical, overflowing, under-specified or zero identity limits.
* DS12991 characterized error is NOT automatic approval at32 MHz. */
return p && p->approved && p->qualification_id && p->factory_vref > 32 &&
p->factory_vref < 4063 && p->adc_error_half_counts >= 14 &&
p->adc_error_half_counts <= 64 && p->noise_half_counts <= 64 &&
p->factory_error_half_counts >= 14 && p->factory_error_half_counts <= 64 &&
p->upper_divider_ppm >= 3000 && p->upper_divider_ppm <= 20000 &&
p->lower_divider_ppm >= 3000 && p->lower_divider_ppm <= 20000 &&
p->reference_drift_ppm >= 9000 && p->reference_drift_ppm <= 20000 &&
p->leakage_na >= 70 && p->leakage_na <= 1000 && p->filter_tau_us > 0 &&
p->filter_tau_us <= 2000 && p->vdda_slew_uv_per_us <= 10000 &&
p->rail_slew_uv_per_us[0] <= 1000000 && p->rail_slew_uv_per_us[1] <= 1000000;
}
struct rail_conversion {
uint16_t code;
uint64_t vdda_lower_uv, vdda_upper_uv;
uint32_t dynamic_error_mv;
};
static struct pd_voltage_interval convert_rail(const struct rail_conversion *rail,
const struct motor_measurement_profile *p) {
const uint64_t gain_lower_ppm = PPM +
10u * PPM * (PPM - p->upper_divider_ppm) / (PPM + p->lower_divider_ppm);
const uint64_t gain_upper_ppm = PPM +
ceil_div(10u * PPM * (PPM + p->upper_divider_ppm), PPM - p->lower_divider_ppm);
const uint32_t count_error = p->adc_error_half_counts + p->noise_half_counts;
uint64_t half_code = (uint32_t)rail->code * 2u;
uint64_t lower_half_code = half_code > count_error ? half_code - count_error : 0;
uint64_t lower_mv = lower_half_code * rail->vdda_lower_uv * gain_lower_ppm /
(8192u * UINT64_C(1000000000));
uint64_t upper_mv = ceil_div((half_code + count_error) * rail->vdda_upper_uv *
gain_upper_ppm, 8192u * UINT64_C(1000000000));
const uint64_t leakage_mv = ceil_div((uint64_t)p->leakage_na * 100000u *
(PPM + p->upper_divider_ppm), PPM * PPM);
const uint64_t absolute_error_mv = leakage_mv + rail->dynamic_error_mv;
lower_mv = lower_mv > absolute_error_mv ? lower_mv - absolute_error_mv : 0;
upper_mv += absolute_error_mv;
return (struct pd_voltage_interval){.lower_mv = (uint32_t)lower_mv,
.upper_mv = (uint32_t)upper_mv, .valid = true};
}
struct motor_measurement motor_measurement_convert(const struct motor_adc_sample *frame,
const struct motor_measurement_context *conditions) {
const struct motor_measurement invalid = {0};
if (!conditions || !frame || !frame->valid || !profile_valid(conditions->profile) ||
!conditions->reference_stable || !conditions->environment_qualified ||
conditions->brownout) return invalid;
const struct motor_measurement_profile *p = conditions->profile;
if (frame->factory_vref != p->factory_vref || frame->vbus >= 4095 ||
frame->vm >= 4095 || frame->vrefint >= 4095) return invalid;
uint32_t last = frame->started_us;
const unsigned order[3] = {2, 0, 1};
for (unsigned position = 0; position < 3; ++position) {
const unsigned i = order[position];
uint32_t gap = frame->channel_started_us[i] - last;
uint32_t elapsed = frame->channel_completed_us[i] - frame->channel_started_us[i];
if (gap > 500u || !elapsed || elapsed > 500u) return invalid;
last = frame->channel_completed_us[i];
}
if ((uint32_t)(frame->completed_us - last) > 500u ||
(uint32_t)(frame->completed_us - frame->started_us) >= 500u ||
(uint32_t)(conditions->now_us - frame->started_us) > 5000u) return invalid;
const uint64_t factory_half = (uint32_t)frame->factory_vref * 2u;
const uint32_t reference_count_error = p->adc_error_half_counts + p->noise_half_counts;
const uint64_t reference_half = (uint32_t)frame->vrefint * 2u;
if (reference_half <= reference_count_error) return invalid;
/* DS12991 factory supply3.0 V +/-10mV. Include calibration count error,
* current reference acquisition error, temperature/aging/supply drift. */
uint64_t vdda_lower_uv = UINT64_C(2990000) *
(factory_half - p->factory_error_half_counts) * (PPM - p->reference_drift_ppm) /
((reference_half + reference_count_error) * PPM);
uint64_t vdda_upper_uv = ceil_div(UINT64_C(3010000) *
(factory_half + p->factory_error_half_counts) * (PPM + p->reference_drift_ppm),
(reference_half - reference_count_error) * PPM);
/* VREFINT and rail acquisitions are sequential. Bound supply motion since
* the earliest acquisition, including a conservatively slow3% timebase. */
uint64_t frame_age_us = ceil_div((uint64_t)(conditions->now_us - frame->started_us) * 100u, 97u);
uint64_t vdda_motion_uv = frame_age_us * p->vdda_slew_uv_per_us;
if (vdda_lower_uv < vdda_motion_uv) return invalid;
vdda_lower_uv -= vdda_motion_uv; vdda_upper_uv += vdda_motion_uv;
if (vdda_lower_uv < 2400000u || vdda_upper_uv >= 3600000u) return invalid;
const uint16_t codes[2] = {frame->vbus, frame->vm};
struct pd_voltage_interval intervals[2];
for (unsigned i = 0; i < 2; ++i) {
/* First-order RC lag <= tau*max(|dV/dt|) for a qualified slew envelope.
* A constant-looking filtered history alone never proves that envelope.
* Using each conversion START bounds the unknown sampling instant;
* frame START still governs reference skew and whole-frame freshness. */
const uint64_t channel_age_us = ceil_div((uint64_t)(conditions->now_us -
frame->channel_started_us[i]) * 100u, 97u);
uint64_t dynamic_error_mv = ceil_div((channel_age_us + p->filter_tau_us) *
p->rail_slew_uv_per_us[i], 1000u);
intervals[i] = convert_rail(&(struct rail_conversion){codes[i], vdda_lower_uv,
vdda_upper_uv, (uint32_t)dynamic_error_mv}, p);
}
return (struct motor_measurement){.vbus = intervals[0], .motor_rail = intervals[1],
.oldest_sample_us = frame->started_us, .valid = true};
}