AnasSarkiz/usb-c-pwm-pa25
A component library defining an AO3400A N-channel MOSFET, 47 µF polarized capacitor, 3-pin through-hole connector, and 9.1 V Zener diode with PCB footprints and 3D CAD models.
- Version
- 0.1.0-prototype.2
- License
- unset
- Stars
- 0
firmware/main.c
#include "stm32c011xx.h"
#include "control.h"
static volatile uint32_t milliseconds;
static uint16_t bridge_wake_ticks;
void SysTick_Handler(void) { milliseconds++; }
struct RegisterWait { volatile uint32_t *reg; uint32_t mask; bool asserted; };
static bool wait_register(struct RegisterWait condition) {
const uint32_t started = milliseconds;
while (((*condition.reg & condition.mask) != 0) != condition.asserted) {
if ((uint32_t)(milliseconds-started) >= 5) return false;
}
return true;
}
static void delay_ms(uint32_t duration) {
const uint32_t started = milliseconds;
while ((uint32_t)(milliseconds-started) < duration) __WFI();
}
static void safe_outputs(void) {
TIM14->CCR1=0;
GPIOA->BSRR=(1UL<<(6+16))|(1UL<<11);
bridge_wake_ticks=0;
}
static void gpio_init(void) {
RCC->IOPENR |= RCC_IOPENR_GPIOAEN|RCC_IOPENR_GPIOBEN|RCC_IOPENR_GPIOCEN;
(void)RCC->IOPENR;
GPIOA->BSRR=(1UL<<11)|(1UL<<(5+16))|(1UL<<(6+16))|(1UL<<(7+16))|(1UL<<(12+16));
GPIOA->OTYPER |= 1UL<<11; // Released output asserts the external power-inhibit MOSFET.
const uint32_t output_mask=(3UL<<10)|(3UL<<12)|(3UL<<14)|(3UL<<22)|(3UL<<24);
GPIOA->MODER=(GPIOA->MODER & ~(output_mask|(3UL<<8)|(3UL<<16)))|
(1UL<<10)|(1UL<<12)|(1UL<<14)|(1UL<<22)|(1UL<<24)|(2UL<<8)|0xFFUL;
GPIOA->AFR[0]=(GPIOA->AFR[0]&~(15UL<<16))|(4UL<<16); // PA4 AF4 TIM14_CH1.
GPIOC->MODER &= ~((3UL<<28)|(3UL<<30));
GPIOB->OTYPER |= (1UL<<6)|(1UL<<7);
GPIOB->MODER=(GPIOB->MODER&~((3UL<<12)|(3UL<<14)))|(2UL<<12)|(2UL<<14);
GPIOB->AFR[0]=(GPIOB->AFR[0]&~((15UL<<24)|(15UL<<28)))|(6UL<<24)|(6UL<<28);
}
static void timer_init(void) {
RCC->APBENR2 |= RCC_APBENR2_TIM14EN;
TIM14->PSC=0;
TIM14->ARR=599; // HSI48 / 4 = 12 MHz; 12 MHz / 600 = 20 kHz.
TIM14->CCR1=0;
TIM14->CCMR1=(6UL<<TIM_CCMR1_OC1M_Pos)|TIM_CCMR1_OC1PE;
TIM14->CCER=TIM_CCER_CC1E;
TIM14->EGR=TIM_EGR_UG;
TIM14->CR1=TIM_CR1_ARPE|TIM_CR1_CEN;
}
static bool adc_init(void) {
RCC->APBENR2 |= RCC_APBENR2_ADCEN;
ADC1->CFGR2=ADC_CFGR2_CKMODE_0; // PCLK / 2 = 6 MHz.
ADC1->CR=ADC_CR_ADVREGEN;
delay_ms(1);
ADC1->CR |= ADC_CR_ADCAL;
if (!wait_register((struct RegisterWait){&ADC1->CR,ADC_CR_ADCAL,false})) return false;
delay_ms(1);
ADC1->SMPR=ADC_SMPR_SMP1; // Long acquisition for the high-impedance dividers.
ADC1_COMMON->CCR |= ADC_CCR_VREFEN;
ADC1->ISR=ADC_ISR_ADRDY;
ADC1->CR |= ADC_CR_ADEN;
return wait_register((struct RegisterWait){&ADC1->ISR,ADC_ISR_ADRDY,true});
}
static bool adc_read(uint8_t channel, uint16_t *sample) {
ADC1->ISR=ADC_ISR_CCRDY|ADC_ISR_EOC|ADC_ISR_EOS|ADC_ISR_OVR;
ADC1->CHSELR=1UL<<channel;
if (!wait_register((struct RegisterWait){&ADC1->ISR,ADC_ISR_CCRDY,true})) return false;
ADC1->CR |= ADC_CR_ADSTART;
if (!wait_register((struct RegisterWait){&ADC1->ISR,ADC_ISR_EOC,true})) return false;
*sample=(uint16_t)ADC1->DR;
return true;
}
static void i2c_init(void) {
RCC->APBENR1 |= RCC_APBENR1_I2C1EN;
RCC->CCIPR &= ~RCC_CCIPR_I2C1SEL; // PCLK 12 MHz.
I2C1->CR1=0;
// Conservative standard-mode timing, about 90 kHz; 300 ns rise/fall budget.
I2C1->TIMINGR=0x10421B23UL;
I2C1->CR1=I2C_CR1_PE;
}
static bool i2c_wait(uint32_t mask) {
const uint32_t started=milliseconds;
while (!(I2C1->ISR & mask)) {
if ((I2C1->ISR & (I2C_ISR_NACKF|I2C_ISR_BERR|I2C_ISR_ARLO)) ||
(uint32_t)(milliseconds-started)>=5) return false;
}
return true;
}
struct PdRead { uint8_t address; uint8_t length; uint8_t bytes[6]; };
static bool pd_read(struct PdRead *request) {
if (I2C1->ISR & I2C_ISR_BUSY) return false;
I2C1->ICR=I2C_ICR_STOPCF|I2C_ICR_NACKCF|I2C_ICR_BERRCF|I2C_ICR_ARLOCF;
I2C1->CR2=(0x21UL<<1)|(1UL<<I2C_CR2_NBYTES_Pos)|I2C_CR2_START;
if (!i2c_wait(I2C_ISR_TXIS)) return false;
I2C1->TXDR=request->address;
if (!i2c_wait(I2C_ISR_TC)) return false;
I2C1->CR2=(0x21UL<<1)|((uint32_t)(request->length+1)<<I2C_CR2_NBYTES_Pos)|
I2C_CR2_RD_WRN|I2C_CR2_AUTOEND|I2C_CR2_START;
if (!i2c_wait(I2C_ISR_RXNE)) return false;
const uint8_t length=(uint8_t)I2C1->RXDR;
for (unsigned index=0;index<request->length;index++) {
if (!i2c_wait(I2C_ISR_RXNE)) return false;
request->bytes[index]=(uint8_t)I2C1->RXDR;
}
if (!i2c_wait(I2C_ISR_STOPF)) return false;
I2C1->ICR=I2C_ICR_STOPCF;
return length==request->length;
}
static uint32_t little_endian_word(const uint8_t *bytes) {
return bytes[0]|((uint32_t)bytes[1]<<8)|((uint32_t)bytes[2]<<16)|((uint32_t)bytes[3]<<24);
}
static bool qualify_pd(void) {
struct PdRead pdo={.address=0x34,.length=6};
struct PdRead rdo={.address=0x35,.length=4};
if (!pd_read(&pdo) || !pd_read(&rdo)) {
I2C1->CR1=0;
I2C1->CR1=I2C_CR1_PE;
return false; // An unsuccessful read always removes run authorization.
}
return pd_contract_valid(little_endian_word(pdo.bytes),little_endian_word(rdo.bytes));
}
static bool read_inputs(struct Inputs *inputs) {
uint16_t adc[5];
for (uint8_t channel=0;channel<4;channel++) if (!adc_read(channel,&adc[channel])) return false;
if (!adc_read(10,&adc[4]) || adc[4]==0) return false; // STM32C011 internal VREFINT channel 10.
const uint16_t factory_vref=*(const uint16_t *)0x1FFF756AUL;
const uint32_t vdda_mv=(uint32_t)factory_vref*3000U/adc[4];
if (factory_vref==0 || factory_vref==65535 || vdda_mv<3000 || vdda_mv>3500) return false;
inputs->pot_permille=(uint16_t)((uint32_t)adc[0]*1000U/4095U);
if (inputs->pot_permille>=980) inputs->pot_permille=1000;
const uint32_t current_mv=(uint32_t)adc[1]*vdda_mv/4095U;
inputs->current_ma=(uint16_t)(current_mv*1000U/4446U);
inputs->vm_mv=(uint16_t)(((uint32_t)adc[2]*vdda_mv/4095U)*1267U/267U);
inputs->temperature_mv=(uint16_t)((uint32_t)adc[3]*vdda_mv/4095U);
inputs->hardware_healthy=(GPIOA->IDR&(1UL<<8))!=0;
inputs->forward_closed=(GPIOC->IDR&(1UL<<14))==0;
inputs->reverse_closed=(GPIOC->IDR&(1UL<<15))==0;
return true;
}
static void apply_outputs(const struct Controller *controller, uint16_t elapsed_ms) {
if (!controller->bridge_enabled) {
TIM14->CCR1=0;
GPIOA->BSRR=1UL<<(6+16);
bridge_wake_ticks=0;
GPIOA->BSRR=controller->applied_direction==FORWARD ? (1UL<<5) : (1UL<<(5+16));
} else {
GPIOA->BSRR=1UL<<6;
if (bridge_wake_ticks<3) {
bridge_wake_ticks+=elapsed_ms;
TIM14->CCR1=0;
} else TIM14->CCR1=(uint32_t)controller->duty_permille*600U/1000U;
}
GPIOA->BSRR=controller->power_enabled ? (1UL<<(11+16)) : (1UL<<11);
GPIOA->BSRR=controller->mode==FAULT ? (1UL<<7) : (1UL<<(7+16));
GPIOA->BSRR=(controller->mode==READY || controller->mode==RUNNING) ? (1UL<<12) : (1UL<<(12+16));
}
int main(void) {
SystemCoreClockUpdate();
gpio_init();
timer_init();
safe_outputs();
SysTick_Config(SystemCoreClock/1000U);
// Programming verification is part of assembly: BOR must be enabled at the highest levels.
const uint32_t bor_mask=FLASH_OPTR_BOR_EN|FLASH_OPTR_BORR_LEV|FLASH_OPTR_BORF_LEV;
if (SystemCoreClock!=12000000UL || (FLASH->OPTR&bor_mask)!=bor_mask || !adc_init()) {
GPIOA->BSRR=1UL<<7;
for (;;) __WFI();
}
i2c_init();
IWDG->KR=0xCCCC;
IWDG->KR=0x5555;
IWDG->PR=2;
IWDG->RLR=199; // About 100 ms at nominal 32 kHz; tolerate LSI variation.
while (IWDG->SR) {}
IWDG->KR=0xAAAA;
struct Controller controller={0};
struct Inputs inputs={0};
uint32_t last_update=milliseconds;
uint32_t last_pd=milliseconds-20U;
for (;;) {
__WFI();
if ((uint32_t)(milliseconds-last_pd)>=20) {
inputs.pd_valid=qualify_pd();
last_pd=milliseconds;
if (!inputs.pd_valid) safe_outputs();
}
inputs.adc_valid=read_inputs(&inputs);
const uint32_t now=milliseconds;
const uint32_t elapsed=now-last_update;
if (elapsed==0) continue;
if (elapsed>5) controller_fault(&controller,SCHEDULER_FAULT);
else for (uint32_t tick=0;tick<elapsed;tick++) controller_tick(&controller,&inputs);
apply_outputs(&controller,(uint16_t)elapsed);
last_update=now;
IWDG->KR=0xAAAA;
}
}