AnasSarkiz/usb-c-pwm-pa25
A component library defining an AO3400A N-channel MOSFET, 47 µF polarized capacitor, 3-pin through-hole connector, and 9.1 V Zener diode with PCB footprints and 3D CAD models.
- Version
- 0.1.0-prototype.2
- License
- unset
- Stars
- 0
firmware/control.c
#include "control.h"
enum { ZERO_PERMILLE = 20, DEBOUNCE_MS = 30, REARM_MS = 500,
POWER_SETTLE_MS = 500, COAST_MS = 2000, OVERLOAD_MS = 100,
OVERLOAD_MA = 350, DUTY_STEP_PER_MS = 2 };
static enum Direction read_direction(const struct Inputs *inputs) {
if (inputs->forward_closed && inputs->reverse_closed) return INVALID;
if (inputs->forward_closed) return FORWARD;
if (inputs->reverse_closed) return REVERSE;
return STOP;
}
static bool temperature_healthy(const struct Inputs *inputs) {
// 27.4k pull-up / 10k NTC. ~65°C upper trip; high input detects an open NTC.
return inputs->adc_valid && inputs->temperature_mv >= 300 && inputs->temperature_mv <= 1800;
}
static bool rail_healthy(const struct Inputs *inputs) {
return inputs->adc_valid && inputs->vm_mv >= 11100 && inputs->vm_mv <= 12800;
}
static void enter_mode(struct Controller *controller, enum Mode mode) {
controller->mode = mode;
controller->state_ticks = 0;
controller->reset_ticks = 0;
}
void controller_fault(struct Controller *controller, enum Fault fault) {
controller->fault = fault;
controller->duty_permille = 0;
controller->bridge_enabled = false;
controller->power_enabled = false;
controller->overload_ticks = 0;
enter_mode(controller, FAULT);
}
bool pd_contract_valid(uint32_t pdo, uint32_t rdo) {
const uint32_t voltage_mv = ((pdo >> 10) & 1023U) * 50U;
const uint32_t offered_ma = (pdo & 1023U) * 10U;
const uint32_t operating_ma = ((rdo >> 10) & 1023U) * 10U;
const uint32_t maximum_ma = (rdo & 1023U) * 10U;
return (pdo >> 30) == 0 && voltage_mv == 15000 && offered_ma >= 1500 &&
((rdo >> 28) & 7U) != 0 && (rdo & ((1UL << 26) | (1UL << 27))) == 0 &&
operating_ma >= 1000 && maximum_ma >= 1500 && maximum_ma <= offered_ma;
}
void controller_tick(struct Controller *controller, const struct Inputs *inputs) {
const enum Direction raw_direction = read_direction(inputs);
if (raw_direction != controller->observed_direction) {
controller->observed_direction = raw_direction;
controller->direction_ticks = 0;
} else if (controller->direction_ticks < DEBOUNCE_MS) {
controller->direction_ticks++;
} else {
controller->stable_direction = raw_direction;
}
if (controller->state_ticks < 60000) controller->state_ticks++;
if (!inputs->pd_valid) {
controller->power_enabled = false;
controller->bridge_enabled = false;
controller->duty_permille = 0;
controller->overload_ticks = 0;
enter_mode(controller, WAIT_PD);
return;
}
if (controller->mode == WAIT_PD) {
controller->power_enabled = true;
controller->fault = NO_FAULT;
enter_mode(controller, POWER_SETTLE);
return;
}
if (controller->mode == FAULT) {
if (raw_direction == STOP && controller->stable_direction == STOP &&
inputs->pot_permille <= ZERO_PERMILLE && temperature_healthy(inputs)) {
if (++controller->reset_ticks >= REARM_MS) {
controller->power_enabled = true;
controller->fault = NO_FAULT;
enter_mode(controller, POWER_SETTLE);
}
} else {
controller->reset_ticks = 0;
}
return;
}
if (controller->mode == POWER_SETTLE) {
if (controller->state_ticks < POWER_SETTLE_MS) return;
if (!rail_healthy(inputs)) { controller_fault(controller, POWER_FAULT); return; }
if (!inputs->hardware_healthy) { controller_fault(controller, HARDWARE_FAULT); return; }
enter_mode(controller, DISARMED);
}
if (!temperature_healthy(inputs)) { controller_fault(controller, TEMPERATURE_FAULT); return; }
if (!rail_healthy(inputs)) { controller_fault(controller, POWER_FAULT); return; }
if (!inputs->hardware_healthy) { controller_fault(controller, HARDWARE_FAULT); return; }
if (controller->stable_direction == INVALID) { controller_fault(controller, SWITCH_FAULT); return; }
if (controller->bridge_enabled && inputs->current_ma >= OVERLOAD_MA) {
if (++controller->overload_ticks >= OVERLOAD_MS) {
controller_fault(controller, OVERLOAD_FAULT);
return;
}
} else {
controller->overload_ticks = 0;
}
switch (controller->mode) {
case DISARMED:
if (raw_direction == STOP && controller->stable_direction == STOP && inputs->pot_permille <= ZERO_PERMILLE) {
if (++controller->reset_ticks >= REARM_MS) enter_mode(controller, READY);
} else controller->reset_ticks = 0;
break;
case READY:
if (controller->stable_direction == FORWARD || controller->stable_direction == REVERSE) {
// Direction must be selected at zero. Raising the knob first consumes authorization.
if (inputs->pot_permille > ZERO_PERMILLE) { enter_mode(controller, DISARMED); break; }
controller->applied_direction = controller->stable_direction;
enter_mode(controller, RUNNING);
}
break;
case RUNNING:
if (raw_direction != controller->applied_direction) {
enter_mode(controller, RAMP_DOWN);
break;
}
if (inputs->pot_permille <= ZERO_PERMILLE) {
controller->duty_permille = 0;
controller->bridge_enabled = false;
} else {
const uint16_t target = (uint16_t)(((uint32_t)inputs->pot_permille - ZERO_PERMILLE) * 1000U / (1000U - ZERO_PERMILLE));
if (controller->duty_permille + DUTY_STEP_PER_MS < target) controller->duty_permille += DUTY_STEP_PER_MS;
else if (controller->duty_permille > target + DUTY_STEP_PER_MS) controller->duty_permille -= DUTY_STEP_PER_MS;
else controller->duty_permille = target;
// Allow >1 ms driver wake-up before any PWM pulses.
controller->bridge_enabled = true;
}
break;
case RAMP_DOWN:
if (controller->duty_permille > DUTY_STEP_PER_MS) controller->duty_permille -= DUTY_STEP_PER_MS;
else {
controller->duty_permille = 0;
controller->bridge_enabled = false;
enter_mode(controller, COAST);
}
break;
case COAST:
if (controller->state_ticks >= COAST_MS) enter_mode(controller, DISARMED);
break;
default:
break;
}
}