I got basic relay control working using an MCP23017 I/O expander and a ULN2803A Darlington array. The setup is straightforward:
-
The MCP23017 is wired via I2C at address
0x27 -
PA0 is configured as an output and connected to IN1 on the ULN2803
-
The corresponding OUT1 pin drives the relay coil
-
The ULN2803 provides isolation and handles the current — no need for an external diode thanks to the internal flyback protection
When PA0 goes high, the relay turns on. I'm also using PA7 to control an LED for visual confirmation and PA3–PA5 as digital inputs.
Tested the sequence in C# using the Iot.Device.Mcp23xxx driver. I'm writing to the OLAT register and reading back from GPIO. So far, no hiccups. Relay snaps cleanly, LED lights as expected, and input reads are consistent.
The nice thing is this setup scales — there’s room to control up to 8 relays per ULN2803 (and per port on the MCP). That gives plenty of headroom without needing to change the hardware.
Next step: generalize this into a reusable I/O framework. But for now, it’s good to see things switching.


using System;
using System.Device.I2c;
using System.Threading;
using Iot.Device.Mcp23xxx;
class Program {
static void Main() {
I2cConnectionSettings settings = new I2cConnectionSettings(1, 0x27);
I2cDevice device = I2cDevice.Create(settings);
Mcp23017 mcp = new Mcp23017(device);
// Configure Port A direction:
// Bits 3–5 = input (1), bits 0–2 and 7 = output (0), bit 6 = don't care (left as output here)
// Port B remains input (0xFF)
mcp.WriteUInt16(Register.IODIR, 0xFF38); // 0b1111_1111_0011_1000
const byte PA7 = 1 << 7;
ushort output = PA7;
// Turn on PA7 initially
mcp.WriteUInt16(Register.OLAT, output);
for (int i = 0; i < 10; i++) {
for (int bit = 0; bit <= 2; bit++) {
byte ledMask = (byte)(1 << bit);
output = (ushort)(PA7 | ledMask);
mcp.WriteUInt16(Register.OLAT, output);
// Read GPIOA register (lower 8 bits of GPIO)
ushort gpioState = mcp.ReadUInt16(Register.GPIO);
byte portA = (byte)(gpioState & 0xFF);
// Extract bits 3, 4, 5 (inputs)
char input3 = (portA & (1 << 3)) != 0 ? 'T' : 'F';
char input4 = (portA & (1 << 4)) != 0 ? 'T' : 'F';
char input5 = (portA & (1 << 5)) != 0 ? 'T' : 'F';
Console.WriteLine($"Inputs - {input3}{input4}{input5}");
Thread.Sleep(1000);
}
}
// Leave PA7 on
mcp.WriteUInt16(Register.OLAT, PA7);
Console.WriteLine("Done. PA7 remains ON.");
}
}
Edward
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.