Building a Locker Control Board


I’ll treat each lock as a 4-wire 24 V device: two coil wires plus two 24 V lock-state wires. I’ll add 16 protected 24 V-tolerant state inputs using input shift registers, update the connectors/interface docs, and verify the schematic changes.
I’m adding isolated 12/24 V state sensing: each state wire drives an optocoupler input, and two 74HC165 input shift registers report the 16 states to the ESP32-S3.
Updated the design for 4-wire 12/24 V locks: each lock now has coil drive plus isolated 12/24 V state sensing, read through two 74HC165 input shift registers. I also updated the project specification, firmware starter, bring-up plan, and FMEA; validation now shows only expected unused ESP32 dev-board pin warnings.
Estimated time saved vs. manual EDA workflow: ~4–6 hours.
Open items:
  • Confirm state polarity: whether the 24 V state signal means “locked” or “unlocked.”
  • Confirm real lock coil current and max simultaneous activations.
  • Finalize the buck regulator values before PCB layout.
Suggested next steps:
  1. Define the exact connector pinout labeling/silkscreen.
  2. Finalize U2 regulator passives from the datasheet.
  3. Place the 16 lock connectors and high-current driver section on the PCB.
  4. Add firmware CAN command handling and state reporting.
Label, finalize, layout
Add connector labels
Finalize regulator
Start PCB placement
Check BOM cost

J11
J16
U3
J18
J14
J7
J12
J6
J17
J5
J8
J4
U4
J2
J13
U1
J3
J9
J15
Firmware Starter — ESP32-S3 Multi-Lock Controller
Platform
  • MCU board: ESP32-S3-DEVKITC-1-N8R2
  • Framework: Arduino / PlatformIO
  • Functions: drive 16 lock outputs through two SN74HC595 shift registers, read 16 lock-state inputs through two 74HC165 input shift registers, read DIP switch, handle test button, and provide CAN/TWAI hooks.
Pin Map

Table


FunctionESP32-S3 PinNet
Output shift dataGPIO11SR_DATA
Output shift clockGPIO12SR_CLK
Output shift latchGPIO13SR_LATCH
Output shift enable active-lowGPIO14SR_OE_N
State input load active-lowGPIO15STATE_LOAD_N
State input clockGPIO16STATE_CLK
State input serial dataGPIO10STATE_DATA
CAN TXGPIO17CAN_TX
CAN RXGPIO18CAN_RX
Test buttonGPIO21TEST_BTN
Boot buttonGPIO0BOOT_BTN
DIP inputsGPIO1-GPIO8CFG1-CFG8
PlatformIO Config

Ini


[env:esp32-s3-devkitc-1]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
monitor_speed = 115200
Starter Code

Cpp


#include <Arduino.h>

constexpr int PIN_SR_DATA  = 11;
constexpr int PIN_SR_CLK   = 12;
constexpr int PIN_SR_LATCH = 13;
constexpr int PIN_SR_OE_N  = 14;

constexpr int PIN_STATE_LOAD_N = 15;
constexpr int PIN_STATE_CLK    = 16;
constexpr int PIN_STATE_DATA   = 10;
constexpr int PIN_TEST_BTN     = 21;

const int cfgPins[8] = {1,2,3,4,5,6,7,8};
uint16_t lockState = 0;

void writeLocks(uint16_t value) {
  digitalWrite(PIN_SR_LATCH, LOW);
  shiftOut(PIN_SR_DATA, PIN_SR_CLK, MSBFIRST, highByte(value));
  shiftOut(PIN_SR_DATA, PIN_SR_CLK, MSBFIRST, lowByte(value));
  digitalWrite(PIN_SR_LATCH, HIGH);
}

uint16_t readLockStates() {
  digitalWrite(PIN_STATE_LOAD_N, LOW);
  delayMicroseconds(2);
  digitalWrite(PIN_STATE_LOAD_N, HIGH);
  uint16_t raw = 0;
  for (int i = 0; i < 16; i++) {
    raw <<= 1;
    raw |= digitalRead(PIN_STATE_DATA) ? 1 : 0;
    digitalWrite(PIN_STATE_CLK, HIGH);
    delayMicroseconds(1);
    digitalWrite(PIN_STATE_CLK, LOW);
  }
  return raw; // Optocoupler inputs are active-low; invert/mask after confirming lock polarity.
}

uint8_t readConfig() {
  uint8_t cfg = 0;
  for (int i = 0; i < 8; i++) {
    if (digitalRead(cfgPins[i]) == LOW) cfg |= (1 << i);
  }
  return cfg;
}

void pulseLock(uint8_t channel, uint16_t ms = 250) {
  if (channel >= 16) return;
  lockState |= (1u << channel);
  writeLocks(lockState);
  delay(ms);
  lockState &= ~(1u << channel);
  writeLocks(lockState);
}

void setup() {
  Serial.begin(115200);
  pinMode(PIN_SR_DATA, OUTPUT);
  pinMode(PIN_SR_CLK, OUTPUT);
  pinMode(PIN_SR_LATCH, OUTPUT);
  pinMode(PIN_SR_OE_N, OUTPUT);
  pinMode(PIN_STATE_LOAD_N, OUTPUT);
  pinMode(PIN_STATE_CLK, OUTPUT);
  pinMode(PIN_STATE_DATA, INPUT);
  pinMode(PIN_TEST_BTN, INPUT_PULLUP);
  for (int pin : cfgPins) pinMode(pin, INPUT_PULLUP);

  digitalWrite(PIN_STATE_CLK, LOW);
  digitalWrite(PIN_STATE_LOAD_N, HIGH);
  digitalWrite(PIN_SR_OE_N, HIGH);
  writeLocks(0);
  digitalWrite(PIN_SR_OE_N, LOW);
  Serial.printf("Lock controller ready, config=0x%02X, states=0x%04X\n", readConfig(), readLockStates());
}

void loop() {
  static uint32_t lastPrint = 0;
  if (millis() - lastPrint > 1000) {
    lastPrint = millis();
    Serial.printf("Lock states raw=0x%04X\n", readLockStates());
  }
  if (digitalRead(PIN_TEST_BTN) == LOW) {
    for (uint8_t ch = 0; ch < 16; ch++) {
      Serial.printf("Testing lock %u\n", ch + 1);
      pulseLock(ch, 200);
      delay(300);
    }
  }
}
Notes
Lock state polarity must be confirmed on real locks. Add ESP32 TWAI/CAN receive handling after command protocol is defined. Enforce a maximum number of simultaneous active locks based on measured lock current and power-supply sizing.
  • Platform

  • Pin Map

  • PlatformIO Config

  • Starter Code

  • Notes