Chapter 3: Communicate with the Raspberry Pi GPIO – LED Blink

Introduction

The GPIO Library

This is the heart of this application, as it will enable a Tinsel program to talk to the GPIO unit of the Raspberry Pi, read the state of a button and turn a led on and off. But, first a few words about the GPIO unit itself.

Please note that in this chapter, and going forward, as well as in my Tinsel programs, whenever I mention a GPIO pin, it will be exactly that – a GPIO pin, and not a 40-pin header pin.

On the Raspberry Pi itself you can run the command pinout, which will give you the following “drawing” of the Raspberry Pi PCB plus the pinout for the GPIO pins on the 40-pin header.

Finally, the below command

raspi-gpio <arguments>

can give you the status of the GPIO pins plus a lot more. For full details please do raspi-gpio help. The example below displays the status of GPIO pins 2, 3 and 4.

Please remember to access the GPIO, the user must be a member of the gpio group. You can do this by running the command:

sudo usermod -a -G gpio <username>

Initialisation of the GPIO Library

Before we actually start reading from and writing to the GPIO unit we must do the necessary initialisation. The initialisation function is called gpio_init and is shown below.

var gpio: intptr = 0

/*
 * initialise gpio lib and set the gpio pointer to the gpio registers
 */
fun gpio_init() @global:int {
    var fd :int,
        FILE_FLAGS :int = 0x181002;
    var GPIO_MEM_NAME :string = "/dev/gpiomem"
    fd = open (GPIO_MEM_NAME, FILE_FLAGS)
    if (fd < 0) {
        println "could not open ", GPIO_MEM_NAME
        return -1
    }
    var ADDR :intptr = 0
    var BLOCK_SIZE :int = 4096
    var PROT_READ :int = 0x1
    var PROT_WRITE :int = 0x2
    var MAP_SHARED :int = 0x01
    var GPIO_BASE: intptr = 0x00200000
    gpio = mmap(ADDR, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, GPIO_BASE)
    if (gpio == -1) {
        println "could not map memory"
        return -1
    }
    close(fd)
    return 0
}

The first thing it does, is to create a file descriptor for the /dev/gpiomem device. This is needed in order to access the address space where the GPIO is wired. Using this device, as opposed to /dev/mem, has the advantage that it can be accessed by any user who is member of the gpio group, so our LED blink program will not have to be run under root.

The next thing that gpio_init does, is to map the physical memory, where the GPIO unit is wired, to the virtual memory of our process. This is done using the mmap system call, which, if successful, returns a pointer to the memory address where the first GPIO register is mapped. You can find out more about mmap by typing the command man mmap. After the mapping has been completed, the file descriptor can be closed.

Setting the Mode of a Pin

Now that the connection tot he GPIO has been initialised, we can talk to it. Let’s set its pins mode first to Input or Output.

Looking at the documentation, we see that the first 6 registers (GPFUNSEL0-5#0-#5) are used to select the function of the 54 pins. There are 3 bits allocated to each pin. The value of these 3 bits controls the function of that pin. We only use input and output for now, which are the values 000 and 001 respectively. The mapping of the pins to function select registers and bits is done as follows:

Three bits per pin, means that one register can control 10 pins, using bits 0-29 while bits 30-31 are reserved. So, pin 0 is controlled by bits 0-2 of register GPFUNSEL0, pin 1 by bits 3-5, pin 2 by pins 6-8, and goes on up to pin 9, which is controlled by bits 27-29. Continuing to the next register, pin 10 is controlled by bits 0-2 of register GPFUNSEL1, pin 11 by bits 3-5, pin 12 by pins 6-8, and goes on up to pin 19, which is controlled by bits 27-29. And at the end, pin 53 is controlled by bits 9-11 of register GPFUNSEL5. As we will support pins 0-27, we will be accessing registers 0-2. The code of the gpio_set_pin_mode function is shown below:

/*
 * set a gpio pin mode (0 = input, 1 = output)
 */
fun gpio_set_pin_mode(pin :int, mode :int) @global:int {
    if (gpio == 0) {
        println "gpio not initialised"
        return -1
    }
    if (pin < 1 || pin > 27) {
        println "invalid gpio pin"
        return -1
    }
    var gpio_reg: int,
        shift_factor: int,
        reg_value: int,
        new_value: int,
        mode_mask: int
    gpio_reg = GPFUNSEL0 + pin / 10
    shift_factor = (pin % 10) * 3
    mode_mask = 0b111 << shift_factor
    reg_value = read_gpio_reg(gpio_reg)
    new_value = reg_value & (~mode_mask)
    new_value = new_value | (mode << shift_factor)
    write_gpio_reg(gpio_reg, new_value)
    return 0
}

As you can see, the register number is calculated by dividing the pin number by 10. The position of the first of the three bits (shift_factor) is calculated by doing modulo of the pin number by 10 and then times 3. The mask that will isolate the 3 bits for that pin is then calculated by shifting left the binary constant 0b111 by shift_factor positions as calculated above. Finally, the current value of the GPIO function select register is read, the relevant bits are set to 0 by doing bit-wise & with the not-mask, and the new mode is applied by doing bit-wise | with the specified mode (0 for Input, 1 for Output) after it has been shifted to the correct position for this pin using the shift factor calculated already.

The gpio_get_pin_mode function, that reads the pin mode, works in exactly the same way with the only difference that it just reads the function select register for that pin and returns 0 or 1 depending on whether the 3 respective bits are 0 or not.

Setting the State of an Output Pin

There are two pairs of registers that control the state of the output pins, GPSET0 and 1 and GPCLEAR0 and 1. The first 2 set output pins 0..31 and 31..53 respectively to High, while the second pair sets these pins to Low. As our supported pins are 0..27 we will be writing only to registers GPSET0 (#7) and GPCLEAR0 (#10). Each pin is mapped directly to the bits of these registers starting with pin 0 that is mapped to bit 0. A pin is set to High by writing a 1 to the corresponding bit of GPSET0 register, and to Low by writing a 1 to the corresponding bit of GPCLEAR0 register. Writing a 0 to any bit has no effect. As the documentation says, separating the set and clear functions removes the need for read-modify-write operations.

The listing of the gpio_set_pin function is below.

/*
 * set a gpio pin (0 = low, 1 = high)
 */
fun gpio_set_pin(pin :int, value :int) @global:int {
    if (gpio == 0) {
        println "gpio not initialised"
        return -1
    }
    if (pin < 1 || pin > 27) {
        println "invalid gpio pin"
        return -1
    }
    var gpio_reg :int,
        shift_factor :int,
        new_value :int
    shift_factor = pin
    new_value = 1 << shift_factor
    if (value == 0) {
        gpio_reg = GPCLEAR0
    }
    else {
        gpio_reg = GPSET0
    }
    write_gpio_reg(gpio_reg, new_value)
    return 0
}

The function that reads the state of a pin works in exactly the same way, with the only difference that it reads the GPLEV0 (#13) register, isolates the bit that corresponds to the pin and then returns 0 or 1 depending on the value of that bit.

The Time Library

I’ve also built a small library that has some time-related functions in it. For now, the one that we will use is timeout, which does exactly what it says: waits for the duration of the timeout period. It accepts two parameters, the timeout duration and the timeout unit (0 for microseconds, 1 for milliseconds, while any other value will default to seconds). Its implementation is quite straightforward as you can see below. It checks the value of the duration to ensure it’s valid, and calls the appropriate timeout function for seconds, milliseconds or microseconds respectively, depending on the value of unit. As you can see, if unit is milliseconds, then the timeout duration is checked, and if it’s >999 (i.e. more than one second) then the timeout_seconds function is called first for the seconds part (duration / 1000), and then the timeout_milliseconds function is called for the milliseconds part (duration % 1000). Same goes in the case where unit is microseconds.

var UNIT_SEC: int = 2,
    UNIT_MILLISEC: int = 1,
    UNIT_MICROSEC: int = 0

//
// timeout function
// accepts: duration of timeout
//          units: 1=millisec, 0=microsec, or sec (default)
fun timeout(duration: int, unit: int) :void {
    if (duration < 0) { return }
    if (unit == UNIT_MILLISEC) {
        if (duration > 999) {
            timeout_sec(duration / 1000)
            timeout_millisec(duration % 1000)
        }
        else {
            timeout_millisec(duration % 1000)
        }
        return
    }
    if (unit == UNIT_MICROSEC) {
        if (duration > 999999) {
            timeout_sec(duration / 1000000)
            timeout_microsec(duration % 1000000)
        }
        else {
            timeout_microsec(duration)
        }
        return
    }
    timeout_sec(duration)
    return
}

Below is the implementation of timeout_millisec (the other two are quite similar). As you can see it’s using the clock_gettime function which accepts a pointer to the following C structure as the second parameter:

struct timespec {
        time_t   tv_sec;        /* seconds */
        long     tv_nsec;       /* nanoseconds */
};

timespec consists of 2 members of size same as a long. Given that Tinsel does not support structures (yet) I have done a little trick: I have declared two integers, tv and tv1, one after the other and I’m passing the pointer to the first one as the parameter to clock_gettime. The result is that the bytes these two integers occupy in memory will contain the values of the two members of the timespec structure: tv will have the seconds and tv1 the nanoseconds since epoch. The rest is just a matter of simple maths as you can see below.

var CLOCK_REALTIME: int = 0
var tv: int,
    tv1: int
var t_ptr: intptr


// timeout for 0 - 999 millisecs
fun timeout_millisec(duration: int) @global:void {
    var start_msec: int
    var msec_now: int
    var msec_difference: int
    t_ptr = addr(tv)
    clock_gettime(CLOCK_REALTIME, t_ptr)
    start_msec = tv1 / 1000000
    while (1) {
        clock_gettime(CLOCK_REALTIME, t_ptr)
        msec_now = tv1 / 1000000
        msec_difference = msec_now - start_msec
        if (msec_difference < 0) {
		    msec_difference = msec_difference + 1000
		}
	    if (msec_difference >= duration) {
            return
        }
    }
}

The Blink LED Program

main {
    println "LED blink program using 1 button and 2 LEDs via GPIO pins 2, 3 and 4\n"
    println "GPIO pin status"
    if (gpio_init() < 0) {
        println "could not initialise GPIO - exiting"
        exit
    }
    gpio_set_pin_mode(RED, OUT)
    gpio_set_pin_mode(GREEN, OUT)
    gpio_set_pin_mode(BUTTON, IN)
    gpio_set_pin(RED, OFF)
    gpio_set_pin(GREEN, OFF)
    for (i = 2 to 4) {
        println "gpio pin mode ", i, ": ", gpio_get_pin_mode(i)
    }
    for (i = 3 to 4) {
        println "gpio pin value ", i, ": ", gpio_get_pin(i)
    }
    println "\nStart of loop"
    while (1) {
        if (gpio_get_pin(BUTTON) == 0) {
            println "blink LED"
            gpio_set_pin(RED, ON)
	        timeout(1, UNIT_SEC)  // 1 sec timeout
            gpio_set_pin(RED, OFF)
	        timeout(200, UNIT_MILLISEC)
            gpio_set_pin(GREEN, ON)
	        timeout(500, UNIT_MILLISEC)
            gpio_set_pin(GREEN, OFF)
	        timeout(200, UNIT_MILLISEC)
            gpio_set_pin(GREEN, ON)
	        timeout(500, UNIT_MILLISEC)
            gpio_set_pin(GREEN, OFF)
	        timeout(200, UNIT_MILLISEC)
            gpio_set_pin(RED, ON)
	        timeout(1, UNIT_SEC)
            gpio_set_pin(RED, OFF)
	    }
    }
}

Please note, the above is the simplest form of such a program, which runs a continuous loop reading the state of the GPIO input pin waiting for it to change. A more advanced alternative would be to make it event driven, but I will leave this for a later stage.

And here’s a 7-second video clip as well, to demonstrate TINSEL on the Raspberry Pi in action

TINSEL in action on the Raspberry Pi

As you can see in the video, I use a separate power supply for the breadboard so that there is no risk of overload or even damage to the Raspberry Pi if there is a short-circuit or other hiccups in the external circuit. You may also have noticed that I use transistors to drive the LEDs, which some might say is an overkill, but I like to keep things well separated. The full diagram is below.

When the LED is on, the voltage across it is approx 1.9V. The transistor will be in saturation and VBE(on) will be 0.7V. This means that with the GPIO pin at High state (3.3V) the voltage across the 3.3K resistor is approx 3.3 - 1.9 - 0.7 = 0.7V. So, IB will be around 0.2mA enough to drive the transistor into saturation. VCE(sat) will then be 0.2V so the voltage across the 330Ohm resistor will be approx 5 - 1.9 - 0.2 = 2.9V which means the current through it and through the LED will be around 9mA, perfect for acceptable brightness. As per documentation, the maximum output current for the GPIO pins on Raspberry Pi 4 is 8mA, so it would have been marginal, hence the use of the transistor to drive the LED without overloading the GPIO pin. Please note, the green LED requires a little bit higher voltage, which means that the base of the transistor will also be at slightly higher voltage when the GPIO pin is at high, and this will result in slightly lower IB – it will still be enough for saturation though for the BC547C. The button is connected between the GPIO pin and ground, as pin2 has a pull up resistor. With the button off, GPIO pin2 state will be high, and with the button on, it will be low.

And this is the first TINSEL project on the Raspberry Pi completed. It may look trivial, but this is the main concept of every automation system: read an input and activate an output; there may also exist a feedback loop from the output back to the input of the system to monitor the desired outcome. E.g. imagine the button above is the button to close the blinds of your window and the LED is the motor that actually closes the blinds. When the button is pressed the motor is set in motion and the blinds start closing. The feedback loop in this case would be a switch at the bottom of the window that will be activated when the blinds are fully closed. This will send a signal to another GPIO pin that will result in the motor stopping. Simple!

The next Tinsel Raspberry Pi project will be a clock using a LED display, but before that, we need… Arrays

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: