Hardware Prototype Planning


Confirmed. This manufacturer-specified stackup is authoritative and supersedes the earlier standard-1-oz assumption:

Table


LayerFunctionCopper
TopSignal/components0.070 mm / 2 oz
Mid-Layer 1Solid GND plane0.035 mm / 1 oz
Mid-Layer 2Power plane0.035 mm / 1 oz
BottomSignal0.070 mm / 2 oz
I’ll use this for power-trace sizing, thermal calculations, pours, via-current capacity, and manufacturing checks. The board remains top-side assembly only; the bottom layer may still carry routed copper.
For Ethernet and USB pair geometry, copper thickness alone is insufficient—the manufacturer’s dielectric thicknesses and finished board thickness are also needed for an accurate impedance estimate. Since paid impedance control is intentionally omitted, routing will prioritize tight coupling, symmetry, continuous L2 ground reference, and minimal pair skew.
Top Copper 0.070mm 2oz Mid-Layer 1 Copper 0.035mm 1oz (Ground Plane) Mid-Layer 2 Copper 0.035mm 1oz (Power Plane) Bottom Copper 0.070mm 2oz name="custom-4-layer"
Confirmed. The authoritative PCB stackup is:
custom-4-layer

Table


LayerRoleCopper thicknessWeight
TopSignal0.070 mm2 oz
Mid-Layer 1Ground plane0.035 mm1 oz
Mid-Layer 2Power plane0.035 mm1 oz
BottomSignal0.070 mm2 oz
Top-side-only assembly remains required; bottom-side copper routing is allowed.
Agent mode is needed to apply this stackup to the PCB and recalculate copper-dependent rules. Dielectric thicknesses can be added later if you obtain them; they are needed for an impedance estimate but do not block basic stackup configuration.
Continue in Agent Mode

Firmware Starter
Scope and toolchains
This starter is grounded in the final persisted schematic for both controllers.
  • U5 ESP32-S3-WROOM-1U-N16R8: ESP-IDF 5.x, target esp32s3.
  • U4 ATMEGA1284P-AU: avr-gcc/avr-libc, 16 MHz external crystal.
  • Product-specific command framing, authentication, telemetry schemas, serial selection policy, and PSE operating policy are explicitly marked application TODOs.
U5 ESP32-S3 resource map

Table


Module pinNetDirection at U5Peripheral / roleInterruptBoot / strap note
ENESP_ENInput, active highModule enable; J7 pin 9NoExternal 10 kΩ pull-up; service may pull low
IO0ESP_GPIO0_BOOTInput during reset; GPIO laterJ7 pin 10 download-boot controlGPIO interrupt capable after bootStrap: internal weak pull-up; ground only during reset for download mode
IO5AVR_SPI_SCKInputESP-side SPI slave clock from U4SPI peripheralNot a strap
IO6AVR_SPI_MOSIInputESP-side SPI slave MOSI from U4SPI peripheralNot a strap
IO7AVR_SPI_MISOOutput only while selectedESP-side SPI slave MISO to U4SPI peripheralMust tri-state when IO8/CS is inactive
IO8AVR_SPI_SSInputESP-side SPI slave chip select from U4GPIO/SPINot a strap
IO9AVR_RESETOutput, active lowU5 owns U4 reset; J7 pin 4 also exposes resetGPIODrive low only intentionally
IO10ETH_SPI_SCKOutputShared W5500 SPI clockSPI masterShared by U7 and U7B
IO11ETH_SPI_MOSIOutputShared W5500 SPI MOSISPI masterShared by U7 and U7B
IO12ETH_SPI_MISOInputShared W5500 SPI MISOSPI masterShared by U7 and U7B
IO13ETH_CS_LAN_NOutput, active lowU7 LAN W5500 chip selectGPIODefault high before SPI transactions
IO14ETH_CS_DISPLAY_NOutput, active lowU7B display W5500 chip selectGPIODefault high before SPI transactions
IO15ETH_INT_LAN_NInput, active lowU7 interruptGPIO interrupt capableNot a strap
IO16ETH_INT_DISPLAY_NInput, active lowU7B interruptGPIO interrupt capableNot a strap
IO17ETH_RST_LAN_NOutput, active lowU7 resetGPIOHold low during rail startup
IO18ETH_RST_DISPLAY_NOutput, active lowU7B resetGPIOHold low during rail startup
IO19USB_DN_MCUBidirectional analog USBNative USB D− through U6 ESD arrayUSB peripheralNot general GPIO while USB is enabled
IO20USB_DP_MCUBidirectional analog USBNative USB D+ through U6 ESD arrayUSB peripheralNot general GPIO while USB is enabled
IO35PSE_I2C_SCLOpen-drain bidirectionalTPS23861 SCLI2CDedicated PSE bus
IO36PSE_I2C_SDAOpen-drain bidirectionalTPS23861 SDAI/SDAOI2CDedicated PSE bus
IO37PSE_INT_NInput, active lowTPS23861 interruptGPIO interrupt capableUse pull-up if required by final firmware/bench behavior
IO38PSE_RESET_NOutput, active lowTPS23861 resetGPIOR76 10 kΩ pull-down holds PSE reset until firmware releases it
IO39PSE_SHUTDOWN_NOutput, active lowTPS23861 shutdownGPIOInitialize low before releasing PSE reset
TXD0ESP_UART0_TXOutputJ7 pin 7 service/program UARTUART0ROM log/programming path
RXD0ESP_UART0_RXInputJ7 pin 8 service/program UARTUART0ROM programming path
3V3/GND+3V3/GNDPowerModule supply/returnAll module grounds connected
Unused ESP32 GPIOs remain unused/no-connect: IO1, IO2, IO3, IO4, IO21, IO40, IO41, IO42, IO45, IO46, IO47, IO48. The unused strap pins IO3, IO45, and IO46 are not loaded by external functions. No duplicate GPIO assignments were found.
U4 ATmega1284P resource map

Table


PinNetDirectionPeripheral / roleInterrupt capabilityOwnership note
PB3FRAM_CS_NOutput, active lowMB85RS256B chip selectPCINT11U4 owns FRAM
PB4AVR_SPI_SSOutput, active lowSelects U5 SPI-slave linkPCINT12Keep configured output so U4 remains SPI master
PB5AVR_SPI_MOSIOutputShared U4-master MOSI to FRAM and U5PCINT13J7 pin 2 tap
PB6AVR_SPI_MISOInputShared MISO from selected FRAM or U5PCINT14J7 pin 1 tap; only selected slave may drive
PB7AVR_SPI_SCKOutputShared U4-master clock to FRAM and U5PCINT15J7 pin 3 tap
PC0AVR_I2C_SCLOpen-drain bidirectionalDS3231 and ATECC608B SCLPCINT164.7 kΩ pull-up to +5 V
PC1AVR_I2C_SDAOpen-drain bidirectionalDS3231 and ATECC608B SDAPCINT174.7 kΩ pull-up to +5 V
PD0MCU_UART0_RXInputUSART0 receive from selected isolated serial receiverPCINT24 / USART RX IRQTiming-critical machine serial owned by U4
PD1MCU_UART0_TXOutputUSART0 transmit to isolated serial channelsPCINT25 / USART TX IRQConfigured for 9-bit serial where required
PD2SER_SEL_SAS_NOutput, active lowEnables SAS receive selectionINT0 / PCINT26 available but pin used as outputNot used as USART1 RX
PD3SER_SEL_COM1_NOutput, active lowEnables COM1 receive selectionINT1 / PCINT27 available but pin used as outputAlso controls Q3 gate path
PD4SER_SEL_COM2_NOutput, active lowEnables COM2 receive selectionPCINT28Also controls Q4 gate path
PA0PULSE_CREDIT_INInputIsolated credit pulsePCINT0Timing-critical machine input
PA1PULSE_COIN_INInputIsolated coin pulsePCINT1Timing-critical machine input
PA2BYPASS_INInputIsolated bypass statePCINT2Machine input
PA3TAMPER_INInputIsolated tamper statePCINT3Machine input
PA4CREDIT_RETURN_DRVOutputCredit-return optocoupler drivePCINT4U4 owns machine output timing
PA5CREDIT_INJECT_DRVOutputCredit-inject optocoupler drivePCINT5U4 owns machine output timing
PA6ERROR_LAMP_DRVOutputError-lamp optocoupler drivePCINT6U4 owns machine output timing
PA7SYSTEM_ENABLE_DRVOutputSystem-enable optocoupler drivePCINT7Default inactive at reset
RESETAVR_RESETInput, active lowReset from U5 IO9 and J7 pin 4Reset vectorU5 owns remote reset; service header can also assert
XTAL1/XTAL2AVR_XTAL1/2Clock16 MHz crystal and 18 pF C0G loadsExternal clock source
AREFAVR_AREFAnalog reference100 nF to GNDNo external driven reference
VCC/AVCC/GND+5V/GNDPowerMCU supplies and returnsEach supply pin locally bypassed
Unused U4 GPIOs remain unused/no-connect: PB0, PB1, PB2, PC2–PC7, PD5–PD7. There is no dedicated READY wire between U4 and U5; readiness/health is an application-protocol state over the SPI link. No peripheral collision was found. U4 retains all pulse, selection, 9-bit serial, and machine-output timing ownership.
J7 validated service map

Table


PinSignalPurpose
1AVR_SPI_MISOAVR ISP/debug data out
2AVR_SPI_MOSIAVR ISP/debug data in
3AVR_SPI_SCKAVR ISP clock
4AVR_RESETAVR programming/reset
5+5VAVR-side service reference/power
6GNDAVR-side return
7ESP_UART0_TXESP ROM/service UART TX
8ESP_UART0_RXESP ROM/service UART RX
9ESP_ENESP enable/reset control
10ESP_GPIO0_BOOTESP download-boot strap
11+5VService power exposure; do not connect to ESP 3.3 V signals
12+3V3ESP logic reference
13GNDReturn
14GNDReturn
15–18NCReserved; do not populate undocumented functions
19–20GNDReturn/shielding contacts
J7 is retained because it cleanly combines a conventional AVR ISP group and an ESP32 UART/download group. A keyed cable or labeled fixture is required to prevent +5 V/+3.3 V misuse.
ESP-IDF starter (main/main.c)

C


#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/i2c_master.h"
#include "driver/spi_master.h"
#include "driver/spi_slave.h"
#include "esp_check.h"
#include "esp_log.h"

#define ETH_SCLK 10
#define ETH_MOSI 11
#define ETH_MISO 12
#define ETH_CS_LAN 13
#define ETH_CS_DISPLAY 14
#define ETH_INT_LAN 15
#define ETH_INT_DISPLAY 16
#define ETH_RST_LAN 17
#define ETH_RST_DISPLAY 18
#define AVR_SPI_SCLK 5
#define AVR_SPI_MOSI 6
#define AVR_SPI_MISO 7
#define AVR_SPI_CS 8
#define AVR_RESET 9
#define PSE_SCL 35
#define PSE_SDA 36
#define PSE_INT 37
#define PSE_RESET 38
#define PSE_SHUTDOWN 39

static const char *TAG = "intercede";
static i2c_master_bus_handle_t pse_i2c;
static spi_device_handle_t w5500_lan, w5500_display;

static void gpio_safe_defaults(void) {
    gpio_config_t out = {.pin_bit_mask = (1ULL<<ETH_CS_LAN)|(1ULL<<ETH_CS_DISPLAY)|
        (1ULL<<ETH_RST_LAN)|(1ULL<<ETH_RST_DISPLAY)|(1ULL<<AVR_RESET)|
        (1ULL<<PSE_RESET)|(1ULL<<PSE_SHUTDOWN), .mode = GPIO_MODE_OUTPUT};
    ESP_ERROR_CHECK(gpio_config(&out));
    gpio_set_level(ETH_CS_LAN, 1); gpio_set_level(ETH_CS_DISPLAY, 1);
    gpio_set_level(ETH_RST_LAN, 0); gpio_set_level(ETH_RST_DISPLAY, 0);
    gpio_set_level(AVR_RESET, 0);
    gpio_set_level(PSE_SHUTDOWN, 0); gpio_set_level(PSE_RESET, 0);
    gpio_config_t in = {.pin_bit_mask=(1ULL<<ETH_INT_LAN)|(1ULL<<ETH_INT_DISPLAY)|(1ULL<<PSE_INT),
        .mode=GPIO_MODE_INPUT, .pull_up_en=GPIO_PULLUP_ENABLE};
    ESP_ERROR_CHECK(gpio_config(&in));
}

static void init_pse_i2c(void) {
    i2c_master_bus_config_t cfg = {.i2c_port=I2C_NUM_0, .sda_io_num=PSE_SDA,
        .scl_io_num=PSE_SCL, .clk_source=I2C_CLK_SRC_DEFAULT, .glitch_ignore_cnt=7,
        .flags.enable_internal_pullup=true};
    ESP_ERROR_CHECK(i2c_new_master_bus(&cfg, &pse_i2c));
}

static void init_w5500_spi(void) {
    spi_bus_config_t bus = {.sclk_io_num=ETH_SCLK,.mosi_io_num=ETH_MOSI,.miso_io_num=ETH_MISO,
        .quadwp_io_num=-1,.quadhd_io_num=-1,.max_transfer_sz=2048};
    ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &bus, SPI_DMA_CH_AUTO));
    spi_device_interface_config_t dev = {.clock_speed_hz=20000000,.mode=0,.spics_io_num=-1,.queue_size=4};
    ESP_ERROR_CHECK(spi_bus_add_device(SPI2_HOST, &dev, &w5500_lan));
    ESP_ERROR_CHECK(spi_bus_add_device(SPI2_HOST, &dev, &w5500_display));
    /* TODO(application): implement W5500 variable-length data mode and manually assert the selected CS only. */
}

static void init_avr_spi_slave(void) {
    spi_bus_config_t bus = {.sclk_io_num=AVR_SPI_SCLK,.mosi_io_num=AVR_SPI_MOSI,
        .miso_io_num=AVR_SPI_MISO,.quadwp_io_num=-1,.quadhd_io_num=-1,.max_transfer_sz=256};
    spi_slave_interface_config_t slv = {.spics_io_num=AVR_SPI_CS,.mode=0,.queue_size=3};
    ESP_ERROR_CHECK(spi_slave_initialize(SPI3_HOST, &bus, &slv, SPI_DMA_CH_AUTO));
    /* TODO(application): queue framed full-duplex transactions; define CRC, command IDs and READY semantics in protocol. */
}

void app_main(void) {
    gpio_safe_defaults();
    init_pse_i2c();
    init_w5500_spi();
    init_avr_spi_slave();
    vTaskDelay(pdMS_TO_TICKS(20));
    gpio_set_level(AVR_RESET, 1);
    gpio_set_level(ETH_RST_LAN, 1); gpio_set_level(ETH_RST_DISPLAY, 1);
    /* Keep PSE_RESET low until PSE_54V is bench-verified. Then release RESET, wait >=22 ms,
       read the TPS23861 identity/status, and only then deassert SHUTDOWN per product policy. */
    ESP_LOGI(TAG, "safe interfaces initialized; PSE remains disabled");
    while (1) vTaskDelay(pdMS_TO_TICKS(1000));
}
Build:

Sh


idf.py set-target esp32s3
idf.py build
AVR-GCC starter (src/main.c)

C


#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/twi.h>
#include <util/delay.h>

#define FRAM_CS PB3
#define ESP_CS  PB4

static void gpio_init(void) {
    DDRA = 0xF0;                 /* PA0-3 inputs; PA4-7 outputs */
    PORTA = 0x00;                /* outputs default inactive; external pull-ups serve inputs */
    DDRD = _BV(PD1)|_BV(PD2)|_BV(PD3)|_BV(PD4);
    PORTD = _BV(PD2)|_BV(PD3)|_BV(PD4); /* all serial receivers deselected */
}

static void spi_init(void) {
    DDRB |= _BV(FRAM_CS)|_BV(ESP_CS)|_BV(PB5)|_BV(PB7);
    DDRB &= ~_BV(PB6);
    PORTB |= _BV(FRAM_CS)|_BV(ESP_CS);
    SPCR = _BV(SPE)|_BV(MSTR)|_BV(SPR0); /* mode 0, F_CPU/16 */
}
static uint8_t spi_xfer(uint8_t v){ SPDR=v; while(!(SPSR&_BV(SPIF))); return SPDR; }

static void twi_init(void) {
    TWSR = 0; TWBR = 72; /* 100 kHz at 16 MHz */
    TWCR = _BV(TWEN);
}

static void uart0_init_9bit(uint16_t ubrr) {
    UBRR0H = (uint8_t)(ubrr>>8); UBRR0L=(uint8_t)ubrr;
    UCSR0B = _BV(RXEN0)|_BV(TXEN0)|_BV(RXCIE0)|_BV(UCSZ02);
    UCSR0C = _BV(UCSZ01)|_BV(UCSZ00); /* 9 data bits, 1 stop, no parity */
}
static void uart0_put9(uint16_t v) {
    while(!(UCSR0A&_BV(UDRE0)));
    if(v&0x100) UCSR0B|=_BV(TXB80); else UCSR0B&=~_BV(TXB80);
    UDR0=(uint8_t)v;
}

static volatile uint8_t machine_edges;
ISR(PCINT0_vect) { machine_edges |= (PINA & 0x0F); }
ISR(USART0_RX_vect) {
    uint16_t word = (UCSR0B & _BV(RXB80)) ? 0x100 : 0;
    word |= UDR0;
    (void)word; /* TODO(application): selected SAS/COM framing and message parser. */
}

static void machine_irq_init(void) {
    PCICR |= _BV(PCIE0); PCMSK0 = 0x0F; /* PA0..PA3 */
}

int main(void) {
    cli(); gpio_init(); spi_init(); twi_init(); uart0_init_9bit(16); /* 57600 baud */
    machine_irq_init(); sei();
    for (;;) {
        PORTB &= ~_BV(ESP_CS);
        uint8_t status = spi_xfer(0x00); /* TODO(application): replace with framed command/CRC protocol. */
        PORTB |= _BV(ESP_CS);
        (void)status;
        _delay_ms(10);
    }
}
Build:

Sh


avr-gcc -mmcu=atmega1284p -DF_CPU=16000000UL -Os -o main.elf src/main.c
avr-objcopy -O ihex -R .eeprom main.elf main.hex
Startup ownership
  1. U5 boots with both W5500 resets asserted, U4 reset asserted, and TPS23861 RESET/SHUTDOWN asserted.
  2. U5 initializes GPIO and buses, then releases U4 and W5500 resets.
  3. U4 immediately owns machine inputs/outputs, serial selection, 9-bit UART, FRAM, RTC, and secure element.
  4. U5 verifies the 54 V rail before releasing TPS23861 reset. Wait at least 22 ms after reset release before I2C access.
  5. TODO(application): define ESP↔AVR framed messages, watchdog/heartbeat behavior, replay protection, Ethernet addressing, secure-element provisioning, RTC policy, and exact TPS23861 register configuration.
  • Scope and toolchains

  • U5 ESP32-S3 resource map

  • U4 ATmega1284P resource map

  • J7 validated service map

  • ESP-IDF starter (`main/main.c`)

  • AVR-GCC starter (`src/main.c`)

  • Startup ownership