Community project
Arduino Mage Npn 12v/24v 5v Io I2c Led
This industrial I/O expansion board transforms an ESP32 into a robust 24 V control interface for factory automation, machinery, and process control applications. Built around an MCP23017 I2C GPIO expander with optocoupler-isolated inputs, MOSFET-driven outputs, and dual-voltage support (5 V logic, 12–24 V switching), the board safely bridges low-power microcontroller signals to high-power industrial loads while protecting against voltage spikes and noise.
The guide provides a complete wiring diagram, detailed parts list, and step-by-step assembly instructions covering power distribution, input conditioning, output switching stages, and I2C communication setup. Included firmware demonstrates debounced input sampling, safe output sequencing, and diagnostic LED feedback, enabling makers and technicians to deploy reliable automation logic without custom PCB design.
Wiring diagram

Gather all the parts
Assemble it in 8 steps
1. 准备外部 24 V 电源
把符合当地安全规范的封闭式 24 V 直流 DIN 导轨电源安装在机箱或导轨上。它的 +24V 线接到扩展板 J1 的 +24V,0V 线接到 J1 的 0V;这两根线为现场设备和扩展板供电。
- 先用万用表量 J1 两端,确认极性正确且电压在 12–24 V 范围内。
- 交流市电只接外置、封闭且合规的 DIN 导轨电源;绝不能引入这块低压 PCB。
- 把 +24V 与 0V 接反会使保护器件发热或损坏。
2. 安装 XIAO 控制器
将 Seeed Studio XIAO ESP32S3 插入两排 1×7 母座,USB-C 接口朝向 PCB 外缘。确认每一排针脚都进入对应孔位,不要错位一列。
- 先只焊两个对角针脚,确认 USB-C 位置和板边方向正确后,再焊完其余针脚。
- 焊接时不要让 5 V、3.3 V 或 GPIO 针脚之间产生焊锡桥接,否则可能损坏 XIAO。
3. 装配输入区域
在 PCB 的 IN1–IN8 区域装入八颗 PC817C 光耦、每路 2.2 kΩ/0.5 W 输入电阻、4.7 kΩ LED 电阻和红色输入 LED。光耦缺口和 PCB 丝印缺口必须同向;LED 的短脚或外壳色带一侧是负端,按丝印方向安装。
- 每装完一路,用万用表检查该路没有把 +24V 短接到 0V。
- 光耦和 LED 装反会导致该输入无指示或始终不工作。
- 输入电阻必须使用 0.5 W、1206 或更大封装;普通小电阻可能在 24 V 现场侧过热。
4. 装配输出区域
在 OUT1–OUT8 区域依照丝印安装八颗 MOSFET、每路 100 Ω 栅极电阻、100 kΩ 下拉电阻、MF-R110 自恢复保险丝、SS36 续流二极管、红色输出 LED 及其 4.7 kΩ 电阻。SS36 带条纹的一端必须朝向 +24V 丝印,另一端朝向对应 OUTn。
- 先检查每个 MOSFET 的封装方向,再焊接;SOT-23 的引脚很小,建议使用细焊锡和助焊剂。
- 把续流二极管装反会在输出开启时直接短接 24 V,可能损坏保险丝、走线或电源。
- 每路负载不得超过 1 A;八路同时工作时,外置电源、端子和铜箔必须承受总电流。
5. 装配逻辑与电源区域
装入 MCP23017、74AHCT541、5 V 降压稳压器、逻辑上拉电阻、去耦电容和 24 V 保护器件。MCP23017 的 A0、A1、A2 接地,I²C 地址固定为 0x20;RESET 由上拉电阻接到 3.3 V。每颗 IC 的 100 nF 电容要紧贴其电源脚。
- 使用放大镜检查 TSSOP 引脚;相邻引脚间不应有焊锡连桥。
- MCP23017 和 XIAO 只能使用 3.3 V 逻辑;不要把 5 V 接到 SDA、SCL 或 XIAO 的 GPIO。
6. 连接传感器与执行器
每个三线 NPN 传感器:棕线接 +24V 公共端,蓝线接 0V 公共端,黑线接 IN1–IN8 中对应的一路。每个直流负载:正端接输出端子的 +24V_COM,负端接对应 OUT1–OUT8;该输出会把负端接到 0V 来启动负载。
- 先只接一个小型 24 V 指示灯或继电器线圈测试 OUT1,再逐路接入其余设备。
- 感性负载必须使用本板对应通道的续流二极管;否则关断瞬间的尖峰可能损坏 MOSFET。
- 不要把 NPN 传感器的黑色输出线直接接到 +24V。
7. 选择并连接 5 V GPIO 与 I2C
J4 的 IO1–IO4 经过单通道电平转换器连接到 XIAO GPIO1–GPIO4;按每路 DIR 焊盘或跳线选择方向,输出模式把 DIR 接 3.3V,输入模式把 DIR 接 GND。J5 的四针顺序为 3V3、GND、SDA、SCL,SDA 接 GPIO5,SCL 接 GPIO6。
- 第一次使用 5 V GPIO 时,先选择输入模式并用万用表确认外部设备没有把电压送入错误端。
- 不要把超过 5 V 的信号接到 J4;也不要把 24 V 现场信号接到 J4 或 J5。
8. 第一次低风险上电
先不接执行器,仅连接 XIAO 的 USB-C 与外部 24 V。确认 5 V 稳压输出为约 5.0 V、XIAO 的 3.3 V 为约 3.3 V,并确认所有 OUT 灯在上电时熄灭。随后一次连接一个传感器和一个小负载验证 INn 动作时 OUTn 与两颗状态灯一起变化。
- 若某一路上电即导通,立即断开 24 V,检查该路 100 kΩ 栅极下拉、MOSFET 朝向和 74AHCT541 的 OE 接地。
- 进行接线或更换负载前先断开 24 V 电源;带电插拔可能产生火花并损坏端子或输出器件。
Review all connections
1. Connections between "field_psu_24v" and "ESP32"
2. Connections between "tb_power" and "ESP32"
3. Connections between "field_protection" and "ESP32"
4. Connections between "pcb_reg_5v" and "ESP32"
5. Connections between "mcp23017_ic" and "ESP32"
6. Connections between "opto_pc817c" and "ESP32"
7. Connections between "input_series_resistor" and "ESP32"
8. Connections between "input_indicator_led" and "ESP32"
9. Connections between "tb_inputs" and "ESP32"
10. Connections between "output_buffer" and "ESP32"
11. Connections between "gate_resistor" and "ESP32"
12. Connections between "mosfet_gate_pulldown" and "ESP32"
13. Connections between "output_mosfet" and "ESP32"
14. Connections between "output_flyback_diode" and "ESP32"
15. Connections between "output_indicator_led" and "ESP32"
16. Connections between "tb_outputs" and "ESP32"
17. Connections between "hdr_i2c" and "ESP32"
18. Connections between "logic_passives" and "ESP32"
19. Connections between "input_led_resistor" and "ESP32"
20. Connections between "output_led_resistor" and "ESP32"
21. Connections between "opto_pullup_resistor" and "ESP32"
22. Connections between "board_hardware" and "ESP32"
23. Connections between "output_fuse" and "ESP32"
24. Connections between "buffer_input_pulldown" and "ESP32"
25. Connections between "mcp_config" and "ESP32"
26. Connections between "gpio_dir_jumper" and "ESP32"
27. Connections between "gpio_translator_1" and "ESP32"
28. Connections between "gpio_translator_2" and "ESP32"
29. Connections between "gpio_translator_3" and "ESP32"
30. Connections between "gpio_translator_4" and "ESP32"
31. Connections between "tb_gpio5v" and "ESP32"
Deploy the firmware
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_MCP23X17.h>
// Forward declarations
void setOutputsOff();
constexpr uint8_t I2C_SDA_PIN = 5;
constexpr uint8_t I2C_SCL_PIN = 6;
constexpr uint8_t GPIO5V_1_PIN = 1;
constexpr uint8_t GPIO5V_2_PIN = 2;
constexpr uint8_t GPIO5V_3_PIN = 3;
constexpr uint8_t GPIO5V_4_PIN = 4;
constexpr uint8_t MCP_ADDRESS = 0x20;
constexpr uint8_t CHANNEL_COUNT = 8;
constexpr uint32_t INPUT_STABLE_MS = 20;
Adafruit_MCP23X17 io;
bool stableInput[CHANNEL_COUNT] = {false};
bool sampledInput[CHANNEL_COUNT] = {false};
uint32_t lastChangeMs[CHANNEL_COUNT] = {0};
void setOutputsOff() {
for (uint8_t channel = 0; channel < CHANNEL_COUNT; ++channel) {
io.digitalWrite(channel + 8, LOW);
}
}
void setup() {
Serial.begin(115200);
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
pinMode(GPIO5V_1_PIN, INPUT);
pinMode(GPIO5V_2_PIN, INPUT);
pinMode(GPIO5V_3_PIN, INPUT);
pinMode(GPIO5V_4_PIN, INPUT);
if (!io.begin_I2C(MCP_ADDRESS, &Wire)) {
Serial.println("MCP23017 not found at 0x20; outputs remain unavailable.");
while (true) delay(1000);
}
for (uint8_t channel = 0; channel < CHANNEL_COUNT; ++channel) {
io.pinMode(channel, INPUT_PULLUP);
io.pinMode(channel + 8, OUTPUT);
}
setOutputsOff();
Serial.println("Industrial 8-in/8-out board ready.");
}
void loop() {
const uint32_t now = millis();
for (uint8_t channel = 0; channel < CHANNEL_COUNT; ++channel) {
const bool sensorOn = (io.digitalRead(channel) == LOW);
if (sensorOn != sampledInput[channel]) {
sampledInput[channel] = sensorOn;
lastChangeMs[channel] = now;
}
if (sampledInput[channel] != stableInput[channel] &&
now - lastChangeMs[channel] >= INPUT_STABLE_MS) {
stableInput[channel] = sampledInput[channel];
io.digitalWrite(channel + 8, stableInput[channel] ? HIGH : LOW);
Serial.printf("IN%u=%s, OUT%u=%s\n", channel + 1,
stableInput[channel] ? "ON" : "OFF", channel + 1,
stableInput[channel] ? "ON" : "OFF");
}
}
}Remix this project
Make it yours in one click
Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.




