Firework Stepper Module

Yes—the right-side RJ45 can be a direct 1:1 parallel pass-through of the COBRA input, while the selector only taps the six channel returns. I’ll add and verify that connector, then size a practical board and place the input on the left, pass-through on the right, three outputs along the bottom, and four corner mounting holes.
The pass-through and full board placement are complete. I’m doing the final placement review now; routing remains intentionally untouched for your approval checkpoint.
PCB placement is complete and ready for review:
  • 120 × 100 mm, four-layer board
  • COBRA input on left; direct 1:1 pass-through on right
  • Three firing-output RJ45s across the bottom
  • Battery connector at the top
  • Four 3.2 mm corner mounting holes
  • All components placed with no unintended overlaps, out-of-board parts, missing footprints, or ERC errors
  • Routing has not started; 248 airwires remain as expected
Please review the placement. Once approved, I’ll configure high-current routing rules, planes, and route the board.
Approve and route PCB
Approve placement
Route power first
Review high-current paths
Check 3D placement

Refine this doc
Ask about this doc
Firmware Starter
Safety scope
Starter firmware for U1 STM32G031C8T6 using STM32CubeG0 HAL. This code is a prototype baseline, not a certification artifact. It implements fail-safe GPIO startup, active-low pulse measurement on 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.
Never test initial firmware with live initiators. Use dummy resistive loads only.
Hardware input pinout: J8 RJ45 pins 7 and 8 are the fixed paralleled positive input. Pins 1–6 are return candidates selected by J9 positions 1–6; fit exactly one jumper. This selection is passive hardware configuration and does not change the firmware-visible active-low TRIGGER_ISO_N signal on PC13.
Live-schematic pin map

Table


U1 pinNetFirmware useSafe reset state
PC13TRIGGER_ISO_NActive-low isolated command input from U3Input, no pull; R40 provides 10 kΩ pull-up
PA0..PA10CH1_CMD..CH11_CMDActive-high channel requests 1–11Output low
PA11[PA9]CH12_CMDActive-high channel 12Output low
PA12[PA10]CH13_CMDActive-high channel 13Output low
PB0CH14_CMDActive-high channel 14Output low
PB1CH15_CMDActive-high channel 15Output low
PB2CH16_CMDActive-high channel 16Output low
PB3CH17_CMDActive-high channel 17Output low
PB4CH18_CMDActive-high channel 18Output low
PB9FIRE_WINDOW_TRIGPositive trigger request to U4 channel 1Output low
PB8GLOBAL_FIRE_ENABLEShared with J6 ARM loop and U5 /SHDNInput/high-impedance; never drive high
PB11LOCKOUT_INHIBITHigh drives R48/Q19 and holds U4 MONO_RESET_N lowOutput low initially; high after CH18
PB10LOCKOUT_LEDActive-high LED3 through R47Output low initially
PA13SWDIOSWD data, J7-3SWD alternate function
PA14-BOOT0SWCLK_BOOT0SWD clock, J7-4; R41 10 kΩ pulldownSWD alternate function
PF2-NRSTNRSTReset, J7-5; C10 100 nF to GNDReset function
VDD/VDDA, VBAT, VREF+3V3MCU power/reference
VSS/VSSAGNDGround
J7 SWD header: pin 1 3V3, pin 2 GND, pin 3 SWDIO, pin 4 SWCLK_BOOT0, pin 5 NRST.
Required behavior
State machine:
  1. Boot with all channel commands, FIRE_WINDOW_TRIG, LOCKOUT_INHIBIT, and LOCKOUT_LED low. PB8 is high-impedance so firmware cannot bypass open J6 ARM contacts.
  2. A request begins when TRIGGER_ISO_N goes low.
  3. A low interval shorter than 70 ms is rejected without advancing.
  4. At 70 ms continuous low, select exactly one channel, issue a short rising trigger on PB9, and hold that one channel high for no more than 100 ms.
  5. Deassert the channel after 100 ms even if the input remains low. Require the input to return high before accepting another request.
  6. After channel 18 completes, assert PB11 and PB10 and remain locked out until reset/power cycle.
  7. ARM is hardware-controlled: J6 pin 1 is 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.
Compilable STM32 HAL control logic
Create an STM32CubeIDE project for STM32G031C8Tx with HAL enabled. Use the default internal HSI system clock and replace 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) { }
}
The example defines and initializes 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.
Watchdog, brownout, and startup guidance
  • Program option bytes for an enabled BOR threshold appropriate to reliable 3.3 V operation; validate with slow ramps and fast interruptions across the 15–21 V battery range.
  • Prefer hardware-start IWDG option bytes so application code cannot disable the watchdog.
  • On every reset cause, initialize all outputs low before any diagnostic or delay. Do not restore a previous channel from nonvolatile memory in this prototype; reset starts at channel 1.
  • Any clock, assertion, or invariant failure calls Error_Handler(), turns channels off, asserts hardware inhibit, and waits for watchdog reset.
  • Retain external 100 kΩ MOSFET gate pulldowns R20–R37; firmware is not a substitute.
  • PB8 must remain input/high-impedance. Driving it high can bypass an open J6 ARM loop because it shares GLOBAL_FIRE_ENABLE with U5 /SHDN.
Build, flash, and SWD
  1. Install STM32CubeIDE with STM32CubeG0 HAL package and ST-LINK drivers.
  2. Create project for STM32G031C8Tx, HAL, HSI clock, Serial Wire debug.
  3. Enable IWDG and configure the project as described above.
  4. Build: CubeIDE Project → Build All, or from the generated project directory use make -j when a Makefile toolchain was selected.
  5. With the firing battery removed, connect ST-LINK to J7: 1=VTref 3.3 V, 2=GND, 3=SWDIO, 4=SWCLK, 5=NRST.
  6. Flash using CubeIDE Run/Debug, or STM32_Programmer_CLI -c port=SWD mode=UR -w build/<img>.elf -v -rst.
  7. If connection is difficult, use connect-under-reset. Never apply 5 V to J7 pin 1.
  8. Before attaching firing power, verify every CHx_CMD, PB9, PB10, and PB11 state on a logic analyzer and prove PB8 remains high-impedance.
Required firmware tests
  • Repeat input qualification for each J9 position 1–6 using exactly one jumper at a time; the corresponding J8 pin must be the only return that asserts TRIGGER_ISO_N. Never fit multiple selector jumpers.
  • 0–69 ms low pulses: no channel output and no sequence advance.
  • 70–100 ms low pulse: exactly one channel request; repeat only after a high release interval.
  • Input held low: one request only, bounded to 100 ms.
  • Two valid pulses separated by release: CH1 then CH2, never overlap.
  • Eighteen valid pulses: CH1 through CH18; PB11/PB10 then remain high and later pulses do nothing.
  • Reset/brownout/watchdog at every point: all channels low; sequence returns to CH1 only after clean reboot.
  • Open J6 ARM loop: no firing-rail enable even when a qualified request occurs.
Release gate: field firmware is not approved until the 70 ms threshold, 100 ms software bound, U4 hardware bound, ARM non-bypass, reset/brownout behavior, and all 18 one-hot outputs pass instrumented prototype tests.
  • 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

18-Channel Firework Stepper

18-Channel Firework Stepper thumbnail
Battery-powered 18-channel sequential pyrotechnic initiator controller triggered by qualified 18 V COBRA pulses, with three six-channel RJ45 outputs and lockout after channel 18.

Properties

Properties describe core aspects of the project.

Pricing & Availability

Distributor

Qty 1

Arrow

$27.46–$33.85

Digi-Key

$0.27

HQonline

$0.90

LCSC

$2.74–$2.79

Mouser

$31.29

TME

$21.84

Verical

$12.31–$21.40

Controls