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/stm32_adc_test.c

#include "stm32_adc.h"
#include "stm32_clock.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static unsigned assertions;
#define CHECK(x) do { ++assertions; if (!(x)) { fprintf(stderr, "%s:%d: %s\n", __FILE__, __LINE__, #x); exit(1); } } while (0)
enum fault { NONE, HSI_STUCK, CAL_STUCK, READY_STUCK, CHANNEL_STUCK, CONVERSION_STUCK,
             OVERRUN, NO_EOS, BAD_DATA, ZERO_REFERENCE, CAL_NO_FLAG, BACKWARDS,
             STALLED_TIME, JUMP_TIME, DISABLE_STUCK };
struct model {
  RCC_TypeDef rcc; SYSCFG_TypeDef syscfg;
  GPIO_TypeDef a, b, c;
  ADC_TypeDef adc; ADC_Common_TypeDef common;
  uint16_t factory, codes[3];
  uint32_t now, hsi_due, cal_due, ready_due, channel_due, conversion_due;
  uint32_t regulator_at, calibration_done, reference_at;
  unsigned rearms;
  unsigned writes, drop_write, calls, channels, corrupt_at;
  enum fault fault;
  bool hsi, cal, ready, channel, converting;
  struct motor_gpio gpio;
  struct motor_adc port;
};
static struct model *active;
static void bsrr(GPIO_TypeDef *port, uint32_t bits) {
  port->BSRR = bits;
  port->ODR = (port->ODR & ~(bits >> 16)) | (bits & UINT32_C(0xffff));
}
static bool due(uint32_t now, uint32_t time) { return (uint32_t)(now - time) < UINT32_C(0x80000000); }
static uint32_t now_us(void *context) {
  struct model *m = context;
  ++m->calls;
  if (m->fault == BACKWARDS) --m->now;
  else if (m->fault == JUMP_TIME) m->now += 1001;
  else if (m->fault != STALLED_TIME) ++m->now;
  if (m->hsi && due(m->now, m->hsi_due) && m->fault != HSI_STUCK) {
    m->rcc.CR |= RCC_CR_HSIRDY; m->hsi = false;
  }
  if (m->cal && due(m->now, m->cal_due) && m->fault != CAL_STUCK) {
    m->adc.CR &= ~ADC_CR_ADCAL;
    if (m->fault != CAL_NO_FLAG) m->adc.ISR |= ADC_ISR_EOCAL;
    m->cal = false; m->calibration_done = m->now;
  }
  if (m->ready && due(m->now, m->ready_due) && m->fault != READY_STUCK) {
    m->adc.ISR |= ADC_ISR_ADRDY; m->ready = false;
  }
  if (m->channel && due(m->now, m->channel_due) && m->fault != CHANNEL_STUCK) {
    m->adc.ISR |= ADC_ISR_CCRDY; m->channel = false;
  }
  if (m->converting && due(m->now, m->conversion_due) && m->fault != CONVERSION_STUCK) {
    unsigned i = m->adc.CHSELR == ADC_CHSELR_CHSEL0 ? 0 :
      m->adc.CHSELR == ADC_CHSELR_CHSEL1 ? 1 : 2;
    m->adc.DR = m->fault == BAD_DATA ? 4096u :
      (m->fault == ZERO_REFERENCE && i == 2) ? 0u : m->codes[i];
    m->adc.ISR |= ADC_ISR_EOC;
    if (m->fault != NO_EOS) m->adc.ISR |= ADC_ISR_EOS;
    if (m->fault == OVERRUN) m->adc.ISR |= ADC_ISR_OVR;
    m->adc.CR &= ~ADC_CR_ADSTART; m->converting = false;
  }
  if (m->corrupt_at && m->calls == m->corrupt_at) m->adc.CFGR2 = ADC_CFGR2_CKMODE;
  return m->now;
}
static void write_mmio(void *context, const struct motor_adc_write *op) {
  struct model *m = context;
  ++m->writes;
  if (!m->port.initialized) {
    CHECK((m->a.ODR & ((1u << 6) | (1u << 7))) == (1u << 7));
    CHECK((m->a.OTYPER & ((1u << 6) | (1u << 7))) == (1u << 7));
  }
  if (m->drop_write == m->writes) return;
  if (op->reg == &m->adc.ISR) { m->adc.ISR &= ~op->value; return; }
  if (op->reg == &m->adc.CR) {
    const uint32_t rs = ADC_CR_ADEN | ADC_CR_ADDIS | ADC_CR_ADSTART | ADC_CR_ADSTP | ADC_CR_ADCAL;
    uint32_t previous = m->adc.CR;
    m->adc.CR = (op->value & ~rs) | ((op->value | previous) & rs);
    if ((op->value & ADC_CR_ADVREGEN) && !(previous & ADC_CR_ADVREGEN)) m->regulator_at = m->now;
    if (op->value & ADC_CR_ADDIS) {
      CHECK((previous & rs) == ADC_CR_ADEN);
      ++m->rearms;
      if (m->fault != DISABLE_STUCK) m->adc.CR &= ~(ADC_CR_ADEN | ADC_CR_ADDIS);
    }
    if (op->value & ADC_CR_ADCAL) {
      CHECK(!(previous & rs)); CHECK((uint32_t)(m->now - m->regulator_at) >= 20);
      CHECK(m->adc.CFGR1 == 0); CHECK(m->adc.CFGR2 == 0);
      m->cal = true; m->cal_due = m->now + 14;
    }
    if (op->value & ADC_CR_ADEN) {
      CHECK(!(previous & rs)); CHECK((uint32_t)(m->now - m->calibration_done) >= 1);
      m->ready = true; m->ready_due = m->now + 3;
    }
    if (op->value & ADC_CR_ADSTART) {
      CHECK((previous & rs) == ADC_CR_ADEN);
      CHECK(m->adc.ISR & ADC_ISR_CCRDY);
      CHECK(!(m->adc.ISR & (ADC_ISR_EOC | ADC_ISR_EOS | ADC_ISR_OVR)));
      CHECK((uint32_t)(m->now - m->reference_at) >= 12);
      CHECK(m->adc.SMPR == ADC_SMPR_SMP1);
      ++m->channels; m->converting = true; m->conversion_due = m->now + 22;
    }
    return;
  }
  *op->reg = op->value;
  if (op->reg == &m->rcc.CR && (op->value & RCC_CR_HSION)) {
    m->hsi = true; m->hsi_due = m->now + 4;
  }
  if (op->reg == &m->common.CCR) m->reference_at = m->now;
  if (op->reg == &m->adc.CHSELR) { m->channel = true; m->channel_due = m->now + 2; }
}
static void setup(struct model *m) {
  memset(m, 0, sizeof(*m)); active = m;
  m->factory = 1650; m->codes[0] = 2250; m->codes[1] = 1350; m->codes[2] = 1500;
  m->a.MODER = UINT32_C(0xabcdefab); m->a.ODR = UINT32_C(0xffff);
  m->rcc.CCIPR = UINT32_C(0x12345678);
  m->rcc.CR = RCC_CR_PLLON | RCC_CR_PLLRDY;
  m->rcc.PLLCFGR = MOTOR_PLL_CONFIG;
  m->gpio = (struct motor_gpio){.rcc=&m->rcc, .syscfg=&m->syscfg, .a=&m->a,
    .b=&m->b, .c=&m->c, .write_bsrr=bsrr};
  CHECK(motor_gpio_initialize(&m->gpio));
  m->adc.CFGR1 = ADC_CFGR1_CONT | ADC_CFGR1_AUTOFF | ADC_CFGR1_DMAEN;
  m->adc.CFGR2 = ADC_CFGR2_CKMODE; m->adc.SMPR = 0;
  m->adc.IER = ADC_IER_EOCIE;
  m->adc.ISR = ADC_ISR_ADRDY | ADC_ISR_EOCAL | ADC_ISR_EOC | ADC_ISR_EOS | ADC_ISR_CCRDY;
  m->port = (struct motor_adc){.gpio=&m->gpio, .adc=&m->adc, .common=&m->common,
    .factory_vref=&m->factory, .context=m, .now_us=now_us, .write=write_mmio};
}
static void check_off(struct model *m) {
  CHECK(m->port.faulted); CHECK(!m->port.initialized);
  CHECK((m->a.ODR & UINT32_C(0xc0)) == UINT32_C(0x80));
  CHECK((m->a.OTYPER & UINT32_C(0xc0)) == UINT32_C(0x80));
  unsigned writes = m->writes;
  CHECK(!motor_adc_initialize(&m->port)); CHECK(m->writes == writes);
}
static void invalid_sample(struct model *m) {
  struct motor_adc_sample sample = motor_adc_capture(&m->port);
  CHECK(!sample.valid); CHECK(!sample.vbus && !sample.vm && !sample.vrefint && !sample.factory_vref);
  CHECK(!sample.started_us && !sample.completed_us);
  for (unsigned i = 0; i < 3; ++i) { CHECK(!sample.channel_completed_us[i]); CHECK(!sample.channel_started_us[i]); }
  check_off(m);
}
static void valid_sample(struct model *m) {
  struct motor_adc_sample sample = motor_adc_capture(&m->port);
  CHECK(sample.valid); CHECK(sample.vbus == m->codes[0]); CHECK(sample.vm == m->codes[1]);
  CHECK(sample.vrefint == m->codes[2]); CHECK(sample.factory_vref == m->factory);
  CHECK((uint32_t)(sample.completed_us - sample.started_us) < 500);
  uint32_t last = sample.started_us;
  const unsigned order[3]={2,0,1};
  for (unsigned position = 0; position < 3; ++position) {
    const unsigned i=order[position];
    CHECK((uint32_t)(sample.channel_started_us[i] - last) < 500);
    CHECK((uint32_t)(sample.channel_completed_us[i] - sample.channel_started_us[i]) >= 22);
    last = sample.channel_completed_us[i];
  }
}
int main(void) {
  struct model m;
  setup(&m); uint32_t feedback_mode = m.a.MODER & UINT32_C(0xf00);
  uint32_t feedback_odr = m.a.ODR & UINT32_C(0x30);
  CHECK(motor_adc_initialize(&m.port));
  CHECK((m.rcc.CCIPR & ~RCC_CCIPR_ADCSEL) == (UINT32_C(0x12345678) & ~RCC_CCIPR_ADCSEL));
  CHECK((m.a.MODER & UINT32_C(0xf00)) == feedback_mode);
  CHECK((m.a.ODR & UINT32_C(0x30)) == feedback_odr);
  for (unsigned i = 0; i < 4096; ++i) {
    m.codes[0] = i; m.codes[1] = 4095u - i; m.codes[2] = 1200u + i % 300u;
    valid_sample(&m);
  }
  CHECK(m.channels == 3u * 4096);
  unsigned baseline_writes;
  setup(&m); CHECK(motor_adc_initialize(&m.port)); valid_sample(&m); baseline_writes = m.writes;
  /* Every MMIO write may be lost. Benign writes of an already-correct value
   * need not fault; a returned valid frame must still match fresh hardware. */
  for (unsigned i = 1; i <= baseline_writes; ++i) {
    setup(&m); m.drop_write = i;
    if (!motor_adc_initialize(&m.port)) check_off(&m);
    else {
      struct motor_adc_sample s = motor_adc_capture(&m.port);
      if (s.valid) { CHECK(s.vbus == m.codes[0]); CHECK(s.vm == m.codes[1]); CHECK(s.vrefint == m.codes[2]); }
      else check_off(&m);
    }
  }
  for (enum fault f = HSI_STUCK; f <= JUMP_TIME; f++) {
    setup(&m);
    bool capture_fault = f == CHANNEL_STUCK || f == CONVERSION_STUCK || f == OVERRUN ||
      f == NO_EOS || f == BAD_DATA || f == ZERO_REFERENCE;
    if (capture_fault) CHECK(motor_adc_initialize(&m.port));
    m.fault = f;
    if (capture_fault) invalid_sample(&m);
    else { CHECK(!motor_adc_initialize(&m.port)); check_off(&m); }
    CHECK(m.calls < 9000);
  }
  /* Faults after startup, including frozen/backwards clocks, fail closed. */
  for (enum fault f = BACKWARDS; f <= JUMP_TIME; f++) {
    setup(&m); CHECK(motor_adc_initialize(&m.port)); m.fault = f; invalid_sample(&m);
    CHECK(m.calls < 9000);
  }
  const uint32_t busy[] = {ADC_CR_ADEN, ADC_CR_ADCAL, ADC_CR_ADSTART, ADC_CR_ADSTP, ADC_CR_ADDIS};
  for (unsigned i = 0; i < sizeof(busy)/sizeof(busy[0]); ++i) {
    setup(&m); m.adc.CR = busy[i]; CHECK(!motor_adc_initialize(&m.port)); check_off(&m);
  }
  for (unsigned i = 0; i < 3; ++i) {
    setup(&m); m.factory = i == 0 ? 0 : i == 1 ? 4096 : UINT16_MAX;
    CHECK(!motor_adc_initialize(&m.port)); check_off(&m);
  }
  /* Detect corrupted owned configuration before using a frame. */
  for (unsigned i = 0; i < 13; ++i) {
    setup(&m); CHECK(motor_adc_initialize(&m.port));
    switch (i) {
      case 0: m.adc.CFGR1 = ADC_CFGR1_DMAEN; break;
      case 1: m.adc.CFGR2 = ADC_CFGR2_CKMODE; break;
      case 2: m.adc.SMPR = 0; break;
      case 3: m.adc.IER = ADC_IER_EOCIE; break;
      case 4: m.common.CCR = ADC_CCR_PRESC_0; break;
      case 5: m.rcc.CCIPR &= ~RCC_CCIPR_ADCSEL; break;
      case 6: m.rcc.APBENR2 &= ~RCC_APBENR2_ADCEN; break;
      case 7: m.rcc.CR &= ~RCC_CR_HSIRDY; break;
      case 8: m.adc.CR |= ADC_CR_ADCAL; break;
      case 9: m.a.MODER &= ~UINT32_C(0xf); break;
      case 10: m.a.PUPDR |= 1; break;
      case 11: m.syscfg.CFGR2 |= SYSCFG_CFGR2_PA1_CDEN; break;
      default: ++m.factory; break;
    }
    invalid_sample(&m);
  }
  setup(&m); CHECK(motor_adc_initialize(&m.port));
  for (unsigned i = 1; i < 82; ++i) {
    setup(&m); CHECK(motor_adc_initialize(&m.port)); m.corrupt_at = m.calls + i;
    struct motor_adc_sample s = motor_adc_capture(&m.port);
    if (!s.valid) check_off(&m);
    else CHECK(m.calls < m.corrupt_at);
  }
  setup(&m); m.now = UINT32_MAX - 20; CHECK(motor_adc_initialize(&m.port)); valid_sample(&m);
  setup(&m); m.now = UINT32_MAX - 100; CHECK(motor_adc_initialize(&m.port)); valid_sample(&m);
  setup(&m); m.port.now_us = NULL; CHECK(!motor_adc_initialize(&m.port)); check_off(&m);
  setup(&m); m.port.write = NULL; CHECK(!motor_adc_initialize(&m.port)); check_off(&m);
  /* A frame after a long idle must rearm before using a conversion. */
  setup(&m); CHECK(motor_adc_initialize(&m.port)); valid_sample(&m);
  CHECK(m.rearms == 0);
  m.now += 1000u;
  m.a.ODR = (m.a.ODR & ~UINT32_C(0xc0)) | UINT32_C(0x40);
  uint32_t allowed_outputs = m.a.ODR & UINT32_C(0xc0);
  valid_sample(&m); CHECK(m.rearms == 1);
  CHECK((m.a.ODR & UINT32_C(0xc0)) == allowed_outputs);
  valid_sample(&m); CHECK(m.rearms == 1);
  for (enum fault f = READY_STUCK; f <= DISABLE_STUCK; f++) {
    if (f != READY_STUCK && f != DISABLE_STUCK) continue;
    setup(&m); CHECK(motor_adc_initialize(&m.port)); m.now += 1000u;
    m.fault = f; invalid_sample(&m); CHECK(m.rearms == 1); CHECK(m.calls < 9000);
  }
  /* Lost disable/ready-clear/enable writes each fail closed on the rearm path. */
  for (unsigned i = 1; i <= 3; ++i) {
    setup(&m); CHECK(motor_adc_initialize(&m.port)); m.now += 1000u;
    m.drop_write = m.writes + i; invalid_sample(&m); CHECK(m.calls < 9000);
  }
  setup(&m); m.now = UINT32_MAX - 500u; CHECK(motor_adc_initialize(&m.port));
  m.now += 1000u; valid_sample(&m); CHECK(m.rearms == 1);
  CHECK(!motor_adc_initialize(NULL)); CHECK(!motor_adc_capture(NULL).valid);
  struct motor_adc_clock clock = {.context=&m, .now_us=now_us};
  struct motor_adc target = motor_adc_target(&m.gpio, &clock);
  CHECK(target.adc == ADC1 && target.common == ADC1_COMMON);
  CHECK((uintptr_t)target.factory_vref == UINT32_C(0x1fff75aa));
  CHECK(target.now_us == now_us && target.context == &m && target.write);
  CHECK(active == &m);
  printf("STM32 bounded ADC host tests passed: %u assertions; raw acquisition only\n", assertions);
  return 0;
}