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_owner.c

#include "motor_owner.h"

void motor_owner_invalidate(struct motor_owner *o, enum motor_owner_fault fault) {
  if (!o) return;
  /* Permission is physically revoked before clearing provenance or changing
   * sequencing state. Feedback remains at its last established setting. */
  (void)motor_gpio_inhibit(o->gpio);
  pd_sequence_abort(&o->sequence);
  o->ps_rdy_request_id = o->accept_request_id = 0;
  o->source_count = 0; o->acquiring = false;
  o->requested_capabilities = o->requested_accept = false;
  if (fault != MOTOR_OWNER_OK) { o->fault = fault; o->initialized = false; }
}
static bool owner_valid(const struct motor_owner *o) {
  return o && o->gpio && o->adc && o->bus.read && o->bus.write && o->bus.now_us &&
    o->pending_interrupt && o->enter_critical && o->leave_critical;
}
bool motor_owner_initialize(struct motor_owner *o) {
  if (!owner_valid(o)) { motor_owner_invalidate(o, MOTOR_OWNER_CONFIGURATION_MISSING); return false; }
  if (o->fault != MOTOR_OWNER_OK || o->initialized || !motor_gpio_inhibit(o->gpio)) {
    motor_owner_invalidate(o, MOTOR_OWNER_GPIO); return false;
  }
  if (!o->config.nvm_image || !o->config.nvm_qualification_id ||
      !o->config.receive_path_qualification_id || !o->config.measurement_profile ||
      !o->config.measurement_profile->approved ||
      !o->config.measurement_profile->qualification_id) {
    motor_owner_invalidate(o, MOTOR_OWNER_CONFIGURATION_MISSING); return false;
  }
  const struct stusb_nvm_check check = {.state=&o->nvm,
    .expected_image=o->config.nvm_image,.started_us=o->bus.now_us(o->bus.context),
    .power_path_inhibited=true,.bridge_inhibited=true,.provenance_invalidated=true};
  if (stusb_nvm_verify(&o->bus,&check) != STUSB_NVM_VERIFIED) {
    motor_owner_invalidate(o,MOTOR_OWNER_NVM); return false;
  }
  o->last_step_us=o->bus.now_us(o->bus.context); o->selector_bits=3;
  o->initialized=true;
  return true;
}
struct read_byte { uint8_t address; uint8_t *byte; uint32_t deadline_us; };
static bool advance_time(struct motor_owner *o, uint32_t now_us) {
  const uint32_t elapsed_us=now_us-o->last_step_us;
  if(elapsed_us>5000u) return false;
  o->last_step_us=now_us;
  o->remainder_us+=elapsed_us;
  o->uptime_ms+=o->remainder_us/1000u; o->remainder_us%=1000u;
  return true;
}
static bool read_checked_byte(struct motor_owner *o, const struct read_byte command) {
  const struct stusb_read read={.register_address=command.address,.byte_count=1,
    .bytes=command.byte,.deadline_us=command.deadline_us};
  return o->bus.read(o->bus.context,&read);
}
static bool acquire(struct motor_owner *o, uint32_t now_us) {
  motor_owner_invalidate(o,MOTOR_OWNER_OK);
  if (o->acquisition_id==UINT32_MAX) return false;
  const struct stusb_startup startup={.state=&o->startup,
    .acquisition_id=++o->acquisition_id,.started_us=now_us,
    .power_path_inhibited=true,.bridge_inhibited=true,.provenance_invalidated=true};
  if (stusb_startup_acquire(&o->bus,&startup)!=STUSB_STARTUP_SENT) return false;
  o->sequence.state=PD_SEQUENCE_IDLE;
  o->acquiring=true;
  return true;
}
static bool source_equal(const struct motor_owner *o, const struct stusb_message *m) {
  if(m->object_count!=o->source_count) return false;
  for(unsigned i=0;i<o->source_count;++i) if(m->source_pdos[i]!=o->source_pdos[i]) return false;
  return true;
}
static bool apply_message(struct motor_owner *o, const struct stusb_message *m) {
  /* The independently qualified receive path must establish SOP, RX atomicity
   * and ALERT ordering. No undocumented PHY_STATUS encoding is read here. */
  if (!o->config.receive_path_qualification_id) return false;
  if (m->kind==STUSB_MESSAGE_SOFT_RESET || m->kind==STUSB_MESSAGE_REJECT ||
      m->kind==STUSB_MESSAGE_WAIT) return false;
  if (o->acquiring) {
    if ((uint32_t)(m->alert_us-o->startup.command_started_us)>=UINT32_C(0x80000000)) return false;
    if(m->kind!=STUSB_MESSAGE_SOURCE_CAPABILITIES) return true; /* SoftReset Accept is not Request Accept. */
    if (!m->object_count || o->source_generation==UINT32_MAX) return false;
    for(unsigned i=0;i<m->object_count;++i) o->source_pdos[i]=m->source_pdos[i];
    o->source_count=m->object_count; ++o->source_generation; o->acquiring=false;
    const struct pd_capabilities capabilities={.source_pdos=o->source_pdos,
      .count=o->source_count,.source_generation=o->source_generation};
    const struct pd_sequence_request request={.plan=pd_make_plan(o->selector_bits,&capabilities),
      .now_ms=o->uptime_ms};
    return pd_sequence_begin(&o->sequence,&request);
  }
  if (o->request.completed_request_id != o->sequence.outputs.request_id ||
      !o->request.completed_request_id ||
      (uint32_t)(m->alert_us-o->request.command_started_us)>=UINT32_C(0x80000000)) {
    /* An unsolicited capability update invalidates the old contract. */
    return m->kind!=STUSB_MESSAGE_SOURCE_CAPABILITIES && m->kind!=STUSB_MESSAGE_PS_RDY;
  }
  if(m->kind==STUSB_MESSAGE_SOURCE_CAPABILITIES) {
    if(!source_equal(o,m) || o->requested_capabilities || o->requested_accept) return false;
    o->requested_capabilities=true;
  } else if(m->kind==STUSB_MESSAGE_ACCEPT) {
    if(!o->requested_capabilities) return true; /* Accept of the host SoftReset. */
    if(o->requested_accept) return false;
    o->requested_accept=true; o->accept_request_id=o->request.completed_request_id;
    o->accept_us=m->captured_us;
  } else if(m->kind==STUSB_MESSAGE_PS_RDY) {
    if(!o->requested_capabilities || !o->requested_accept ||
        o->accept_request_id!=o->sequence.outputs.request_id ||
        (uint32_t)(m->alert_us-o->accept_us)>=UINT32_C(0x80000000) ||
        o->ps_rdy_request_id) return false;
    o->ps_rdy_request_id=o->accept_request_id;
  }
  return true;
}
void motor_owner_step(struct motor_owner *o, const struct motor_owner_event *event) {
  if (!owner_valid(o) || !event) { motor_owner_invalidate(o,MOTOR_OWNER_CONFIGURATION_MISSING); return; }
  if (!o->initialized || o->fault!=MOTOR_OWNER_OK) {
    (void)motor_gpio_inhibit(o->gpio);
    /* A failed qualification remains inhibited. Bring-up raw diagnostics do
     * not become valid physical intervals or permit feedback changes. */
    if (o->adc->initialized && !o->adc->faulted) o->last_raw_frame=motor_adc_capture(o->adc);
    return;
  }
  uint32_t now=o->bus.now_us(o->bus.context);
  if(!advance_time(o,now)) { motor_owner_invalidate(o,MOTOR_OWNER_TIME); return; }
  if(event->alert_overrun || event->brownout) { motor_owner_invalidate(o,MOTOR_OWNER_PD_EVENT); return; }
  struct motor_gpio_inputs inputs=motor_gpio_read_inputs(o->gpio);
  if(o->gpio->faulted) { motor_owner_invalidate(o,MOTOR_OWNER_GPIO); return; }
  uint8_t status=0, summary=0;
  uint32_t deadline=now+2500u;
  if (!read_checked_byte(o,(struct read_byte){0x0e,&status,deadline}) ||
      !read_checked_byte(o,(struct read_byte){0x0b,&summary,deadline})) {
    motor_owner_invalidate(o,MOTOR_OWNER_TRANSPORT); return;
  }
  if(!(status&1u) || status&8u || summary&0xfdu) {
    motor_owner_invalidate(o,MOTOR_OWNER_PD_EVENT); return;
  }
  if(inputs.selector_bits!=o->selector_bits) {
    o->selector_bits=inputs.selector_bits;
    motor_owner_invalidate(o,MOTOR_OWNER_OK);
    if(inputs.selector_bits!=3 && !acquire(o,o->bus.now_us(o->bus.context)))
      motor_owner_invalidate(o,MOTOR_OWNER_PD_REQUEST);
    return;
  }
  if(inputs.selector_bits==3) { (void)motor_gpio_inhibit(o->gpio); return; }
  if(event->alert_pending) {
    struct stusb_message message;
    const struct stusb_capture capture={.alert_us=event->alert_us,.message=&message};
    enum stusb_rx_result result=stusb_capture_message(&o->bus,&capture);
    if(result!=STUSB_RX_NO_MESSAGE && (result!=STUSB_RX_OK || !apply_message(o,&message))) {
      motor_owner_invalidate(o,MOTOR_OWNER_PD_EVENT); return;
    }
  } else if(summary&2u) {
    /* A message without its original edge timestamp is ambiguous/stale. */
    motor_owner_invalidate(o,MOTOR_OWNER_PD_EVENT); return;
  }
  uint8_t pe_state=0, rdo_bytes[4]={0};
  now=o->bus.now_us(o->bus.context); deadline=now+2000;
  const struct stusb_read rdo_read={.register_address=0x91,.byte_count=4,
    .bytes=rdo_bytes,.deadline_us=deadline};
  if(!read_checked_byte(o,(struct read_byte){0x29,&pe_state,deadline}) ||
      !o->bus.read(o->bus.context,&rdo_read)) {
    motor_owner_invalidate(o,MOTOR_OWNER_TRANSPORT); return;
  }
  o->last_raw_frame=motor_adc_capture(o->adc);
  if(!o->last_raw_frame.valid) { motor_owner_invalidate(o,MOTOR_OWNER_ADC); return; }
  const struct motor_measurement_context conditions={.profile=o->config.measurement_profile,
    .now_us=o->bus.now_us(o->bus.context),.reference_stable=true,
    .environment_qualified=event->environment_qualified,.brownout=event->brownout};
  o->last_measurement=motor_measurement_convert(&o->last_raw_frame,&conditions);
  if(!o->last_measurement.valid) { motor_owner_invalidate(o,MOTOR_OWNER_MEASUREMENT); return; }
  now=o->bus.now_us(o->bus.context);
  if(!advance_time(o,now)) { motor_owner_invalidate(o,MOTOR_OWNER_TIME); return; }
  const struct motor_gpio_inputs checked_inputs=motor_gpio_read_inputs(o->gpio);
  if(o->gpio->faulted || checked_inputs.selector_bits!=inputs.selector_bits ||
      (o->sequence.outputs.host_allow && !checked_inputs.pd_enabled)) {
    motor_owner_invalidate(o,MOTOR_OWNER_GPIO); return;
  }
  const struct pd_sequence_sample sample={.observation={
    .attached=true,.communication_ok=true,.fresh_ps_rdy=o->ps_rdy_request_id!=0,
    .motor_fault=checked_inputs.motor_fault,.pe_fsm_state=pe_state,.source_generation=o->source_generation,
    .rdo=(uint32_t)rdo_bytes[0]|(uint32_t)rdo_bytes[1]<<8|(uint32_t)rdo_bytes[2]<<16|
      (uint32_t)rdo_bytes[3]<<24,.vbus=o->last_measurement.vbus,
    .motor_rail=o->last_measurement.motor_rail},
    .now_ms=o->uptime_ms,.adc_sample_ms=o->uptime_ms-
      ((now-o->last_measurement.oldest_sample_us+999u)/1000u),
    .completed_request_id=o->request.completed_request_id,.ps_rdy_request_id=o->ps_rdy_request_id,
    .selector_bits=inputs.selector_bits,.adc_valid=true};
  pd_sequence_step(&o->sequence,&sample);
  if(o->sequence.state==PD_SEQUENCE_FAULT) {
    motor_owner_invalidate(o,MOTOR_OWNER_PD_EVENT); return;
  }
  if(o->acquiring || o->sequence.state==PD_SEQUENCE_IDLE ||
      o->sequence.state==PD_SEQUENCE_DECAY) {
    /* Charged/back-driven VM is a normal reason to wait in DECAY. Do not
     * initialize or change feedback until its entire interval is <=1V. */
    (void)motor_gpio_inhibit(o->gpio); return;
  }
  /* Commit permissions in a bounded IRQ-masked section. Hardware pending flags
   * are checked on both sides of the writes; events during the commit revoke
   * permission before restoring the previous interrupt mask. IRQs themselves
   * retain timestamp-only ownership. An event after this section is handled
   * by the next foreground pass and the independent hardware inhibit path. */
  const uint32_t previous_mask=o->enter_critical(o->interrupt_context);
  const struct motor_gpio_inputs commit_inputs=motor_gpio_read_inputs(o->gpio);
  enum motor_owner_fault commit_fault=MOTOR_OWNER_OK;
  if(o->gpio->faulted || commit_inputs.selector_bits!=inputs.selector_bits)
    commit_fault=MOTOR_OWNER_GPIO;
  else if(o->sequence.outputs.host_allow && (!commit_inputs.pd_enabled ||
      commit_inputs.motor_fault || o->pending_interrupt(o->interrupt_context)))
    commit_fault=MOTOR_OWNER_PD_EVENT;
  const struct motor_gpio_command gpio_command={.outputs=o->sequence.outputs,
    .motor_rail=o->last_measurement.motor_rail,.oldest_sample_us=o->last_measurement.oldest_sample_us,
    .now_us=o->bus.now_us(o->bus.context)};
  if(commit_fault==MOTOR_OWNER_OK && !motor_gpio_apply(o->gpio,&gpio_command))
    commit_fault=MOTOR_OWNER_GPIO;
  if(commit_fault==MOTOR_OWNER_OK && o->sequence.outputs.host_allow &&
      o->pending_interrupt(o->interrupt_context)) commit_fault=MOTOR_OWNER_PD_EVENT;
  if(commit_fault!=MOTOR_OWNER_OK) motor_owner_invalidate(o,commit_fault);
  o->leave_critical(o->interrupt_context,previous_mask);
  if(commit_fault!=MOTOR_OWNER_OK) return;
  if(o->sequence.outputs.request_contract &&
      o->request.completed_request_id!=o->sequence.outputs.request_id) {
    o->requested_capabilities=o->requested_accept=false; o->ps_rdy_request_id=o->accept_request_id=0;
    const struct stusb_request request={.state=&o->request,.plan=&o->sequence.plan,
      .request_id=o->sequence.outputs.request_id,.source_generation=o->source_generation,
      .started_us=o->bus.now_us(o->bus.context),.selector_bits=inputs.selector_bits,
      .power_path_inhibited=!o->gpio->host_allow,.bridge_inhibited=!o->gpio->release_bridge,
      .feedback_settled=o->gpio->feedback_known};
    if(stusb_request_contract(&o->bus,&request)!=STUSB_REQUEST_SENT)
      motor_owner_invalidate(o,MOTOR_OWNER_PD_REQUEST);
  }
}