techmannih/nema34-mounted-stepper-controller
A 48 V USB‑C PD 3.1 EPR NEMA‑34 stepper controller PCB combining STM32/TMC5160 control, external MOSFET motor bridges, protected power and regenerative braking, USB data, telemetry, diagnostics, status LEDs, and buzzer alarms.
- Version
- 0.3.92
- License
- unset
- Stars
- 1
firmware/src/control.c
#include "control.h"
#include "telemetry.h"
#define CONTRACT_STABLE_REQUIRED_MS 100u
#define MOTOR_STABLE_REQUIRED_MS 200u
#define REQUIRED_CONTRACT_MV 48000u
#define REQUIRED_CONTRACT_MA 5000u
static uint32_t saturating_add(uint32_t value, uint32_t increment, uint32_t limit) {
if (value >= limit || increment >= limit - value) return limit;
return value + increment;
}
void pd1180_control_reset(pd1180_control_t *state) {
*state = (pd1180_control_t){0};
}
void pd1180_control_disarm(pd1180_control_t *state) {
pd1180_control_reset(state);
}
static bool safe_contract(const pd1180_inputs_t *in) {
return in->pd_app_mode && in->epr_contract && in->contract_mv == REQUIRED_CONTRACT_MV &&
in->contract_ma == REQUIRED_CONTRACT_MA && in->configuration_verified && in->sample_fresh &&
in->motion_wiring_verified && !in->pd_fault && !in->efuse_fault &&
in->watchdog_healthy && !in->stop_active && in->temperature_valid &&
in->temperature_deci_c >= -400 && in->temperature_deci_c < 700;
}
bool pd1180_control_arm(pd1180_control_t *state, const pd1180_inputs_t *inputs) {
if (!safe_contract(inputs) || inputs->temperature_deci_c > 550 || state->fault_latched) return false;
state->armed = true;
return true;
}
void pd1180_control_tick(pd1180_control_t *state, const pd1180_inputs_t *inputs, uint32_t elapsed_ms) {
const bool contract_valid = safe_contract(inputs) && elapsed_ms > 0 && elapsed_ms <= 20u;
if (!contract_valid || !state->armed) {
if (state->armed) state->fault_latched = true;
state->armed = false;
state->contract_stable_ms = 0;
state->motor_stable_ms = 0;
state->power_start_ms = 0;
state->power_permit = false;
state->mcu_run = false;
state->motor_commands_enabled = false;
state->current_limit_permille = 0;
return;
}
state->contract_stable_ms = saturating_add(state->contract_stable_ms, elapsed_ms, CONTRACT_STABLE_REQUIRED_MS);
state->current_limit_permille = pd1180_thermal_limit_permille(inputs->temperature_deci_c);
state->power_permit = state->contract_stable_ms >= CONTRACT_STABLE_REQUIRED_MS;
if (state->power_permit && !state->mcu_run) {
state->power_start_ms = saturating_add(state->power_start_ms, elapsed_ms, 1000u);
if (state->power_start_ms >= 1000u) {
pd1180_control_reset(state);
state->fault_latched = true;
return;
}
}
const bool motor_valid = state->power_permit && inputs->motor_power_good && inputs->vmotor_in_range && inputs->driver_ready && inputs->brake_preflight_ok;
if (!motor_valid) {
if (state->mcu_run) {
state->fault_latched = true;
state->armed = false;
state->power_permit = false;
}
state->motor_stable_ms = 0;
state->mcu_run = false;
state->motor_commands_enabled = false;
return;
}
state->motor_stable_ms = saturating_add(state->motor_stable_ms, elapsed_ms, MOTOR_STABLE_REQUIRED_MS);
state->mcu_run = state->motor_stable_ms >= MOTOR_STABLE_REQUIRED_MS;
state->motor_commands_enabled = state->mcu_run;
}