web-dev-qa-db-fra.com

Conduite de Beaglebone GPIO via / dev / mem

J'essaie d'écrire un programme C pour faire clignoter une LED sur le Beaglebone. Je sais que je peux utiliser la méthode sysfs ... mais j'aimerais voir s'il est possible d'obtenir le même résultat en mappant l'espace d'adressage physique avec/dev/mem.

J'ai un fichier d'en-tête, beaglebone_gpio.h avec le contenu suivant:

#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

puis j'ai mon programme 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 sortie est:

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

mais je ne vois pas la led clignoter.

Comme vous pouvez le voir sur la sortie du programme, la configuration est correcte, FE1FFFFF, est cohérente car GPIO1_21, GPIO1_22, GPIO1_23 et GPIO1_24 sont configurés en sorties, chacune pilotant une LED.

Une idée de la raison?

28
Salvatore

La solution est:

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

Faites attention. Cela fonctionne à première vue, mais il écrit directement dans un registre que le pilote du contrôleur GPIO pense posséder. Cela entraînera des effets secondaires étranges et difficiles à détecter, soit sur cette ligne GPIO, soit sur un GPIO qui se trouve dans la même banque. Pour que cela fonctionne de manière fiable, vous devez désactiver toute la banque à partir du pilote GPIO du noyau.

12
user3078565

Le code affiché dans l'article d'origine ne fonctionne pas avec le dernier Beaglebone Black et son noyau 3.12 associé. Les décalages du registre de contrôle semblent avoir changé; le code suivant est vérifié pour fonctionner correctement:

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

Je poste ceci ici car il semble que l'accès mmap-ed ait cessé de fonctionner autour du noyau 3.8, et personne n'a posté de solution de travail depuis lors. J'ai dû inverser l'ingénierie des décalages du registre de contrôle en utilisant l'interface/sys/class/gpio; J'espère que cette réponse réduit une partie de la frustration associée à l'utilisation des GPIO BeagleBone avec les noyaux plus récents.

Le code est sous licence BSD - n'hésitez pas à l'utiliser partout.

EDIT: user3078565 est correct dans sa réponse ci-dessus. Vous devrez désactiver les pilotes GPIO LED par défaut de l'utilisateur en définissant leurs déclencheurs sur aucun ou en les masquant complètement du noyau via la modification de l'arborescence des périphériques. Si vous ne le faites pas, les voyants clignotent comme ils sont censés le faire, mais leur état est parfois remplacé par le pilote GPIO du noyau.

Ce n'était pas un problème pour mon application d'origine car elle utilise la banque GPIO 0, qui est largement ignorée par les pilotes GPIO du noyau.

8
madscientist159

Vous devrez peut-être également activer l'horloge pour tout matériel que vous essayez de contrôler dans l'espace utilisateur. Heureusement, vous pouvez utiliser dev/mem et mmap () pour jouer avec le registre de contrôle d'horloge de votre matériel particulier, comme ce code que j'ai écrit pour activer SPI0: (les valeurs définies proviennent toutes des descriptions de registre 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

Une fois que j'ai exécuté ce fragment de code, je peux accéder aux registres SPI0 en utilisant un autre pointeur mmap (). Si je n'active pas d'abord l'horloge du module SPI0, j'obtiens une erreur de bus lorsque j'essaie d'accéder à ces registres SPI. L'activation de l'horloge est persistante: une fois activée, elle reste allumée jusqu'à ce que vous le désactivez, ou peut-être jusqu'à ce que vous utilisiez le spidev, puis le fermez ou redémarrez. Donc, si votre application a terminé avec le matériel que vous avez activé, vous voudrez peut-être le désactiver pour économiser de l'énergie.

4
ggrotke

pour activer les banques 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);
    }
}

Où...

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)

J'ai écrit une petite bibliothèque qui pourrait peut-être vous intéresser. Pour le moment, ne fonctionne qu'avec des broches numériques.

Cordialement

2
Manuel Egío

Cette anomalie semble être un artefact de décodage d'adresse incomplet dans la puce AM335x. Il est logique que 0x4D, 0x4E et 0x4F fonctionnent comme des décalages par rapport à l'adresse de base pour accéder à ces registres. L'arithmétique du pointeur C/C++ multiplie ces décalages par 4 pour produire de vrais décalages de 0x134, 0x138 et 0x13C. Cependant, une copie "fantôme" de ces registres est accessible via 0x14D, 0x14E et 0x14F. J'ai vérifié que les deux ensembles de compensations fonctionnent. Je n'ai pas pris la peine d'essayer 0x24D, etc.

Le registre GPIO_CLEARDATAOUT est accessible à l'aide de l'offset 0x64 et le registre GPIO_SETDATAOUT est accessible à l'aide de l'offset 0x65.

1
Bob Koen

RÉF: 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

adresse offset int non signée dérivée de l'adresse char non signée

1
Krishna Murthy