Firework Stepper Module
TRIGGER_ISO_N, qualification only after 70 ms continuous assertion, mandatory release before another request, one-hot channels 1–18, a 100 ms software maximum, and irreversible-until-reset lockout after channel 18. The independent U4 hardware window and external ARM loop remain mandatory.TRIGGER_ISO_N signal on PC13.Table
| U1 pin | Net | Firmware use | Safe reset state |
|---|---|---|---|
| PC13 | TRIGGER_ISO_N | Active-low isolated command input from U3 | Input, no pull; R40 provides 10 kΩ pull-up |
| PA0..PA10 | CH1_CMD..CH11_CMD | Active-high channel requests 1–11 | Output low |
| PA11[PA9] | CH12_CMD | Active-high channel 12 | Output low |
| PA12[PA10] | CH13_CMD | Active-high channel 13 | Output low |
| PB0 | CH14_CMD | Active-high channel 14 | Output low |
| PB1 | CH15_CMD | Active-high channel 15 | Output low |
| PB2 | CH16_CMD | Active-high channel 16 | Output low |
| PB3 | CH17_CMD | Active-high channel 17 | Output low |
| PB4 | CH18_CMD | Active-high channel 18 | Output low |
| PB9 | FIRE_WINDOW_TRIG | Positive trigger request to U4 channel 1 | Output low |
| PB8 | GLOBAL_FIRE_ENABLE | Shared with J6 ARM loop and U5 /SHDN | Input/high-impedance; never drive high |
| PB11 | LOCKOUT_INHIBIT | High drives R48/Q19 and holds U4 MONO_RESET_N low | Output low initially; high after CH18 |
| PB10 | LOCKOUT_LED | Active-high LED3 through R47 | Output low initially |
| PA13 | SWDIO | SWD data, J7-3 | SWD alternate function |
| PA14-BOOT0 | SWCLK_BOOT0 | SWD clock, J7-4; R41 10 kΩ pulldown | SWD alternate function |
| PF2-NRST | NRST | Reset, J7-5; C10 100 nF to GND | Reset function |
| VDD/VDDA, VBAT, VREF+ | 3V3 | MCU power/reference | — |
| VSS/VSSA | GND | Ground | — |
3V3, pin 2 GND, pin 3 SWDIO, pin 4 SWCLK_BOOT0, pin 5 NRST.FIRE_WINDOW_TRIG, LOCKOUT_INHIBIT, and LOCKOUT_LED low. PB8 is high-impedance so firmware cannot bypass open J6 ARM contacts.TRIGGER_ISO_N goes low.FIRE_WINDOW_Q; J6 pin 2 is GLOBAL_FIRE_ENABLE. Firmware does not drive PB8. An open ARM loop must keep U5 disabled through R44.Core/Src/main.c with the following. It uses only generated HAL headers/startup and does not require interrupts.C
#include "main.h" #include <stdbool.h> #include <stdint.h> #define QUALIFY_MS 70u #define MAX_REQUEST_MS 100u #define MONO_TRIG_MS 1u IWDG_HandleTypeDef hiwdg; typedef struct { GPIO_TypeDef *port; uint16_t pin; } gpio_ref_t; static const gpio_ref_t channels[18] = { {GPIOA, GPIO_PIN_0}, {GPIOA, GPIO_PIN_1}, {GPIOA, GPIO_PIN_2}, {GPIOA, GPIO_PIN_3}, {GPIOA, GPIO_PIN_4}, {GPIOA, GPIO_PIN_5}, {GPIOA, GPIO_PIN_6}, {GPIOA, GPIO_PIN_7}, {GPIOA, GPIO_PIN_8}, {GPIOA, GPIO_PIN_9}, {GPIOA, GPIO_PIN_10}, {GPIOA, GPIO_PIN_11}, {GPIOA, GPIO_PIN_12}, {GPIOB, GPIO_PIN_0}, {GPIOB, GPIO_PIN_1}, {GPIOB, GPIO_PIN_2}, {GPIOB, GPIO_PIN_3}, {GPIOB, GPIO_PIN_4} }; static uint8_t next_channel = 0u; static bool locked_out = false; static void SystemClock_Config(void); static void MX_GPIO_Init_FailSafe(void); static void MX_IWDG_Init(void); static void Error_Handler(void); static void all_channels_off(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4, GPIO_PIN_RESET); } static void enter_lockout(void) { all_channels_off(); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET); /* FIRE_WINDOW_TRIG */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_SET); /* inhibit U4 via Q19 */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET); /* lockout LED */ locked_out = true; } static void trigger_hardware_window(void) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET); HAL_Delay(1u); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET); HAL_Delay(MONO_TRIG_MS); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET); } static void fire_one_channel(uint8_t index) { uint32_t started; if (index >= 18u || locked_out) { enter_lockout(); return; } all_channels_off(); HAL_GPIO_WritePin(channels[index].port, channels[index].pin, GPIO_PIN_SET); trigger_hardware_window(); started = HAL_GetTick(); while ((uint32_t)(HAL_GetTick() - started) < MAX_REQUEST_MS) { /* Request remains bounded even if TRIGGER_ISO_N stays asserted. */ HAL_IWDG_Refresh(&hiwdg); /* Remove only if IWDG is not enabled/generated. */ } all_channels_off(); next_channel++; if (next_channel >= 18u) { enter_lockout(); } } int main(void) { bool measuring = false; bool accepted_this_assertion = false; uint32_t low_since = 0u; HAL_Init(); SystemClock_Config(); MX_GPIO_Init_FailSafe(); MX_IWDG_Init(); for (;;) { const bool asserted = (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_RESET); if (locked_out) { all_channels_off(); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET); HAL_IWDG_Refresh(&hiwdg); continue; } if (asserted) { if (!measuring) { measuring = true; accepted_this_assertion = false; low_since = HAL_GetTick(); } if (!accepted_this_assertion && (uint32_t)(HAL_GetTick() - low_since) >= QUALIFY_MS) { accepted_this_assertion = true; fire_one_channel(next_channel); } } else { /* Release is mandatory before another qualification interval. */ measuring = false; accepted_this_assertion = false; } HAL_IWDG_Refresh(&hiwdg); } } static void MX_GPIO_Init_FailSafe(void) { GPIO_InitTypeDef g = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); /* Preload every safety-critical output LOW before changing its mode. */ all_channels_off(); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11, GPIO_PIN_RESET); g.Mode = GPIO_MODE_OUTPUT_PP; g.Pull = GPIO_NOPULL; g.Speed = GPIO_SPEED_FREQ_LOW; g.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12; HAL_GPIO_Init(GPIOA, &g); g.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11; HAL_GPIO_Init(GPIOB, &g); /* PB8 is wired to GLOBAL_FIRE_ENABLE: high impedance prevents ARM bypass. */ g.Pin = GPIO_PIN_8; g.Mode = GPIO_MODE_INPUT; g.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOB, &g); g.Pin = GPIO_PIN_13; g.Mode = GPIO_MODE_INPUT; g.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOC, &g); } static void SystemClock_Config(void) { RCC_OscInitTypeDef osc = {0}; RCC_ClkInitTypeDef clk = {0}; osc.OscillatorType = RCC_OSCILLATORTYPE_HSI; osc.HSIState = RCC_HSI_ON; osc.HSIDiv = RCC_HSI_DIV1; osc.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; if (HAL_RCC_OscConfig(&osc) != HAL_OK) Error_Handler(); clk.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1; clk.SYSCLKSource = RCC_SYSCLKSOURCE_HSI; clk.AHBCLKDivider = RCC_SYSCLK_DIV1; clk.APB1CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&clk, FLASH_LATENCY_0) != HAL_OK) Error_Handler(); } static void MX_IWDG_Init(void) { /* About 500 ms nominal with a 32 kHz LSI: (250 + 1) * 64 / 32000. */ hiwdg.Instance = IWDG; hiwdg.Init.Prescaler = IWDG_PRESCALER_64; hiwdg.Init.Window = IWDG_WINDOW_DISABLE; hiwdg.Init.Reload = 250u; if (HAL_IWDG_Init(&hiwdg) != HAL_OK) Error_Handler(); } static void Error_Handler(void) { __disable_irq(); all_channels_off(); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_SET); while (1) { } }
hiwdg directly, with a nominal ~500 ms timeout based on the typical 32 kHz LSI. Measure the actual timeout because LSI tolerance is significant. Configure the option bytes for hardware/free-running IWDG so application code cannot disable it. If watchdog use is intentionally deferred, remove the three HAL_IWDG_Refresh() calls and MX_IWDG_Init() only for bench development, never for field firmware.Error_Handler(), turns channels off, asserts hardware inhibit, and waits for watchdog reset.GLOBAL_FIRE_ENABLE with U5 /SHDN.STM32G031C8Tx, HAL, HSI clock, Serial Wire debug.make -j when a Makefile toolchain was selected.STM32_Programmer_CLI -c port=SWD mode=UR -w build/<img>.elf -v -rst.CHx_CMD, PB9, PB10, and PB11 state on a logic analyzer and prove PB8 remains high-impedance.TRIGGER_ISO_N. Never fit multiple selector jumpers.Safety scope
Live-schematic pin map
Required behavior
Compilable STM32 HAL control logic
Watchdog, brownout, and startup guidance
Build, flash, and SWD
Required firmware tests