Pin Allocation Table
| Pin | Peripheral / Function | Target Signal | Description |
|---|---|---|---|
| PD5 | USART1_TX |
Host Link | Telemetry transmission to host |
| PD6 | USART1_RX |
Host Link | Command and register requests from host |
| PC1 | I2C1_SDA |
Power Sensor | Bidirectional data to INA219/INA226 |
| PC2 | I2C1_SCL |
Power Sensor | Clock line for telemetry acquisition |
| PC3 | GPIO_Out |
VDD_CORE_EN |
Stage 1 Power Rail Gate |
| PC4 | GPIO_Out |
VDD_MEM_EN |
Stage 2 Power Rail Gate |
| PC0 | GPIO_Out |
PWR_GOOD |
Asserted when all rails stabilize |
| PD4 | GPIO_Out |
PROCHOT |
Active-high emergency shutdown interrupt |
2. Low-Level Firmware Implementation
To execute within 2 KB SRAM, the firmware uses:
-
Zero-Heap Allocation: All buffers and state machine contexts are allocated statically in the
.bsssegment. - Deterministic State Machine: Non-blocking transitions stepped by a 1 ms hardware SysTick timer.
- Q15 Fixed-Point Mathematics: Power calculation and Exponential Moving Average (EMA) smoothing executed entirely with integer shifts.
File: main.c
c
/**
* Mini-PMU: Accelerator Power Management & Telemetry Coprocessor
* Target: VSDSquadron Mini (WCH CH32V003F4U6 - RV32EC)
*/
#include "ch32v00x.h"
#include <stdint.h>
#include <stdbool.h>
#define INA219_ADDR 0x40
#define INA219_REG_BUS_VOLTAGE 0x02
#define INA219_REG_CURRENT 0x04
#define CURRENT_CRITICAL_MA 4200 // 4.2A Trip Threshold
#define VOLTAGE_MIN_MV 4500 // 4.5V Under-Voltage Lockout (UVLO)
#define VOLTAGE_MAX_MV 13200 // 13.2V Over-Voltage Lockout (OVLO)
#define MAX_REGS 8
typedef enum {
STATE_STANDBY = 0,
STATE_RAMP_CORE,
STATE_RAMP_MEM,
STATE_POWER_GOOD,
STATE_FAULT_TRIP
} pmu_state_t;
// Telemetry state structure (Static allocation)
typedef struct {
uint16_t bus_voltage_mv;
uint16_t current_ma;
uint32_t filtered_power_mw;
uint8_t temperature_c;
pmu_state_t state;
uint32_t uptime_ms;
} pmu_telemetry_t;
static volatile pmu_telemetry_t g_telemetry = {
.bus_voltage_mv = 0,
.current_ma = 0,
.filtered_power_mw = 0,
.temperature_c = 38,
.state = STATE_STANDBY,
.uptime_ms = 0
};
static volatile uint32_t g_state_timer = 0;
static volatile bool g_host_pwr_enable = true;
/* Peripheral Prototypes */
void GPIO_Init_PMU(void);
void USART1_Init_PMU(void);
void I2C1_Init_PMU(void);
void SysTick_Init_PMU(void);
uint16_t INA219_ReadRegister(uint8_t reg);
void UART_TransmitByte(uint8_t data);
void Process_Mailbox(uint8_t reg_addr);
/* 1ms SysTick Interrupt Handler */
void SysTick_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void SysTick_Handler(void) {
SysTick->SR = 0;
g_telemetry.uptime_ms++;
g_state_timer++;
}
int main(void) {
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
SystemCoreClockUpdate();
GPIO_Init_PMU();
USART1_Init_PMU();
I2C1_Init_PMU();
SysTick_Init_PMU();
while (1) {
// 1. Ingest Raw Sensor Data (Simulated or via INA219)
uint16_t raw_v = INA219_ReadRegister(INA219_REG_BUS_VOLTAGE);
uint16_t raw_i = INA219_ReadRegister(INA219_REG_CURRENT);
// Scaling conversions
g_telemetry.bus_voltage_mv = (raw_v >> 3) * 4;
g_telemetry.current_ma = raw_i;
// 2. Fixed-Point Exponential Moving Average (EMA) Power Filter
// Alpha = 1/8 -> S[k] = S[k-1] + ((X[k] - S[k-1]) >> 3)
uint32_t inst_power_mw = ((uint32_t)g_telemetry.bus_voltage_mv *
(uint32_t)g_telemetry.current_ma) / 1000;
if (g_telemetry.filtered_power_mw == 0) {
g_telemetry.filtered_power_mw = inst_power_mw;
} else {
int32_t delta = (int32_t)inst_power_mw - (int32_t)g_telemetry.filtered_power_mw;
g_telemetry.filtered_power_mw += (delta >> 3);
}
// 3. Deterministic Power Rail Sequencing FSM
switch (g_telemetry.state) {
case STATE_STANDBY:
if (g_host_pwr_enable) {
// Turn on VDD_CORE (PC3 HIGH)
GPIOC->BSHR = GPIO_Pin_3;
g_state_timer = 0;
g_telemetry.state = STATE_RAMP_CORE;
}
break;
case STATE_RAMP_CORE:
// Delay 5ms for in-rush charge settling
if (g_state_timer >= 5) {
// Turn on VDD_MEM (PC4 HIGH)
GPIOC->BSHR = GPIO_Pin_4;
g_state_timer = 0;
g_telemetry.state = STATE_RAMP_MEM;
}
break;
case STATE_RAMP_MEM:
// Delay 5ms before asserting Power Good
if (g_state_timer >= 5) {
// Assert PWR_GOOD (PC0 HIGH)
GPIOC->BSHR = GPIO_Pin_0;
g_telemetry.state = STATE_POWER_GOOD;
}
break;
case STATE_POWER_GOOD:
// Hardware Fault Checking (< 10us tripping path)
if (g_telemetry.current_ma > CURRENT_CRITICAL_MA ||
g_telemetry.bus_voltage_mv > VOLTAGE_MAX_MV ||
g_telemetry.bus_voltage_mv < VOLTAGE_MIN_MV) {
// Assert PROCHOT instantly (PD4 HIGH)
GPIOD->BSHR = GPIO_Pin_4;
// Drop all downstream rails
GPIOC->BCR = GPIO_Pin_0 | GPIO_Pin_3 | GPIO_Pin_4;
g_telemetry.state = STATE_FAULT_TRIP;
}
break;
case STATE_FAULT_TRIP:
// Latch fault state until power cycle
break;
}
// 4. Host Mailbox Command Ingestion
if (USART1->STATR & USART_STATR_RXNE) {
uint8_t cmd_reg = (uint8_t)(USART1->DATAR & 0xFF);
Process_Mailbox(cmd_reg);
}
}
}
/* Mailbox Register Interface */
void Process_Mailbox(uint8_t reg_addr) {
uint16_t response = 0xFFFF;
switch (reg_addr) {
case 0x01: // Bus Voltage (mV)
response = g_telemetry.bus_voltage_mv;
break;
case 0x02: // Current (mA)
response = g_telemetry.current_ma;
break;
case 0x03: // Filtered Power (mW)
response = (uint16_t)g_telemetry.filtered_power_mw;
break;
case 0x04: // Junction Temperature (C)
response = (uint16_t)g_telemetry.temperature_c;
break;
case 0x05: // PMU State
response = (uint16_t)g_telemetry.state;
break;
case 0xAA: // Reset Fault Command
GPIOD->BCR = GPIO_Pin_4; // Clear PROCHOT
g_telemetry.state = STATE_STANDBY;
response = 0x0000;
break;
default:
response = 0xE001; // Invalid Register Error
break;
}
// Write back 2-byte response (Big Endian)
UART_TransmitByte((uint8_t)(response >> 8));
UART_TransmitByte((uint8_t)(response & 0xFF));
}
void GPIO_Init_PMU(void) {
RCC->APB2PCENR |= RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD;
// Outputs: PC0 (PWR_GOOD), PC3 (CORE_EN), PC4 (MEM_EN) - Push-Pull 10MHz
GPIOC->CFGLR &= ~((0xF << (0 * 4)) | (0xF << (3 * 4)) | (0xF << (4 * 4)));
GPIOC->CFGLR |= ((0x1 << (0 * 4)) | (0x1 << (3 * 4)) | (0x1 << (4 * 4)));
// Output: PD4 (PROCHOT) - Fast Push-Pull 50MHz
GPIOD->CFGLR &= ~(0xF << (4 * 4));
GPIOD->CFGLR |= (0x3 << (4 * 4));
// Ensure all power rails start LOW
GPIOC->BCR = GPIO_Pin_0 | GPIO_Pin_3 | GPIO_Pin_4;
GPIOD->BCR = GPIO_Pin_4;
}
void USART1_Init_PMU(void) {
RCC->APB2PCENR |= RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOD;
// PD5: TX (Multiplexed Push-Pull)
GPIOD->CFGLR &= ~(0xF << (5 * 4));
GPIOD->CFGLR |= (0xB << (5 * 4));
// PD6: RX (Floating Input)
GPIOD->CFGLR &= ~(0xF << (6 * 4));
GPIOD->CFGLR |= (0x4 << (6 * 4));
// 115200 Baud @ 24MHz PCLK2
USART1->BRR = 0x00D0;
USART1->CTLR1 = USART_CTLR1_TE | USART_CTLR1_RE | USART_CTLR1_UE;
}
void UART_TransmitByte(uint8_t data) {
while (!(USART1->STATR & USART_STATR_TXE));
USART1->DATAR = data;
}
void I2C1_Init_PMU(void) {
RCC->APB2PCENR |= RCC_APB2Periph_GPIOC;
RCC->APB1PCENR |= RCC_APB1Periph_I2C1;
// PC1: SDA, PC2: SCL (Alternate Function Open-Drain)
GPIOC->CFGLR &= ~((0xF << (1 * 4)) | (0xF << (2 * 4)));
GPIOC->CFGLR |= ((0xF << (1 * 4)) | (0xF << (2 * 4)));
I2C1->CTLR2 = 24; // 24MHz APB1 Clock
I2C1->CKCFGR = 120; // 100 kHz Standard Mode
I2C1->CTLR1 |= I2C_CTLR1_PE; // Enable I2C1
}
uint16_t INA219_ReadRegister(uint8_t reg) {
// Non-blocking fallback stub for standalone compilation
// In live hardware, execute standard I2C start -> addr -> write reg -> restart -> read 2 bytes
(void)reg;
return 1200; // Simulated nominal 1.2V / 1.2A
}
void SysTick_Init_PMU(void) {
SysTick->SR = 0;
SysTick->CNT = 0;
SysTick->CMP = SystemCoreClock / 1000 - 1; // 1 ms interval
SysTick->CTLR = 0xF; // Enable SysTick with Interrupt
NVIC_EnableIRQ(SysTick_IRQn);
}
Top comments (0)