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_safe_gpio.c
#include "stm32_safe_gpio.h"
#define BIT(pin) (UINT32_C(1) << (pin))
#define MODE(pin, value) ((uint32_t)(value) << (2u * (pin)))
#define FIELD(pin) MODE(pin, 3u)
#define PA_SAFETY (BIT(6) | BIT(7))
#define PA_OUTPUT (PA_SAFETY | BIT(11))
#define PA_OWNED (FIELD(0) | FIELD(1) | FIELD(2) | FIELD(3) | FIELD(6) | \
FIELD(7) | FIELD(8) | FIELD(9) | FIELD(10) | FIELD(11) | \
FIELD(12) | FIELD(15))
#define PA_MODES (MODE(0, 3) | MODE(1, 3) | MODE(6, 1) | MODE(7, 1) | \
MODE(9, 3) | MODE(10, 3) | MODE(11, 1) | MODE(15, 3))
#define PB_OWNED UINT32_C(0x000fffff)
#define PB_MODES UINT32_C(0x000affff)
#define PC_OWNED (FIELD(14) | FIELD(15))
#define PC_MODES MODE(14, 3)
#define PORT_CLOCKS (RCC_IOPENR_GPIOAEN | RCC_IOPENR_GPIOBEN | RCC_IOPENR_GPIOCEN)
static bool valid(const struct motor_gpio *g) {
return g && g->rcc && g->syscfg && g->a && g->b && g->c && g->write_bsrr;
}
static void target_bsrr(GPIO_TypeDef *port, uint32_t bits) { port->BSRR = bits; }
struct motor_gpio motor_gpio_target(void) {
return (struct motor_gpio){.rcc = RCC, .syscfg = SYSCFG, .a = GPIOA,
.b = GPIOB, .c = GPIOC, .write_bsrr = target_bsrr};
}
static bool safety_readback(const struct motor_gpio *g) {
return (g->rcc->IOPENR & PORT_CLOCKS) == PORT_CLOCKS &&
(g->a->MODER & (FIELD(6) | FIELD(7))) == (MODE(6, 1) | MODE(7, 1)) &&
(g->a->OTYPER & PA_SAFETY) == BIT(7) &&
(g->a->PUPDR & (FIELD(6) | FIELD(7))) == 0 &&
(g->a->ODR & PA_SAFETY) ==
((g->host_allow ? BIT(6) : 0) | (g->release_bridge ? 0 : BIT(7)));
}
bool motor_gpio_inhibit(struct motor_gpio *g) {
if (!valid(g)) {
if (g) { g->faulted = true; g->initialized = false; }
return false;
}
g->rcc->IOPENR |= PORT_CLOCKS;
/* Read back clock enable before accessing GPIO registers. */
if ((g->rcc->IOPENR & PORT_CLOCKS) != PORT_CLOCKS) {
g->faulted = true; g->initialized = false; return false;
}
/* Remove power permission before changing any other peripheral or pin. */
g->write_bsrr(g->a, BIT(6) << 16);
g->a->OTYPER &= ~BIT(6);
g->a->PUPDR &= ~FIELD(6);
g->a->MODER = (g->a->MODER & ~FIELD(6)) | MODE(6, 1);
/* Q6 base is directly on PA7. Open drain BEFORE setting the high latch:
* a push-pull high would source unresisted current into that B-E junction. */
g->a->OTYPER |= BIT(7);
g->write_bsrr(g->a, BIT(7));
g->a->PUPDR &= ~FIELD(7);
g->a->MODER = (g->a->MODER & ~FIELD(7)) | MODE(7, 1);
g->host_allow = false;
g->release_bridge = false;
if (!safety_readback(g)) {
g->faulted = true; g->initialized = false; return false;
}
return true;
}
static bool feedback_readback(const struct motor_gpio *g);
static bool configuration_readback(const struct motor_gpio *g) {
return safety_readback(g) &&
(g->rcc->APBENR2 & RCC_APBENR2_SYSCFGEN) != 0 &&
(g->rcc->BDCR & RCC_BDCR_LSEON) == 0 &&
(g->syscfg->CFGR1 & (SYSCFG_CFGR1_PA11_RMP | SYSCFG_CFGR1_PA12_RMP)) == 0 &&
(g->syscfg->CFGR2 & SYSCFG_CFGR2_PA1_CDEN) == 0 &&
(g->a->MODER & PA_OWNED) == PA_MODES &&
(g->a->PUPDR & PA_OWNED) == 0 &&
(g->a->OSPEEDR & (FIELD(6) | FIELD(7) | FIELD(11))) == 0 &&
(g->a->OTYPER & PA_OUTPUT) == BIT(7) && (g->a->ODR & BIT(11)) == 0 &&
(g->b->MODER & PB_OWNED) == PB_MODES && (g->b->PUPDR & PB_OWNED) == 0 &&
(g->b->OTYPER & (BIT(8) | BIT(9))) == (BIT(8) | BIT(9)) &&
(g->b->OSPEEDR & (FIELD(8) | FIELD(9))) == 0 &&
(g->b->AFR[1] & UINT32_C(0xff)) == UINT32_C(0x66) &&
(g->c->MODER & PC_OWNED) == PC_MODES && (g->c->PUPDR & PC_OWNED) == 0 &&
feedback_readback(g);
}
bool motor_gpio_initialize(struct motor_gpio *g) {
if (!motor_gpio_inhibit(g)) return false;
g->initialized = false;
if (g->faulted || (g->rcc->BDCR & RCC_BDCR_LSEON)) {
g->faulted = true; return false;
}
g->rcc->APBENR2 |= RCC_APBENR2_SYSCFGEN;
if (!(g->rcc->APBENR2 & RCC_APBENR2_SYSCFGEN)) {
g->faulted = true; return false;
}
/* Disable bonded aliases first. Never touch feedback PA4/PA5 or SWD13/14. */
g->a->MODER = (g->a->MODER & ~(FIELD(9) | FIELD(10) | FIELD(15))) |
MODE(9, 3) | MODE(10, 3) | MODE(15, 3);
g->a->PUPDR &= ~(FIELD(9) | FIELD(10) | FIELD(15));
g->b->MODER = (g->b->MODER & ~UINT32_C(0xffff)) | UINT32_C(0xffff);
g->b->PUPDR &= ~UINT32_C(0xffff);
g->c->MODER = (g->c->MODER & ~FIELD(14)) | MODE(14, 3);
g->c->PUPDR &= ~FIELD(14);
g->syscfg->CFGR1 &= ~(SYSCFG_CFGR1_PA11_RMP | SYSCFG_CFGR1_PA12_RMP);
g->syscfg->CFGR2 &= ~SYSCFG_CFGR2_PA1_CDEN;
/* Active-high RESET remains low. A bounded pulse belongs to a later owner. */
g->write_bsrr(g->a, BIT(11) << 16);
g->a->OTYPER &= ~BIT(11);
g->a->OSPEEDR &= ~(FIELD(6) | FIELD(7) | FIELD(11));
g->a->PUPDR &= ~PA_OWNED;
g->a->MODER = (g->a->MODER & ~PA_OWNED) | PA_MODES;
/* I2C1: PB8/PB9 AF6, open drain, external pull-ups. */
g->b->OTYPER |= BIT(8) | BIT(9);
g->write_bsrr(g->b, BIT(8) | BIT(9));
g->b->AFR[1] = (g->b->AFR[1] & ~UINT32_C(0xff)) | UINT32_C(0x66);
g->b->PUPDR &= ~PB_OWNED;
g->b->OSPEEDR &= ~(FIELD(8) | FIELD(9));
g->b->MODER = (g->b->MODER & ~PB_OWNED) | PB_MODES;
g->c->PUPDR &= ~PC_OWNED;
g->c->MODER = (g->c->MODER & ~PC_OWNED) | PC_MODES;
if (!configuration_readback(g)) {
g->faulted = true;
(void)motor_gpio_inhibit(g);
return false;
}
g->initialized = true;
return true;
}
struct motor_gpio_inputs motor_gpio_read_inputs(struct motor_gpio *g) {
struct motor_gpio_inputs result = {.selector_bits = 3, .pd_alert = true,
.motor_fault = true, .pd_enabled = false};
if (!valid(g) || !g->initialized || g->faulted) return result;
if (!configuration_readback(g)) {
g->faulted = true; g->initialized = false;
(void)motor_gpio_inhibit(g);
return result;
}
uint32_t a = g->a->IDR;
result.selector_bits = (uint8_t)((a >> 2) & 3u);
result.motor_fault = (a & BIT(12)) == 0;
result.pd_enabled = (a & BIT(8)) == 0;
result.pd_alert = (g->c->IDR & BIT(15)) == 0;
return result;
}
static bool feedback_readback(const struct motor_gpio *g) {
return !g->feedback_known ||
((g->a->MODER & (FIELD(4) | FIELD(5))) == (MODE(4, 1) | MODE(5, 1)) &&
(g->a->OTYPER & (BIT(4) | BIT(5))) == 0 &&
(g->a->PUPDR & (FIELD(4) | FIELD(5))) == 0 &&
(g->a->OSPEEDR & (FIELD(4) | FIELD(5))) == 0 &&
(g->a->ODR & (BIT(4) | BIT(5))) ==
((g->drive_9v ? BIT(4) : 0) | (g->drive_12v ? BIT(5) : 0)));
}
static bool apply_failure(struct motor_gpio *g) {
if (g) {
g->faulted = true;
g->initialized = false;
(void)motor_gpio_inhibit(g);
}
return false;
}
bool motor_gpio_apply(struct motor_gpio *g, const struct motor_gpio_command *command) {
if (!valid(g) || !command || !g->initialized || g->faulted ||
!configuration_readback(g))
return apply_failure(g);
const struct pd_sequence_outputs *outputs = &command->outputs;
if ((outputs->drive_9v && outputs->drive_12v) ||
(outputs->release_bridge && !outputs->host_allow)) return apply_failure(g);
if (outputs->host_allow && (!command->motor_rail.valid ||
command->motor_rail.lower_mv > command->motor_rail.upper_mv ||
(uint32_t)(command->now_us - command->oldest_sample_us) > 5000))
return apply_failure(g);
const bool feedback_change = !g->feedback_known ||
outputs->drive_9v != g->drive_9v || outputs->drive_12v != g->drive_12v;
if (feedback_change) {
/* Revoke permissions before touching a voltage-selection transistor. */
if (!motor_gpio_inhibit(g)) return apply_failure(g);
if (outputs->host_allow || outputs->release_bridge || !command->motor_rail.valid ||
command->motor_rail.lower_mv > command->motor_rail.upper_mv ||
command->motor_rail.upper_mv > 1000 ||
(uint32_t)(command->now_us - command->oldest_sample_us) > 5000)
return apply_failure(g);
const uint32_t feedback_latches = (outputs->drive_9v ? BIT(4) : BIT(4) << 16) |
(outputs->drive_12v ? BIT(5) : BIT(5) << 16);
g->write_bsrr(g->a, feedback_latches);
g->a->OTYPER &= ~(BIT(4) | BIT(5));
g->a->PUPDR &= ~(FIELD(4) | FIELD(5));
g->a->OSPEEDR &= ~(FIELD(4) | FIELD(5));
g->a->MODER = (g->a->MODER & ~(FIELD(4) | FIELD(5))) | MODE(4, 1) | MODE(5, 1);
g->drive_9v = outputs->drive_9v;
g->drive_12v = outputs->drive_12v;
g->feedback_known = true;
if (!feedback_readback(g)) return apply_failure(g);
}
if (!outputs->host_allow) return motor_gpio_inhibit(g);
/* Power permission rises only after feedback is established. Q6 remains
* inhibited until the sequencing owner explicitly commands bridge wake. */
g->write_bsrr(g->a, BIT(6) | (outputs->release_bridge ? BIT(7) << 16 : BIT(7)));
g->host_allow = outputs->host_allow;
g->release_bridge = outputs->release_bridge;
if (!configuration_readback(g)) return apply_failure(g);
return true;
}