Conduite BEAGLEBONE GPIO à travers / dev / mem
J'essaie d'écrire un programme C pour clignoter une LED sur le Beaglebone. Je sais que je peux utiliser la manière sysfs...mais j'aimerais voir s'il est possible d'obtenir le même résultat 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
Et 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 GPIOn");
exit(1);
}
printf("GPIO mapped to %pn", gpio_addr);
printf("GPIO OE mapped to %pn", gpio_oe_addr);
printf("GPIO SETDATAOUTADDR mapped to %pn", gpio_setdataout_addr);
printf("GPIO CLEARDATAOUT mapped to %pn", gpio_cleardataout_addr);
reg = *gpio_oe_addr;
printf("GPIO1 configuration: %Xn", reg);
reg = reg & (0xFFFFFFFF - USR1_LED);
*gpio_oe_addr = reg;
printf("GPIO1 configuration: %Xn", reg);
printf("Start blinking LED USR1n");
while(1) {
printf("ONn");
*gpio_setdataout_addr= USR1_LED;
sleep(1);
printf("OFFn");
*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 à partir de la sortie du programme la configuration est correcte, FE1FFFFF, est cohérent puisque GPIO1_21, GPIO1_22, GPIO1_23 et GPIO1_24 sont configurés comme sorties, chacun conduisant une LED.
Une idée de la raison?
7 réponses
Attention. Cela fonctionne à première vue, mais il écrit directement dans un registre que le pilote du contrôleur GPIO croit posséder. Cela entraînera des effets secondaires étranges et difficiles à détecter, que ce soit sur cette ligne GPIO ou 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 du pilote GPIO du noyau.
Le correctif est:
pio_addr = mmap(0, GPIO1_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO1_START_ADDR);
Le code affiché dans le post original 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 mmap-ed access a cessé de fonctionner autour du noyau 3.8, et personne n'a posté de solution de travail depuis lors. J'ai dû désosser les 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 nouveaux noyaux.
Le code est sous licence BSD-n'hésitez pas à l'utiliser n'importe où.
EDIT: user3078565 est correct dans sa réponse ci-dessus. Vous devrez désactiver les pilotes GPIO par défaut en définissant leurs déclencheurs sur Aucun ou en les cachant complètement du noyau via l'édition de l'arborescence des périphériques. Si vous ne le faites pas les voyants clignoteront comme ils sont censés le faire, mais aussi parfois avoir leurs états remplacés par le pilote GPIO du noyau.
Ce n'était pas un problème pour mon application d'origine car elle utilise GPIO bank 0, qui est largement ignorée par les pilotes GPIO du noyau.
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 pour votre matériel particulier, comme ce code que j'ai écrit pour activer SPI0: (définir les valeurs sont 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 le module SPI0 horloge d'abord, puis je reçois une erreur de bus quand j'essaie d'accéder à ces registres SPI. L'activation de l'horloge est persistante: une fois activée de cette façon, elle reste activée jusqu'à ce que vous la désactiviez, ou peut-être jusqu'à ce que vous utilisiez le spidev, puis le fermiez ou redémarriez. Donc, si votre application est terminée avec le matériel que vous avez activé, vous pouvez le désactiver pour économiser de l'énergie.
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
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 Que peut-être vous pourriez être intéressé. À l'heure actuelle ne fonctionne qu'avec des broches numériques.
Cordialement
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
Adresse de décalage int non signée dérivée de l'adresse char non signée
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 de l'adresse de base pour accéder à ces registres. L'arithmétique du pointeur c / c++ multiplie ces décalages par 4 pour produire des décalages réels de 0x134, 0x138 et 0x13C. cependant, une copie' shadow ' de ces registres est accessible via 0x14D, 0x14E et 0x14F. j'ai vérifié que les deux ensembles de décalages fonctionnent. Je n'ai pas pris la peine d'essayer 0x24D etc.
Le registre GPIO_CLEARDATAOUT est accessible à l'aide de offset 0x64 et le registre GPIO_SETDATAOUT est accessible à l'aide de offset 0x65.