Morse code on a big, bright, LEDBorg

I have already proved to myself that I can set up and control the OK LED on the Raspberry PI board from a bare-metal C program, but I didn't really feel that I entirely understood what was going on. The cop-and-paste code to set up and switch the GPIO line used for the OK LED was very specific - I would prefer to develop my own functions which will let me easily set and switch any of the GPIO pins.

Recently I also bought a LEDBorg, a very bright, three-colour LED which plugs in to the GPIO header and is controlled by switching pins on and off. This seemed a good choice to use to develop my general-purpose GPIO routines. As for what to do with the LEDBorg, the obvious choice was to get my Morse Code example working with the new super-bright light. The folks at PiBorg have also written a Corse Code example in Python running on Linux to drive their light, so the challenge is on!

First, I overhauled my GPIO code, based on reading the widely available "Dom and Gert" example from this time last year:

gpio.h

#ifndef GPIO_H
#define GPIO_H

#include <stdint.h>

#define BCM2708_PERI_BASE 0x20000000
#define GPIO_BASE (BCM2708_PERI_BASE + 0x200000) /* GPIO controller */
#define TIMER_BASE (BCM2708_PERI_BASE + 0x3000) /* Timer */

#define GPFSEL1 (GPIO_BASE + 0x04)
#define GPSET0 (GPIO_BASE + 0x1C)
#define GPCLR0 (GPIO_BASE + 0x28)
#define GPPUD (GPIO_BASE + 0x94)
#define GPPUDCLK0 (GPIO_BASE + 0x98)

#define TIMER (TIMER_BASE + 0x00)
#define COUNTER (TIMER_BASE + 0x04)

#define UART_BASE (GPIO_BASE + 0x15000)
#define AUX_ENABLES     0x20215004
#define AUX_MU_IO_REG   0x20215040
#define AUX_MU_IER_REG  0x20215044
#define AUX_MU_IIR_REG  0x20215048
#define AUX_MU_LCR_REG  0x2021504C
#define AUX_MU_MCR_REG  0x20215050
#define AUX_MU_LSR_REG  0x20215054
#define AUX_MU_MSR_REG  0x20215058
#define AUX_MU_SCRATCH  0x2021505C
#define AUX_MU_CNTL_REG 0x20215060
#define AUX_MU_STAT_REG 0x20215064
#define AUX_MU_BAUD_REG 0x20215068

enum gpio_direction { GPIO_INPUT, GPIO_OUTPUT };
void raspi_set_gpio_direction(int pin, enum gpio_direction direction);

enum gpio_level { GPIO_LOW=0, GPIO_HIGH=1 };
void raspi_set_gpio_level(int pin, enum gpio_level level);

void raspi_okled_set(enum gpio_level level);
void raspi_timer_wait(int usec);
void raspi_mini_uart_send_char(char c);
void raspi_mini_uart_send_string(const char* s);
void raspi_mini_uart_send_hex(uint32_t n);
void raspi_mini_uart_send_newline(void);

void raspi_okled_init();
void raspi_mini_uart_init();

#endif

gpio.c

#include <stdint.h>
#include <stdint.h>

#include "raspi.h"
#include "gpio.h"

static uint32_t* __gpio = (uint32_t*)GPIO_BASE;

// BCM2835-ARM-Peripherals.pdf section 6.1
// Each GPFSELn has 10 x 3 bits (with bits 31:30 reserved) 'b000 = i/p, 'b001 = o/p, others = alternate uses
// Always use INP_GPIO(x) before using OUT_GPIO(x) as this will force 000 before setting 001

#define INP_GPIO(g) *(__gpio+((g)/10)) &= ~(7<<(((g)%10)*3)) // existing bits ANDed with 111..111000111..111
#define OUT_GPIO(g) *(__gpio+((g)/10)) |=  (1<<(((g)%10)*3)) // existing bits ORed  with 000..000001000..000

// GPSETn is at PERI_BASE + GPIO_BASE + 0x1C bytes. 0x1C = 28 bytes = 7 dwords
#define GPIO_SET *(__gpio+7)  // assigning 1<<n will set bit(n) only

// GPCLRn is at PERI_BASE + GPIO_BASE + 0x28 bytes. 0x28 = 40 bytes = 10 dwords
#define GPIO_CLR *(__gpio+10) // assigning 1<<n will clear bit(n) only

#define PIN_BIT(pin) (1 << pin)

void raspi_set_gpio_direction(int pin, enum gpio_direction direction) {
  INP_GPIO(pin);
  if (GPIO_OUTPUT == direction) {
    OUT_GPIO(pin);
  }
}

void raspi_set_gpio_level(int pin, enum gpio_level level) {
  PUT32(level ? GPCLR0 : GPSET0, PIN_BIT(pin));
}

#define OKLED_PIN 16

void raspi_okled_init() {
  raspi_set_gpio_direction(OKLED_PIN, GPIO_OUTPUT);
}

void raspi_okled_set(enum gpio_level level) {
  raspi_set_gpio_level(OKLED_PIN, level);
}
...

While I could have just copied my Morse code example and edited it to work with the new device, I thought it would be a better idea to adjust it so that it will work with any kind of signalling device, even ones I don't yet know about. The way I chose to do this was to allow users to specify a function to switch the signalling device on or off. By default the code will use a function which switches the OK LED, but if one is supplied which switches the LEDBorg, the Morse Code logic will use that instead. The rest of the Morse Code stuff remains unchanged.

morse.h

...
typedef void (*switch_fn)(enum gpio_level);
void morse_set_switch(switch_fn fn);
...

morse.c

...
static switch_fn switcher = &raspi_okled_set;

void morse_set_switch(switch_fn fn) {
  switcher = fn;
}

void switch_off(int duration) {
  switcher(GPIO_LOW);
  raspi_timer_wait(duration);
}

void switch_on(int duration) {
  switcher(GPIO_HIGH);
  raspi_timer_wait(duration);
}
...

I tried that out, and it happily flashed the OK LED. Now to write the code to switch the LEDBorg. For a first go I shall just switch all three colours either to fully off, or fully on. Fancy stuff with different colours and levels can follow later. As can detecting the revision of the Raspberry Pi board and using the appropriate pins. I developed this for my Rev 1 board, so it uses the original pins.

I wanted to keep this code separate from the rest of the code, so I created a new header and a new c file just for this device. They are automatically picked up by the makefile:

ledborg.h

#ifndef LEDBORG_H
#define LEDBORG_H

#include "gpio.h"

void ledborg_set_all(enum gpio_level level);
void ledborg_set(enum gpio_level red, enum gpio_level green, enum gpio_level blue);
void ledborg_init();

#endif

ledborg.c

#include "gpio.h"
#include "ledborg.h"

#define RED_PIN 17
#define GREEN_PIN 21
#define BLUE_PIN 22

void ledborg_set_all(enum gpio_level level) {
  ledborg_set(level, level, level);
}

void ledborg_set(enum gpio_level red, enum gpio_level green, enum gpio_level blue) {
  raspi_set_gpio_level(17, red);
  raspi_set_gpio_level(21, green);
  raspi_set_gpio_level(22, red);
}

void ledborg_init() {
  raspi_set_gpio_direction(RED_PIN, GPIO_OUTPUT);
  raspi_set_gpio_direction(GREEN_PIN, GPIO_OUTPUT);
  raspi_set_gpio_direction(BLUE_PIN, GPIO_OUTPUT);
}

All that remained was to plumb this new "driver" into "main.c" so I can see it working:

main.c


int main(void) {
  ledborg_init();
  morse_set_switch(&ledborg_set_all);
  halt("ledborg ");
}

And yes, it certainly is bright. I had to put the case back on the Pi while I tried a few variations, just to tone it down a bit.

The end result of all this doesn't appear particularly significant from the outside - it's just flashing a different light. Inside the code, though it's a big improvement. I now have the start of some general purpose GPIO control, and I have shown how even at this level, the system can cope with adding new hardware. Excellent.

If you want to read the code in more detail, it's all on GitHub. The latest is at https://github.com/raspberry-alpha-omega/corn-mainline, or this specific version is at https://github.com/raspberry-alpha-omega/corn-mainline/tree/article-2013-01-16

2 Comments

  1. Pingback: Hardware abstraction layers | Raspberry Alpha Omega

  2. Pingback: Developing memory-mapped IO | Raspberry Alpha Omega

Leave a Reply

Your email address will not be published. Required fields are marked *