463 lines
12 KiB
C
463 lines
12 KiB
C
#include "dcdc.h"
|
|
|
|
#include "app_config.h"
|
|
#include "board.h"
|
|
#include "stm32g474xx.h"
|
|
|
|
#define HRTIM_TIMER_C_INDEX 2U
|
|
#define ADC_SAMPLE_TIME_47CYCLES 5U
|
|
#define HRTIM_DLL_READY_TIMEOUT 1000000UL
|
|
|
|
static DCDC_State s_state = DCDC_STATE_STOPPED;
|
|
static DCDC_Fault s_fault = DCDC_FAULT_NONE;
|
|
static uint32_t s_duty_ticks = DCDC_START_DUTY_TICKS;
|
|
static uint32_t s_period_ticks = DCDC_HRTIM_PERIOD_TICKS;
|
|
static int32_t s_integrator_ticks;
|
|
static bool s_hrtim_ready;
|
|
|
|
static void gpio_init_for_dcdc(void);
|
|
static void adc1_init(void);
|
|
static uint16_t adc1_read_channel(uint32_t channel);
|
|
static bool hrtim1_timer_c_init(void);
|
|
static bool hrtim1_wait_dll_ready(void);
|
|
static void hrtim1_outputs_enable(bool enable);
|
|
static void hrtim1_set_duty(uint32_t duty_ticks);
|
|
static uint32_t hrtim_period_from_clock(void);
|
|
static uint32_t hrtim_max_duty_ticks(void);
|
|
static uint32_t adc_raw_to_mv(uint16_t raw);
|
|
static uint32_t sense_mv_to_voltage_mv(uint32_t sense_mv, uint32_t scale_ppm);
|
|
static uint32_t sense_mv_to_current_ma(uint32_t sense_mv);
|
|
static void set_usbpd_input_switch(bool enable);
|
|
static void set_loads_off(void);
|
|
static void latch_fault(DCDC_Fault fault);
|
|
|
|
void DCDC_Init(void)
|
|
{
|
|
gpio_init_for_dcdc();
|
|
set_usbpd_input_switch(false);
|
|
set_loads_off();
|
|
|
|
adc1_init();
|
|
s_hrtim_ready = hrtim1_timer_c_init();
|
|
if (s_hrtim_ready)
|
|
{
|
|
hrtim1_outputs_enable(false);
|
|
}
|
|
|
|
s_state = DCDC_STATE_READY;
|
|
s_fault = DCDC_FAULT_NONE;
|
|
}
|
|
|
|
void DCDC_Start(void)
|
|
{
|
|
if (s_fault != DCDC_FAULT_NONE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!s_hrtim_ready)
|
|
{
|
|
latch_fault(DCDC_FAULT_HRTIM);
|
|
return;
|
|
}
|
|
|
|
#if DCDC_CONNECT_USBPD_INPUT
|
|
set_usbpd_input_switch(true);
|
|
#endif
|
|
|
|
s_integrator_ticks = 0;
|
|
hrtim1_set_duty(DCDC_START_DUTY_TICKS);
|
|
HRTIM1->sMasterRegs.MCR |= HRTIM_MCR_TCCEN;
|
|
hrtim1_outputs_enable(true);
|
|
s_state = DCDC_STATE_RUNNING;
|
|
}
|
|
|
|
void DCDC_Stop(void)
|
|
{
|
|
hrtim1_outputs_enable(false);
|
|
hrtim1_set_duty(DCDC_MIN_DUTY_TICKS);
|
|
HRTIM1->sMasterRegs.MCR &= ~HRTIM_MCR_TCCEN;
|
|
set_usbpd_input_switch(false);
|
|
s_state = DCDC_STATE_STOPPED;
|
|
}
|
|
|
|
void DCDC_ControlStep(void)
|
|
{
|
|
DCDC_Measurements m;
|
|
int32_t error_mv;
|
|
int32_t feed_forward;
|
|
int32_t duty;
|
|
|
|
DCDC_ReadMeasurements(&m);
|
|
|
|
if (s_state != DCDC_STATE_RUNNING)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (m.vin_mv < DCDC_MIN_VIN_MV)
|
|
{
|
|
latch_fault(DCDC_FAULT_UNDERVOLTAGE);
|
|
return;
|
|
}
|
|
|
|
if (m.vout_mv > DCDC_MAX_VOUT_MV)
|
|
{
|
|
latch_fault(DCDC_FAULT_OVERVOLTAGE);
|
|
return;
|
|
}
|
|
|
|
if (m.iin_ma > DCDC_HARD_INPUT_CURRENT_MA)
|
|
{
|
|
latch_fault(DCDC_FAULT_OVERCURRENT);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Starter buck controller:
|
|
* 1. feed-forward estimates duty from Vout/Vin,
|
|
* 2. PI term removes static voltage error,
|
|
* 3. current term pulls duty down before hard over-current trips.
|
|
*
|
|
* This is deliberately readable, not a final compensated SMPS loop.
|
|
*/
|
|
feed_forward = (int32_t)(((uint64_t)DCDC_TARGET_VOUT_MV * s_period_ticks) / m.vin_mv);
|
|
error_mv = (int32_t)DCDC_TARGET_VOUT_MV - (int32_t)m.vout_mv;
|
|
|
|
s_integrator_ticks += (error_mv * DCDC_KI_TICKS_PER_100MV) / 100;
|
|
if (s_integrator_ticks > 3000)
|
|
{
|
|
s_integrator_ticks = 3000;
|
|
}
|
|
else if (s_integrator_ticks < -3000)
|
|
{
|
|
s_integrator_ticks = -3000;
|
|
}
|
|
|
|
duty = feed_forward +
|
|
((error_mv * DCDC_KP_TICKS_PER_100MV) / 100) +
|
|
s_integrator_ticks;
|
|
|
|
if (m.iin_ma > DCDC_MAX_INPUT_CURRENT_MA)
|
|
{
|
|
duty -= (int32_t)((m.iin_ma - DCDC_MAX_INPUT_CURRENT_MA) * DCDC_CURRENT_LIMIT_KP);
|
|
}
|
|
|
|
if (duty < (int32_t)DCDC_MIN_DUTY_TICKS)
|
|
{
|
|
duty = (int32_t)DCDC_MIN_DUTY_TICKS;
|
|
}
|
|
else if (duty > (int32_t)hrtim_max_duty_ticks())
|
|
{
|
|
duty = (int32_t)hrtim_max_duty_ticks();
|
|
}
|
|
|
|
hrtim1_set_duty((uint32_t)duty);
|
|
}
|
|
|
|
void DCDC_ReadMeasurements(DCDC_Measurements *out)
|
|
{
|
|
uint32_t vin_sense_mv;
|
|
uint32_t iin_sense_mv;
|
|
uint32_t vout_sense_mv;
|
|
|
|
out->vin_raw = adc1_read_channel(2U);
|
|
out->iin_raw = adc1_read_channel(3U);
|
|
out->vout_raw = adc1_read_channel(4U);
|
|
|
|
vin_sense_mv = adc_raw_to_mv(out->vin_raw);
|
|
iin_sense_mv = adc_raw_to_mv(out->iin_raw);
|
|
vout_sense_mv = adc_raw_to_mv(out->vout_raw);
|
|
|
|
out->vin_mv = sense_mv_to_voltage_mv(vin_sense_mv, DCDC_VIN_SCALE_PPM);
|
|
out->iin_ma = sense_mv_to_current_ma(iin_sense_mv);
|
|
out->vout_mv = sense_mv_to_voltage_mv(vout_sense_mv, DCDC_VOUT_SCALE_PPM);
|
|
}
|
|
|
|
DCDC_State DCDC_GetState(void)
|
|
{
|
|
return s_state;
|
|
}
|
|
|
|
DCDC_Fault DCDC_GetFault(void)
|
|
{
|
|
return s_fault;
|
|
}
|
|
|
|
bool DCDC_IsHrtimReady(void)
|
|
{
|
|
return s_hrtim_ready;
|
|
}
|
|
|
|
uint32_t DCDC_GetDutyTicks(void)
|
|
{
|
|
return s_duty_ticks;
|
|
}
|
|
|
|
uint32_t DCDC_GetPeriodTicks(void)
|
|
{
|
|
return s_period_ticks;
|
|
}
|
|
|
|
const char *DCDC_StateText(DCDC_State state)
|
|
{
|
|
switch (state)
|
|
{
|
|
case DCDC_STATE_STOPPED: return "stopped";
|
|
case DCDC_STATE_READY: return "ready";
|
|
case DCDC_STATE_RUNNING: return "running";
|
|
case DCDC_STATE_FAULT: return "fault";
|
|
default: return "unknown";
|
|
}
|
|
}
|
|
|
|
const char *DCDC_FaultText(DCDC_Fault fault)
|
|
{
|
|
switch (fault)
|
|
{
|
|
case DCDC_FAULT_NONE: return "none";
|
|
case DCDC_FAULT_UNDERVOLTAGE: return "vin undervoltage";
|
|
case DCDC_FAULT_OVERVOLTAGE: return "vout overvoltage";
|
|
case DCDC_FAULT_OVERCURRENT: return "input overcurrent";
|
|
case DCDC_FAULT_HRTIM: return "hrtim dll timeout";
|
|
default: return "unknown";
|
|
}
|
|
}
|
|
|
|
static void gpio_init_for_dcdc(void)
|
|
{
|
|
RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN | RCC_AHB2ENR_GPIOBEN | RCC_AHB2ENR_GPIOCEN;
|
|
(void)RCC->AHB2ENR;
|
|
|
|
/* PA1/PA2/PA3 are analog feedback signals: VIN, input current, VOUT. */
|
|
GPIOA->MODER |= (3UL << (1U * 2U)) | (3UL << (2U * 2U)) | (3UL << (3U * 2U));
|
|
GPIOA->PUPDR &= ~((3UL << (1U * 2U)) | (3UL << (2U * 2U)) | (3UL << (3U * 2U)));
|
|
|
|
/* PB12/PB13 are HRTIM1 Timer C outputs for the synchronous buck leg. */
|
|
GPIOB->MODER &= ~((3UL << (12U * 2U)) | (3UL << (13U * 2U)));
|
|
GPIOB->MODER |= ((2UL << (12U * 2U)) | (2UL << (13U * 2U)));
|
|
GPIOB->OTYPER &= ~((1UL << 12U) | (1UL << 13U));
|
|
GPIOB->OSPEEDR |= ((3UL << (12U * 2U)) | (3UL << (13U * 2U)));
|
|
GPIOB->PUPDR &= ~((3UL << (12U * 2U)) | (3UL << (13U * 2U)));
|
|
GPIOB->AFR[1] &= ~((0xFUL << ((12U - 8U) * 4U)) | (0xFUL << ((13U - 8U) * 4U)));
|
|
GPIOB->AFR[1] |= ((13UL << ((12U - 8U) * 4U)) | (13UL << ((13U - 8U) * 4U)));
|
|
|
|
/* PC3 controls USBPD_VBUS to VIN switch. PC14/PC15 switch onboard loads. */
|
|
GPIOC->MODER &= ~((3UL << (3U * 2U)) | (3UL << (14U * 2U)) | (3UL << (15U * 2U)));
|
|
GPIOC->MODER |= ((1UL << (3U * 2U)) | (1UL << (14U * 2U)) | (1UL << (15U * 2U)));
|
|
GPIOC->OTYPER &= ~((1UL << 3U) | (1UL << 14U) | (1UL << 15U));
|
|
GPIOC->OSPEEDR |= ((2UL << (3U * 2U)) | (2UL << (14U * 2U)) | (2UL << (15U * 2U)));
|
|
GPIOC->PUPDR &= ~((3UL << (3U * 2U)) | (3UL << (14U * 2U)) | (3UL << (15U * 2U)));
|
|
}
|
|
|
|
static void adc1_init(void)
|
|
{
|
|
RCC->AHB2ENR |= RCC_AHB2ENR_ADC12EN;
|
|
(void)RCC->AHB2ENR;
|
|
|
|
/* Synchronous ADC clock HCLK/4 = 42.5 MHz. */
|
|
ADC12_COMMON->CCR = (ADC12_COMMON->CCR & ~ADC_CCR_CKMODE) |
|
|
(ADC_CCR_CKMODE_1 | ADC_CCR_CKMODE_0);
|
|
|
|
ADC1->CR &= ~ADC_CR_DEEPPWD;
|
|
ADC1->CR |= ADC_CR_ADVREGEN;
|
|
Board_DelayMs(1U);
|
|
|
|
ADC1->CR |= ADC_CR_ADCAL;
|
|
while ((ADC1->CR & ADC_CR_ADCAL) != 0U)
|
|
{
|
|
__NOP();
|
|
}
|
|
|
|
ADC1->CFGR = ADC_CFGR_OVRMOD;
|
|
ADC1->SMPR1 =
|
|
(ADC_SAMPLE_TIME_47CYCLES << ADC_SMPR1_SMP2_Pos) |
|
|
(ADC_SAMPLE_TIME_47CYCLES << ADC_SMPR1_SMP3_Pos) |
|
|
(ADC_SAMPLE_TIME_47CYCLES << ADC_SMPR1_SMP4_Pos);
|
|
|
|
ADC1->ISR = ADC_ISR_ADRDY;
|
|
ADC1->CR |= ADC_CR_ADEN;
|
|
while ((ADC1->ISR & ADC_ISR_ADRDY) == 0U)
|
|
{
|
|
__NOP();
|
|
}
|
|
}
|
|
|
|
static uint16_t adc1_read_channel(uint32_t channel)
|
|
{
|
|
ADC1->SQR1 = (channel << ADC_SQR1_SQ1_Pos);
|
|
ADC1->ISR = ADC_ISR_EOC | ADC_ISR_EOS;
|
|
ADC1->CR |= ADC_CR_ADSTART;
|
|
|
|
while ((ADC1->ISR & ADC_ISR_EOC) == 0U)
|
|
{
|
|
__NOP();
|
|
}
|
|
|
|
return (uint16_t)(ADC1->DR & 0x0FFFU);
|
|
}
|
|
|
|
static bool hrtim1_timer_c_init(void)
|
|
{
|
|
HRTIM_Timerx_TypeDef *timer = &HRTIM1->sTimerxRegs[HRTIM_TIMER_C_INDEX];
|
|
|
|
RCC->APB2ENR |= RCC_APB2ENR_HRTIM1EN;
|
|
(void)RCC->APB2ENR;
|
|
|
|
HRTIM1->sCommonRegs.ODISR = HRTIM_ODISR_TC1ODIS | HRTIM_ODISR_TC2ODIS;
|
|
s_period_ticks = hrtim_period_from_clock();
|
|
|
|
HRTIM1->sCommonRegs.ICR = HRTIM_ICR_DLLRDYC;
|
|
HRTIM1->sCommonRegs.DLLCR = HRTIM_DLLCR_CALEN |
|
|
HRTIM_DLLCR_CALRTE_1 |
|
|
HRTIM_DLLCR_CAL;
|
|
/* In rescue clock mode DLLRDY may never rise; keep diagnostics alive. */
|
|
if (!hrtim1_wait_dll_ready())
|
|
{
|
|
HRTIM1->sCommonRegs.ODISR = HRTIM_ODISR_TC1ODIS | HRTIM_ODISR_TC2ODIS;
|
|
HRTIM1->sMasterRegs.MCR &= ~HRTIM_MCR_TCCEN;
|
|
return false;
|
|
}
|
|
|
|
timer->TIMxCR = HRTIM_TIMCR_CONT;
|
|
timer->PERxR = s_period_ticks;
|
|
timer->REPxR = 0U;
|
|
timer->CMP1xR = DCDC_MIN_DUTY_TICKS;
|
|
timer->CMP2xR = hrtim_max_duty_ticks();
|
|
timer->CMP3xR = 1000U;
|
|
|
|
timer->DTxR =
|
|
((DCDC_DEADTIME_RISING_TICKS & 0x1FFUL) << HRTIM_DTR_DTR_Pos) |
|
|
((DCDC_DEADTIME_FALLING_TICKS & 0x1FFUL) << HRTIM_DTR_DTF_Pos) |
|
|
(HRTIM_DTR_DTPRSC_1 | HRTIM_DTR_DTPRSC_0);
|
|
|
|
/*
|
|
* Complementary buck PWM:
|
|
* CHC1 goes active at period event and inactive at CMP1.
|
|
* CHC2 goes active at CMP1 and inactive at period event.
|
|
* HRTIM dead-time block delays transitions to avoid shoot-through.
|
|
*/
|
|
timer->SETx1R = HRTIM_SET1R_PER;
|
|
timer->RSTx1R = HRTIM_RST1R_CMP1;
|
|
timer->SETx2R = HRTIM_SET2R_CMP1;
|
|
timer->RSTx2R = HRTIM_RST2R_PER;
|
|
timer->OUTxR = HRTIM_OUTR_DTEN | HRTIM_OUTR_FAULT1_1 | HRTIM_OUTR_FAULT2_1;
|
|
|
|
/* Trigger point for future synchronized ADC sampling. */
|
|
HRTIM1->sCommonRegs.ADC1R = HRTIM_ADC1R_AD1TCC3;
|
|
|
|
HRTIM1->sMasterRegs.MCR &= ~HRTIM_MCR_TCCEN;
|
|
return true;
|
|
}
|
|
|
|
static bool hrtim1_wait_dll_ready(void)
|
|
{
|
|
uint32_t timeout = HRTIM_DLL_READY_TIMEOUT;
|
|
|
|
while (((HRTIM1->sCommonRegs.ISR & HRTIM_ISR_DLLRDY) == 0U) && (timeout > 0U))
|
|
{
|
|
timeout--;
|
|
}
|
|
|
|
return timeout > 0U;
|
|
}
|
|
|
|
static void hrtim1_outputs_enable(bool enable)
|
|
{
|
|
if (enable)
|
|
{
|
|
HRTIM1->sCommonRegs.OENR = HRTIM_OENR_TC1OEN | HRTIM_OENR_TC2OEN;
|
|
}
|
|
else
|
|
{
|
|
HRTIM1->sCommonRegs.ODISR = HRTIM_ODISR_TC1ODIS | HRTIM_ODISR_TC2ODIS;
|
|
}
|
|
}
|
|
|
|
static void hrtim1_set_duty(uint32_t duty_ticks)
|
|
{
|
|
if (duty_ticks < DCDC_MIN_DUTY_TICKS)
|
|
{
|
|
duty_ticks = DCDC_MIN_DUTY_TICKS;
|
|
}
|
|
else if (duty_ticks > hrtim_max_duty_ticks())
|
|
{
|
|
duty_ticks = hrtim_max_duty_ticks();
|
|
}
|
|
|
|
HRTIM1->sTimerxRegs[HRTIM_TIMER_C_INDEX].CMP1xR = duty_ticks;
|
|
s_duty_ticks = duty_ticks;
|
|
}
|
|
|
|
static uint32_t hrtim_period_from_clock(void)
|
|
{
|
|
uint64_t ticks = (((uint64_t)SystemCoreClock * 32ULL) + (DCDC_PWM_FREQUENCY_HZ / 2ULL)) /
|
|
DCDC_PWM_FREQUENCY_HZ;
|
|
|
|
if (ticks < 100ULL)
|
|
{
|
|
ticks = 100ULL;
|
|
}
|
|
else if (ticks > 0xFFFFULL)
|
|
{
|
|
ticks = 0xFFFFULL;
|
|
}
|
|
|
|
return (uint32_t)ticks;
|
|
}
|
|
|
|
static uint32_t hrtim_max_duty_ticks(void)
|
|
{
|
|
uint32_t max_ticks = DCDC_MAX_DUTY_TICKS;
|
|
|
|
if (s_period_ticks > 10U && max_ticks >= (s_period_ticks - 1U))
|
|
{
|
|
max_ticks = s_period_ticks - 1U;
|
|
}
|
|
|
|
return max_ticks;
|
|
}
|
|
|
|
static uint32_t adc_raw_to_mv(uint16_t raw)
|
|
{
|
|
return ((uint32_t)raw * ADC_REFERENCE_MV) / ADC_FULL_SCALE_COUNTS;
|
|
}
|
|
|
|
static uint32_t sense_mv_to_voltage_mv(uint32_t sense_mv, uint32_t scale_ppm)
|
|
{
|
|
return (uint32_t)(((uint64_t)sense_mv * 1000000ULL) / scale_ppm);
|
|
}
|
|
|
|
static uint32_t sense_mv_to_current_ma(uint32_t sense_mv)
|
|
{
|
|
return (sense_mv * 1000UL) / DCDC_IIN_UV_PER_MA;
|
|
}
|
|
|
|
static void set_usbpd_input_switch(bool enable)
|
|
{
|
|
if (enable)
|
|
{
|
|
GPIOC->BSRR = (1UL << 3U);
|
|
}
|
|
else
|
|
{
|
|
GPIOC->BRR = (1UL << 3U);
|
|
}
|
|
}
|
|
|
|
static void set_loads_off(void)
|
|
{
|
|
GPIOC->BRR = (1UL << 14U) | (1UL << 15U);
|
|
}
|
|
|
|
static void latch_fault(DCDC_Fault fault)
|
|
{
|
|
hrtim1_outputs_enable(false);
|
|
hrtim1_set_duty(DCDC_MIN_DUTY_TICKS);
|
|
HRTIM1->sMasterRegs.MCR &= ~HRTIM_MCR_TCCEN;
|
|
set_usbpd_input_switch(false);
|
|
|
|
s_fault = fault;
|
|
s_state = DCDC_STATE_FAULT;
|
|
}
|