Conducción de Beaglebone GPIO a través de / dev / mem


Estoy tratando de escribir un programa en C para parpadear un LED en el Beaglebone. Sé que puedo usar el modo sysfs...pero me gustaría ver si es posible obtener el mismo resultado mapeando el espacio de direcciones físicas con /dev/mem.

Tengo un archivo de cabecera, 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

Y luego tengo mi programa C, 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;
}

La salida es:

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
...

Pero no puedo ver el led parpadeando.

Como se puede ver en la salida de la programa la configuración es correcta, FE1FFFFF, es coherente ya que GPIO1_21, GPIO1_22, GPIO1_23 y GPIO1_24 están configurados como salidas, cada uno manejando un LED.

¿Alguna idea sobre la razón?

Author: Salvatore, 2012-10-29

7 answers

Ten cuidado. Esto funciona a primera vista, pero escribe directamente en un registro que el controlador del controlador GPIO cree que posee. Causará efectos secundarios extraños y difíciles de rastrear, ya sea en esta línea GPIO o en un GPIO que está en el mismo banco. Para que esto funcione de manera confiable, debe deshabilitar todo el banco desde el controlador GPIO del núcleo.

 12
Author: user3078565,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-07 21:29:47

La solución es:

pio_addr = mmap(0, GPIO1_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO1_START_ADDR);
 8
Author: Salvatore,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-02 07:24:39

El código mostrado en el post original no funciona con el último Beaglebone Black y su núcleo 3.12 asociado. Las compensaciones del registro de control parecen haber cambiado; se verifica que el siguiente código funciona correctamente:

#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;
}

Publico esto aquí ya que parece que el acceso mmap-ed dejó de funcionar alrededor del núcleo 3.8, y nadie ha publicado una solución que funcione desde entonces. Tuve que aplicar ingeniería inversa a las compensaciones del registro de control utilizando la interfaz / sys / class / gpio; espero que esta respuesta reduce parte de la frustración asociada con el uso de los GPIOs de BeagleBone con los núcleos más nuevos.

El código está licenciado bajo una licencia BSD feel siéntase libre de usarlo donde quiera.

EDITAR: user3078565 es correcto en su respuesta anterior. Tendrá que desactivar los controladores GPIO por defecto, ya sea configurando sus disparadores en ninguno o escondiéndolos completamente del núcleo a través de la edición del árbol de dispositivos. Si no se hace esto, los led parpadearán, ya que se supone que deben, pero también de vez en cuando tener sus estados anulados por el controlador GPIO del núcleo.

Esto no fue un problema para mi aplicación original, ya que utiliza GPIO bank 0, que es ignorado en gran medida por los controladores GPIO del núcleo.

 8
Author: madscientist159,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-02 01:17:26

Es posible que también necesite habilitar el reloj para cualquier pieza de hardware que esté tratando de controlar en el espacio de usuario. Afortunadamente, puedes usar dev / mem y mmap () para jugar con el registro de control de reloj para tu pieza de hardware en particular, como este código que escribí para habilitar SPI0: (definir valores son todos de spruh73i.pdf registro descripciones)

#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

Una vez que he ejecutado este fragmento de código, puedo acceder a los registros SPI0 usando otro puntero mmap (). Si no habilito el módulo SPI0 clock primero, luego recibo un error de bus cuando intento acceder a esos registros SPI. Habilitar el reloj es persistente: Una vez habilitado de esta manera, permanece encendido hasta que lo deshabilite, o tal vez hasta que use el spidev y luego lo cierre, o reinicie. Por lo tanto, si su aplicación ha terminado con el hardware que habilitó, es posible que desee desactivarla para ahorrar energía.

 4
Author: ggrotke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-29 06:19:17

Para habilitar bancos GPIO....

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);
    }
}

Dónde...

MMAP_OFFSET = 0x44C00000

MMAP_SIZE = 0x481AEFFF-MMAP_OFFSET

GPIO_REGISTER_SIZE = 4

MODULEMODE_ENABLE = 0x02

IDLEST_MASK = (0x03

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)

He escrito una pequeña biblioteca que quizás podría interesarle. Por el momento solo funciona con pines digitales.

Saludos

 2
Author: Manuel Egío,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-02 08:33:03

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

Dirección de desplazamiento int sin signo derivada de la dirección char sin signo

 1
Author: Krishna Murthy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-23 11:49:08

Esta anomalía parece ser un artefacto de decodificación incompleta de direcciones en el chip AM335x. Tiene sentido que 0x4D, 0x4E y 0x4F funcionen como compensaciones desde la dirección base para acceder a estos registros. La aritmética del puntero C / C++ multiplica estas compensaciones por 4 para producir compensaciones verdaderas de 0x134, 0x138 y 0x13C. Sin embargo, se puede acceder a una copia' sombra ' de estos registros a través de 0x14D, 0x14E y 0x14F. He verificado que ambos conjuntos de compensaciones funcionan. No me molesté en probar 0x24D sucesivamente.

Se puede acceder al registro GPIO_CLEARDATAOUT usando el offset 0x64 y al registro GPIO_SETDATAOUT usando el offset 0x65.

 1
Author: Bob Koen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-30 16:39:10