Monday, June 11, 2012

GPIO on Raspberry Pi

Finally playing some with Raspberry Pi HW. http://elinux.org/Rpi_Low-level_peripherals describes the 26 pin header available on the board; a number of GPIO pins, plus some serial lines etc. http://www.alliedelec.com/images/Products/mkt/lp/1203/resources/Broadcom_datasheet.pdf details the hardware of the Raspberry Pi unit, including the memory map for the device. Peripherals start at physical address 0x20000000 which maps to peripheral bus address 0x7E000000.

Video of blinking lights :)

Pg 89 begins the discussion of GPIO, which are based at 0x7E200000 (physical address 0x20200000). Setup is similar to e.g. PXA270: set the Function Select register to specify alternate functions (but also to specify straight input or output); bits are SET with the GPSETn registers, and cleared with the GPCLRn registers. There are also rising edge detect enable regs, etc.

Here's some C code, partially copied from the web and modified to make a stand-along function for mapping a block (4096 bytes) of physical addresses to virtual space:


#include <stdio.h>
#include <stdlib.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>


// map a page (4096 bytes) starting at physical address base
int *phys_to_virt(long base)
{
  int mem_fd;
  char *io_mem;
  char *io_m;
  volatile unsigned *io;

  mem_fd=open("/dev/mem",O_RDWR|O_SYNC);

// create page-aligned pointer to 4096 bytes
  io_mem=(char *) malloc(8192);
  io_mem+=4096;
  io_mem=(char*) (((int) io_mem) & 0xfffff000);

  io_m=(char *) mmap((caddr_t)io_mem,4096,
                     PROT_READ|PROT_WRITE,
                     MAP_SHARED|MAP_FIXED,
                     mem_fd,base);
  //io=(volatile unsigned *)io_m;
  return((int *)io_m);
}

This is called with a base address, such as 0x20200000; it returns a pointer to a block of virtual memory mapped into physical memory starting at that base address. You can compile this with

       gcc -c phys_to_virt.o phys_to_virt.c

No error checking: if something goes wrong you'll probably get a seg fault.

Now you can write standard ARM assembly to interact with the GPIO pins (or the RTC, etc.) You only need to call phys_to_virt() in the beginning, save the return value (from R0), and then use that as a base address into the memory-mapped space.

Here's an ARM assembly program for blinking an LED!


@
@ GPIO demonstration code NJM
@ Just calls phys_to_virt (a C program that calls mmap())
@ to map a given physical page to virtual memory
@ Must be run as root of course :) Use at your own risk!
@
.text
.align 2
.global main
main:
@ Map GPIO page (0x20200000) to our virtual address space
ldr r0, =0x20200000
bl phys_to_virt
mov r7, r0 @ r7 points to that physical page
ldr r6, =myloc
str r7, [r6] @ save this just for fun

@ Need to set the function register at [r7+4] so that bits <26:24>=001
@@@ Ooops forgot to do this! do the usual bit-twiddling lol
@@@ AND with 0xF8FFFFFF
@@@ OR with 0x01000000

Loop:
mov r2, #0x40000 @ mask with a 1 in bit 18
str r2, [r7,#0x1c] @ set bit in GPSET0

bl wait @ wait a bit

mov r2, #0x40000
str r2, [r7,#0x28] @ set bit in GPCLR0

bl wait
b Loop @ and do this forever :)

@ brief pause routine
wait:
mov r0, #0x4000000 @ big number
sleepLoop:
subs r0,#1
bne sleepLoop @ loop delay
mov pc,lr

.data
.align 2
myloc: .word 0

.end

R7 contains the base address of the GPIO registers. Blinking an LED on GPIO18 is a simple matter of:

  1. setting the function register to turn port 18 into an output;
  2. writing a 1 to bit 18 of the GPSET0 register and pausing; and
  3. writing a 0 to bit 18 of the GPCLR0 register and pausing.
Steps 2 and 3 are then repeated indefinitely.

For the board setup, I put a socket ont he header and pulled 5V from pin 2; ground form pin 6; and GPIO18 from pin 12. I put this into a simple transistor amplifier to drive an LED with very little current draw.

Note that the documentation warns that some of these header pins are directly connected to the main CPU chip, with no buffering etc. So proceed with caution!

Assemble with

    as -o gpio.o gpio.s -g

(-g if you want debug symbols)
 then link with gcc (to include the libraries needed for phys_to_virt):

   gcc -o gpio gpio.o phys_to_virt.o

Then run as

    sudo ./gpio

You can run this from gdb, but within gdb you can't directly read protected memory locations, even if you run gdb with sudo. But you can step through code that accesses protected memory and that works fine.

For a simpler demo, map phys address 0x20003000 into your virtual address space, then read the word (32 bits) at address offset 0x4. That's the lower word of a free-running system timer counter: it contains the number of microseconds since some basetime. Divide by 1,000,000 and you get seconds yay! So you can write code that just reads that location to keep track of time etc.