embedded embedded-linux mmap gpio

embedded - Conduciendo Beaglebone GPIO a través de/dev/mem



embedded-linux mmap (7)

El código que se muestra en la publicación original no funciona con el último Beaglebone Black y su kernel 3.12 asociado. Las compensaciones del registro de control parecen haber cambiado; El siguiente código está verificado para funcionar 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 kernel 3.8, y nadie ha publicado una solución de trabajo desde entonces. Tuve que aplicar ingeniería inversa a las compensaciones de registro de control utilizando la interfaz / sys / class / gpio; Espero que esta respuesta reduzca 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, siéntase libre de usarlo en cualquier lugar.

EDIT: user3078565 es correcto en su respuesta anterior. Deberá deshabilitar los controladores de GPIO LED de usuario predeterminados ya sea configurando sus disparadores en ninguno o ocultándolos completamente del kernel mediante la edición del árbol de dispositivos. Si no lo hace, los LED parpadearán como se supone que deben hacerlo, pero también pueden ocasionar que el controlador GPIO del kernel anule sus estados.

Esto no fue un problema para mi aplicación original, ya que utiliza el banco 0 de GPIO, que los controladores GPIO del kernel ignoran en gran medida.

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 asignando el espacio de direcciones físicas con / dev / mem.

Tengo un archivo de encabezado, beaglebone_gpio.h con el siguiente contenido:

#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 de 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 del 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 una de las cuales controla un LED.

¿Alguna idea sobre la razón?


Es posible que también deba habilitar el reloj para cualquier pieza de hardware que esté intentando controlar en el espacio de usuario. Afortunadamente, puede usar dev / mem y mmap () para jugar con el registro de control del reloj para su hardware en particular, como este código que escribí para habilitar SPI0: (los valores de definición son todos de las descripciones de los registros spruh73i.pdf)

#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 haya ejecutado este fragmento de código, puedo acceder a los registros SPI0 utilizando otro puntero mmap (). Si no habilito el reloj del módulo SPI0 primero, luego obtengo un error de bus cuando intento acceder a esos registros SPI. La habilitación del reloj es persistente: una vez habilitada de esta manera, permanece encendida hasta que la deshabilita, 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 deshabilitarlo para ahorrar energía.


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

Se puede acceder al registro GPIO_CLEARDATAOUT utilizando el desplazamiento 0x64 y se puede acceder al registro GPIO_SETDATAOUT utilizando el desplazamiento 0x65.


La solución es:

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


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 compensación int sin firmar derivada de la dirección de char sin firmar


Ten cuidado. Esto funciona a primera vista, pero se 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 se encuentre en el mismo banco. Para que esto funcione de manera confiable, debe deshabilitar todo el banco desde el controlador GPIO del kernel.


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

He escrito una pequeña biblioteca que quizás te pueda interesar. Por el momento solo funciona con pines digitales.

Saludos