2012-10-29 13 views
28

Próbuję napisać program C do mrugania diody LED na Beaglebone. Wiem, że mogę używać metody sysfs ... ale chciałbym zobaczyć, czy możliwe jest uzyskanie tego samego wyniku mapowania fizycznej przestrzeni adresowej za pomocą/dev/mem.Prowadzenie GPIO Beaglebone przez/dev/mem

Mam plik nagłówka, beaglebone_gpio.h dowcip o następującej treści:

#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 

a potem mam program 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; 
} 

Wyjście jest:

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

ale nie widzę migającej diody.

Jak widać z wyjścia programu konfiguracja jest poprawna, FE1FFFFF, spójny od GPIO1_21, GPIO1_22, GPIO1_23 i GPIO1_24 są skonfigurowane jako wyjścia, każdy prowadzenia LED.

Jakieś pojęcie o przyczynie?

+1

znalazłem rozwiązanie ... to jest wymagane tylko do korzystania MAP_SHARED w mmap zamiast MAP_PRIVATE. I tak zostawiam pytanie. Może to będzie przydatne dla kogoś innego. – salvo

+0

Dobrze jest odpowiedzieć na własne pytanie, o ile inni mają dość szansy, aby na nie odpowiedzieć. – Lundin

Odpowiedz

8

Rozwiązaniem jest:

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

Czy jesteś pewien, że jest to poprawka, którą myślałem, że potrzebujesz MAP_SHARED w mmap zamiast MAP_PRIVATE. –

+0

Masz rację ... dzięki – salvo

+0

Czy możesz wyjaśnić, dlaczego MAP_SHARED jest potrzebna? – RicoRico

12

Bądź ostrożny. Działa to na pierwszy rzut oka, ale bezpośrednio zapisuje do rejestru, który sterownik sterownika GPIO uważa za swój. Wywołuje to dziwne i trudne do wyśledzenia efekty uboczne, albo na tej linii GPIO, albo na GPIO, który jest w tym samym banku. Aby to działało niezawodnie, musisz wyłączyć cały bank ze sterownika GPIO jądra.

+0

To prawda ... dziękuję. – salvo

+1

Czy istnieje sposób wyłączenia sterownika kontrolera GPIO, aby nie stanowiło to problemu? – sheridp

8

Kod widoczny w oryginalnym wpisie nie działa z najnowszą wersją Beaglebone Black i powiązanym z nią jądrem 3.12. Wydaje się, że przesunięcia rejestru kontrolnego uległy zmianie; następujący kod jest weryfikowana będzie działał prawidłowo:

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

mogę napisać to tutaj, ponieważ wydaje się, że dostęp mmap-ed przestał działać wokół jądra 3.8, i nikt nie opublikował roztworu roboczego od tamtego czasu. Musiałem odtworzyć mechanizmy korekcji rejestru kontrolnego za pomocą interfejsu/sys/class/gpio; Mam nadzieję, że ta odpowiedź zmniejszy część frustracji związanej z używaniem GPIO BeagleBone z nowszymi jądrami.

Kod jest licencjonowany na podstawie licencji BSD - możesz go używać wszędzie.

EDYCJA: user3078565 ma poprawną odpowiedź w powyższej odpowiedzi. Konieczne będzie wyłączenie domyślnych sterowników LED GPIO użytkownika poprzez ustawienie ich wyzwalaczy na none lub całkowite ukrycie ich od jądra poprzez edycję drzewa urządzeń. Niewykonanie tej czynności spowoduje, że diody LED będą migać, tak jak powinny, ale czasami również ich status zostanie nadpisany przez sterownik GPIO jądra.

Nie stanowiło to problemu dla mojej pierwotnej aplikacji, ponieważ wykorzystuje bank GPIO 0, który jest w dużej mierze ignorowany przez sterowniki jądra systemu GPIO.

+0

Cześć, thx za twój kod. Zastanawiam się tylko, dlaczego go nie zamykasz, używając czegoś w stylu 'close (mem_fd);'. Jako początkujący, chcę tylko wiedzieć, czy zamknięcie jest konieczne? –

+0

Błąd przesunięcia. Popraw to jako 0x4d, 0x4e, 0x4f zamiast 0x14d, 0x14e, 0x14f –

+0

dzięki za napiwek na eksport przed dostępem banków, ciężko jest debugować. – DXM

4

Może być również konieczne włączenie zegara dla dowolnego sprzętu, który próbujesz kontrolować w przestrzeni użytkownika. Na szczęście możesz użyć dev/mem i mmap() do fiddle z rejestrem kontroli zegara dla twojego określonego sprzętu, takiego jak ten napisany przeze mnie, aby umożliwić SPI0: (zdefiniować wszystkie wartości z spruh73i.Opisy pdf rejestru)

#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 

Raz wykonaliśmy ten fragment kodu, można uzyskać dostęp za pomocą innego SPI0 rejestrów mmap() wskaźnika. Jeśli nie włączę najpierw zegara modułu SPI0, wtedy otrzymam błąd magistrali, gdy próbuję uzyskać dostęp do rejestrów SPI. Włączenie zegara jest trwałe: Po włączeniu w ten sposób pozostaje włączone, dopóki go nie wyłączysz, lub może, dopóki nie użyjesz spidev, a następnie zamkniesz lub zrestartujesz komputer. Jeśli więc twoja aplikacja zostanie zakończona z włączonym sprzętem, możesz chcieć go wyłączyć, aby oszczędzać energię.

1

Zob 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 przesunięcia adresu otrzymanego z adresu unsigned char

1

Ta anomalia wydaje się być artefaktem niecałkowitego dekodowanie adresów w układzie AM335x. Ma sens, że 0x4D, 0x4E i 0x4F działają jako przesunięcia z adresu bazowego, aby uzyskać dostęp do tych rejestrów. Arytmetyka wskaźnika C/C++ mnoży te przesunięcia o 4, aby uzyskać prawdziwe przesunięcia 0x134, 0x138 i 0x13C. Jednak kopia "cienia" tych rejestrów może być dostępna poprzez 0x14D, 0x14E i 0x14F. Weryfikowałem, że działają oba zestawy przesunięć. Nie próbowałem próbować 0x24D itd.

Dostęp do rejestru GPIO_CLEARDATAOUT można uzyskać za pomocą przesunięcia 0x64, a do rejestru GPIO_SETDATAOUT można uzyskać dostęp za pomocą przesunięcia 0x65.

2

dla umożliwienia bankom 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); 
    } 
} 

Gdzie ...

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)

Napisałem small library, że być może zainteresuje Cię to. W tej chwili działa tylko z pinami cyfrowymi.

Pozdrowienia

+1

Nie trzeba wywoływać destruktora w C++, robi się to automatycznie, gdy obiekt jest niszczony (albo przez pozostawienie zakresu dla przydzielonych obiektów na stosie, albo podczas używania usuwania dla przydzielonych obiektów sterty). Podobnie, nie musisz wyodrębniać enum do unsigned char, to nie jest tak naprawdę szybsze, ponieważ kompilator prawdopodobnie zarezerwuje kilka rejestrów do przechowywania "stałych", zmniejszając w ten sposób swą wolność do następnego kodu, prowadząc do mniej wydajnego push/pop do schemat stosu. – xryl669

Powiązane problemy