main
John Lauer R0 engineering checkpoint: checked schematic, sourcing, servo evidence and safety-policy code; PCB and qualification incomplete 10054d3 1h ago
/* Portable policy core. Hardware adapter and USB stack are not yet implemented.
 * No production enable: assembly_qualified must come from verified configuration,
 * never a serial command. Default 5W bring-up ceiling, target 25W after qualification.
 */
#include "load_core.h"
#include <math.h>
#include <string.h>
static uint32_t age(uint32_t now,uint32_t then){return now-then;}
static uint32_t checks(const load_core *c,const load_sample *s){
 uint32_t f=0;
 if(!s->sensors_valid || !isfinite(s->volts) || !isfinite(s->amps) || !isfinite(s->case_c) || age(s->now_ms,s->sample_ms)>50) f|=F_SENSOR;
 if(!s->hw_ok) f|=F_HARDWARE;
 if(age(s->now_ms,s->last_tach_ms)>250) f|=F_FAN;
 if(age(s->now_ms,c->lease_ms)>1000) f|=F_LINK;
 if(s->case_c>=55 || s->case_c< -20) f|=F_THERMAL;
 if(s->volts<4.5f || s->volts>24 || s->amps< -0.02f || s->amps>3.15f) f|=F_ENVELOPE;
 return f;
}
void load_init(load_core *c,uint32_t now){memset(c,0,sizeof(*c));c->state=LOAD_OFF;c->last_tick_ms=now;c->lease_ms=now;c->power_limit_w=5;}
void load_off(load_core *c){c->enable=false;c->dac_code=0;c->applied_a=0;c->requested_a=0;if(c->state!=LOAD_FAULT)c->state=LOAD_OFF;}
bool load_set_current(load_core *c,float a){if(!isfinite(a)||a<0||a>3)return false;c->requested_a=a;return true;}
void load_heartbeat(load_core *c,uint32_t now){c->lease_ms=now;}
bool load_arm(load_core *c,const load_sample *s){
 if(c->state!=LOAD_OFF || !s->assembly_qualified || checks(c,s))return false;
 c->state=LOAD_ARMED;c->last_tick_ms=s->now_ms;return true;
}
bool load_clear_fault(load_core *c,const load_sample *s){
 if(c->state!=LOAD_FAULT || checks(c,s) || s->latch_q)return false;
 c->faults=0;c->state=LOAD_OFF;load_off(c);return true;
}
void load_tick(load_core *c,const load_sample *s){
 uint32_t dt=age(s->now_ms,c->last_tick_ms);c->last_tick_ms=s->now_ms;
 if(c->state==LOAD_OFF || c->state==LOAD_FAULT){c->enable=false;c->dac_code=0;c->applied_a=0;return;}
 uint32_t f=checks(c,s);
 if(!s->assembly_qualified || (c->state==LOAD_RUNNING && !s->latch_q))f|=F_HARDWARE;
 if(dt>50)f|=F_SENSOR;
 if(f){c->faults|=f;c->state=LOAD_FAULT;load_off(c);return;}
 /* ARM only prepares the state. Adapter holds enable low, DAC zero, pulses ARM_CLK,
    reads LATCH_Q, then calls tick. It must time out an unacknowledged arm. */
 if(c->state==LOAD_ARMED && !s->latch_q){c->enable=false;c->dac_code=0;return;}
 c->state=LOAD_RUNNING;
 float limit=fminf(3.0f,c->power_limit_w/s->volts);
 float wanted=fminf(c->requested_a,limit);
 float rise=(float)dt/1000.0f; /* 1 A/s upward; downward limiting immediate. */
 c->applied_a=fminf(wanted,c->applied_a+rise);
 /* DAC60501 net gain=1 (reference /2, buffer x2), reference=2.5V, divider 38.6k/1k, Rs=20mOhm.
    Floor the command. 4096 is the DAC denominator; full scale is code 4095. */
 float code=floorf(c->applied_a*0.020f*39.6f/2.5f*4096.0f);
 c->dac_code=(uint16_t)fminf(4095,fmaxf(0,code));c->enable=c->dac_code>0;
}