Raspberry Pi – Driving a Relay using GPIO

There’s something exciting about crossing the boundary between the abstract world of software and the physical ‘real world’, and a relay driven from a GPIO pin seemed like a good example of this. Although a simple project, I still learned some new things about the Raspberry Pi while doing it.

There are only four components required, and the cost for these is around 70p, so it would be a good candidate for a classroom exercise. Even a cheap relay like the Omron G5LA-1 5DC can switch loads of 10A at 240V.

Raspberry Pi – Driving a Relay using GPIO

A word of caution: don’t tinker with mains voltages unless you’re really (really) sure about what you’re doing. A mechanical relay allows a safe learning environment, since you can switch any load with it (e.g. a 9V DC battery/bulb circuit for testing), and the concept of a mechanical switch is very easy to grasp. A more efficient alternative to switch an AC load would be to use a solid-state relay (e.g. opto-coupled Triac), but it’s quite easy to make a wrong assumption and blow everything up with a loud bang and a big spark. I recommend sticking with mechanical relays until you’re entirely sure about what you’re doing. Tip: you can buy plug-in low-voltage AC power-supplies if you want to play with triacs.

The Circuit

There are four components to this circuit. A relay (5V DC coil), a BC337 NPN transistor, a diode, and 1K resistor. Essentially, the transistor is used to energise the relay’s coil with the required voltage and current.

A relay will often have 3 significant voltage/current ratings specified; coil, AC load, and DC load. The most important to our circuit is the coil rating, which is the current at a specified voltage required to energise the coil (activate the switch), sometimes expressed as milliwatts (mW).

The AC and DC load ratings relate to the switch-contacts, and state the maximum load current (e.g. for your lamp, motor, etc.) that can be carried at the given AC and DC voltages. DC loads are rated lower because they arc (spark) more, which eventually wears the contacts to the point of failure. In general, large loads need heavier contacts, which in turn need bigger coils to switch them, and bigger coils need more power from your circuit.

Relays sometimes don’t fit easily onto a breadboard, so you might want to build the circuit on veroboard instead, or just mount the relay on veroboard and add two pins for the coil contacts (allowing you to breadboard it). Don’t ever put AC mains into your breadboard!

The GPIO pin used in the example code is GPIO_17, which appears on pin 11 of the Raspberry Pi’s 26-pin expansion header (opposite GPIO_18 (PCM_CLK) and beside GPIO_21 (PCM_DOUT)). The choice of GPIO 17 was simply because I considered it less likely to conflict with other peripherals likely to be in use.

Although the pin is marked 3.3V on the schematic, don’t confuse this with the 3V3 pin – I labelled it with the voltage to highlight that a 3.3V GPIO pin is driving a 5V load – it could also drive a 24V coil, for example, if an appropriate DC power supply is used rather than the Raspi’s 5V line.

Essentially, to activate the relay, all the circuit does is send a few milliamps at 3.3V from the GPIO pin, through a 1K resistor (you may choose to increase this to 1.2K if you want to be strictly below 3mA). This current is enough to saturate the BC337 transistor, causing current to flow on the 5V rail through the transistor, and therefore also through the relay’s coil.

Raspberry Pi – Driving a Relay using GPIO schematicMost general purpose NPN transistors with an minimum hFE of say 50 to 100 could be used in place of the BC337 – it will depend on a) how much current you’re willing to draw from the GPIO pin, b) how much current is required to energise the relay’s coil, c) the actual hFE of the transistor in your hand, since they vary wildly and the current gain could easily be significantly more than the stated minimum.

The diode in the circuit is there to conduct the current generated by the de-energising coil back across the coil (e.g. when switched off), allowing the power to dissipate more gradually, avoiding a voltage spike.

Take care to orient the diode correctly, or you’ll short 5V to ground via the transistor when the GPIO is high. Similarly, take care to correctly identify the collector, base, and emitter pins on your transistor. The pin ordering varies by type, so check the datasheet. I’d recommend you double check these two components before powering up.

The breadboard photo shows it wired up. The pin numbering on my IDC plug should be though of from above the connector, to make it correspond with the 26-pin header numbering. Blue is 5V, and brown is Ground. The green wire connects from GPIO 17 (pin 11 on the Raspi’s 26-pin header) to the transistor base via resistor R1.

You can test that the relay is working by disconnecting the wire from GPIO 17 (pin 11 of the 26-pin header) and touching it to 3V3 (pin 1). You should hear a click as you connect/disconnect 3V3. Make sure you keep the resistor in the circuit (e.g. don’t just take a wire from 3V3 to the transistor’s base pin).

Note that the circuit assumes the GPIO pin will be configured as an output. If it’s likely to also spend some time as an input, then a resistor (10K would do) between the base and ground would ensure the transistor is fully off, rather than having a floating voltage applied.

Using the relay via the ‘/sys’ filesystem

Enable GPIO 17 access via the Kernel on path ‘/sys/class/gpio/’, and configure it as an output pin: –

echo "17" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio17/direction

View the current state of GPIO 17: –

cat /sys/class/gpio/gpio17/value

Set the state of GPIO 17 by writing “1” for high (relay on) and “0” for low (relay off): –

echo "1" > /sys/class/gpio/gpio17/value
echo "0" > /sys/class/gpio/gpio17/value

Finally, to use the C code instead, remove the pin from the control of the Kernel driver: –

echo "17" > /sys/class/gpio/unexport

The C Code Alternative

The C source code below shows how to drive the relay using the GPIO peripheral’s hardware registers. It’s all in one file for simplicity and for clarity, though there’s not much to it.

The usleep(1) call has been used to create a short delay before reading the LEVn register to feed back the pin status. This is because the rise time for a GPIO pin (the time for the voltage on the pin to rise to a level that’s considered ‘high’) is around 100ns to 3V. The ‘high’ threshold is probably less than half that, but even if it’s 30ns, that’s 21 ARM clock cycles at 700MHz, and is enough time to read the LEVn register before it has transitioned.

/*
  * gpio_relay.c - example of driving a relay using the GPIO peripheral on a BCM2835 (Raspberry Pi)
  *
  * Copyright 2012 Kevin Sangeelee.
  * Released as GPLv2, see <http://www.gnu.org/licenses/>
  *
  * This is intended as an example of using Raspberry Pi hardware registers to drive a relay using GPIO. Use at your own
   * risk or not at all. As far as possible, I've omitted anything that doesn't relate to the Raspi registers. There are more
  * conventional ways of doing this using kernel drivers.
  */
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>

#define IOBASE   0x20000000

#define GPIO_BASE (IOBASE + 0x200000)

#define GPFSEL0    *(gpio.addr + 0)
#define GPFSEL1    *(gpio.addr + 1)
#define GPFSEL2    *(gpio.addr + 2)
#define GPFSEL3    *(gpio.addr + 3)
#define GPFSEL4    *(gpio.addr + 4)
#define GPFSEL5    *(gpio.addr + 5)
// Reserved @ word offset 6
#define GPSET0    *(gpio.addr + 7)
#define GPSET1    *(gpio.addr + 8)
// Reserved @ word offset 9
#define GPCLR0    *(gpio.addr + 10)
#define GPCLR1    *(gpio.addr + 11)
// Reserved @ word offset 12
#define GPLEV0    *(gpio.addr + 13)
#define GPLEV1    *(gpio.addr + 14)

#define BIT_17 (1 << 17)

#define PAGESIZE 4096
#define BLOCK_SIZE 4096

struct bcm2835_peripheral {
    unsigned long addr_p;
    int mem_fd;
    void *map;
    volatile unsigned int *addr;
};

struct bcm2835_peripheral gpio = {GPIO_BASE};

// Some forward declarations...
int map_peripheral(struct bcm2835_peripheral *p);
void unmap_peripheral(struct bcm2835_peripheral *p);

int gpio_state = -1;

////////////////
//  main()
////////////////
int main(int argc, char *argv[]) {

    if(argc == 2) {
        if(!strcmp(argv[1], "on"))
            gpio_state = 1;
        if(!strcmp(argv[1], "off"))
            gpio_state = 0;
    }

    if(map_peripheral(&gpio) == -1) {
        printf("Failed to map the physical GPIO registers into the virtual memory space.\n");
        return -1;
    }

    /* Set GPIO 17 as an output pin */
    GPFSEL1 &= ~(7 << 21); // Mask out bits 23-21 of GPFSEL1 (i.e. force to zero)
    GPFSEL1 |= (1 << 21);  // Set bits 23-21 of GPFSEL1 to binary '001'

    if(gpio_state == 0)
        GPCLR0 = BIT_17;
    else if(gpio_state == 1)
        GPSET0 = BIT_17;

    usleep(1);    // Delay to allow any change in state to be reflected in the LEVn, register bit.

    printf("GPIO 17 is %s\n", (GPLEV0 & BIT_17) ? "high" : "low");

    unmap_peripheral(&gpio);

    // Done!
}

// Exposes the physical address defined in the passed structure using mmap on /dev/mem
int map_peripheral(struct bcm2835_peripheral *p)
{
   // Open /dev/mem
   if ((p->mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
      printf("Failed to open /dev/mem, try checking permissions.\n");
      return -1;
   }

   p->map = mmap(
      NULL,
      BLOCK_SIZE,
      PROT_READ|PROT_WRITE,
      MAP_SHARED,
      p->mem_fd,  // File descriptor to physical memory virtual file '/dev/mem'
      p->addr_p      // Address in physical map that we want this memory block to expose
   );

   if (p->map == MAP_FAILED) {
        perror("mmap");
        return -1;
   }

   p->addr = (volatile unsigned int *)p->map;

   return 0;
}

void unmap_peripheral(struct bcm2835_peripheral *p) {

    munmap(p->map, BLOCK_SIZE);
    close(p->mem_fd);
}

The code can be compiled and run with

[email protected]:~# gcc -o gpio_relay gpio_relay.c
[email protected]:~# ./gpio_relay on
GPIO 17 is high
[email protected]:~# ./gpio_relay off
GPIO 17 is low

Note: there was an error in the original example code, where it was initialising GPFSEL0 rather than GPFSEL1. If the pin fails to go high, maybe you’ve got the original code.

Form more detail: Raspberry Pi – Driving a Relay using GPIO

Scroll to Top