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

#include "stm32_adc.h"
#include "stm32_clock.h"

#define RS (ADC_CR_ADCAL | ADC_CR_ADEN | ADC_CR_ADDIS | ADC_CR_ADSTART | ADC_CR_ADSTP)
#define FLAGS (ADC_ISR_ADRDY | ADC_ISR_EOSMP | ADC_ISR_EOC | ADC_ISR_EOS | \
               ADC_ISR_OVR | ADC_ISR_AWD1 | ADC_ISR_AWD2 | ADC_ISR_AWD3 | \
               ADC_ISR_EOCAL | ADC_ISR_CCRDY)
#define CONVERSION_FLAGS (ADC_ISR_EOSMP | ADC_ISR_EOC | ADC_ISR_EOS | ADC_ISR_OVR | ADC_ISR_CCRDY)
#define COMMON (ADC_CCR_PRESC_0 | ADC_CCR_VREFEN)
#define POLL_LIMIT 4096u
#define INIT_LIMIT_US 1000u
#define CAPTURE_LIMIT_US 500u

static void target_write(void *context, const struct motor_adc_write *op) {
  (void)context;
  *op->reg = op->value;
}
struct motor_adc motor_adc_target(struct motor_gpio *gpio,
                                 const struct motor_adc_clock *clock) {
  struct motor_adc port = {.gpio = gpio, .adc = ADC1, .common = ADC1_COMMON,
    .factory_vref = (const volatile uint16_t *)UINT32_C(0x1fff75aa),
    .write = target_write};
  if (clock) { port.context = clock->context; port.now_us = clock->now_us; }
  return port;
}
static bool context_valid(const struct motor_adc *p) {
  return p && p->gpio && p->gpio->rcc && p->gpio->syscfg && p->gpio->a &&
    p->gpio->b && p->gpio->c && p->gpio->write_bsrr &&
    p->adc && p->common && p->factory_vref && p->now_us && p->write;
}
static bool fail(struct motor_adc *p) {
  if (p) {
    p->faulted = true; p->initialized = false;
    (void)motor_gpio_inhibit(p->gpio);
  }
  return false;
}
static void write_reg(struct motor_adc *p, const struct motor_adc_write op) {
  p->write(p->context, &op);
}
struct wait_condition {
  volatile uint32_t *reg;
  uint32_t mask, value, started, limit;
};
/* Time subtraction permits one uint32 wrap. A stalled clock still terminates. */
static bool wait_bits(struct motor_adc *p, const struct wait_condition c) {
  for (unsigned n = 0; n < POLL_LIMIT; ++n) {
    if ((uint32_t)(p->now_us(p->context) - c.started) >= c.limit) return false;
    if ((*c.reg & c.mask) == c.value) return true;
  }
  return false;
}
struct delay_condition { uint32_t started, delay, operation_started, limit; };
static bool delay_us(struct motor_adc *p, const struct delay_condition c) {
  for (unsigned n = 0; n < POLL_LIMIT; ++n) {
    uint32_t now = p->now_us(p->context);
    if ((uint32_t)(now - c.operation_started) >= c.limit) return false;
    if ((uint32_t)(now - c.started) >= c.delay) return true;
  }
  return false;
}
static bool pins_valid(const struct motor_adc *p) {
  return p->gpio->initialized && !p->gpio->faulted &&
    (p->gpio->rcc->IOPENR & RCC_IOPENR_GPIOAEN) != 0 &&
    (p->gpio->rcc->APBENR2 & RCC_APBENR2_SYSCFGEN) != 0 &&
    (p->gpio->a->MODER & UINT32_C(0xf)) == UINT32_C(0xf) &&
    (p->gpio->a->PUPDR & UINT32_C(0xf)) == 0 &&
    (p->gpio->syscfg->CFGR2 & SYSCFG_CFGR2_PA1_CDEN) == 0;
}
static bool settings_valid(const struct motor_adc *p) {
  return pins_valid(p) && (p->gpio->rcc->CR & (RCC_CR_HSION | RCC_CR_HSIRDY)) ==
    (RCC_CR_HSION | RCC_CR_HSIRDY) &&
    (p->gpio->rcc->CR & (RCC_CR_PLLON | RCC_CR_PLLRDY)) ==
      (RCC_CR_PLLON | RCC_CR_PLLRDY) &&
    p->gpio->rcc->PLLCFGR == MOTOR_PLL_CONFIG &&
    (p->gpio->rcc->APBENR2 & RCC_APBENR2_ADCEN) != 0 &&
    (p->gpio->rcc->CCIPR & RCC_CCIPR_ADCSEL) == RCC_CCIPR_ADCSEL_0 &&
    p->common->CCR == COMMON && p->adc->CFGR1 == 0 && p->adc->CFGR2 == 0 &&
    p->adc->SMPR == ADC_SMPR_SMP1 && p->adc->IER == 0;
}
static bool configured(const struct motor_adc *p) {
  return settings_valid(p) &&
    (p->adc->CR & (RS | ADC_CR_ADVREGEN)) == (ADC_CR_ADEN | ADC_CR_ADVREGEN) &&
    *p->factory_vref == p->factory_code;
}
bool motor_adc_initialize(struct motor_adc *p) {
  /* Even a malformed ADC context attempts the separate GPIO safety path. */
  if (!p) return false;
  bool inhibited = motor_gpio_inhibit(p->gpio);
  p->initialized = false;
  if (!inhibited || !context_valid(p) || p->faulted || !pins_valid(p)) return fail(p);
  uint32_t started = p->now_us(p->context);
  write_reg(p, (struct motor_adc_write){&p->gpio->rcc->APBENR2,
    p->gpio->rcc->APBENR2 | RCC_APBENR2_ADCEN});
  if (!(p->gpio->rcc->APBENR2 & RCC_APBENR2_ADCEN) || (p->adc->CR & RS)) return fail(p);
  write_reg(p, (struct motor_adc_write){&p->gpio->rcc->CR,
    p->gpio->rcc->CR | RCC_CR_HSION});
  if (!wait_bits(p, (struct wait_condition){&p->gpio->rcc->CR, RCC_CR_HSIRDY,
      RCC_CR_HSIRDY, started, INIT_LIMIT_US})) return fail(p);
  write_reg(p, (struct motor_adc_write){&p->gpio->rcc->CCIPR,
    (p->gpio->rcc->CCIPR & ~RCC_CCIPR_ADCSEL) | RCC_CCIPR_ADCSEL_0});
  write_reg(p, (struct motor_adc_write){&p->adc->IER, 0});
  write_reg(p, (struct motor_adc_write){&p->adc->CFGR1, 0});
  write_reg(p, (struct motor_adc_write){&p->adc->CFGR2, 0});
  write_reg(p, (struct motor_adc_write){&p->adc->SMPR, ADC_SMPR_SMP1});
  write_reg(p, (struct motor_adc_write){&p->common->CCR, COMMON});
  write_reg(p, (struct motor_adc_write){&p->adc->ISR, FLAGS});
  write_reg(p, (struct motor_adc_write){&p->adc->CR, ADC_CR_ADVREGEN});
  /* Calibration is forbidden with DMA/auto-off enabled. Verify all owned
   * settings BEFORE commanding it, not merely after the ADC is enabled. */
  if (!settings_valid(p) || (p->adc->CR & (RS | ADC_CR_ADVREGEN)) != ADC_CR_ADVREGEN ||
      (p->adc->ISR & FLAGS)) return fail(p);
  uint32_t regulator_start = p->now_us(p->context);
  if (!delay_us(p, (struct delay_condition){regulator_start, 20, started, INIT_LIMIT_US})) return fail(p);
  write_reg(p, (struct motor_adc_write){&p->adc->CR, ADC_CR_ADVREGEN | ADC_CR_ADCAL});
  if (!wait_bits(p, (struct wait_condition){&p->adc->CR, ADC_CR_ADCAL, 0, started, INIT_LIMIT_US}) ||
      !(p->adc->ISR & ADC_ISR_EOCAL)) return fail(p);
  uint32_t calibrated = p->now_us(p->context);
  /* 1 us exceeds two ADC clocks throughout the32 MHz +/-3% envelope. */
  if (!delay_us(p, (struct delay_condition){calibrated, 1, started, INIT_LIMIT_US})) return fail(p);
  write_reg(p, (struct motor_adc_write){&p->adc->ISR, ADC_ISR_ADRDY});
  p->last_active_us = p->now_us(p->context);
  write_reg(p, (struct motor_adc_write){&p->adc->CR, ADC_CR_ADVREGEN | ADC_CR_ADEN});
  if (!wait_bits(p, (struct wait_condition){&p->adc->ISR, ADC_ISR_ADRDY,
      ADC_ISR_ADRDY, started, INIT_LIMIT_US})) return fail(p);
  /* Conservatively wait reference startup again after ADC ready. */
  uint32_t reference_start = p->now_us(p->context);
  if (!delay_us(p, (struct delay_condition){reference_start, 12, started, INIT_LIMIT_US})) return fail(p);
  p->factory_code = *p->factory_vref;
  if (!p->factory_code || p->factory_code >= 4096 || !configured(p)) return fail(p);
  p->initialized = true;
  return true;
}
/* DS12991 table56 permits at most100 us idle without rearm. The80 us
 * command-time guard is conservative even with a5% slow timebase. It does
 * not substitute for qualification of the selected ADC accuracy profile. */
static bool rearm_if_idle(struct motor_adc *p, uint32_t started) {
  uint32_t now = p->now_us(p->context);
  if ((uint32_t)(now - p->last_active_us) < 80u) return true;
  if (!configured(p) || (p->adc->CR & ADC_CR_ADSTART)) return false;
  write_reg(p, (struct motor_adc_write){&p->adc->CR, ADC_CR_ADVREGEN | ADC_CR_ADDIS});
  if (!wait_bits(p, (struct wait_condition){&p->adc->CR, RS, 0, started, CAPTURE_LIMIT_US}) ||
      !settings_valid(p) || !(p->adc->CR & ADC_CR_ADVREGEN)) return false;
  write_reg(p, (struct motor_adc_write){&p->adc->ISR, ADC_ISR_ADRDY});
  if (p->adc->ISR & ADC_ISR_ADRDY) return false;
  p->last_active_us = p->now_us(p->context);
  write_reg(p, (struct motor_adc_write){&p->adc->CR, ADC_CR_ADVREGEN | ADC_CR_ADEN});
  return wait_bits(p, (struct wait_condition){&p->adc->ISR, ADC_ISR_ADRDY,
    ADC_ISR_ADRDY, started, CAPTURE_LIMIT_US}) && configured(p);
}
struct channel_capture { uint32_t channel, started; uint16_t code; uint32_t command_started, completed; };
static bool capture_channel(struct motor_adc *p, struct channel_capture *c) {
  if (!configured(p) || (p->adc->ISR & ADC_ISR_OVR) || !rearm_if_idle(p, c->started)) return false;
  write_reg(p, (struct motor_adc_write){&p->adc->ISR, CONVERSION_FLAGS});
  if (p->adc->ISR & CONVERSION_FLAGS) return false;
  write_reg(p, (struct motor_adc_write){&p->adc->CHSELR, c->channel});
  if (!wait_bits(p, (struct wait_condition){&p->adc->ISR, ADC_ISR_CCRDY,
      ADC_ISR_CCRDY, c->started, CAPTURE_LIMIT_US}) || p->adc->CHSELR != c->channel)
    return false;
  p->last_active_us = p->now_us(p->context);
  c->command_started = p->last_active_us;
  write_reg(p, (struct motor_adc_write){&p->adc->CR,
    ADC_CR_ADVREGEN | ADC_CR_ADSTART});
  if (!wait_bits(p, (struct wait_condition){&p->adc->ISR, ADC_ISR_EOC | ADC_ISR_EOS,
      ADC_ISR_EOC | ADC_ISR_EOS, c->started, CAPTURE_LIMIT_US}) ||
      (p->adc->ISR & ADC_ISR_OVR) || !configured(p)) return false;
  uint32_t code = p->adc->DR; /* Hardware DR read clears EOC. Explicit W1C also follows. */
  c->completed = p->now_us(p->context);
  if (code >= 4096 || (p->adc->ISR & ADC_ISR_OVR) || !configured(p) ||
      (uint32_t)(c->completed - c->started) >= CAPTURE_LIMIT_US) return false;
  c->code = (uint16_t)code;
  write_reg(p, (struct motor_adc_write){&p->adc->ISR, CONVERSION_FLAGS});
  return (p->adc->ISR & CONVERSION_FLAGS) == 0;
}
struct motor_adc_sample motor_adc_capture(struct motor_adc *p) {
  const struct motor_adc_sample invalid = {0};
  if (!context_valid(p) || !p->initialized || p->faulted || !configured(p)) {
    (void)fail(p); return invalid;
  }
  struct motor_adc_sample result = {.factory_vref = p->factory_code,
    .started_us = p->now_us(p->context)};
  const uint32_t channels[3] = {ADC_CHSELR_CHSEL0, ADC_CHSELR_CHSEL1, ADC_CHSELR_CHSEL13};
  uint16_t codes[3];
  /* VREF first, VBUS next, VM last: fastest possible fresh motor observation.
   * Array identities remain VBUS=0, VM=1, VREF=2. Frame START is preserved. */
  const unsigned order[3] = {2, 0, 1};
  for (unsigned position = 0; position < 3; ++position) {
    const unsigned i = order[position];
    struct channel_capture c = {.channel = channels[i], .started = result.started_us};
    if (!capture_channel(p, &c)) { (void)fail(p); return invalid; }
    codes[i] = c.code; result.channel_started_us[i] = c.command_started;
    result.channel_completed_us[i] = c.completed;
  }
  result.completed_us = p->now_us(p->context);
  if (!codes[2] || !configured(p) || (p->adc->ISR & ADC_ISR_OVR) ||
      (uint32_t)(result.completed_us - result.started_us) >= CAPTURE_LIMIT_US) {
    (void)fail(p); return invalid;
  }
  result.vbus = codes[0]; result.vm = codes[1]; result.vrefint = codes[2]; result.valid = true;
  return result;
}