Close

Driving a Relay with MCP23017 + ULN2803

A project log for SigCore UC - Universal Controller

Universal industrial I/O controller with relays, analog/digital I/O, Modbus & OPC-UA support. Powered by Raspberry Pi.

edwardEdward 08/04/2025 at 05:190 Comments

I got basic relay control working using an MCP23017 I/O expander and a ULN2803A Darlington array. The setup is straightforward:

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.");
    }
}

Discussions