30

I'm trying to write a C program for blinking a LED on the Beaglebone. I know I can use the sysfs way...but I'd like to see if it is possible to get the same result mapping the physical address space with /dev/mem.

I have a header file, beaglebone_gpio.h wit the following contents:

#ifndef _BEAGLEBONE_GPIO_H_
#define _BEAGLEBONE_GPIO_H_

#define GPIO1_START_ADDR 0x4804C000
#define GPIO1_END_ADDR 0x4804DFFF
#define GPIO1_SIZE (GPIO1_END_ADDR - GPIO1_START_ADDR)
#define GPIO_OE 0x134
#define GPIO_SETDATAOUT 0x194
#define GPIO_CLEARDATAOUT 0x190

#define USR0_LED (1<<21)
#define USR1_LED (1<<22)
#define USR2_LED (1<<23)
#define USR3_LED (1<<24)

#endif

and then I have my C program, gpiotest.c

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

int main(int argc, char *argv[]) {
    volatile void *gpio_addr = NULL;
    volatile unsigned int *gpio_oe_addr = NULL;
    volatile unsigned int *gpio_setdataout_addr = NULL;
    volatile unsigned int *gpio_cleardataout_addr = NULL;
    unsigned int reg;
    int fd = open("/dev/mem", O_RDWR);

    printf("Mapping %X - %X (size: %X)\n", GPIO1_START_ADDR, GPIO1_END_ADDR, GPIO1_SIZE);

    gpio_addr = mmap(0, GPIO1_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO1_START_ADDR);

    gpio_oe_addr = gpio_addr + GPIO_OE;
    gpio_setdataout_addr = gpio_addr + GPIO_SETDATAOUT;
    gpio_cleardataout_addr = gpio_addr + GPIO_CLEARDATAOUT;

    if(gpio_addr == MAP_FAILED) {
        printf("Unable to map GPIO\n");
        exit(1);
    }
    printf("GPIO mapped to %p\n", gpio_addr);
    printf("GPIO OE mapped to %p\n", gpio_oe_addr);
    printf("GPIO SETDATAOUTADDR mapped to %p\n", gpio_setdataout_addr);
    printf("GPIO CLEARDATAOUT mapped to %p\n", gpio_cleardataout_addr);

    reg = *gpio_oe_addr;
    printf("GPIO1 configuration: %X\n", reg);
    reg = reg & (0xFFFFFFFF - USR1_LED);
    *gpio_oe_addr = reg;
    printf("GPIO1 configuration: %X\n", reg);

    printf("Start blinking LED USR1\n");
    while(1) {
        printf("ON\n");
        *gpio_setdataout_addr= USR1_LED;
        sleep(1);
        printf("OFF\n");
        *gpio_cleardataout_addr = USR1_LED;
        sleep(1);
    }

    close(fd);
    return 0;
}

The output is:

Mapping 4804C000 - 4804DFFF (size: 1FFF)
GPIO mapped to 0x40225000
GPIO OE mapped to 40225134
GPIO SEDATAOUTADDR mapped to 0x40225194
GPIO CLEARDATAOUTADDR mapped to 0x40225190
GPIO1 configuration: FE1FFFFF
GPIO1 configuratino: FE1FFFFF
Start blinking LED USR1
ON
OFF
ON
OFF
...

but I can't see the led blinking.

As you can see from the output of the program the configuration is correct, FE1FFFFF, is coherent since GPIO1_21, GPIO1_22, GPIO1_23 and GPIO1_24 are configured as outputs, each one driving a LED.

Any idea about the reason?

Salvatore
  • 1,145
  • 3
  • 21
  • 42
  • 2
    I have found the solution...it is only required to use MAP_SHARED in mmap instead of MAP_PRIVATE. I leave the question anyway. Maybe it will be useful for someone else. – Salvatore Oct 29 '12 at 15:44
  • 1
    It is perfectly fine practice to answer your own question, as long as others are given a fair chance to answer it as well. – Lundin Oct 30 '12 at 07:33

7 Answers7

12

Be careful. This works at first blush, but it directly writes to a register that the GPIO controller driver believes it owns. It will cause odd and hard to track down side effects, either on this GPIO line or on a GPIO that is in the same bank. For this to work reliably you need to disable the entire bank from the kernel GPIO driver.

user3078565
  • 121
  • 1
  • 3
9

The fix is:

pio_addr = mmap(0, GPIO1_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO1_START_ADDR);
Salvatore
  • 1,145
  • 3
  • 21
  • 42
  • 1
    Are you sure that this is the fix I thought you needed to use MAP_SHARED in mmap instead of MAP_PRIVATE. – Rex Logan Aug 01 '13 at 19:46
  • Can you explain why MAP_SHARED is needed? – ocirocir May 18 '17 at 09:50
  • 1
    @RicoRico MAP_SHARED implies that writes to the mmap'd region are committed back to the mapped memory area...while this does not happen for MAP_PRIVATE which results in data to be copied to a different region of memory due to COPY ON WRITE. – Salvatore May 19 '17 at 18:27
8

The code shown in the original post does not work with the latest Beaglebone Black and its associated 3.12 kernel. The control register offsets appear to have changed; the following code is verified to work properly:

#define GPIO0_BASE 0x44E07000
#define GPIO1_BASE 0x4804C000
#define GPIO2_BASE 0x481AC000
#define GPIO3_BASE 0x481AE000

#define GPIO_SIZE  0x00000FFF

// OE: 0 is output, 1 is input
#define GPIO_OE 0x14d
#define GPIO_IN 0x14e
#define GPIO_OUT 0x14f

#define USR0_LED (1<<21)
#define USR1_LED (1<<22)
#define USR2_LED (1<<23)
#define USR3_LED (1<<24)

int mem_fd;
char *gpio_mem, *gpio_map;

// I/O access
volatile unsigned *gpio;

static void io_setup(void)
{
    // Enable all GPIO banks
    // Without this, access to deactivated banks (i.e. those with no clock source set up) will (logically) fail with SIGBUS
    // Idea taken from https://groups.google.com/forum/#!msg/beagleboard/OYFp4EXawiI/Mq6s3sg14HoJ
    system("echo 5 > /sys/class/gpio/export");
    system("echo 65 > /sys/class/gpio/export");
    system("echo 105 > /sys/class/gpio/export");

    /* open /dev/mem */
    if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
            printf("can't open /dev/mem \n");
            exit (-1);
    }

    /* mmap GPIO */
    gpio_map = (char *)mmap(
            0,
            GPIO_SIZE,
            PROT_READ|PROT_WRITE,
            MAP_SHARED,
            mem_fd,
            GPIO1_BASE
    );

    if (gpio_map == MAP_FAILED) {
            printf("mmap error %d\n", (int)gpio_map);
            exit (-1);
    }

    // Always use the volatile pointer!
    gpio = (volatile unsigned *)gpio_map;

    // Get direction control register contents
    unsigned int creg = *(gpio + GPIO_OE);

    // Set outputs
    creg = creg & (~USR0_LED);
    creg = creg & (~USR1_LED);
    creg = creg & (~USR2_LED);
    creg = creg & (~USR3_LED);

    // Set new direction control register contents
    *(gpio + GPIO_OE) = creg;
}

int main(int argc, char **argv)
{
    io_setup();
    while (1) {
        // Set LEDs
        *(gpio + GPIO_OUT) = *(gpio + GPIO_OUT) | USR0_LED;
        *(gpio + GPIO_OUT) = *(gpio + GPIO_OUT) | USR1_LED;
        *(gpio + GPIO_OUT) = *(gpio + GPIO_OUT) | USR2_LED;
        *(gpio + GPIO_OUT) = *(gpio + GPIO_OUT) | USR3_LED;

        sleep(1);

        // Clear LEDs
        *(gpio + GPIO_OUT) = *(gpio + GPIO_OUT) & (~USR0_LED);
        *(gpio + GPIO_OUT) = *(gpio + GPIO_OUT) & (~USR1_LED);
        *(gpio + GPIO_OUT) = *(gpio + GPIO_OUT) & (~USR2_LED);
        *(gpio + GPIO_OUT) = *(gpio + GPIO_OUT) & (~USR3_LED);

        sleep(1);
    }

    return 0;
}

I post this here as it appears that mmap-ed access stopped working around the 3.8 kernel, and no one has posted a working solution since then. I had to reverse-engineer the control register offsets using the /sys/class/gpio interface; I hope this answer reduces some of the frustration associated with using the BeagleBone GPIOs with the newer kernels.

The code is licensed under a BSD license--feel free to use it wherever.

EDIT: user3078565 is correct in his answer above. You will need to disable the default user LED GPIO drivers either by setting their triggers to none or by completely hiding them from the kernel via editing the device tree. Failure to do this will result in the LEDs flashing as they are supposed to, but also occasionally having their states overridden by the kernel GPIO driver.

This was not an issue for my original application as it uses GPIO bank 0, which is largely ignored by the kernel GPIO drivers.

madscientist159
  • 550
  • 4
  • 12
  • Hi,thx for your code.Just wondering why you didn't close it using something like `close(mem_fd);`. As a newbie, I just want to know if closing it is necessary? – Yue Wang Feb 14 '14 at 00:31
  • Offset address Error. Correct It as 0x4d, 0x4e, 0x4f instead of 0x14d, 0x14e, 0x14f – Krishna Murthy Oct 23 '14 at 11:44
  • thanks for the tip on exporting before access banks, it's a hard one to debug. – DXM Oct 23 '17 at 23:16
4

You might also need to enable the clock for any piece of hardware you are trying to control in user-space. Fortunately, you can use dev/mem and mmap() to fiddle with the clock control register for your particular piece of hardware, like this code I wrote to enable SPI0: (define values are all from spruh73i.pdf register descriptions)

#define CM_PER_BASE     0x44E00000  /* base address of clock control regs */
#define CM_PER_SPI0_CLKCTRL     0x4C        /* offset of SPI0 clock control reg */

#define SPIO_CLKCTRL_MODE_ENABLE 2          /* value to enable SPI0 clock */

int mem;            // handle for /dev/mem

int  InitSlaveSPI(void) // maps the SPI hardware into user space
{
    char *pClockControl;    // pointer to clock controlregister block (virtualized by OS)
    unsigned int value;

    // Open /dev/mem:
    if ((mem = open ("/dev/mem", O_RDWR | O_SYNC)) < 0)
    {
        printf("Cannot open /dev/mem\n");
        return 1;
    }
    printf("Opened /dev/mem\n");

    // map a pointer to the clock control block:
    pClockControl = (char *)mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, mem, CM_PER_BASE);

    if(pClockControl == (char *)0xFFFFFFFF) 
    {
        printf("Memory map failed. error %i\n", (uint32_t)pClockControl);
        close( mem );
        return 2;
    }

    value = *(uint32_t *)(pClockControl + CM_PER_SPI0_CLKCTRL);
    printf("CM_PER_SPI0_CLKCTRL was 0x%08X\n", value);

    *(uint32_t *)(pClockControl + CM_PER_SPI0_CLKCTRL) = SPIO_CLKCTRL_MODE_ENABLE;

    value = *(uint32_t *)(pClockControl + CM_PER_SPI0_CLKCTRL);
    printf("CM_PER_SPI0_CLKCTRL now 0x%08X\n", value);

    munmap( pClockControl, 4096 );              // free this memory map element

Once I have executed this code fragment, I can access SPI0 registers using another mmap() pointer. If I don't enable the SPI0 module clock first, then I get a bus error when I try to access those SPI registers. Enabling the clock is persistent: Once enabled this way it stays on until you disable it, or maybe until you use the spidev and then close it, or reboot. So if your application is finished with the hardware you enabled, you might want to disable it to save power.

noufal
  • 940
  • 3
  • 15
  • 32
ggrotke
  • 41
  • 1
2

for enable GPIO banks....

enableClockModules () {
    // Enable disabled GPIO module clocks.
    if (mapAddress[(CM_WKUP_GPIO0_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] & IDLEST_MASK) {
      mapAddress[(CM_WKUP_GPIO0_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] |= MODULEMODE_ENABLE;
      // Wait for the enable complete.
      while (mapAddress[(CM_WKUP_GPIO0_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] & IDLEST_MASK);
    }
    if (mapAddress[(CM_PER_GPIO1_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] & IDLEST_MASK) {
      mapAddress[(CM_PER_GPIO1_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] |= MODULEMODE_ENABLE;
      // Wait for the enable complete.
      while (mapAddress[(CM_PER_GPIO1_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] & IDLEST_MASK);
    }
    if (mapAddress[(CM_PER_GPIO2_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] & IDLEST_MASK) {
      mapAddress[(CM_PER_GPIO2_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] |= MODULEMODE_ENABLE;
      // Wait for the enable complete.
      while (mapAddress[(CM_PER_GPIO2_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] & IDLEST_MASK);
    }
    if (mapAddress[(CM_PER_GPIO3_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] & IDLEST_MASK) {
      mapAddress[(CM_PER_GPIO3_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] |= MODULEMODE_ENABLE;
      // Wait for the enable complete.
      while (mapAddress[(CM_PER_GPIO3_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] & IDLEST_MASK);
    }
}

Where...

MMAP_OFFSET = 0x44C00000

MMAP_SIZE = 0x481AEFFF - MMAP_OFFSET

GPIO_REGISTER_SIZE = 4

MODULEMODE_ENABLE = 0x02

IDLEST_MASK = (0x03 << 16)

CM_WKUP = 0x44E00400

CM_PER = 0x44E00000

CM_WKUP_GPIO0_CLKCTRL = (CM_WKUP + 0x8)

CM_PER_GPIO1_CLKCTRL = (CM_PER + 0xAC)

CM_PER_GPIO2_CLKCTRL = (CM_PER + 0xB0)

CM_PER_GPIO3_CLKCTRL = (CM_PER + 0xB4)

I have written a small library that perhaps you might be interested. At the moment only works with digital pins.

Regards

Manuel Egío
  • 173
  • 1
  • 9
  • 2
    You don't need to call a destructor in C++, it's done automatically when the object is destructed (either by leaving the scope for stack allocated objects, or when using delete for heap allocated objects). Similarly, you don't need to extract enum to unsigned char, it's not faster indeed, since the compiler will likely reserve some registers to store the "constants" thus reducing its freedom for next code, leading to less efficient push/pop to the stack scheme. – xryl669 Jun 13 '15 at 21:48
1

REF: madscientist159

// OE: 0 is output, 1 is input
#define GPIO_OE 0x14d
#define GPIO_IN 0x14e
#define GPIO_OUT 0x14f
should be
// OE: 0 is output, 1 is input
#define GPIO_OE 0x4d
#define GPIO_IN 0x4e
#define GPIO_OUT 0x4f

unsigned int offset address derived from the unsigned char address

Krishna Murthy
  • 141
  • 1
  • 5
1

This anomaly appears to be an artifact of incomplete address decoding in the AM335x chip. It makes sense that 0x4D, 0x4E, and 0x4F work as offsets from the base address for accessing these registers. C/C++ pointer arithmetic multiplies these offsets by 4 to produce true offsets of 0x134, 0x138, and 0x13C. However a 'shadow' copy of these registers can be accessed through 0x14D, 0x14E, and 0x14F. I have verified that both sets of offsets work. I didn't bother trying 0x24D etc.

The GPIO_CLEARDATAOUT register can be accessed using offset 0x64 and the GPIO_SETDATAOUT register can be accessed using offset 0x65.

Bob Koen
  • 11
  • 1