Bare-metal Raspberry Pi Libraries
(Last updated 22 Aug 2015)

The Raspberry Pi (RasPi) offers an excellent micro with a ridiculous amount of memory for $35 or so.  But even better, it comes with a built-in GPU that can be used from bare-metal (non-Linux) code.  Add a keyboard and this becomes a great way to break the tether to TeraTerm (or Hyperterm or whatever), and drive high-res video with your bare-metal system.  All you need is a bit of code.

My project reads characters from a PS/2 keyboard and displays the characters on the RasPi's video display at 800x600 resolution.  The program uses a GPIO interrupt on the PS/2 clock line to accept the character, then uses a bare-metal printf(), with support for redirection, to update the display.  The project contains a number of libraries suitable for inclusion in your own projects.  And the executable image weighs in at just 12K bytes of code.

Although my project doesn't do color graphics (I am a text-based life form), the video library in this project includes code for drawing pixels of any color.

Note I am using a Raspberry Pi B (2 USB ports, 26-pin I/O connector at P2).  I have not yet ported this project to a RasPi-2.  My check of the web shows this probably won't be difficult, I just haven't done it yet.


Keyboard and video and text, oh my!

This is a display generated on a bare-metal RasPi at 800x600 resolution, using chars read from a PS/2 keyboard hooked to a couple of GPIO lines.


The PS/2 keyboard interface

There isn't much to the interface electronics.  You only need a couple of resistor dividers to drop the 5 VDC signals from the keyboard down to the 3.3 VDC that the RasPi needs.


Here's the whole setup

This is what the whole setup looks like.  The grey ribbon cable connects to the RasPi's P2 connector for GPIO and power for the keyboard.  I salvaged a short cable with a PS/2 keyboard connector on the end for hooking to the keyboard.


Credits
The code in the following libraries is based on several sources.

Getting started
You will need a toolchain.  These libraries all use GCC for compiling and linking.  Rather than repeat all the information, refer to Brian's pages above for downloading and setting up the Launchpad compiler suite; see Part 1 of the tutorials.

Note that Brian uses CMake for building his projects, while I use make.  Refer to the various makefiles in my projects for details.

I also use Visual Studio 2008 as my IDE.  You may need to modify the Launchpad setup to accomodate whatever IDE you choose.

You will need the Broadcom BCM2835 ARM Peripherals reference document.  Be warned, this is one of the worst MCU reference manuals I've ever had to use; poorly written, confusing, full of errors and outright omissions.  If you're lucky, the information you want is in there somewhere.  Otherwise, it's the web or serious experimentation.

After you build your project, you will need to write the binary file to your RasPi SD card as kernel.img.  This replaces the original kernel.img, which was a Linux image.  Rebooting your RasPi will load your new kernel.img file into memory at address 0x8000 and start execution.  Refer to Brian's tutorials for more details.


Target-agnostic libraries
I've worked to write my libraries so you compile them one time and never edit them afterwards.  I don't want to edit a PS/2 keyboard library because project B uses a different pair of GPIO pins from project A; that's just asking for trouble.

To support this target-independence, the libraries expect the main program to register the GPIO pins involved.  This means that when the main program runs, it calls a specific init() function and passes in identifiers for the GPIO pins in use.  The library code uses this info to initialize the correct pins and the interrupt support.  This lets you define your I/O setup at run-time, not compile-time.  You'll see an example of this in the kbdtest.c code below.


PS/2 keyboard
I originally looked at connecting an USB keyboard to the RasPi.  That's what the connectors are for, right?  But I didn't feel like digging into the complexities of USB, so I hit the web for projects.  I did find a few sites where people had done bare-metal USB keyboards on the RasPi, but their comments often implied it was a work in progress.  I saw comments that the code worked for 50% of the keyboards, or certain features were missing, or it locks up sometimes.  Not very encouraging.

Since I have a few PS/2 keyboards laying around (who doesn't, right?), I decided it might be simpler just to hook one up to the RasPi.  Besides, this would let me try out GPIO interrupts.

First off, the PS/2 keyboard uses 5-volt logic.  The RasPi inputs cannot tolerate 5 VDC levels.  Connecting a 5-volt peripheral to the RasPi is asking to blow up a few inputs, if not the entire chip.

Here is a schematic for a simple resistor divider that lets you safely connect a PS/2 keyboard to the RasPi:

Hooking a PS/2 keyboard to the RasPi

The code supporting the PS/2 keyboard lives in the ps2kbd library and is based on Paul Stoffregen's code for the Teensy.  I have changed a few of the key mappings, such as the arrow keys and the backspace key.

The code uses a falling-edge on P2-16 (GPIO 23) to mark the valid edge of the PS/2 CLK signal.  This falling edge causes an interrupt.  The ISR handler records the state of P2-18 (GPIO 24) as the DATA level, then adds this new bit of data to the accumulated bits to build up the keycode.  If all eight bits of a keyboard byte have been accumulated, the ISR code adds the new byte to a holding buffer and resets its counters for the next byte.  Control then leaves the ISR.

I have provided functions for checking availability of keyboard data and for reading the next byte of data.  Like Paul's original code, my version does not output anything to the keyboard; no control of LEDs, for example.  I also do not handle the CAPS LOCK key or any of the Window's specific keys. 

Not writing data to the keyboard could cause an issue if you use an adaptor from a USB keyboard to PS/2 connector; some adapters output a polling sequence, waiting for the host to write back an acknowledge command of some kind.  Since this code doesn't write anything out, this ack won't happen.

Note that unlike Paul's original code, my version does not directly use an interrupt handler.  Instead, the interrupt code is wrapped in a callback function, which is called by other, higher-level code that contains the true ISR.  I'll provide more details on this feature below.


GPIO interrupts
Most RasPi tutorials start with using a GPIO to blink an LED.  Brian covers this very well in his tutorials, so I won't bother.

Eventually, though, you need to detect a change on a GPIO input and trigger an interrupt.  That is a bit trickier, since the Broadcom BCM2835 interrupt system is not like anything I've used on other ARMs.

The BCM2835 funnels almost all interrupts (SPI, GPIO, timers, whatever) through a single interrupt handler, the IRQ.  (A second handler, the FIQ (fast interrupt request), provides high priority for a single interrupt source.)  This means your interrupt service routine (ISR) must wade through all the interrupt sources your code has enabled, to find the source that caused the current interrupt.

You enable a GPIO interrupt with these steps:
Here is the code for setting up the PS/2 keyboard:

void  ps2kbd_Init(uint8_t  dataGPIO,
                 uint8_t  clkGPIO,
                 const PS2Keymap_t  *map,
                 void  (**Callback)(void))
{
    uint32_t            mask;

    clkPinMask = (1 << (clkGPIO % 32));
    dataPinMask = (1 << (dataGPIO % 32));

/*
 *  First, set the flags in the GPIO module so a falling edge
 *  on the CLK pin will set an interrupt flag.  The register
 *  for this is GPFEN0 for GPIO bits 0-31 or GPENF1 for GPIO
 *  bits 32-53.
 */
    if (clkGPIO < 32)
    {
        EventDetStatusReg = &(BCM2835_GPIO->GPEDS0);
        BCM2835_GPIO->GPFEN0 |= clkPinMask;

    }
    else
    {
        EventDetStatusReg = &(BCM2835_GPIO->GPEDS1);
        BCM2835_GPIO->GPFEN1 |= clkPinMask;
    }

/*
 *  The appropriate bit in the GPIO Event Detect Status Register
 *  GPEDS0 or GPEDS1) will be set whenever the CLK input goes low.
 *  To be safe, clear that bit now by writing a 1 to it.
 */
    *EventDetStatusReg = clkPinMask;

/*
 *  Next, set the proper bit in the IRQ module so an IRQ interrupt
 *  fires when the CLK pin goes low.  The register for this is
 *  IRQ_ENABLE2.
 *
 *  Unfortunately, the BCM2835 ARM Peripherals manual sucks.  The
 *  table listing the interrupts from the ARM peripherals includes
 *  the following GPIO interrupt information:
 *
 *  IRQ 49 is gpio_int[0]
 *  IRQ 50 is gpio_int[1]
 *  IRQ 51 is gpio_int[2]
 *  IRQ 52 is gpio_int[3]
 *
 *  However, this is the only place in the document where the string
 *  "gpio_int" appears.  No clue as to what the four subsets of gpio_int
 *  mean or how to tell to which subset a GPIO pin belongs.
 *
 *  By experimentation, I discovered that if clkGPIO is 24, the GPIO
 *  interrupt flag I need is IRQ 49.  No idea why that is the case.
 *  The following code uses a /32 to arrive at a shift value for
 *  creating an IRQ mask, but it could well be wrong.  If you use
 *  a clkGPIO other than 24 and this code breaks, dork around with
 *  other flags until you find one that works.  Sorry.
 */
    mask = 1 << ((clkGPIO / 32) + 17);
    BCM2835_IRQ->IRQ_ENABLE2 = mask;

    if (dataGPIO < 32)
    {
        InputReg = &(BCM2835_GPIO->GPLEV0);
    }
    else
    {
        InputReg = &(BCM2835_GPIO->GPLEV1);
    }

    keymap = map;
    head = 0;
    tail = 0;

    *Callback = &ps2kbd_Callback;
}

The calling routine specifies a GPIO bit number (0-52) for the CLK pin and for the DATA pin.  It also specifies a keyboard map, based on Paul's original code; this can be a US, French, or German keymap.  Finally, the calling routine provides a location for storing the keyboard-specific ISR handler function pointer.

This function pointer, known as a callback, lets the main program define the ISR for all interrupts, but invoke the callback function so the PS/2 code can deal with any incoming chars.  This means the main code is not responsible for handling chars or checking the keymap.  It also means the PS/2 library code doesn't know in advance which GPIO pins are used.


Video and the mailboxes
The ARM part of the BCM2835 talks with the GPU part of the BCM2835 through mailboxes.  These are a set of BCM2835 registers and corresponding RAM buffers dedicated to exchanging info between the processors.  I will defer to Arjan's code and Github posts, as he provides considerable detail on this and he has working examples of bare-metal code.  I have rolled his code into my video and console projects here; read through the code for details.

Arjan's code includes functions for character I/O (like printing a text string to the console).  I have left his code intact but I don't use it.  I moved that functionality to a dedicated layer of character support code; see my printf and chario files.

Note that I've modified Arjan's code slightly, to support an 800x600 display versus his original 800x480 display.  Regardless of which resuolution you use, you will likely need to modify the config.txt file on your RasPi SD card.  Open config.txt with an editor and locate the lines that refer to the framebuffer_width and framebuffer_height values.  Edit these to match the WIDTH and HEIGHT macros you use in console.c.  In my case, I made these changes:

# uncomment to force a console size. By default it will be display's size minus
# overscan.
framebuffer_width=800
framebuffer_height=600

When you reboot your RasPi with these settings, the video display will default to 800x600.

If you run the console code and you get a smear of dots across the display, it's likely because your framebuffer settings don't match the settings in your video code.


printf and chario
printf() has always been an issue in the bare-metal world.  The newlib library was intended to provide a printf() (and other functions) that work without an underlying OS, but I've never liked the newlib concept.  This is mostly because newlib requires some variant of sbrk(), used by malloc() to allocate memory buffers.  This is a layer of complexity I am not comfortable with.  So my solution was to build a suite of printf() routines that do not use newlib.

Actually, I started with Arjan's printf code and modified it.  It now calls a putchar() function to write a character as part of printf().  My putchar() uses a function pointer to route the character to some active output device.  Using a function pointer for selecting the output device lets me redirect the output from printf() on-the-fly.  Like the traditional STDOUT stream mechanism, I can send chars to the console, then change and send them to a UART.

This character redirection happens in the chario library.  The function pointer to use is passed from the main program to the chario routines through a registration function.  There is one registration function each for character output, character input, and checking for available input chars.  You can see this at work in the following code from my kbdtest.c file:

/*
 *  Hook the character I/O system into the video console for output and the
 *  PS/2 keyboard for input.
 */
    chario_OutRegister((outcharFuncPtr)&console_putc);            // aim printf at the video display
    chario_InRegister((incharFuncPtr)&ps2kbd_Read);               // read chars from PS/2 keyboard
    chario_AvailRegister((availcharFuncPtr)&ps2kbd_Available);    // check PS/2 kbd for chars

    printf("\n\rkbdtest\n\r");

I included getchar() in my printf library module, for reading a character from an input device, simply because I didn't want to be bothered with yet another library module.  I also included two routines for reading a string (with editing) from the input device.  One of these routines locks until it sees a carriage return (CR), while the other version lets you run background tasks until a CR is received.  My test program uses this second version, called get_line_r().


kbdtest
That's about it.  All of the above code is rolled into a set of libraries and used by a program called kbdtest.  This program sets up the video display and the PS/2 keyboard.  It then displays a title on the video screen and prompts you for input.  Type some chars (you can use backspace to erase mistakes) and hit Enter; the program echoes your text.

Not a lot on the surface, but all of the important issues for character I/O, GPIO interrupts, video control, and the other low-level details are covered.

You can use this code a starting point for several RasPi projects.  Home control, hot-house monitoring, data logging; this is a pretty solid foundation for your next RasPi project.

Thanks to the underlying libraries, the kbdtest program is pretty small.  Here's the whole kbdtest.c source file:

/*
 *  kbdtest.c      basic PS/2 keyboard input test
 *
 *  Test operation of a PS/2 keyboard wired to P1 on
 *  the Raspberry Pi.
 *
 *  PS/2 CLK line goes to P1-16 through voltage divider
 *  PS/2 DATA line goes to P1-18 through voltage divider
 *  PS/2 5 VDC line goes to P1-2 or P1-4
 *  PS/2 GND line goes to P1-6
 *
 *  You MUST use a voltage divider between the PS/2 CLK
 *  or DATA lines and the RasPi P1 connector!  The RasPi
 *  cannot handle 5 VDC logic signals.  If you don't
 *  divide the PS/2 signals down to 3.3 VDC, you will
 *  damage the RasPi!
 *
 *  Building this code will yield a kernel.img file.  Copy
 *  this file to the root directory in a RasPi boot SD card,
 *  install the card into your RasPi, power it up, and the
 *  video display should show the test results.
 *
 *  Note that you will have to set the display parameters
 *  in your SD card's config.txt file to match those of
 *  the console display parameters.  For example, if your
 *  console library is built to use 800 x 600 pixel display,
 *  you will need to edit your SD card's config.txt to
 *  show a framebuffer_width of 800 and a framebuffer_height
 *  of 600.
 *
 *  Karl Lunt   7 Aug 2015
 */


#include  <stdio.h>
#include  "target.h"
#include  "bcm2835.h"
#include  "raspi.h"
#include  "chario.h"
#include  "printf.h"
#include  "console.h"
#include  "systimer.h"
#include  "ps2kbd.h"

static void        (*CallbackForPS2Kbd)();
int                c;


/** Main function - we'll never return from here */
void kernel_main( unsigned int r0, unsigned int r1, unsigned int atags )
{
    int                index;
    char            buff[1024];

    console_init();

/*
 *  Hook the character I/O system into the video console for output and the
 *  PS/2 keyboard for input.
 */
    chario_OutRegister((outcharFuncPtr)&console_putc);    // aim printf at the video display
    chario_InRegister((incharFuncPtr)&ps2kbd_Read);        // read chars from PS/2 keyboard
    chario_AvailRegister((availcharFuncPtr)&ps2kbd_Available);    // check PS/2 kbd for chars

    printf("\n\rkbdtest\n\r");
   
/*
 *  Define the GPIO pins used by the PS/2 keyboard.  Also select the keyboard
 *  map and record the PS/2 function for handling keyboard interrupts.
 */
    ps2kbd_Init(PS2_KBD_DATA_PIN, PS2_KBD_CLK_PIN, &PS2Keymap_US, &CallbackForPS2Kbd);


    _enable_interrupts();

    /* Never exit as there is no OS to exit to! */
    while(1)
    {
        index = 0;
        printf("\n\r> ");
        while (get_line_r(buff, sizeof(buff)-1, &index) == 0)
        {
            // I don't have any background stuff, but if I did, it would go here.
        }
        printf("\n\rYou typed: %s", buff);
    }
}

/*
 *  Declare the universal ISR handler for IRQ interrupts.
 *
 *  The only interrupt we expect is from the PS/2 keyboard.  Code
 *  for handling that interrupt is in the callback function recorded
 *  from our earlier call to ps2kbd_Init().  All we have to do is
 *  invoke that callback function and the keyboard interrupt is
 *  handled.
 */
void __attribute__((interrupt("IRQ"))) interrupt_vector(void)
{
    CallbackForPS2Kbd();                        // process any keyboard interrupt
}


Wrapping up
The source, header files, and make files are in this ZIP archive.  I have preserved the folder structure I use in my development.  The support folder contains the project folders for all of the library code, while the library folder contains the libxxx.a libraries.  The common folder contains the low-level source file used to configure the MCU on startup and to provide the interrupt vector table.  It also contains the loader file (raspi.ld), which places the executable image in memory and allocates RAM for the program.

You can read the individual make files (xxx.mak) to see how all of these files play together.  You don't have to maintain this layout, of course.  Feel free to rearrange as best suits your dev tools.

OK, that's it.  Another long post, but this was a fun project.  If you hit any snags, drop me an email.


Home