Saturday, January 8, 2011

Digital I/O on the beagleboard using GPIO and the expansion header

I needed to send a digital output signal from my Beagleboard to a data acquisition system. This turned out to be more challenging than I thought it would be. I'm posting these notes in case they're helpful for anyone else.

I used as my reference this excellent post [Edit: the post moved to here]. Because I wanted to do output rather than input, it wasn't perfectly applicable, but a lot of what they do applies.

To access the necessary registers from user space, they use memory mapping. They then set up the Beagleboard expansion header pins to map to GPIO Bank 5, set the appropriate pullup registers, and enable input. They then set up Bank 5 as an input, and read from it.

I first tried to mirror that setup, but doing output instead. This didn't exactly work for me. One /huge/ problem is that I am communicating with my beagleboard over a USB network adaptor. For some reason (that I have yet to figure out), toggling some of the bits on GPIO5 clobbers the USB interface. I don't know why, but I do know that only happens with the high bits, so I settled for using the low bits.

Here's the relevant C code for output, based on the aforementioned post:

includes:


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


Setting the pin configuration:
//O_SYNC makes the memory uncacheable

int fd = open("/dev/mem", O_RDWR | O_SYNC);
if (fd < 0) {
  sprintf(stderr,"Could not open memory\n");
  return 0;
}
// Pad configuration
volatile ulong *pinconf;
pinconf = (ulong*) mmap(NULL, 0x10000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x48000000);
if (pinconf == MAP_FAILED) {
  sprintf(stderr,"Pinconf Mapping failed\n");
  close(fd);
  return 0;
}
// set lower 16 pins to GPIO bank5
pinconf[0x2158/4] = 0x00040004;
pinconf[0x215C/4] = 0x00040004;
pinconf[0x2160/4] = 0x00040004;
pinconf[0x2164/4] = 0x00040004;
/* pinconf[0x2168/4] = 0x00040004; */
/* pinconf[0x216C/4] = 0x00040004; */
/* pinconf[0x2170/4] = 0x00040004; */
/* pinconf[0x2188/4] = 0x00040004; */
close(fd);

GPIO Bank 5 configuration:

volatile ulong * gpio_fd = open("/dev/mem", O_RDWR | O_SYNC);
if (gpio_fd < 0) {
  sprintf(stderr,"Could not open memory\n");
  return 0;
}
// First set all output on bank5 to high
// (set_data_out has offset 0x94)
gpio[0x6094/4]=0xFFFFFFFF;

// Configure low 16 GPIO pins on bank 5 as output.
// GPIO 5 is at physical address 0x49056000 = 0x49050000+0x6000
// GPIO Output enable (GPIO_OE) is offset by 0x34 for each bank
// (set low for output)
gpio[0x6034/4] = 0x00000000;
// Also disable the wakeupenable and irqenable intertupts
// GPIO clear_Wakeupenable is offset by 0x80 for each bank
gpio[0x6080/4] = 0x0000FFFF;
// GPIO clear_irqenable1 is offset by 0x60 for each bank
gpio[0x6060/4] = 0x0000FFFF;
// GPIO clear_irqenable2 is offset by 0x70 for each bank
gpio[0x6070/4] = 0x0000FFFF;


Toggling the pins from high to low, waste some time, then toggle pin back to high:

//clear_data_out has offset 0x90
gpio[0x6090/4]=0x0000FFFF;
usleep(500);
for (i=0;i<25000;i++);
//set_data_out has offset 0x94
gpio[0x6094/4]=0x0000FFFF;
usleep(500);


I do not know if the "usleep" commands are absolutely necessary, but I couldn't get this to work without them.