7. I/O prostor mapovaný do paměti

Osnova cvičení

  1. zápis na I/O bránu
  2. čtení z I/O brány
  3. zobrazení grafiky displeji s řídicím a datový registrem

Co bych si měl na cvičení zopakovat/připravit

  1. MipsIt simulátor
  2. rozumět programům z předchozích cvičení

Náplň cvičení

Cílem je z programu přistupovat na periferie procesorového zařízení.

Úkoly

S problematikou se seznamte nejdříve v simulátoru QtMips. Jednoduchá vstupně výstupní periferie je mapovaná od adresy 0xffffc100.

Zápis hodnoty na výstupní port

  1. Napište program, který zobrazí 32-bitové číslo z registru na výstupní periferii - registr SPILED_REG_LED_LINE. Jedná se o řádku 32 LED diod na desce MZ_APO (fyzická adresa 0x43c40004) nebo ekvivalentním výstupu v simulátoru QtMips (přímo přístupná adresa 0xffffc104).
  2. Rozšiřte program tak, aby v nekonečné smyčce stále dokola zobrazoval na výstupu sekvenci následujících čísel, dále prodlužte efekt na celých 32 bitů.

pole:
.word  1, 2, 4, 8, 16, 32, 65, 48, 66, 51, 54, 65, 80, 79, 1, 2, 3

Čtení ze vstupního portu

  1. Napište program, který do registru načte 32-bitovou hodnotu ze vstupní periferie (trojice otočných voličů). Na desce MZ_APO je stav voličů k přečtení z fyzické adresy 0x43c40024). V simulátoru QtMips je nastavení voličů dostupné na adrese 0xffffc124.
  2. Rozšiřte program tak, že ze vstupu přečte číslo a na výstup zobrazí hodnotu z o odpovídající pozice ve výše uvedeném poli. Pokud je zadáno číslo, překračující rozsah pole, zobrazí program na výstupu samé jedničky (rozsvítí všechny LED).

Čtení a zápis z programu v jazyce C

Kompletní popis periferií implementovaných v simulátoru QtMips naleznete na stránce třetího cvičení v odstavci periferie mapované do paměťového adresního prostoru případně přímo v souboru README.md přímo z projektu. Součástí třetího cvičení je i ukázka překladu z jazyka C do assembleru s rozborem generovaného kódu. Nalézá se tam také archiv s programem. Program naleznete in v adresáři /opt/apo/qtmips_binrep. Program se shodnou funkcí pro desku MZ_APO naleznete v adresáři /opt/apo/binrep/mzapo_binrep. Porovnání analýzy kódu zkompilovaného pro architekturu ARM naleznete na konci dnešního cvičení.

Binární reprezentace na přípravu MicroZed APO

K přihlášení do operačního systému GNU/Linux běžícímu na desce je možné použít protokol SSH.

ssh -i /opt/zynq/ssh-connect/mzapo-root-key root@192.168.202.xxx

Adresu desky je možné zjistit i z výpisu na obrazovce. Adresa je zobrazená binárně v každé řádce vždy od nejvýznamnějšího bitu (MSB) k bitu nejméně významnému (LSB). Další informace k připojení přes sériový port i SSH naleznete na stránce s návodem v sekci dokumentace. Zde se nachází i popis hardware desky.

Do adresářové struktury cílového systému je do adresářů /opt/apo a /opt/zynq připojený shodný svazek/obsah jako je na hostitelském PC

Nachází se zde i adresář se zdrojovými kódy programu pro test binární reprezentace

/opt/apo/binrep/print_binrep

Adresář vidí jak na host systému (PC) tak na target systému (Zynq). Ale na obou je jen read-only. Pro kompilaci je potřeba adresář zkopírovat a spustit v něm příkaz

cp -r /opt/apo/binrep/print_binrep /root
cd /root/print_binrep
make

Pro vynucení překompilování lze použít cíl clean pro smazání a cíl all pro opětovnou kompilaci. Cíl all je shodný s prvním uvedeným cílem, který je vybraný při volání bez parametrů.

make clean all

Na cílovém systému je přidaná i kopie zdrojového kódu v adresáři /root/apo/binrep/print_binrep, ten je stejně jako většina systému překrytý pod overlayem, takže lze přímo používat. Pozor, po restartu systému budou veškerá data ztracena. Vše se odehrává jen v lokální RAM (tmpfs, zjednodušeně RAM disk). Jednoduchá kompilace a spuštění

cd /root/apo/binrep/print_binrep
make
./print_binrep

Přímo na cílovém systému lze zdrojové kódy editovat programy

  • mcedit
  • vi/vim
  • nano (vhodne pouze pri pripojeni pres ssh)

Ty jsou k dispozici i na hostitelském PC, kde jsou navíc nainstalované i editory/prostředí

  • geanny
  • eclipse

Grafický výstup na display desky MZ_APO

K vývojovému kitu MZ_APO je připojený grafický displej s lokálním řadičem, který se stará o obnovování obrazu na TFT LCD display s rozměry 480 x 320 bodů.

Řadič přijímá příkazy zapisované na 16-bitovou bránu mapovanou do fyzického paměťového prostoru na adresu 0x43c00008 (PARLCD_REG_BASE_PHYS+PARLCD_REG_CMD_o).

Většina příkazů pak vyžaduje pro provedení vlastní operace zapsání dat. Data je potřeba zapsat na adresu 0x43c0000c (PARLCD_REG_BASE_PHYS+PARLCD_REG_DATA_o).

Příklad, jak zpřístupnit určitý rozsah fyzických adres pro práci z programu napsaného v jazyce C naleznete v prvním cvičení. Příklady použití mapování v aplikacích naleznete v adresářích

/opt/apo/binrep/mzapo_binrep
/opt/apo/lcd/mzapo_lcdtest

Kontrolér displeje vyžaduje konfiguraci množství parametrů pro nastavení parametrů volbu propojení vlastního LCD (“skla”) a pro nastavení napěťových úrovní a časování aby byl obraz stabilní a kontrastní.

Tuto funkci však není nutné z vašich programů provádět, protože počáteční nastavení provede již aplikace pro zobrazení IP adresy na displeji.

Pro zobrazení grafického výstupu tedy stačí vyslat příkaz 0x2c, který je následovaný požadovanými barvami jednotlivých bodů zadávaných ve formátu RGB 565 postupně řádek po řádku.

Složka počet bitů Bity
R - red 5 15 .. 11
G - green 6 10 .. 5
B - blue 5 4 .. 0

Požadovaný grafický výstup je možné připravovat přímo během vysílání dat na display (takto jsou implementované předložené testovací programy), ale pro přípravu složitějšího výstupu je mnohem vhodnější vykreslovat objekty to bloku paměti (frame-buffer) a poté vyslat dokončený obsah na display.

uint16_t *fb = (uint16_t*)malloc(sizeof(uint16_t) * width * height)

Úkoly

  1. zobrazte červený obdélník
  2. zobrazte zelený kruh, který částečně překrývá obdélník

Pro zdatnější je pak možné přidat nastavování barvy obdélníku s využitím rotačních voličů. Případně je možné voliči měnit polohu objektů nebo jejich velikost. Režim je možné přepínat například stlačením voliče.

Kompletní popis příkazů, kterými je možné konfigurovat řadič displeje naleznete v souboru

/opt/apo/zynq/doc/mz_apo/components/HX8357-C.pdf

Grafický výstup na simulátoru QtMips

Simulátor obsahuje jednoduchý model grafického displaye s rozlišením 480 x 320 bodů. V okně LCD display je zobrazovaná paměť jednotlivých bodů, která začíná na adrese 0xffe00000. Body jsou uložené za sebou postupně v řádcích s tím, že každý bod je reprezentovaný 16-bitovou hodnotou stejně jako na přípravku MZ_APO. Nejvýznamnějších 5 bitů odpovídá červené složce, poté následuje 8 bitů zelené složky a nakonec 5 bitů modré složky. Pozor, při jiném přístupu než 16-bit unsigned integer (například po slovech, nebo bytech) je chování odlišné od přípravku MZ_APO, protože se jedná o architekturu big-endian.

Rozšíření, úkoly pro přípravu na další úlohy

Výpis textů, bitmapové fonty

Existuje mnoho možností jak definovat glyfy (grafické podoby) jednotlivých znaků, číslic, symbolů. Pokročilejší metody využívající popis glyfu křivkami umožňují zobrazit znak o libovolné velikosti. Jednodušší řešení přímo definuje podobu písmene informací, které body mají být nastaveny na barvu textu. Ostatní jsou ponechány s původní barvou nebo jsou vyplněné barvou pozadí.

Příkladem definice glyfů přímo binárně v matici 14×16 bodů může být následující soubor

https://github.com/ghaerr/microwindows/blob/master/src/fonts/winFreeSystem14x16.c

z projektu Microwindows.

Pole winFreeSystem14x16_bits definuje nastavení jednotlivých bitů v matici. Typ prvků pole MWIMAGEBITS odpovídá typu uint16_t. Nejvíce významný bit (MSB, bit 15) odpovídá nejlevějšímu bodu znaku/symbolu. Postupně následují další body v řádce. Nejméně významné bity pak většinou nejsou využité, znaky fontu 14×16 jsou užší než 16 bodů. Další položka pole odpovídá dalšímu řádku.

Pole winFreeSystem14x16_offset není pro daný font používané, protože výška každého znaku/symbolu je 16 bodů, to je 16 16-bitových čísel. Přitom první grafická representace odpovídá v kódování ASCII znaku mezera (0x20, 32 dekadicky). Pole winFreeSystem14x16_width definuje šířku každého znaku v bodech. Další informace a parametry již nejsou pro implementaci jednoduchého zobrazování textů podstatné.

  1. pokuste se přidat do vykreslovaného obrazu i zobrazení aktuálního času

Soubory s dalšími definicemi fontů v různé velkosti naleznete v adresáři

/opt/apo/lcd/fonts

počítačů v laboratoři.

Největší velikost obsahuje jen číslice. Některé fonty obsahují i několik úseků z kódové tabulky Unicode a pokrývají tak kompletně potřeby českého jazyka, Ruštiny i některé arabské jazyky. Základ aplikace s již definovanými hlavičkovými soubory pro použití fontů naleznete v adresáři

/opt/apo/mzapo_template

Aplikace je též dostupná z fakultního serveru GIT https://gitlab.fel.cvut.cz/b35apo/mzapo_template.

git clone https://gitlab.fel.cvut.cz/b35apo/mzapo_template.git

Datový typ použitý pro definice fontů naleznete v souboru font_types.h.

Příklad přístupu k periferiím na desce MZ_APO

Jednoduchý program, který kopíruje hodnotu z RGB voličů na RGB LED a řádku LED diod.

Program naleznete v adresáři /opt/apo/binrep/mzapo_binrep a na cílovém systému i v adresáři /root/apo/binrep/print_binrep.

/*******************************************************************
  Simple program to demostrate binary reprezentation on MicroZed
  based MZ_APO board designed by Petr Porazil at PiKRON

  mzapo_binrep.c       - main and only file

  (C) Copyright 2004 - 2017 by Pavel Pisa
      e-mail:   pisa@cmp.felk.cvut.cz
      homepage: http://cmp.felk.cvut.cz/~pisa
      work:     http://www.pikron.com/
      license:  any combination GPL, LGPL, MPL or BSD licenses

 *******************************************************************/

#define _POSIX_C_SOURCE 200112L

#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <malloc.h>
#include <string.h>
#include <byteswap.h>
#include <getopt.h>
#include <inttypes.h>
#include <time.h>

char *memdev="/dev/mem";

/*
 * Next macros provides location of knobs and LEDs peripherals
 * implemented in MZ_APO FPGA design.
 *
 * The complete list of peripheral implemented in the design
 * can be found on the page
 *   https://cw.fel.cvut.cz/wiki/courses/b35apo/documentation/mz_apo/start
 */

/*
 * Base address of the region used for mapping of the knobs and LEDs
 * peripherals in the ARM Cortex-A9 physical memory address space.
 */
#define SPILED_REG_BASE_PHYS 0x43c40000

/* Valid address range for the region */
#define SPILED_REG_SIZE      0x00004000

/*
 * Byte offset of the register which controls individual LEDs
 * in the row of 32 yellow LEDs. When the corresponding bit
 * is set (value 1) then the LED is lit.
 */
#define SPILED_REG_LED_LINE_o           0x004

/*
 * The register to control 8 bit RGB components of brightness
 * of the first RGB LED
 */
#define SPILED_REG_LED_RGB1_o           0x010

/*
 * The register to control 8 bit RGB components of brightness
 * of the second RGB LED
 */
#define SPILED_REG_LED_RGB2_o           0x014

/*
 * The register which combines direct write to RGB signals
 * of the RGB LEDs, write to the keyboard scan register
 * and control of the two additional individual LEDs.
 * The direct write to RGB signals is orred with PWM
 * signal generated according to the values in previous
 * registers.
 */
#define SPILED_REG_LED_KBDWR_DIRECT_o   0x018

/*
 * Register providing access to unfiltered encoder channels
 * and keyboard return signals.
 */
#define SPILED_REG_KBDRD_KNOBS_DIRECT_o 0x020

/*
 * The register representing knobs positions as three
 * 8-bit values where each value is incremented
 * and decremented by the knob relative turning.
 */
#define SPILED_REG_KNOBS_8BIT_o         0x024


/*
 * The support function which returns pointer to the virtual
 * address at which starts remapped physical region in the
 * process virtual memory space.
 */
void *map_phys_address(off_t region_base, size_t region_size, int opt_cached)
{
  unsigned long mem_window_size;
  unsigned long pagesize;
  unsigned char *mm;
  unsigned char *mem;
  int fd;

  /*
   * Open a device ("/dev/mem") representing physical address space
   * in POSIX systems
   */
  fd = open(memdev, O_RDWR | (!opt_cached? O_SYNC: 0));
  if (fd < 0) {
    fprintf(stderr, "cannot open %s\n", memdev);
    return NULL;
  }

  /*
   * The virtual to physical address mapping translation granularity
   * corresponds to memory page size. This call obtains the page
   * size used by running operating system at given CPU architecture.
   * 4kB are used by Linux running on ARM, ARM64, x86 and x86_64 systems.
   */
  pagesize=sysconf(_SC_PAGESIZE);

  /*
   * Extend physical region start address and size to page size boundaries
   * to cover complete requested region.
   */
  mem_window_size = ((region_base & (pagesize-1)) + region_size + pagesize-1) & ~(pagesize-1);

  /*
   * Map file (in our case physical memory) range at specified offset
   * to virtual memory ragion/area (see VMA Linux kernel structures)
   * of the process.
   */
  mm = mmap(NULL, mem_window_size, PROT_WRITE|PROT_READ,
              MAP_SHARED, fd, region_base & ~(pagesize-1));

  /* Report failure if the mmap is not allowed for given file or its region */
  if (mm == MAP_FAILED) {
    fprintf(stderr,"mmap error\n");
    return NULL;
  }

  /*
   * Add offset in the page to the returned pointer for non-page-aligned
   * requests.
   */
  mem = mm + (region_base & (pagesize-1));

  return mem;
}

/*
 * The main entry into example program
 */
int main(int argc, char *argv[])
{

  unsigned char *mem_base;

  /*
   * Setup memory mapping which provides access to the peripheral
   * registers region of RGB LEDs, knobs and line of yellow LEDs.
   */
  mem_base = map_phys_address(SPILED_REG_BASE_PHYS, SPILED_REG_SIZE, 0);

  /* If mapping fails exit with error code */
  if (mem_base == NULL)
    exit(1);

  while (1) {
     uint32_t rgb_knobs_value;
     int int_val;
     unsigned int uint_val;

     /* Initialize structure to 0 seconds and 200 milliseconds */
     struct timespec loop_delay = {.tv_sec = 0, .tv_nsec = 200 * 1000 * 1000};

     /*
      * Access register holding 8 bit relative knobs position
      * The type "(volatile uint32_t*)" casts address obtained
      * as a sum of base address and register offset to the
      * pointer type which target in memory type is 32-bit unsigned
      * integer. The "volatile" keyword ensures that compiler
      * cannot reuse previously read value of the location.
      */
     rgb_knobs_value = *(volatile uint32_t*)(mem_base + SPILED_REG_KNOBS_8BIT_o);

     /* Store the read value to the register controlling individual LEDs */
     *(volatile uint32_t*)(mem_base + SPILED_REG_LED_LINE_o) = rgb_knobs_value;

     /*
      * Store RGB knobs values to the corersponding components controlling
      * a color/brightness of the RGB LEDs
      */
     *(volatile uint32_t*)(mem_base + SPILED_REG_LED_RGB1_o) = rgb_knobs_value;

     *(volatile uint32_t*)(mem_base + SPILED_REG_LED_RGB2_o) = rgb_knobs_value;

     /* Assign value read from knobs to the basic signed and unsigned types */
     int_val = rgb_knobs_value;
     uint_val = rgb_knobs_value;

     /* Print values */
     printf("int %10d uint 0x%08x\n", int_val, uint_val);

     /*
      * Wait for time specified by "loop_delay" variable.
      * Use monotonic clocks as time reference to ensure
      * that wait interval is not prolonged or shortened
      * due to real time adjustment.
      */
     clock_nanosleep(CLOCK_MONOTONIC, 0, &loop_delay, NULL);
  }

  return 0;
}

Klíčová sekvence kódu přístupu k periferiím, jak jí vidí procesor ARM

Program je zkompilovaný překladačem GCC pro architekturu ARM Cortex-A (arm-linux-gnueabihf-gcc). Pro zkrácení kódu je využitá varianta kódování instrukcí Thumb, která kombinuje instrukce kódované do 16 a 32-bitů. Pro dekódování byl použitý nástroj objdump

arm-linux-gnueabihf-objdump --source mzapo_binrep | less

Při kompilaci nebyla požadovaná žádná optimalizace, proto kód opakovaně přistupuje do lokálních proměnných a řádky v assembleru lze velmi dobře porovnávat s řádky zdrojového kódu.

Pro případ bez optimalizace

     rgb_knobs_value = *(volatile uint32_t*)(mem_base + SPILED_REG_KNOBS_8BIT_o);
                           load value of the local variable "mem_base" from the
                           memory location relative to the register r7
   106e6:       69fb            ldr     r3, [r7, #28]
                           read value from the address corresponding to the
                           sum of "mem_base" and "SPILED_REG_KNOBS_8BIT_o"
                           peripheral register offset
   106e8:       6a5b            ldr     r3, [r3, #36]   ; 0x24
                           store read value to the local variable "rgb_knobs_value"
   106ea:       61bb            str     r3, [r7, #24]

     *(volatile uint32_t*)(mem_base + SPILED_REG_LED_LINE_o) = rgb_knobs_value;
                           read "mem_base"
   106ec:       69fb            ldr     r3, [r7, #28]
                           use short form (Thumb) instruction to add "SPILED_REG_LED_LINE_o"
   106ee:       3304            adds    r3, #4
                           read "rgb_knobs_value" local variable
   106f0:       69ba            ldr     r2, [r7, #24]
                           store "rgb_knobs_value" to the LED line peripheral register
   106f2:       601a            str     r2, [r3, #0]

     *(volatile uint32_t*)(mem_base + SPILED_REG_LED_RGB1_o) = rgb_knobs_value;
                           load "mem_base"
   106f4:       69fb            ldr     r3, [r7, #28]
                           add offset of the first RGB LED
   106f6:       3310            adds    r3, #16
                           load "rgb_knobs_value"
   106f8:       69ba            ldr     r2, [r7, #24]
                           store value to control color
   106fa:       601a            str     r2, [r3, #0]

     *(volatile uint32_t*)(mem_base + SPILED_REG_LED_RGB2_o) = rgb_knobs_value;
                           load "mem_base"
   106fc:       69fb            ldr     r3, [r7, #28]
                           add offset of the second RGB LED
   106fe:       3314            adds    r3, #20
                           load "rgb_knobs_value"
   10700:       69ba            ldr     r2, [r7, #24]
                           store value to control color
   10702:       601a            str     r2, [r3, #0]

     int_val = rgb_knobs_value;
                           load "rgb_knobs_value"
   10704:       69bb            ldr     r3, [r7, #24]
                           store value to "int_val"
   10706:       617b            str     r3, [r7, #20]
                           no conversion between unsigned
                           32-bit value and signed signed int.
                           They are of the same size and there
                           is no conversion between the unsigned
                           and the second complement signed representation.
                           The half range silently overflows/wraps.

     uint_val = rgb_knobs_value;
                           load "rgb_knobs_value"
   10708:       69bb            ldr     r3, [r7, #24]
                           store value to "uint_val"
   1070a:       613b            str     r3, [r7, #16]
                           no conversion, the same size

     printf("int %10d uint 0x%08x\n", int_val, uint_val);
                           the first argument register (r0 .. a0)
                           is set to the start address of the format string
   1070c:       f240 70ac       movw    r0, #1964       ; 0x7ac
   10710:       f2c0 0001       movt    r0, #1
                           load "int_val" into the second argument register (r1 .. a1)
   10714:       6979            ldr     r1, [r7, #20]
                           load "uint_val" into the third argument register (r2 .. a2)
   10716:       693a            ldr     r2, [r7, #16]
                           call the function
   10718:       f7ff eea8       blx     1046c <printf@plt>

Pro případ se základní optimalizací -O1

  Setup memory mapping which provides access to the peripheral
  registers region of RGB LEDs, knobs and line of yellow LEDs.
  mem_base = map_phys_address(SPILED_REG_BASE_PHYS, SPILED_REG_SIZE, 0);
   106b8:	2200      	movs	r2, #0
   106ba:	f44f 4180 	mov.w	r1, #16384	; 0x4000
   106be:	4610      	mov	r0, r2
                                  argument 0 (r0) set to physical base
                                  of the SPILED registers block. Use instruction
                                  to set upper 16 bits of the register
   106c0:	f2c4 30c4 	movt	r0, #17348	; 0x43c4
   106c4:	f7ff ffa4 	bl	10610 <map_phys_address>

  /* If mapping fails exit with error code */
  if (mem_base == NULL)
   106c8:	b910      	cbnz	r0, 106d0 <main+0x1c>
    exit(1);
   106ca:	2001      	movs	r0, #1
   106cc:	f7ff ef18 	blx	10500 <exit@plt>

                                  else setup r4 to virtual base of the mapping
   106d0:	4604      	mov	r4, r0

                                  prepare address of text "int %10d uint 0x%08x\n"
                                  outside of the loop, the 16 lower bits the first
   106dc:	f240 7670 	movw	r6, #1904	; 0x770
                                  set upper 16 bits   
   106e0:	f2c0 0601 	movt	r6, #1

                                  prepare value for struct timespec loop_delay   
   106e4:	9500      	str	r5, [sp, #0]
   106e6:	9701      	str	r7, [sp, #4]

     rgb_knobs_value = *(volatile uint32_t*)(mem_base + SPILED_REG_KNOBS_8BIT_o);
                           read value from the address corresponding to the
                           sum of "mem_base" and "SPILED_REG_KNOBS_8BIT_o"
                           peripheral register offset
   106e8:	6a61      	ldr	r1, [r4, #36]	; 0x24
     *(volatile uint32_t*)(mem_base + SPILED_REG_LED_LINE_o) = rgb_knobs_value;
                           store "rgb_knobs_value" to the LED line peripheral register
   106ea:	6061      	str	r1, [r4, #4]
     *(volatile uint32_t*)(mem_base + SPILED_REG_LED_RGB1_o) = rgb_knobs_value;
                           store value to color register LED1
   106ec:	6121      	str	r1, [r4, #16]
     *(volatile uint32_t*)(mem_base + SPILED_REG_LED_RGB2_o) = rgb_knobs_value;
                           store value to color register LED2
   106ee:	6161      	str	r1, [r4, #20]
     printf("int %10d uint 0x%08x\n", int_val, uint_val);
                           copy value of argument 1 (r1) even to argument 2 (r2)  
   106f0:	460a      	mov	r2, r1
                           set argument r0 from saved register r6
   106f2:	4630      	mov	r0, r6
   106f4:	f7ff eeda 	blx	104ac <printf@plt>

courses/b35apo/tutorials/07/start.txt · Last modified: 2019/06/30 21:01 by pisa