Operace s framebufferem na Raspberry Pi

21. 1. 2016
Doba čtení: 19 minut

Sdílet

Raspberry Pi zajisté není nutné čtenářům podrobně představovat. Jedním z cílů projektu bylo vytvořit počítač, jehož fungování bude snadno pochopitelné. Tento cíl se sice používáním vysokoúrovňových jazyků a knihoven poněkud vytratil, ale nikdy není pozdě se seznámit i s hardware. Začneme u framebufferu.

Obsah

1. Operace s framebufferem na Raspberry Pi

2. Čipy s architekturou VideoCore

3. BCM2835 a BCM2836 použité v projektu Raspberry Pi

4. Přístup k framebufferu na nejnižší úrovni (mailboxy a přímý přístup do videopaměti)

5. Využití Linux framebufferu

6. První demonstrační příklad: přečtení základních informací o framebufferu

7. Druhý demonstrační příklad: přečtení identifikace čipu, velikosti framebufferu a délky obrazového řádku

8. Třetí demonstrační příklad: přístup do framebufferu s využitím mmap

9. Repositář s demonstračními příklady

10. Funkce použité v demonstračních příkladech

11. Odkazy na Internetu

1. Operace s framebufferem na Raspberry Pi

Jednodeskový mikropočítač Raspberry Pi se v současnosti používá v mnoha projektech, ať již se jedná o jeho využití při výuce, implementaci různých řídicích systémů, multimediálních center, jednoúčelových serverů či o levný desktop nebo webový kiosek. Jedním z cílů, který si tvůrci tohoto projektu vytkli, bylo navržení takového mikropočítače, jehož princip fungování bude tak jednoduchý, že i relativní začátečníci nebudou mít problémy s pochopením, co a proč se interně v jednotlivých integrovaných obvodech, z nichž se Raspberry Pi skládá, odehrává (ostatně právě i z tohoto důvodu se Raspberry Pi často, a to nikoli neprávem, srovnává se slavným ZX Spectrem). Tento cíl byl – až na jednu výjimku zmíněnou v dalším textu – splněn; Raspberry Pi například nemá ani vlastní BIOS, bootovací proces je poměrně přesně popsán, stejně jako způsob ovládání jednotlivých pinů (GPIO) apod.

Mnoho projektů či výukových seriálů, v nichž se Raspberry Pi používá, se zaměřuje hlavně na použití GPIO. Na tom samozřejmě není nic špatného, ovšem nesmíme zapomenout na to, že ten stejný čip, který obsahuje jádro ARM, zajišťuje bootování, poskytuje vývojářům všechny funkce dostupné přes GPIO (SPI, PWM atd.), obsahuje i další neméně užitečnou část. Tou je video řadič spojený s programovatelným grafickým akcelerátorem. V dnešním článku si nejprve popíšeme, jak lze (prozatím čistě teoreticky) ovládat takzvaný framebuffer přímým přístupem k řídicím registrům tohoto video řadiče a přímým přístupem do paměti framebufferu. Posléze se zaměříme na použití rozhraní k video řadiči poskytovaného linuxovým kernelem. Ten totiž umožňuje aplikacím ovládat video řadič přes speciální zařízení /dev/fb0. Díky tomu je možné na Raspberry Pi vytvářet aplikace s grafickým výstupem bez nutnosti spouštění systému X Window, správců oken, desktopových prostředí a dalších mnohdy zbytečných vrstev mezi uživatelskou aplikací a hardwarem (aplikací s přímým přístupem k framebufferu existuje velké množství, například prohlížeč obrázků fbi, multimediální přehrávač MPlayer, webové prohlížeče links2 a NetSurf, mnohé hry založené na knihovně SDL atd.).

Poznámka: pro přístup k framebufferu lze použít i knihovnu DirectFB nebo (nepřímo) SDL, ale dnes se zaměříme skutečně na tu nejjednodušší možnost, tj. na použití zařízení /dev/fb0 z céčkového programu. Mezi našimi programy a hardwarem tak bude stát jen jediná mezivrstva.

2. Čipy s architekturou VideoCore

To, že mikropočítače Raspberry Pi mají s ohledem na jejich cenu a energetické nároky velmi dobré multimediální schopnosti, je zajištěno použitím čipu založeného na architektuře grafických (spíše však obecně multimediálních) procesorů pojmenovaných VideoCore. Tato architektura byla vyvinuta firmou Alphamosaic a současně je vlastněna společností Broadcom, což je ostatně jeden z důvodů, proč tuto architekturu nenalezneme u dalších výrobců čipů s jádrem ARM (ovšem v této oblasti existuje velká konkurence, takže se setkáme i s dalšími podobnými GPU, například od firem TI, NVidia či Freescale). Architektura VideoCore je plně programovatelná, což je poměrně důležité, protože je pro ni možné vytvářet nové kodeky, používat kodeky, které nemusí být licencovány, využít VideoCore i v těch aplikacích, které nepotřebují vysoký multimediální výkon, ale například provádí mnoho operací s maticemi atd. Právě poslední možnost, tj. použití VideoCore pro urychlení „běžných“ aplikací, otevírá nové možnosti i pro mikropočítače typu Raspberry Pi (SoC použitý v Raspberry Pi je navržen tak, že hlavní procesor by měl mít na starost pouze řízení GPU, což je splněno při jeho použití v multimediálních centrech apod.).

V současnosti existují minimálně čtyři generace grafických procesorů založených na VideoCore. Tyto procesory jsou většinou umístěny na jednom čipu s hlavním procesorem a řadičem periferních zařízení, takže vzniká SoC (System on a Chip), což je ostatně i případ mikropočítače Raspberry Pi. Podívejme se na některé čipy obsahující VideoCore a popř. i hlavní procesor a další pomocné obvody:

# Čip GPU CPU
1 VC01 VideoCore 1 ×
2 BCM2702 (VC02) VideoCore 2 ×
3 BCM2705 (VC05) VideoCore 2 ×
4 BCM2091 VideoCore 4 ?
5 BCM2722 VideoCore 2 ×
6 BCM2724 VideoCore 2 ×
7 BCM2727 VideoCore 3 ×
8 BCM11181 VideoCore 3 ×
9 BCM2763 VideoCore 4 ×
10 BCM2820 VideoCore 4 ARM1176
11 BCM2835 VideoCore 4 ARM1176 (standardně 700 MHz)
12 BCM2836 VideoCore 4 Quad-core Cortex-A7 (standardně 900 MHz)
13 BCM11182 VideoCore 4 ×
14 BCM11311 VideoCore 4 Dual-core Cortex-A9
15 BCM21654 VideoCore 4 Cortex-A9 + Cortex-R4
16 BCM21654G VideoCore 4 Cortex-A9 (až do 1 GHz)
17 BCM21663 VideoCore 4 Cortex-A9 (až do 1.2 GHz)
18 BCM21664 VideoCore 4 Cortex-A9 (až do 1 GHz)
19 BCM21664T VideoCore 4 Cortex-A9 (až do 1.2 GHz)
20 BCM28150 VideoCore 4 Dual-core Cortex-A9
21 BCM21553 VideoCore 4 ARM11
22 BCM28145/28155 VideoCore 4 Dual-core Cortex-A9 (až do 1.2 GHz)
23 BCM23550 VideoCore 4 Quad-core Cortex-A7 (až do 1.2 GHz)

3. BCM2835 a BCM2836 použité v projektu Raspberry Pi

Z tabulky vypsané v předchozí kapitole nás nyní budou zajímat především řádky číslo 11 a 12, protože v mikropočítačích Raspberry Pi nalezneme buď čip BMC2835 nebo BMC2836:

Mikropočítač SoC
Raspberry Pi 1 Model A BCM2835
Raspberry Pi 1 Model A+ BCM2835
Raspberry Pi 1 Model B BCM2835
Raspberry Pi 1 Model B+ BCM2835
Raspberry Pi Zero BCM2835
   
Raspberry Pi 2 Model B BCM2836

Důležité je si uvědomit, že zatímco hlavní procesor je v čipu BCM2835 (jednojádrový ARM11) odlišný od procesoru použitého v čipu BCM2836 (čtyřjádrový ARM Cortex-A7), samotný grafický procesor má prakticky stejné vlastnosti, což se týká jak maximálních podporovaných rozlišení, tak i možností jeho naprogramování. I z tohoto důvodu v dalším textu nebudu rozlišovat mezi jednotlivými modely Raspberry Pi.

Poznámka: všechny příklady byly odzkoušeny na Raspberry Pi Model B+ se systémem Raspbian.

Programovatelnost GPU čipů BCM2835 a BCM2836 je v současnosti omezena tím, že základní funkcionalita je poskytována binárním blobem (který teoreticky může obsahovat jakýkoli kód). Až nad tímto blobem se nachází open source driver, který poskytuje své rozhraní dalším knihovnám (EGL, OpenMax) či přímo aplikacím.

4. Přístup k framebufferu na nejnižší úrovni (mailboxy a přímý přístup do videopaměti)

GPU, který je implementován na čipech BCM2835 a BCM2836, obsahuje i programovatelný video řadič založený na použití klasického framebufferu. Zjednodušeně řečeno lze říci, že video řadič postupně načítá z nadefinované oblasti fyzické paměti data, tato data interpretuje jako barvy pixelů a následně tyto barvy posílá společně se synchronizačními signály na HDMI popř. je slučuje do kompozitního video signálu posílaného na konektor RCA (či na jack u některých modelů). Video řadič je tedy nejprve nutné nakonfigurovat – předat mu informace o framebufferu a taktéž informace potřebné pro správné časování signálů posílaných na monitor. Na nejnižší úrovni se tyto informace předávají přes takzvané mailboxy, které si můžeme představit jako malé fronty (FIFO) s kapacitou osmi slov o šířce 32 bitů. Do těchto front může z jedné strany zapisovat procesor (ARM) a z druhé strany může zprávy vybírat GPU či naopak.

Nastavit či přečíst lze tyto informace:

  1. Alokace a dealokace paměti pro framebuffer (čistě teoreticky lze tuto operaci provést i za běhu systému, prakticky se provádí jednou při bootování)
  2. Nastavení fyzického rozlišení (nemusí odpovídat virtuálnímu rozlišení, GPU může provést změnu měřítka)
  3. Nastavení virtuálního rozlišení (může být menší než fyzické rozlišení, potom lze obrazem posouvat či měnit měřítko)
  4. Nastavení barevné hloubky (počtu bitů na pixel)
  5. Specifikace kódování pixelů (zde lze jen volit mezi BGR a RGB)
  6. Specifikace alfa kanálu (ignorován, použit, použit, ale s invertovanými hodnotami)
  7. Nastavení barvové palety
  8. Nastavení offsetu mezi obrazovými řádky (takzvaný pitch)
  9. Konfigurace kurzoru (autor článku ještě bude muset prozkoumat, jestli tato volba skutečně funguje :-)

5. Využití Linux framebufferu

Přístup ke GPU přes mailboxy není zcela triviální, protože je nutné zajistit, aby se zapisované hodnoty neukládaly pouze do cache, ale skutečně se „propsaly“ až do fyzického řídicího registru. Taktéž je nutné testovat, zda GPU potvrdil operaci či vrátil požadovanou hodnotu atd. Z tohoto důvodu se prozatím použití mailboxů vyhneme a využijeme namísto toho služeb poskytovaných kernelem. Kernel sám zajišťuje nízkoúrovňové ovládání framebufferu a v uživatelském prostoru (kde budou spouštěny naše demonstrační příklady) se k funkcím kernelu souvisejícím s framebufferem dostaneme přes speciální zařízení /dev/fb0. Přístup k tomuto zařízení mají všichni uživatelé ve skupině video, takže si pro jistotu nechte vypsat:

ls -la /dev/fb0
crw-rw---- 1 root video 29, 0 led 20 19:28 /dev/fb0

(důležité je jméno skupiny, tedy skutečně video)

Dále je vhodné se ujistit, že aktuální uživatel je skutečně přidán do skupiny video (což by v Raspbianu mělo být splněno):

groups
pi adm cdrom sudo dip video plugdev lpadmin sambashare

Pokud aktuální uživatel není ve skupině video, stačí ho tam přidat (a znovu se přihlásit! nebo použít newgrp), popř. spouštět všechny další příkazy a příklady pod právy roota (su/sudo, nicméně doporučuji spíše první možnost).

Se zařízením /dev/fb0 lze provádět tři základní operace:

  1. Čtením se získají barvy jednotlivých pixelů.
  2. Zápisem se mění barvy pixelů (postupně řádek po řádku).
  3. Operací ioctl se získávají další údaje o framebufferu či se naopak mění jeho konfigurace. Díky existenci této operace bylo možné ponechat nízký počet služeb kernelu, protože většina pokročilejších operací se provádí právě přes ioctl aplikované na nějaké speciální zařízení.

Podívejme se nyní na jednoduchý a relativně neškodný příklad – jak na obrazovku vykreslit barevný šum a současně si vizuálně otestovat pomalost generátoru náhodných čísel. Na konzoli (na Raspbianu ale klidně i v X Window) si zkuste pustit následující příkaz:

cp /dev/urandom /dev/fb0
cp: error writing ‘/dev/fb0’: No space left on device
cp: failed to extend ‘/dev/fb0’: No space left on device

Obrazovka by se skutečně měla pomalu zaplnit barevnými pixely a nakonec by se do tohoto zmatku měly vypsat dva řádky informující o tom, že framebuffer je zaplněn (což vidíme) a že ho nelze (logicky) zvětšit. Překreslení obrazovky do původního stavu zajistí příkaz clear, pro obnovení okrajů pak přepnutí do druhé konzoly a zpět (Alt+F2, Alt+F1 atd.).

Pokud si chcete udělat screenshot obrazovky ve formátu „raw“ (což vlastně není žádný formát, jen otisk pixelů), postačuje provést:

cp /dev/fb0 framebuffer.raw

6. První demonstrační příklad: přečtení základních informací o framebufferu

Čtení a zápis barev pixelů tedy dokážeme provést i přímo z shellu (což by čistě teoreticky mohlo některým uživatelům dostačovat například pro implementaci prográmku, který bude na základě stavu čidel připojených na GPIO zobrazovat nějaké jednodušší grafy – takový dotaz již na Rootu zazněl). Podívejme se nyní na zbývající operaci, tedy na ioctl (Input/Output Control), kterou již budeme volat z céčkového programu. Jak již víme z předchozího textu, lze použitím této funkce například získat informace o framebufferu. Konkrétně je tato informace vrácena v datové struktuře pojmenované fb_var_screeninfo, která má tento formát (schválně jsem ponechal i původní komentáře):

struct fb_var_screeninfo {
        __u32 xres;                     /* visible resolution           */
        __u32 yres;
        __u32 xres_virtual;             /* virtual resolution           */
        __u32 yres_virtual;
        __u32 xoffset;                  /* offset from virtual to visible */
        __u32 yoffset;                  /* resolution                   */
 
        __u32 bits_per_pixel;           /* guess what                   */
        __u32 grayscale;                /* 0 = color, 1 = grayscale,    */
                                        /* >1 = FOURCC                  */
        struct fb_bitfield red;         /* bitfield in fb mem if true color, */
        struct fb_bitfield green;       /* else only length is significant */
        struct fb_bitfield blue;
        struct fb_bitfield transp;      /* transparency                 */      
 
        __u32 nonstd;                   /* != 0 Non standard pixel format */
 
        __u32 activate;                 /* see FB_ACTIVATE_*            */
 
        __u32 height;                   /* height of picture in mm    */
        __u32 width;                    /* width of picture in mm     */
 
        __u32 accel_flags;              /* (OBSOLETE) see fb_info.flags */
 
        /* Timing: All values in pixclocks, except pixclock (of course) */
        __u32 pixclock;                 /* pixel clock in ps (pico seconds) */
        __u32 left_margin;              /* time from sync to picture    */
        __u32 right_margin;             /* time from picture to sync    */
        __u32 upper_margin;             /* time from sync to picture    */
        __u32 lower_margin;
        __u32 hsync_len;                /* length of horizontal sync    */
        __u32 vsync_len;                /* length of vertical sync      */
        __u32 sync;                     /* see FB_SYNC_*                */
        __u32 vmode;                    /* see FB_VMODE_*               */
        __u32 rotate;                   /* angle we rotate counter clockwise */
        __u32 colorspace;               /* colorspace for FOURCC-based modes */
        __u32 reserved[4];              /* Reserved for future compatibility */
};

Jak se tato struktura získá v céčkovém programu?

  1. Otevřeme zařízení /dev/fb0 v režimu čtení (O_RDONLY) funkcí open().
  2. Zavoláme funkci ioctl(), které se předají tři parametry: handle zařízení (hodnota získaná přes open() v předchozím kroku), konstanta FBIOGET_VSCREENINFO a ukazatel na strukturu typu fb_var_screeninfo. Funkce ioctl() naplní všechny položky této struktury a vrátí nulovou hodnotu. V případě nějaké chyby vrátí nenulovou hodnotu, kterou ihned otestujeme.
  3. Zavřeme zařízení /dev/fb0 funkcí close().

Podívejme se nyní, jak by mohl vypadat úplný demonstrační příklad, v němž navíc provádíme kontroly, zda operace open() a ioctl() proběhly v pořádku. Povšimněte si, které hlavičkové soubory je nutné použít (všechny by měly být dostupné ve výchozí instalaci Raspbianu, pokud bude překladač hlásit chyby, vyřešíme v komentářích pod článkem):

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
 
/* Datova struktura, do niz se ulozi informace o framebufferu. */
typedef struct fb_var_screeninfo FramebufferInfo;
 
/* Vypis zakladnich informaci o framebufferu. */
void printFramebufferInfo(int framebufferDevice)
{
    FramebufferInfo framebufferInfo;
    /* Pokud operace ioctl probehne v poradku, vrati se 0 */
    if (ioctl(framebufferDevice, FBIOGET_VSCREENINFO, &framebufferInfo)) {
        perror("Nelze precist informace o framebufferu");
        return;
    }
    /* Nyni je datova struktura FramebufferInfo naplnena. */
    printf("Realne rozliseni:    %dx%d\n", framebufferInfo.xres, framebufferInfo.yres);
    printf("Virtualni rozliseni: %dx%d\n", framebufferInfo.xres_virtual, framebufferInfo.yres_virtual);
    printf("Bitu na pixel:       %d\n", framebufferInfo.bits_per_pixel);
}
 
int main(int argc, char **argv)
{
    int framebufferDevice = 0;
 
    /* Ze zarizeni potrebujeme pouze cist.*/
    framebufferDevice = open("/dev/fb0", O_RDONLY);
 
    /* Pokud otevreni probehlo uspesne, nacteme
     * a nasledne vypiseme informaci o framebufferu.*/
    if (framebufferDevice != -1) {
        printFramebufferInfo(framebufferDevice);
        close(framebufferDevice);
        return 0;
    }
    /* Otevreni se nezadarilo, vypiseme chybove hlaseni.*/
    else {
        perror("Nelze otevrit ovladac /dev/fb0");
        return 1;
    }
}

Překlad se provede tím nejjednodušším možným způsobem:

gcc -o rpi_fb1 rpi_fb1.c

Spuštění:

./rpi_fb1

Na mém Raspberry Pi se konkrétně vypíše:

Realne rozliseni:    1280x1024
Virtualni rozliseni: 1280x1024
Bitu na pixel:       16

U notebooku (který má samozřejmě taktéž framebuffer) pak:

Realne rozliseni:    1440x900
Virtualni rozliseni: 1440x900
Bitu na pixel:       32

7. Druhý demonstrační příklad: přečtení identifikace čipu, velikosti framebufferu a délky obrazového řádku

Ve skutečnosti v datové struktuře fb_var_screeninfo popsané a použité v předchozí kapitole nezískáme všechny informace nutné pro ovládání framebufferu. Další sada konfiguračních parametrů je dostupná ve struktuře nazvané fb_fix_screeninfo. Zajímavé je, že zde nalezneme i identifikaci GPU, ovšem důležitější jsou údaje o skutečném délku obrazového řádku (v bajtech) a celkové velikosti framebufferu:

struct fb_fix_screeninfo {
        char id[16];                    /* identification string eg "TT Builtin" */
        unsigned long smem_start;       /* Start of frame buffer mem */
                                        /* (physical address) */
        __u32 smem_len;                 /* Length of frame buffer mem */
        __u32 type;                     /* see FB_TYPE_*                */
        __u32 type_aux;                 /* Interleave for interleaved Planes */
        __u32 visual;                   /* see FB_VISUAL_*              */ 
        __u16 xpanstep;                 /* zero if no hardware panning  */
        __u16 ypanstep;                 /* zero if no hardware panning  */
        __u16 ywrapstep;                /* zero if no hardware ywrap    */
        __u32 line_length;              /* length of a line in bytes    */
        unsigned long mmio_start;       /* Start of Memory Mapped I/O   */
                                        /* (physical address) */
        __u32 mmio_len;                 /* Length of Memory Mapped I/O  */
        __u32 accel;                    /* Indicate to driver which     */
                                        /*  specific chip/card we have  */
        __u16 capabilities;             /* see FB_CAP_*                 */
        __u16 reserved[2];              /* Reserved for future compatibility */
};

Způsob naplnění této datové struktury je ukázán v dnešním druhém demonstračním příkladu. Povšimněte si, že postup je naprosto shodný s předchozím příkladem, ovšem druhý a třetí parametr předaný do funkce ioctl() je odlišný. Druhým parametrem je konstanta FBIOGET_FSCREENINFO (pozor na to, že rozdíl je jen v jediném znaku!) a třetím parametrem pak ukazatel na datovou strukturu typu fb_fix_screeninfo (nikoli fb_var_screeninfo):

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
 
/* Datova struktura, do niz se ulozi informace o framebufferu. */
typedef struct fb_var_screeninfo FramebufferInfo;
 
typedef struct fb_fix_screeninfo ModeInfo;
 
void printFramebufferInfo(int framebufferDevice)
{
    FramebufferInfo framebufferInfo;
    ModeInfo        modeInfo;
 
    /* Pokud operace ioctl probehne v poradku, vrati se 0 */
    if (ioctl(framebufferDevice, FBIOGET_VSCREENINFO, &framebufferInfo)) {
        perror("Nelze precist informace o framebufferu");
        return;
    }
    /* Nyni je datova struktura FramebufferInfo naplnena. */
    printf("Realne rozliseni:       %dx%d pixelu\n", framebufferInfo.xres, framebufferInfo.yres);
    printf("Virtualni rozliseni:    %dx%d pixelu\n", framebufferInfo.xres_virtual, framebufferInfo.yres_virtual);
    printf("Bitu na pixel:          %d bitu\n", framebufferInfo.bits_per_pixel);
 
    if (ioctl(framebufferDevice, FBIOGET_FSCREENINFO, &modeInfo)) {
        perror("Nelze precist informace o rezimu");
        return;
    }
    /* Nyni je datova struktura ModeInfo naplnena. */
    printf("Identifikace:           %s\n", modeInfo.id);
    printf("Delka obrazoveho radku: %d bajtu\n", modeInfo.line_length);
    printf("Velikost framebuffer:   %d bajtu\n", modeInfo.smem_len);
}
 
int main(int argc, char **argv)
{
    int framebufferDevice = 0;
 
    /* Ze zarizeni potrebujeme pouze cist.*/
        framebufferDevice = open("/dev/fb0", O_RDONLY);
 
    /* Pokud otevreni probehlo uspesne, nacteme
     * a nasledne vypiseme informaci o framebufferu.*/
    if (framebufferDevice != -1) {
        printFramebufferInfo(framebufferDevice);
        close(framebufferDevice);
        return 0;
    }
    /* Otevreni se nezadarilo, vypiseme chybove hlaseni.*/
    else {
        perror("Nelze otevrit ovladac /dev/fb0");
        return 1;
    }
}

Překlad druhého příkladu se opět provede tím nejjednodušším možným způsobem:

gcc -o rpi_fb2 rpi_fb2.c

Spuštění pak příkazem:

./rpi_fb2

Na mém Raspberry Pi se konkrétně vypíše (povšimněte si detekce identifikace GPU, podle mě není zcela přesné):

Realne rozliseni:       1280x1024 pixelu
Virtualni rozliseni:    1280x1024 pixelu
Bitu na pixel:          16 bitu
Identifikace:           BCM2708 FB
Delka obrazoveho radku: 2560 bajtu
Velikost framebuffer:   2621440 bajtu

Na notebooku s GPU od firmy Intel se vypíše:

Realne rozliseni:       1440x900 pixelu
Virtualni rozliseni:    1440x900 pixelu
Bitu na pixel:          32 bitu
Identifikace:           inteldrmfb
Delka obrazoveho radku: 5760 bajtu
Velikost framebuffer:   5185536 bajtu

8. Třetí demonstrační příklad: přístup do framebufferu s využitím mmap

Informace, které již o framebufferu umíme získat, nyní využijeme k přístupu k jednotlivým pixelům. Zde musíme použít další systémovou funkci, která je velmi užitečná. Jedná se o funkci nazvanou mmap(), k níž existuje i protějšek munmap(). Funkce mmap() dokáže namapovat „obsah“ nějakého souboru či zařízení do paměti, takže se s touto pamětí může pracovat stejně jako s jakoukoli jinou oblastí paměti s využitím ukazatelů atd. Funkce munmap() naopak toto mapování odstraní.

Volání funkce mmap() bude v našem konkrétním případě vypadat takto:

char *pixels = (char*)mmap(   /* přetypování výsledku funkce mmap() */
    0,                        /* necháme na rozhodnutí jádra, aby zvolilo první adresu */
    bufferSize,               /* velikost namapované oblasti, zde velikost framebufferu (fb_fix_screeninfo.smem_len) */
    PROT_READ | PROT_WRITE,   /* budeme číst i zapisovat barvy pixelů */
    MAP_SHARED,               /* změna ve speciálním zařízení bude viditelná v celém systému */
    framebufferDevice,        /* handle vrácený funkcí open() */
    0);                       /* offset v rámci framebufferu, my k němu budeme přistupovat od začátku */

Jakmile funkce mmap() vrátila ukazatel na paměť s namapovaným speciálním zařízením, můžeme snadno změnit hodnoty všech pixelů, a to nezávisle na jejich struktuře (uložení barev atd.):

int x;
for (x=0; x<bufferSize; x++)
    pixels[x] = x;

popř. pokud preferujete použití ukazatelů:

int x;
char *p = pixels;
for (x=0; x<bufferSize; x++)
    *p++ = x;

Na závěr je nutné funkcí munmap() zrušit mapování. Podívejme se nyní na úplný zdrojový kód tohoto příkladu:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
 
/* Datova struktura, do niz se ulozi informace o framebufferu. */
typedef struct fb_var_screeninfo FramebufferInfo;
 
typedef struct fb_fix_screeninfo ModeInfo;
 
long int printFramebufferInfo(int framebufferDevice)
{
    FramebufferInfo framebufferInfo;
    ModeInfo        modeInfo;
 
    /* Pokud operace ioctl probehne v poradku, vrati se 0 */
    if (ioctl(framebufferDevice, FBIOGET_VSCREENINFO, &framebufferInfo)) {
        perror("Nelze precist informace o framebufferu");
        return 0;
    }
    /* Nyni je datova struktura FramebufferInfo naplnena. */
    printf("Realne rozliseni:       %dx%d pixelu\n", framebufferInfo.xres, framebufferInfo.yres);
    printf("Virtualni rozliseni:    %dx%d pixelu\n", framebufferInfo.xres_virtual, framebufferInfo.yres_virtual);
    printf("Bitu na pixel:          %d bitu\n", framebufferInfo.bits_per_pixel);
 
    if (ioctl(framebufferDevice, FBIOGET_FSCREENINFO, &modeInfo)) {
        perror("Nelze precist informace o rezimu");
        return 0;
    }
    /* Nyni je datova struktura ModeInfo naplnena. */
    printf("Identifikace:           %s\n", modeInfo.id);
    printf("Delka obrazoveho radku: %d bajtu\n", modeInfo.line_length);
    printf("Velikost framebuffer:   %d bajtu\n", modeInfo.smem_len);
 
    return modeInfo.smem_len;
}
 
void draw(int framebufferDevice, long int bufferSize)
{
    char *pixels = (char*)mmap(0, bufferSize,
                               PROT_READ | PROT_WRITE,
                               MAP_SHARED, framebufferDevice,
                               0);
    if (pixels != MAP_FAILED) {
        int x;
        for (x=0; x<bufferSize; x++) pixels[x] = x;
        getchar();
        munmap(pixels, bufferSize);
    }
    else {
        perror("Nelze pristupovat k framebufferu");
    }
}
 
int main(int argc, char **argv)
{
    int framebufferDevice = 0;
 
    /* Ze zarizeni potrebujeme pouze cist.*/
    framebufferDevice = open("/dev/fb0", O_RDWR);
 
    /* Pokud otevreni probehlo uspesne, nacteme
     * a nasledne vypiseme informaci o framebufferu.*/
    if (framebufferDevice != -1) {
        long int bufferSize = printFramebufferInfo(framebufferDevice);
        if (bufferSize) {
            draw(framebufferDevice, bufferSize);
        }
        close(framebufferDevice);
        return 0;
    }
    /* Otevreni se nezadarilo, vypiseme chybove hlaseni.*/
    else {
        perror("Nelze otevrit ovladac /dev/fb0");
        return 1;
    }
}

Překlad třetího příkladu se opět provede tím nejjednodušším možným způsobem:

gcc -o rpi_fb3 rpi_fb3.c

Spuštění pak příkazem:

./rpi_fb3
Realne rozliseni:       1280x1024 pixelu
Virtualni rozliseni:    1280x1024 pixelu
Bitu na pixel:          16 bitu
Identifikace:           BCM2708 FB
Delka obrazoveho radku: 2560 bajtu
Velikost framebuffer:   2621440 bajtu

Příště si ukážeme některé další triky s framebufferem i s použitím mailboxů. Již nyní je však možné na prezentovaném příkladu stavět jednodušší aplikace, které na Raspberry Pi využijí grafický výstup bez nutnosti používat plnohodnotné desktopové prostředí a komplikované knihovny.

9. Repositář s demonstračními příklady

Všechny tři demonstrační příklady, s nimiž jsme se v dnešním článku seznámili, byly uloženy do Git repositáře umístěného na GitHubu na adrese (https://github.com/tisnik/pre­sentations):

ict ve školství 24

Pro překlad těchto tří demonstračních příkladů je zapotřebí mít nainstalován překladač GNU C (či Clang), linker a vývojářskou verzi libc.

10. Funkce použité v demonstračních příkladech

Popis funkcí (ve skutečnosti se vlastně jedná pouze o pětici systémových volání), které byly použity v dnešních demonstračních příkladech, naleznete buď ve druhé sekci manuálových stránek (man 2 jméno_funkce), popř. pokud preferujete použití webového prohlížeče se lze na adrese https://www.kernel.org/doc/man-pages/ podívat na ty samé manuálové stránky, ovšem zkonvertované do formátu HTML:

# Funkce Popis
1 open() man 2 open
2 close() man 2 close
3 ioctl() man 2 ioctl
4 mmap() man 2 mmap
5 munmap() man 2 unmap

11. Odkazy na Internetu

  1. Raspberry Pi pages
    https://www.raspberrypi.org/
  2. BCM2835 registers
    http://elinux.org/BCM2835_registers
  3. VideoCore (archiv stránek společnosti Alphamosaic)
    http://web.archive.org/web/20030209213838/www­.alphamosaic.com/videocore/
  4. VideoCore (Wikipedia)
    https://en.wikipedia.org/wi­ki/Videocore
  5. RPi lessons: Lesson 6 Screen01
    http://www.cl.cam.ac.uk/pro­jects/raspberrypi/tutorial­s/os/screen01.html
  6. Raspberry Pi forum: Bare metal
    https://www.raspberrypi.or­g/forums/viewforum.php?f=72
  7. C library for Broadcom BCM 2835 as used in Raspberry Pi
    http://www.airspayce.com/mi­kem/bcm2835/
  8. Raspberry Pi Hardware Components
    http://elinux.org/RPi_Har­dware#Components
  9. (Linux) Framebuffer
    http://wiki.linuxquestion­s.org/wiki/Framebuffer
  10. (Linux) Framebuffer HOWTO
    http://tldp.org/HOWTO/Framebuffer-HOWTO/
  11. Linux framebuffer (Wikipedia)
    https://en.wikipedia.org/wi­ki/Linux_framebuffer
  12. RPi Framebuffer
    http://elinux.org/RPi_Framebuffer
  13. HOWTO: Boot your Raspberry Pi into a fullscreen browser kiosk
    http://blogs.wcode.org/2013/09/howto-boot-your-raspberry-pi-into-a-fullscreen-browser-kiosk/
  14. Zdrojový kód fb.c pro RPI
    https://github.com/jncronin/rpi-boot/blob/master/fb.c
  15. RPiconfig
    http://elinux.org/RPi_config.txt
  16. Mailbox framebuffer interface
    https://github.com/raspbe­rrypi/firmware/wiki/Mailbox-framebuffer-interface

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.