Programovatelný vstup/výstup je nový typ hardwaru vytvořený pro RP2040. Dovoluje nám vytvářet nové typy hardwarových rozhraní na RP2040 zařízení. Tento článěk je překlad do češtiny kapitoly 3 z rp2040-datasheet.pdf.
Přehled
V procesoru RP2040 jsou dva identické PIO bloky. Každý blok má vyhrazená spojení ke společné sběrnici, GPIO a řadiči přerušení. Schematický diagram jednoho PIO bloku je na obrázku.

Programovatelný vstupně výstupní blok je univerzální hardwarové rozhraní. Podporuje množství vstupně/výstupních standardů:
-
8080 a 6800 paralelní sběrnici
-
\(I^2C\)
-
třívodičovou I2S
-
SDIO
-
SPI, DSPI, QSPI
-
UART
-
DVI nebo VGA (pomocí rezistorů DAC)
PIO je programovatelné tím samým způsobem jako procesor. V každém z obou PIO bloků jsou čtyři stavové automaty, které mohou nezávisle na sobě provádět sekvenční programy, manipulovat s GPIO a přenášet data. PIO je vysoce specializovaný pro vstupně výstupní operace na rozdíl od hlavního procesoru, se zaměřením na předvídatelnost, přesné časování a těsnou kooperaci s hardwarem. Každý stavový automat je vybaven:
-
dvěma 32bitovými posuvnými registry, které mohou fungovat oběma směry (doleva i doprava) a mohou se posunovat o libovolný počet bitů
-
dvěma 32bitovými scratch registry
-
dvěma sběrnicovými frontami (FIFO) o velikosti 4x32bitů, pracující oběma směry (TX/RX), které se dají překonfigurovat na jednu frontu 8x32 bitů pracující jedním směrem.
-
děličkou hodin (16bitů celých a 8bitů desetinných)
-
pružným mapováním GPIO
-
rozhraním DMA s propustností jedno slovo za takt ze systémového DMA
-
nastavením IRQ flagů set/clear/status
Každý stavový automat zabírá na křemíkovém plátku přibližně stejnou oblast jak standardní sériový blok, třeba řadič SPI nebo \(I^2C\). Avšak tento stavový automat může být konfigurován a rekonfigurován dynamicky a tak je možné implementovat různá hardwarová rozhraní.
Vytvoření stavových automatů programovatelných softwarovým způsobem, spíše než plně konfigurovatelnou logickou strukturou, jako je a CPLD umožňuje nabízet více hardwarových rozhraní se stejnou cenou a výkonem. To také představuje známější programovací model a jednodušší použití nástrojů pro ty, kteří chtějí využít plnou flexibilitu PIO při jeho programování přímo, než pomocí předem připraveného rozhraní z knihovny PIO.
PIO je vysoce výkonný a zároveň flexibilní díky pečlivě zvolenému hardwaru s pevnou funkcí uvnitř každého stavového automatu. Při výstupu DPI může PIO udržet rychlost 360 Mb/s během aktivního období skenování při běhu s 48 MHz systémovými hodinami. V tomto příkladu jeden stavový automat zpracovává časování snímku/skenování a generuje pixel hodiny, zatímco jiný zpracovává data pixelů a rozbaluje řádky zakódované v délce běhu.
Vstupy a výstupy stavových strojů jsou mapovány až na 32 GPIO (u RP2040 omezeno na 30 GPIO) a všechny stavové stroje mají nezávislý, současný přístup k jakémukoli GPIO. Například standardní kód UART umožňuje TX, RX, CTS a RTS jsou libovolné čtyři libovolné GPIO a I2C povoluje totéž pro SDA a SCL. Množství dostupné svobody závisí na tom, jak přesně se daný program PIO rozhodne používat zdroje mapování pinů PIO, ale na Minimálně lze rozhraní libovolně posunout nahoru nebo dolů o určitý počet GPIO.
Programátorský model
Čtyři stavové automaty používají sdílenou pamět pro instukce. Systémový software natáhne programy do této paměti, nakonfiguruje stavový automat a vstupně výstupní mapování a potom se může automat spustit. Programy PIO mohou mít různý původ: přeložené přímo uživatelem, vytvořené pomocí PIO knihovny nebo generovány programově uživatelským softwarem.
Z tohoto pohledu jsou stavové automaty autonomní, systémový software s ním muže komunikovat pomocí DMA, přerušení a řídícími registry podobně jako s jinými periferiemi na RP2040.
Data tečou tam a zpět skrze pár front (FIFO). Stavový automat vykonává program, který přenáší data mezi těmito frontami a množinou interních registrů a pinů.
Dělička hodin může zpomalovat rychlost stavového automatu o konstantu.
PIO programy
PIO automat provádí krátké binární programy.
Programy pro běžná rozhraní jako jsou UART, SPI nebo \(I^2C\) jsou již naprogramovány v PIO knihovně, takže v mnoha případech nemusíme psát PIO programy. Avšak přímo programované PIO je flexibilnější a má daleko širší záběr, který nemusí být zpočátku zřejmý.
PIO má celkem 9 instrukcí: JMP, WAIT, IN, OUT, PUSH, PULL, MOV, IRQ a SET. V další části se jimi budeme podrobně zabývat.
Přestože má PIO jenom devět instrukcí, je docela obtížné ručne upravovat PIO programy. PIO asembler je textový formát, popisující PIO program, kde každý příkaz odpovídá jedné instrukci ve výstupní binárce. Zde je jeden příklad v PIO asembleru:
.program squarewave
set pindirs, 1 ; Nastavíme pin jako výstupní
again:
set pins, 1 [1] ; Nastavíme na pinu 1 a potom počkáme jeden tik
set pins, 0 ; Nastavíme na pinu 0
jmp again ; Nastavíme programový čítač PC, aby ukazoval na návěstí `again`
PIO asembler je nástroj SDK, jmenuje se pioasm. Tento program projde vstupní textový soubor v jazyce PIO asembleru, který může obsahovat více programů a vytvoří použitelný program. V SDK jou tyto prográmky dělané jako Céčkový hlavičkový soubor, obsahující konstantní pole:: Více informací je v části 3.
Vykonávání programu
Při každém tiku systémových hodin každý automat načte, dekóduje a vykoná jednu instrukci. Každá instrukce trvá přesně jeden tik, pokud není explicitně zpomajena (třeba pomocí instrukce WAIT). Instrukce může dále vytvořit pauzu o délce až 31 tiků a až poté je vykonána další instrukce, takže můžeme vytvářet časově přesné programy.
Programový čítač PC (program counter) ukazuje do paměti na instrukci, která je vykonávána v současném tiku. Programový čítač je inkrementován v každém tiku a pokud dojde na konec jemu vyhrazené paměti, tak se vrátí zpět na začátek. Vyjimku představuje instrukce JMP (jump — skok), která explicitně nastavuje příští hodnotu čítače programu.
Náš příklad asemblerovského programu (jmenuje se .program squarewave výše) ukazuje oba koncepty v praxi.
Vytváří obdélníkový průběh se střídou 50% na GPIO s periodou 4 tiky.
Pokud bychom použili jiné vlastnosti (třeba side-set), šlo by to udělat s periodou 2 tiky.
|
Side-set je vedlejší efekt, kdy stavový automat doplňkově nastavuje malý počet GPIO pinů (1 až 5) navíc k instrukci, kterou právě vykonává. Popsáno je to v další části. |
Systém (myslí se tím obvykle Céčkový program) může do instukční paměti jenom zapisovat, to je používá při zavádění programu:
// Load the assembled program directly into the PIO's instruction memory. (1)
// Each PIO instance has a 32-slot instruction memory, which all 4 state
// machines can see. The system has write-only access.
for (int i = 0; i < count_of(squarewave_program_instructions); ++i)
pio->instr_mem[i] = squarewave_program_instructions[i];
| 1 | Zapíšeme asemblerový program přímo do instrukční paměti PIO. Každý PIO má k dispozici instrukční paměť s 32 řádky (každý 16bitů), všechny 4 stavové automaty vidí tuto paměť. Systém může do ní pouze zapisovat. |
Dělička hodinového kmitočtu zpomaluje stavový stroj o konstantu, která je vyjádřena číslem s pevnou desetinnou čárkou 16bitů,8bitů. Jestliže bude naprogramováno dělení kmitočtu 2.5, tak bude mít obdélníková vlna periodu \(4 \times 2.5 = 10\) tiků. To je užitečné pro přesné nastavení rychlosti přenostu třeba u sériového rozhraní UART.
// Configure state machine 0 to run at sysclk/2.5. The state machines can (1)
// run as fast as one instruction per clock cycle, but we can scale their
// speed down uniformly to meet some precise frequency target, e.g. for a
// UART baud rate. This register has 16 integer divisor bits and 8
// fractional divisor bits.
pio->sm[0].clkdiv = (uint32_t) (2.5f * (1 << 16));
| 1 | Nastavíme stavový automat PIO0, aby běžel 2.5 krát pomaleji než systémové hodiny. Stavový automat může bežet stejně rychle jako systémové hodiny, jedna instrukce za jeden tik, můžeme však snížit jeho rychlost tak, jak to vyžaduje naše zařízení, třeba UART. Tento registr má k dělení 16 celých bitů a 8 desetinných bitů. |
Výše uvedená část programu vytváří obdelníkovou vlnu o frekvenci 12.5 MHz na pinu GPIO 0. Můžeme také použít instrukci WAIT PIN, kterou pozastavíme vykonávání instrukcí stavového automatu, nebo instrukci JMP PIN, která závisí na stavu pinu, takže vykonávání programu může být závislé na stavu pinu.
// There are five pin mapping groups (out, in, set, side-set, jmp pin)
// which are used by different instructions or in different circumstances.
// Here we're just using SET instructions. Configure state machine 0 SETs
// to affect GPIO 0 only; then configure GPIO0 to be controlled by PIO0,
// as opposed to e.g. the processors.
pio->sm[0].pinctrl =
(1 << PIO_SM0_PINCTRL_SET_COUNT_LSB) |
(0 << PIO_SM0_PINCTRL_SET_BASE_LSB);
gpio_set_function(0, GPIO_FUNC_PIO0);
Systém může spustit nebo zastavit každý stavový automat kdykoliv pomocí řídícího registru (CTRL register). Několik stavových automatů může být spuštěno současně a předvídatelná přirozenost PIO znamená, že budou tyto automaty přesně synchronizovány.
// Set the state machine running. The PIO CTRL register is global within a
// PIO instance, so you can start/stop multiple state machines
// simultaneously. We're using the register's hardware atomic set alias to
// make one bit high without doing a read-modify-write on the register.
hw_set_bits(&pio->ctrl, 1 << (PIO_CTRL_SM_ENABLE_LSB + 0));
Většina instrukcí je vykonávána z instrukční paměti, ale jsou i jiné možnosti, které můžeme používat:
-
Instrukce zapsaná so speciálního konfiguračního registru (SMx INSTR) je okamžitě vykonána a v tom samém okamžiku přerušuje vykonání jiné instrukce. Například instrukce JMP zapsaná do SMx INSTR způsobí, že stavový automat začne vykonávat instrukce z jiné části instrukční paměti.
-
Instrukce může být vykonána z registru, používá se k tomu instrukce MOV EXEC.
-
Instrukce může být vykonána z výstupního registru, k tomu se používá instrukce OUT EXEC.
Poslední z těcho instrukcí je velmi všestranná: intrukce může být vložena do proudu předávaných dat skrze frontu FIFO. Příklad programování rozhraní \(I^2C\) používá vkládání podmínek STOP a RESTART vedle normálních dat. U instrukcí MOV EXEC a OUT EXEC, samotný MOV/OUT se provede v jednom cyklu a a co se má vykonat (EXEC) v dalším.
Registry
Každý stavový automat má malý počet vnitřních registrů. Tyto uchovávají vstupní nebo výstupní data a dočasné hodnoty, jako třeba proměnná čítače smyčky.
Výstupní posuvný registr

Výstupní posuvný registr (Output Shift Register — OSR) udržuje a posunuje výstupní data mezi vstupní frontou TX FIFO a piny (nebo jinam, třeba do scratch registru).
-
Instrukce PULL vezme 32 bitové slovo z TX fronty a přemístí ho do OSR.
-
Instrukce OUT posune data z OSR jinam, 1 až 32 bitů najednou.
-
Když jsou data posunuty ven, jejich původní místo je zaplněno nulami.
-
Pokud je zapnut autopull, potom stavový automat automaticky naplní registr OSR z FIFO pomocí instrukce OUT, jestliže je dosažena mez.
-
Posunování může být doleva nebo doprava, to se nastavuje pomocí konfiguračního registru.
Například, proud dat přes FIFO vycházející z pinů s rychlostí jeden bajt za dva tiky hodin:
.program pull_example1
loop:
out pins, 8
public entry_point:
pull
out pins, 8 [1]
out pins, 8 [1]
out pins, 8
jmp loop
Autopull (viz část o autopull) umožňuje aby hardware ve většině případů automaticky naplňoval výstupní posuvný registr. Stavový automat se pozastaví, jesliže se pokusíme použít instrukci OUT a mám prázdný výstupní posuvný registr. To má dvě výhody:
-
Nemusíme se zabývat tím, abychom explicitně naplňovali frontu v pravý čas.
-
Vyšší propustnost: můžeme poslat na výstup 32 bitů při každém tiku hodin, pokud FIFO zůstává naplněné.
Jestliže použijeme autopull, výše uvedený program může být zjednodušen a chová se úplně stejně:
.program pull_example2
loop:
out pins, 8
public entry_point:
jmp loop
Zabalení programu (viz Program wrapping) nám dovoluje další zjednodušení a jestliže je to požadováno, tak posílat do výstupu 1 bajt za každý takt hodin.
.program pull_example3
public entry_point:
.wrap_target
out pins, 8 [1]
.wrap
Vstupní posuvný registr (ISR)
Data vstupují do vstupního posuvného registru (Input Shift Register — ISR) v množství 1 až 32 bitů za takt, obsah registru se posouvá doleva nebo doprava, aby bylo místo pro další data. Jakmile je registr plný, jeho obsah je zapsán do výstupní fronty RX FIFO.

-
Instrukce IN posune najednou 1 až 32 bitů do registru
-
Instrukce PUSH zapíše obsah vstupního posuvného registru do výstupní fronty RX FIFO.
-
Vstupní posuvný registr je vynulován po každé instrukci PUSH
-
Pokud použijeme autopush, pak stavový automat automaticky tlačí data do ISR pomocí IN instrukce, jakmile je dosažena hranice posunutí.
-
Směr posunutí je možné konfigurovat procesorem pomocí konfiguračních registrů.
Některé periferie, jako třeba UART, musí posunovat zleva aby dosáhly správné pořadí bitů, protože na drátech je pořadí nejdříve LSB; avšak procesor může očekávat že výsledný bajt je zarovnán doprava. Problémse řeší pomocí speciálního vstupniho zdroje null, který dovoluje programátorovi posunovat množství nul z ISR za přečtenými daty.
Čítače posunutí
Stavový automat si pamatuje, kolik bitů v celku bylo posunuto ven z OSR pomocí instrukce OUT a dovnitř ISR pomocí instrukce IN. Tato informace je neustále sledována párem hardwarových čítačů, výstupního posuvného čítače a vstupního posuvného čítače, každý může počítat od 0 do 32. Při každé operaci posunutí odpovídající čítač se zvýší o počet posunutí až do maximální hodnoty 32 (což je šířka posuvného registru). Stavový automat může být nastaven tak, aby provedl určitou akci, když cítač dosáhne nakonfigurované hranice:
-
Výstupní posuvný registr může být automaticky naplněn jakmile je určitý počet bitů vysunut ven. Viz část autopull a autopush
-
Vstupní posuvný registr může být automaticky vyprázdněn jakmile je určitý počet bitů načten dovnitř. Viz část autopull a autopush
-
Instrukce PUSH a PULL mohou mít v sobě podmínku založenou na vstupním nebo výstupním čítači posunutí.
Při resetu PIO nebo při signálu CTRL_SM_RESTART vstupní čítač posunutí je vynulován (nic nebylo posunuto do vstupního registru) a výstupní čítač je nastaven na 32 (nic není potřeba vyplivnout z výstupního posuvného registru). Některé jiné instrukce mají vliv na čítače posunutí.
-
Úspěšná instrukce PULL vynuluje výstupní čítač posunutí.
-
Úspěšná instrukce PUSH vynuluje vstupní čítač posunutí.
-
MOV OSR, … (tj. libovolná MOV instrukce, která zapisuje do OSR) vynuluje výstupní čítač posunutí.
-
MOV ISR, … (tj. libovolná MOV instrukce, která zapisuje do ISR) vynuluje vstupní čítač posunutí.
-
OUT ISR, počet nastaví vstupní čítač posunutí na počet
Stírací registry
Každý stavový automat má dva 32bitové interní stírací registry, nazývané X a Y.
Používají se jako:
-
Zdroj nebo cíl v instrukcích IN, OUT, SET a MOV
-
Zdroj pro podmínku větvení.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.program ws2812_led
public entry_point:
pull
set x, 23 ; Loop over 24 bits
bitloop:
set pins, 1 ; Drive pin high
out y, 1 [5] ; Shift 1 bit out, and write it to y
jmp !y skip ; Skip the extra delay if the bit was 0
nop [5]
skip:
set pins, 0 [5]
jmp x-- bitloop ; Jump if x nonzero, and decrement x
jmp entry_point
V tomto programu používáme X jako čítač průchodu smyčkou a Y je použito jako dočasná proměnná pro rozvětvení na základě jednoho bitu z OSR. Tento program je použit k řízení WS2812 LED, ačkoliv lepší a menší implementace je možná (pouhé 3 instrukce).
instrukce MOV umožňuje používat stírací registy k uložení a obnovení posuvného registru, když například chcete opakovaně vysílat stejnou posloupnosti bitů.
|
Kompaktnější příklad řízení dispeje WS2812 (celkem 4 instrukce) najdete v datasheetu v částu 3.6.2 |
Fronty (FIFO)
Každý stavový stroj má dvojici čtyřslovných front (FIFO), jednu pro přenos dat ze systému do stavového automatu (TX) a druhou pro přenos ze stavového stroje do systému (RX). Do TX FIFO se zapisuje systémem buď přímo procesorem procesor nebo pomocí DMA řadiče a do RX FIFO je zapisováno stavovým automatem. FIFO oddělují časování stavových automatů PIO a systémové sběrnice, což umožňuje stavovým strojům běžet delší dobu bez zásahu procesoru.
FIFO také generují signály požadavku na data (DREQ), které umožňují systémovému řadiči DMA pokojně číst data na základě přítomnosti dat v RX FIFO do hlavní paměti nebo zapisovat nová data do TX FIFO, pokud tam je místo. To umožňuje procesoru nastavit poměrně dlouhé transakce, které mohou zahrnovat mnoho kilobajtů dat, které budou pokračovat bez dalšího zásahu procesoru.
Stavový automat často přenáší data pouze jedním směrem. V tomto případě můžeme pomocí SHIFTCTRL_FJOIN sloučit dvě fronty do jedné delší fronty s 8 vstupy fungující pouze jedním směrem. To je užitečné pro rozhraní s velkou šířkou pásma, jako je DPI.
Pozastavení
Stavové automaty mohou být dočasně pozastaveny z několika důvodů:
-
Intrukcí WAIT jejíž podmínka není dosud splněna
-
Blokující instrukcí PULL jestliže je TX FIFO prázdné anebo blokující instrukcí PUSH je-li RX FIFO plné.
-
Instrukcí IRQ WAIT která má nastavený flag a čeká až tento flag bude vynulován.
-
Instrukcí OUT když je nastavena vlastnost autopull a výstupní posuvný registr (OSR) dosáhl meze naplnění.
-
Instrukcí IN když je nastavena vlastnost autopush a vstupní posuvný registr (ISR) dosáhl meze naplnění a RX FIFO je plná.
V tomto případě se počítadlo programu neposune a stavový automat bude pokračovat ve vykonávání této instrukce v dalším cyklu. Pokud instrukce specifikuje určitý počet cyklů zpoždění před spuštěním další instrukce, tyto nezačnou, dokud blokování nebude uvolněno.
|
Vlastnost side-set (viz dále) nepodléhá pozastavení, ale je vždy provedeno na začátku instrukce. |
Mapování pinů
PIO řídí výstupní úroveň a směr až 32 GPIO a může sledovat jejich vstupní úrovně. Při každém tiku systémových hodin, každý stavový automat může provádět žádnou, jednu nebo obě následující:
-
Změnit úroveň nebo směr některých GPIO pomocí instrukce OUT nebo SET nebo načíst některé GPIO pomocí instrukce IN
-
Změnit úroveň nebo směr některých GPIO pomocí operace pomocí vlastnosti side-set
Každá z těchto operací je na jednom ze čtyř sousedících rozsahů GPIO, se základnou a počtem každého rozsahu konfigurováno prostřednictvím registru PINCTRL každého stavového automatu. Existuje rozsah pro každou z operací OUT, SET, IN a side-set. Každý rozsah může pokrýt kterýkoli z GPIO přístupných danému bloku PIO (na RP2040 je to 30 uživatelských GPIO) a rozsahy se mohou překrývat.
Pro každý jednotlivý výstup GPIO (úroveň a směr zvlášť) bere PIO v úvahu všech 8 zápisů, ke kterým může dojít v tomto cyklus a použije zápis ze stavového automatu s nejvyšším číslem. Pokud stejný stavový automat provádí na stejném GPIO současně SET/OUT a side-set, použije se side-set. Pokud žádný stavový stroj nezapisuje do tohoto GPIO výstup, jeho hodnota se oproti předchozímu cyklu nemění. Obecně jsou výstupy každého stavového automatu mapovány na odlišnou skupinu GPIO, které implementují některé rozhraní.
Návěští přerušení
Návěští přerušení (IRQ flags) jsou stavové bity, které mohou být nastaveny nebo vymazány stavovými stroji nebo systémem. Je jich celkem 8: všech 8 je viditelných ve všech stavových strojích a spodní 4 lze také maskovat do jedné z linek požadavku na přerušení PIO prostřednictvím řídícího registru IRQ0_INTE a IRQ1_INTE.
Mají dvě hlavní použití:
-
Prosazování přerušení na systémové úrovni z programu stavového stroje a volitelně čekání na potvrzení zpracování přerušení
-
Synchronizace provádění mezi dvěma stavovými automaty
Stavové stroje komunikují s příznaky prostřednictvím instrukcí IRQ a WAIT.
Spolupráce stavových automatů
Instrukční paměť je implementována jako soubor registrů s 1 zápisem a 4 čtením, takže všechny čtyři stavové stroje mohou číst instrukce na stejném cyklu, bez zastavení. Existují tři způsoby, jak použít vícestavové automaty:
-
Nasměrování více stavových automatů na stejný program
-
Nasměrování více stavových automatů na různé programy
-
Použití více stavových automatů ke spuštění různých částí stejného rozhraní, např. TX a RX strana UART, popř. clock/hsync a pixelová data na displeji DPI
Stavové stroje nemohou komunikovat data, ale mohou se vzájemně synchronizovat pomocí příznaků IRQ. Existuje celkem 8 příznaků (z nichž dolní čtyři lze maskovat pro použití jako systémové IRQ) a každý stavový stroj může nastavit nebo vymazat jakýkoli příznak pomocí instrukce IRQ a může čekat, až příznak přejde na vysokou nebo nízkou hodnotu pomocí instrukce WAIT IRQ. To dovoluje přesnou synchronizace mezi stavovými automaty na úrovni tiků hodin.
PIO asembler
PIO Assembler analyzuje zdrojový soubor PIO programu a vydává sestavenou verzi připravenou k zahrnutí do aplikací na RP2040. To zahrnuje aplikace C a C++ postavené na sadě SDK a programy v jazyce Python běžící na RP2040 portu MicroPython.
Tato část stručně představuje direktivy a instrukce, které lze použít v pioasm input. Hlubší diskuse o tom, jak používat pioasm, jak je integrován do sestavovacího systému SDK, rozšířené funkce, jako je průchod kódu a různé výstupní formáty, které dokáže produkovat, je uveden v knize Raspberry Pi Pico C/C++ SDK.
Při programování v C nebo C++ se PIO programy ukládají do souborů s příponou `.pio`.
Direktivy
Následující direktivy řídí sestavení PIO programu:
|
Definuje celočíselný symbol se jménem <symbol> s hodnotou <hodnota> (viz další část o hodnotách). Jestliže se toto .define objeví ve vstupním souboru před direktivou .program, potom je globální pro všechny PIO programy, jinak je lokální pro program ve kterém se objeví. Jestliže za zadáno klíčové slovo PUBLIC, tak je tento symbol vložen do sestaveného výstupu pro použití v uživatelském kódu. V C/C++ kódu to má formu:
|
|
Začátek nového programu se jménem |
|
Doplňková direktiva, která určuje offset paměti s PIO programem, kam musí být program umístěn. Převážně se toto používá pro programy, které musí být umístěny na offet 0, protože používají skok založený na datech s absolutním cílem skoku. Tuto direktivu nelze použít mimo program. |
|
Je-li použita tato direktiva, |
|
Tuto direktivu umístíme před instrukci, odkud chceme provádět točení programu. Můžeme ji použít jenom jednou.
Tuto direktivu nemůžeme umístit před direktivu |
|
Tuto direktivu umístíme za instrukci a program po vykonání této instrukce skočí na |
|
Určuje volbu, která se vztahuje k jazykovému genrerátoru (viz Language generatorr). Tuto direktivu nemůžeme použít mimo program. |
|
Uloží 16 bitovou hodnotu jako instrukci v programu. Tuto direktivu nemůžeme použít mimo program (nemůže být globální). |
Hodnoty
Následující typy hodnot mohou být použity k definování celých čísel nebo cílů skoků.
| integer | Celočíselná hodnota jako např. 3 nebo -7 |
|---|---|
hex |
Šestnáctková hodnota jako např. 0xf |
binary |
Binární hodnota jako např. 0b1001 |
symbol |
Hodnota definovaná pomocí direktivy .define |
<label> |
Instrukční offset náveští uvnitř programu. Používá se při skocích. |
(<expression>) |
Výraz k vyhodnocení. Závorky jsou povinné. |
Výrazy
Komentáře
Řádkový komentář začíná znaky // nebo ;
Komentář ve stylu jazyka C můžeme také používat. Je ohraničen pomocí /* a */.
Návěští
Návěští mají formu:
<symbol>:
nebo
PUBLIC <symbol>:
a začínají na začátku řádku.
Instrukce
Všechny instrukce PIO asembleru mají následující vzorec:
<instruction> (side <side_set_value>) ([<delay_value>])
Pseudoinstrukce
Pioasembler má jednu pseudoinstrukci, která nedělá nic:
nop Překládá se jako mov y, y. "No operation" nemá žádný vedlejší efekt, ale je důležitá pro operace s vedlejším nastavením (side-set) nebo pro doplňkovou prodlevu.
Množina PIO instrukcí
Přehled
PIO instrukce jsou 16 bitů dlouhé a mají následující kódování:
| Bit: | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
JMP |
0 |
0 |
0 |
prodleva/vedlejší nastavení |
podmínka |
adresa |
||||||||||
WAIT |
0 |
0 |
1 |
prodleva/vedlejší nastavení |
Pol |
zdroj |
Index |
|||||||||
IN |
0 |
1 |
0 |
prodleva/vedlejší nastavení |
zdroj |
počet bitů |
||||||||||
OUT |
0 |
1 |
1 |
prodleva/vedlejší nastavení |
zdroj |
počet bitů |
||||||||||
PUSH |
1 |
0 |
0 |
prodleva/vedlejší nastavení |
0 |
IfF |
Blk |
0 |
0 |
0 |
0 |
0 |
||||
PULL |
1 |
0 |
0 |
prodleva/vedlejší nastavení |
1 |
IfF |
Blk |
0 |
0 |
0 |
0 |
0 |
||||
MOV |
1 |
0 |
1 |
prodleva/vedlejší nastavení |
cíl |
operace |
zdroj |
|||||||||
IRQ |
1 |
1 |
0 |
prodleva/vedlejší nastavení |
0 |
clear |
wait |
index |
||||||||
SET |
1 |
1 |
1 |
prodleva/vedlejší nastavení |
cíl |
data |
||||||||||
Všechny PIO instrukce jsou vykonávány v jednom tiku hodi.
Funkce 5 bitů prodleva/vedlejší nastaveni závisí na konfiguraci SIDESET_COUNT stavového automatu:
-
Až 5 bitů v pořadí LSB čili 5 - SIDESET_COUNT je možné zakódovat počet tiků nečinnosti vložených mezi tuto a následující instrukci.
-
Až 5 bitů v pořadí MSB nastavených pomocí SIDESET_COUNT zakóduje vedlejší nastavení, které budou uplatňovány na GPIO souběžně s prováděním vlastní instrukce-
JMP (skok)
Kódování
| Bit: | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
JMP |
0 |
0 |
0 |
prodleva/vedlejší nastavení |
podmínka |
adresa |
||||||||||
Popis práce
Nastaví čítač instrukcí (program counter — PC) na adresu jestliže je podmínka splněna, jinak nedělá nic.
Prodleva se uplatňuje vždy, ať už je podmínka splněna nebo ne, ale až potom co je podmínka vyhodnocena a čítač instrukcí nastaven.
-
podmínka je splněna (bity 7 až 5):
-
000: žádná podmínka: je splněno vždy
-
001: !X: pokud je registr X nulový
-
010: X--: nejdříve testuje zda je X nenulové a potom se odečte od X jednička
-
011: !Y: pokud je registr Y nulový
-
100: Y--: nejdříve se testuje zda je Y nenulové a potom se odečte od Y jednička
-
101: X!=Y: pokud je X různý od Y
-
110: PIN: větvení na vstupním pinu, pokud je pin na vysoké úrovni 1
-
111: !OSRE: pokud výstupní posuvný registr (OSR) není prázdný
-
-
adresa (bity 4 až 0): adresa na kterou má funkce skočit. Je to absolutní adresa uvnitř instrukční paměti PIO.
JMP PIN větví na základě GPIO, které je zvoleno pomocí konfiguračního pole EXECCTL_JMP_PIN. V tomto poli můžeme zvolit až 32 GPIO pinů, které jsou viditelné stavovému automatu nezávisle na tom, jak je nastaveno vstupní mapování pinů. Větvění (nebo skok) je proveden, pokud je zvolený pin na vysoké úrovni (1).
!OSRE porovná bity vystrčené od předchozího PULL kde práh (počet) vystrčených bitů je nastaven v SHIFTCTRL_PULL_THRESH. Ten samý práh se používá když je aktivován autopull.
JMP X-- a JMP Y-- vždy dekrementuje registry X resp. Y, nezávisle na hodnotě registru X nebo Y. Ke skoku dochází pokud původní hodnota před dekrementací je nenulová. Pokud je hodnota registru 0, ke skoku nedojde.
Syntaxe
jmp (<cond>) <target>
kde:
-
<cond>je volitelná podmínka (třeba !X pro X je nulové). Není-li podmínka uvedena, skok se provede vždy. -
<target>je návěští v programu nebo hodnota, která představuje offset uvnitř programu (první instrukce v programu má offset 0).Protože PIO JMP instrukce používá absolutní adresy uvnitř instrukční paměti, offsety skoků musí být přepočítány při nahrání programu do paměti v čase běhu. To je ošetřeno, když se program nahrává do paměti pomocí SDK, ale je potřeba být opatrný, pokud je zakódována instrukce JMP při použití OUT EXEC.
WAIT (pauza)
Kódování
| Bit: | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
WAIT |
0 |
0 |
1 |
prodleva/vedlejší nastavení |
Pol |
zdroj |
index |
|||||||||
Popis práce
Zastav se, dokud je nějaká podmínka splněna.
-
Polarity (bit 7)
-
1: čekej na jedničku
-
0: čekej na nulu
-
-
Zdroj (bity 6 a 5): na co se má čekat až nastane, hodnoty jsou:
-
00: GPIO: na GPIO vstup zvolený pomocí hodnoty index. Toto je absolutní GPIO index a není ovlivněn stavovým automatem při přemapování pinů.
-
01: PIN: na vstupní pin určený indexem.
-
10: IRQ: na PIO IRQ flag určený indexem.
-
11: Rezervováno
-
-
Index: který pin nebo bit se bude testovat (bity 4 až 0)
WAIT IRQ se chová trochu jinak, než jiné zdroje čekání:
-
Jestlie je polarita rovná 1, zvolený IRQ flag je vynulován stavovým automatem, jestliže je splněna podmínka čekání.
-
Index příznaku se dekóduje stejným způsobem jako pole indexu IRQ: pokud je nastaveno MSB, je ID stavového stroje (0…3) přidáno do indexu IRQ pomocí modulo-4 přidání na dvou LSB. Například stavový automat 2 s příznakem o hodnotě '0x11' bude čekat na příznak 3 a hodnota příznaku '0x13' bude čekat na příznak 1. To umožňuje více stavovým strojům spuštění stejného programu pro vzájemnou synchronizaci.
|
WAIT 1 IRQ x by naměla být používána s IRQ flagem použitém v řadiči přerušení, protože může dojít ke sporu s obluhou přerušení systému. |
Syntaxe
wait <polarity> gpio <gpio_num>
wait <polarity> pin <pin_num>
wait <polarity> irq <irq_num> ( rel )
kde:
<polarity> je hodnota, která určuje polaritu (buď 0 nebo 1)
<pin_num> je hodnota, která určuje číslo pinu mapovaného pomocí vstupního mapování
<gpio_num> je hodnota, která určuje skutečné číslo GPIO pinu
<irq_num> (rel) je hodnota přerušení na které čekáme (0 až 7). Jestliže je v instrukci přítomné rel, tak skutečné číslo přerušení se vypočítá tak, že vezmeme dva nejnižší bity z čísla přerušení irq_num
a nahradíme je dvěma nejnižšími bity ze součtu (irq_num \+ sm_num), kde sm_num je číslo stavoného automatu.
IN (vstup)
Kódování
| Bit: | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
IN |
0 |
1 |
0 |
prodleva/vedlejší nastavení |
zdroj |
počet bitů |
||||||||||
Popis práce
Posune počet bitů ze zdroje do vstupního posuvného registru (ISR). Směr posunování je určen pro každý stavový automat v SHIFTCTRL_IN_SHIFTDIR. Dále můžeme zvýšit počet posunutí o počet bitů až do hodnoty 32.
-
Zdroj:
-
000: PINS
-
001: X (registr X)
-
010: Y (registr Y)
-
011: NULL (samé nuly)
-
100: rezervováno
-
101: rezervováno
-
110: ISR
-
111: OSR
-
-
počet bitů: Kolik bitů máme posunout do ISR. 1 až 32 bitů, 32 bitů je zakódováno jako 00000.
Pokud máme nastaveno autopush, instrukce IN také natlačí obsah registru ISR do RX FIFO, je-li dosaženo prahu (SHIFTCTRL_PUSH_TRESH). Instrukce IN trvá jeden tik a je jedno, zda máme autopush nastaveno nebo ne. Stavový automat se pozastaví, je-li RX FIFO plná, když se objeví automatické tlačení. Autopush vynuluje obsah registru ISR a také vynuluje vstupní posuvný čítač. Viz #auto_push_pull.
Instrukce IN vždy používá nejméně významné bity zdrojových dat. Pokud je například PINCTRL_IN_BASE nastaveno na 5, instrukce IN PINS, 3 převezme hodnoty pinů 5, 6 a 7 a přesune je do ISR. Nejprve se ISR posune doleva aby se uvolnilo místo pro nová vstupní data, pak se vstupní data zkopírují do této mezery, a tam zůstanou. Bitové pořadí vstupních dat nejsou závislá na směru posunování registru ISR.
NULL lze použít k posunutí obsahu ISR. Například UARTy přijímají LSB jako první, takže se musí posunout doprava. Po osmi instrukcích IN PINS, 1 obsadí vstupní sériová data bity 31…24 registru ISR. Instrukce IN NULL, 24 posune do ISR 24 nulových bitů a zarovná vstupních dat v ISR na bity 7…0. Alternativně může procesor nebo DMA provádět čtení bajtů z adresy FIFO + 3, což by zabralo bity 31…24 obsahu FIFO.
Syntaxe
in <zdroj>, <počet_bitů>
kde:
<zdroj> je jeden ze zdrojů uvedený výše.
<počet_bitů> je hodnota, které určuje počet bitů posunutí (může být od 1 do 32 bitů)
OUT (výstup)
Kódování
| Bit: | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
OUT |
0 |
1 |
0 |
prodleva/vedlejší nastavení |
cíl |
počet bitů |
||||||||||
Popis práce
Vysune počet bitů ven z výstupního posuvného registru (OSR) a zapíše tyto bity do cíle. Dále, zvedne počet vysunutých bitů o počet bitů, až na 32.
-
Cíl:
-
000: PINS
-
001: X (registr X)
-
010: Y (registr Y)
-
011: NULL (zahodí data)
-
101: PC
-
110: ISR (také nastaví ISR posuvný čítač na počet bitů )
-
111: EXEC (provede OSR posuvná data jako instrukci)
-
-
Počet bitů: kolik bitů se vysune z OSR. 1 až 32 bitů, 32 je zakódováno jako 00000.
Do cíle se zapíše 32bitová hodnota: bity nižšího počtu bitů pocházejí z OSR a zbytek jsou nuly. Tato hodnota je nejméně významný počet bitů z OSR, pokud je SHIFTCTRL_OUT_SHIFTDIR vpravo, jinak jsou to nejvíce významné bity.
PINS a PINDIRS používají mapování pinů OUT, jak je popsáno v části 3.5.6.
Pokud je nastaven autopull, OSR se automaticky doplní z TX FIFO, pokud je dosažen práh vytažení, SHIFTCTRL_PULL_THRESH. Počet posunů výstupu je současně vymazán na 0. V tomto případě se OUT zastaví, pokud je TX FIFO prázdný, ale jinak se stále provádí v jednom cyklu. Podrobnosti jsou uvedeny v části 3.5.4.
OUT EXEC umožňuje začlenění instrukcí do datového toku FIFO. Samotný OUT se provede v jednom cyklu a instrukce z OSR se provede v dalším cyklu. Neexistují žádná omezení ohledně typů pokynů, které mohou být provedeny tímto mechanismem. Cykly zpoždění na počátečním OUT jsou ignorovány, ale prováděný může vkládat cykly zpoždění jako normální.
OUT PC se chová jako nepodmíněný skok na adresu posunutou z OSR.
Syntaxe
out <cíl>, <počet_bitů>
kde:
<cíl> je jeden z cílů uvedených výše.
<počet_bitů> je hodnota, která určuje počet bitů posunutí (rozsah je 1 až 32)
PUSH
Kódování
| Bit: | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
PUSH |
1 |
0 |
0 |
prodleva/vedlejší nastavení |
0 |
IfFull |
Block |
0 |
0 |
0 |
0 |
0 |
||||
Popis práce
Strčí obsah ISR do RX FIFO, jako 32 bitové slovo. Vynuluje ISR.
-
IfFull: Jestliže je v 6 bitu 1, nedělej nic dokud vstupní celkový počet bitů nedosáhne hranice SHIFTCTRL_PUSH_TRESH (to samé platí pro autopush).
-
Block: Jestliže je v 5 bitu 1, pozastav provádění jestliže je RX FIFO plná.
PUSH IFFULL pomáhá být programům menšími, podobně jako autopush. Je to užitečné v případech, kdy by se IN zastavil v nevhodný čas, pokud bylo povoleno autopush, např. pokud stavový automat při tomto dělá nějaký externí řídicí signál.
PIO asembler standardně nastavuje bit Block na jedničku. Pokud není bit Block nastaven, PUSH se nezastaví při plné frontě RX FIFO, ale pokračuje ihned k další instrukci. Stav a obsah FIFO se v tomto případě nezmění. ISR je stále vymazáno na nuly a je nastaven příznak FDEBUG_RXSTALL (stejný jako blokování PUSH nebo autopush na plné RX FIFO) k označení ztráty dat.
Syntaxe
push ( iffull )
push ( iffull ) block
push ( iffull ) noblock
kde:
iffull je to samé jako IfFull == 1 nahoře. Tj. implicitně pokud není napsáno slovo iffull, tak IfFull == 0.
block je to samé jako Block == 1 nahoře. Toto je implicitní chování funkce push, když není napsáno block ani noblock.
noblock je to samé jako Block == 0 nahoře.
PULL
Kódování
| Bit: | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
PULL |
1 |
0 |
0 |
prodleva/vedlejší nastavení |
1 |
IfEmpty |
Block |
0 |
0 |
0 |
0 |
0 |
||||
Popis práce
Načte 32bitové slovo z TX FIFO do OSR.
-
IfEmpty: Pokud je 1, nedělejte nic, dokud celkový počet posunů výstupu nedosáhl své prahové hodnoty, SHIFTCTRL_PULL_THRESH stejné jako pro autopull; viz část 3.5.4).
-
Block: Pokud je 1, zastaví se, pokud je TX FIFO prázdné. Pokud je 0, stahování z prázdného FIFO zkopíruje scratch X do OSR.
Některá periferní zařízení (UART, SPI…) by se měla zastavit, když nejsou k dispozici žádná data, a vyzvednou je, jakmile přijdou; ostatní (I2S) by měli hodiny nepřetržitě a je lepší vydávat zástupná nebo opakovaná data než zastavit taktování. Toho lze dosáhnout s parametrem Blokovat.
Neblokující PULL na prázdné FIFO má stejný účinek jako MOV OSR, X. Program může buď předem načíst scratch registr X s vhodným výchozím nastavením nebo spusťte MOV X, OSR po každém PULL NOBLOCK, takže bude recyklováno poslední platné slovo FIFO dokud nebudou k dispozici nová data.
PULL IFEMPTY je užitečné, pokud by se OUT s autopullem zablokoval na nevhodném místě, když je TX FIFO prázdný. IfEmpty umožňuje některá ze stejných zjednodušení programu jako autopull – například odstranění vnější smyčky počítadlo — ale k zablokování dojde v kontrolovaném bodě programu.
|
Když je povolen autopull, potom každá instrukce PULL je de facto no-op, když je OSR plné a tak se instrukce PULL chová jako bariéra. OUT NULL, 32 může být použita abychomexplicitně zahodili obsah registru OSR. |
Syntaxe
pull ( ifempty )
pull ( ifempty ) block
pull ( ifempty ) noblock
kde:
ifempty je ekvivalentní IfEmpty == 1 nahoře.
block je ekvivalentní Block == 1. To je implicitní chování pokud není napsáno block ani noblock.
noblock je ekvivalentní Block == 0 nahoře.
MOV
Kódování
| Bit: | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
MOV |
1 |
0 |
1 |
prodleva/vedlejší nastavení |
cíl |
operace |
zdroj |
|||||||||
Kopíruje data ze zdroje do cíle.
-
Cíl:
-
000: PINS (používá stejné mapování pinů jako OUT)
-
001: X (registr X)
-
010: Y (registr Y)
-
011: rezervováno
-
100: EXEC (vykoná data jako instrukci)
-
101: PC
-
110: ISR (při této operaci je vstupní posuvný čítač nastaven na 0, tj. prázdno)
-
111: PSR (při této operaci je výstupní posuvný čítač nastaven na 0, tj. plno)
-
-
Operace:
-
00: nic
-
01: bitová inverze
-
10: bitové prohození
-
11: rezervováno
-
-
Zdroj:
-
000: PINS (používá stejné mapování pinů jako instrukce IN)
-
001: X
-
010: Y
-
011: NULL
-
100: rezervováno
-
101: STATUS
-
110: ISR
-
111: OSR
-
Popis práce
MOV PC způsobí nepodmíněný skok. MOV EXEC má stejné chování jako OUT EXEC (oddíl 3.4.5) a umožňuje aby obsah registru bude považován za instrukci a proveden jako instrukce. Samotná instrukce MOV se provede v 1 tiku a instrukce, která je ve zdroji v dalším tiku. Cykly zpoždění na MOV EXEC jsou ignorovány, ale prováděný může vkládat cykly zpoždění jako normálně.
Zdroj STATUS má hodnotu samé jedničky nebo samé nuly, v závislosti na stavu některého stavového stroje, jako je FIFO plné/prázdné, což se nastavuje pomocí EXECCTRL_STATUS_SEL.
MOV může manipulovat s přenesenými daty omezeným způsobem, určeným argumentem Operace. Bitová inverze nastaví každý bit v cíli do logického NOT odpovídajícího bitu ve zdroji, tj. jedničkový bit se stane nulovým bitem a naopak. Bitový prohození nastaví každý bit n v cíli až bit 31 - n ve zdroji, za předpokladu, že bity jsou číslovány od 0 do 31.
MOV cíl, PINS čte piny podle mapování pinů IN a zapisuje celou 32bitovou hodnotu do cíle bez maskování. LSB čtené hodnoty začíná na pinu ovedenému v PINCTRL_IN_BASE a každý následující bit pochází z dalšího očíslovaného pinu, všechno nepřeleze číslo 31.
Syntaxe
mov <cíl>, ( op ) <zdroj>
kde:
<cíl> je jeden z cílů uvedených výše.
<op> jestliže je přítomno, tak to může být ! nebo ~ pro bitovou negaci a :: pro bitové prohození.
<zdroj> je jeden ze zdrojů uvedených výše.
IRQ (přerušení)
Kódování
| Bit: | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
IRQ |
1 |
1 |
0 |
prodleva/vedlejší nastavení |
0 |
clear |
wait |
index |
||||||||
Popis práce
Nastaví nebo zruší příznak IRQ který je v argumentu Index.
-
Clear: Pokud je 1, vymaže příznak vybraný indexem, místo jeho zvýšení. Pokud je nastaveno Clear, bit Wait nemá žádný účinek.
-
Wait: Pokud je 1, zastaví se staový automat, dokud nebude vztyčená vlajka znovu spuštěna, např. pokud obsluha přerušení systému potvrdila příznak.
-
Index:
-
Bity 2,1 a 0 určují index IRQ od 0 do 7. Tento příznak IRQ bude nastaven/vymazán v závislosti na bitu Clear.
-
Pokud je nastaveno MSB, je k indexu IRQ přidáno ID stavového stroje (0…3) přidáním modulo-4 na dva LSB. Například stavový stroj 2 s hodnotou příznaku 0x11 zvýší příznak 3 a příznak s hodnotou 0x13 vztyčit vlajku 1.
-
Příznaky IRQ 4-7 jsou viditelné pouze pro staové automaty; Příznaky IRQ 0-3 lze směrovat na přerušení na systémové úrovni, na obou ze dvou externích linek požadavku na přerušení PIO, nakonfigurovaných pomocí IRQ0_INTE a IRQ1_INTE.
Modul sčítání bitu umožňuje relativní adresování instrukcí 'IRQ' a 'WAIT' pro synchronizaci stavových strojů na kterých běží stejný program. Bit 2 (třetí LSB) není tímto přidáním ovlivněn.
Pokud je nastaven bit Wait, cykly zpoždění začnou až po uplynutí čekací doby.
Syntaxe
irq <číslo_prerušení> ( rel )
irq set <číslo_přerušení> ( rel )
irq nowait <číslo_přerušení> ( rel )
irq wait <číslo_přerušení> ( rel )
irq clear <číslo_přerušení> ( rel )
kde:
<číslo_přerušení> ( rel ) je hodnota která určuje číslo přerušení na které čekáme (0-7). Je-li doplněno rel, potom skutečné číslo přerušení je vypočítáno tak, že dva nejnižší bity jsou nahrazeny
dvěma nejnižsími bity součtu (číslo_přerušení + sm_num), kde sm_num je číslo stavového automatu.
irq znamená nastav přerušení bez čekání
irq set také znamená nastav přerušení bez čekání
irq nowait opět znamená nastav přerušení bez čekání
irq wait znamená nastav přerušení a čekej, až bude příznak přerušení vynulován
irq clear znamená vynuluj přerušení.
SET
Kódování
| Bit: | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
SET |
1 |
1 |
1 |
prodleva/vedlejší nastavení |
cíl |
data |
||||||||||
Popis práce
Zapiš okamžitě hodnutu v datech do cíle.
-
Cíl:
-
000: PINS
-
001: X (registr X) 5 nejnižších bitů je zapsáno z dat do registru X, ostatní bity jsou vynulovány.
-
010: Y (registr Y) 5 nejnižších bitů je zapsáno z dat do registru Y, ostatní bity jsou vynulovány.
-
011: rezervováno
-
100: PINDIRS
-
101: rezervováno
-
110: rezervováno
-
111: rezervováno
-
-
Data: pětibitová hodnota, která se zapíše do pinů nebo registru.
To lze použít k aktivaci řídicích signálů, jako je výběr hodin nebo čipu, nebo k inicializaci čítačů smyček. Protože data mají 5 bitovou velikost, scratch registry lze NASTAVIT na hodnoty od 0-31, což je dostatečné pro 32 iterační smyčku.
Mapování SET a OUT na piny se konfiguruje nezávisle. Mohou být mapovány na různá místa, například pokud má být jeden pin použit jako hodinový signál a druhý pro data. Mohou to být také překrývající se rozsahy kolíků: vysílač UART může použít SET k aktivaci start a stop bitů a instrukce OUT k posunutí FIFO dat do stejných pinů.
Syntaxe
set <cíl>, <hodnota>
kde:
<cíl> je jedním z cílů uvedených výše.
<hodnota> Hodnota, která má být nastavena (v rozsahu 0 až 31).
Podrobnosti k funkcím
Side-set (vedlejší nastavení)
Side-set (vedlejší nastavení) je funkce, která umožňuje stavovým automatům měnit úroveň nebo směr až 5 pinů, paralelně s tím, co provádí sama instrukce.
Příkladem, kde je to nutné, je rychlé rozhraní SPI: zde musí být přechod hodin (přepínání 1→0 nebo 0→1) současně s datovým přechodem, kdy je nový datový bit posunut z OSR na GPIO. V tomto případě instrukce OUT a side-set dosáhne obojího najednou.
Díky tomu je časování rozhraní přesnější, snižuje se celková velikost programu (samostatná instrukce SET není potřeba k přepínání hodinového pinu) a tak zvyšuje maximální frekvenci, na které může SPI běžet.
Side-set také dělá mapování GPIO mnohem flexibilnější, protože jeho mapování je nezávislé na SET. Příklad I2C kódu umožňuje mapování SDA a SCL na libovolné dva libovolné piny, pokud je zakázáno roztahování hodin. Normálně se SCL přepne, aby synchronizovalo přenos dat a SDA obsahuje datové bity. Avšak některé konkrétní I2C sekvence, jako jsou podmínky Start a Stop, potřebují pevný vzor, aby bylo možné řídit SDA i SCL. Mapování I2C používá k dosáhnout toho je:
-
Side-set → SCL
-
OUT → SDA
-
SET → SDA
To umožňuje stavovému automatu sloužit dvěma případům použití dat na SDA a hodin na SCL, nebo pevných přechodů na obou SDA a SCL, přičemž stále umožňuje mapování SDA a SCL na libovolné dva GPIO podle výběru.
Data vedlejšího nastavení jsou zakódována v poli prodleva/vedlejší nastavení (Delay/side-set) každé instrukce. Jakoukoli instrukci lze kombinovat s vedlejším nastavením, včetně instrukcí, které zapisují na piny, jako jsou OUT PINS nebo SET PINS. Mapování pinů vedlejšího nastavení je nezávislé na mapování OUT a SET, i když se mohou překrývat. Pokud vedlejší nastavení a instrukce OUT nebo SET zapisují na stejný pin současně, jsou použita data z vedlejšího nastavení.
|
Jestliže je instrukce pozastavena, vedlejší nastavení funguje okamžitě. |
1
2
3
4
5
6
.program spi_tx_fast
.side_set 1
loop:
out pins, 1 side 0
jmp loop side 1
Příklad programu spi_tx_fast ukazuje dvě výhody vedlejšího nastavení: přechody dat i hodin mohou být precizněji zarovnány a program může běžet rychleji a produkovat výstupní proud dat jeden bit za dvy tiky hodin. Program také může být menší.
Máme čtyři možnosti jak konfigurovat vedlejší nastavení:
-
Nastavit počet nejvíce významných bitů (MSB) v poli prodleva/vedlejší nastavení aby bylo používáno jako vedlejší nastavení a ne jako prodleva. To se dělá pomocí PINCTRL_SIDESET_COUNT. Je-li PINCTRL_SIDESET_COUNT nastaveno na 5, cykly prodlevy nejsou k dispozici. Je-li nastaveno na 0, nebude se používat vedlejší nastavení.
-
Můžeme nastavit, že nejvýznamnější bit se použije jako povolovací bit (enable bit). Vedlejší nastavení bude fungovat u instrukcí, kde bude enable bit==1. Pokud nebudeme používat enable bit, potom každá instrukce na stavovém automatu provede vedlejší nastavení, je-li SIDESET_COUNT nenulový. To se konfiguruje pomocí EXECCTRL_SIDE_EN.
-
Číslo GPIO, na které se má mapovat nejméně významný bit vedlejšího nastavení. Konfiguruje se pomocí PINCTRL_SIDESET_BASE.
-
Můžeme nastavit, že vedlejší nastavení mění úrovně GPIO nebo směry GPIO. Konfiguruje se pomocí EXECCTRL_SIDE_PINDIR.
Ve výše uvedeném příkladu máme pouze jeden datový bit vedlejšího nastavení a každá instrukce provádí vedlejší nastavení, takže není potřeba žádný povolovací bit. SIDESET_COUNT bude 1, SIDE_EN bude nepravda. SIDE_PINDIR by byl také nepravdivý, protože chceme řídit hodiny vysoká a nízká impedance, nikoli vysoká a nízká impedance. SIDESET_BASE vybere GPIO, ze kterého jsou hodiny řízeny.
Vnější smyčka (Program wrapping)
PIO programy často mají vnější smyčku: opakovaně provádí ty samé kroky přenášejí data mezi FIFO a vnějším světem. Program obdélníkových vln je minimálním příkladem tohoto chování.
1
2
3
4
5
6
.program squarewave
set pindirs, 1 ; Nastaví pin jako výstupní
again:
set pins, 1 [1] ; Nastaví pin na 1 a čeká ještě jeden tik (instrukce trvá 2 tiky)
set pins, 0 ; Nastaví pin na 0 (instrukce trvá 1 tik)
jmp again ; Nastaví PC na návěští `again` (skočí na again), instrukce trvá 1 tik
Hlavní část programu nastavuje pin vysoko a pak nízko, čímž vytváří jednu periodu obdélníkové vlny.
Celý program se poté zacyklí a řídí periodický výstup.
Samotný skok trvá jeden cyklus, stejně jako každá instrukce set.
Abychom zachovali stejnou délku vysokého a nízkého stavu nastavenou na pinu, musí mít instrukce set pins, 1 na řádku 4 přidán jeden tik zpoždění,
který činí stavový automat nečinným po dobu jednoho cyklu před provedením instrukce na 5. řádku set pins, 0.
Proto trvá celá smyčka čtyři cykly.
Máme zde dvě nepříjemnosti:
-
JMP zabírá místo v paměti instrukcí, které lze použít pro jiné programy.
-
Nepotřebný tik nutný k provedení instrukce JMP snižuje maximální výstupní rychlost na polovinu.
Počítadlo programů (PC) přirozeně zalomí na 0, když se jeho hodnota zvýší nad 31.
Mohli bychom tedy vyřešit druhý problém zaplněním celé paměti instrukcí opakujícím se vzorem set pins 1 a set pins 0, ale to je plýtvání.
Stavové stroje mají hardwarovou vlastnost konfigurovanou prostřednictvím jejich řídicího registru EXECCTRL, která řeší tento běžný případ.
1
2
3
4
5
6
7
8
9
.program squarewave_wrap
; Like squarewave, but use the state machine's .wrap hardware instead of an
; explicit jmp. This is a free (0-cycle) unconditional jump.
set pindirs, 1 ; Nastaví pin jako výstupní
.wrap_target
set pins, 1 [1] ; Nastaví pin na vysokou úrověň a čeká ještě jeden tik
set pins, 0 [1] ; Nastaví pin na nízkou úrověň a čeká ještě jeden tik
.wrap
Po provedení instrukce z paměti programu použijí stavové stroje k aktualizaci čítače instrukcí PC následující logiku:
-
Pokud je aktuální instrukce JMP a podmínka je pravdivá, nastavím PC na cíl instrukce JMP.
-
V opačném případě, pokud PC odpovídá EXECCTRL_WRAP_TOP, nastavím PC na EXECCTRL_WRAP_BOTTOM
-
V opačném případě zvýším PC nebo ho nastavím na 0, pokud je aktuální hodnota PC==31.
Direktivy .wrap_target a .wrap sestavení v pioasmu jsou v podstatě štítky.
Exportují konstanty, které mohou být zapsány do řídicích polí WRAP_BOTTOM a WRAP_TOP.
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// --------------- //
// squarewave_wrap //
// --------------- //
#define squarewave_wrap_wrap_target 1
#define squarewave_wrap_wrap 2
static const uint16_t squarewave_wrap_program_instructions[] = {
0xe081, // 0: set pindirs, 1
// .wrap_target
0xe101, // 1: set pins, 1 [1]
0xe100, // 2: set pins, 0 [1]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program squarewave_wrap_program = {
.instructions = squarewave_wrap_program_instructions,
.length = 3,
.origin = -1,
};
static inline pio_sm_config squarewave_wrap_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + squarewave_wrap_wrap_target, offset + squarewave_wrap_wrap);
return c;
}
#endif
Toto je nezpracovaný výstup z assembleru PIO, pioasm, který vytvořil výchozí objekt pio_sm_config obsahující hodnotu WRAP registru z výpisu programu.
Pole řídicího registru lze také inicializovat přímo.
|
WRAP_BOTTOM a WRAP_TOP jsou absolutní adresy v PIO instrukční paměti. Je-li program umístěn s nějakým offsetem, tyto adresy musí bít přepočítány. |
Příklad squarewave_wrap má nastaveno zpoždění u každé instrukce 1 tik, chová se stejně jako původní program squarewave. Díky vnější smyčce může být toto zpoždění odstraněno a tak může být výstupní pin přepínán dvakrát rychleji a bude přitom zachována symetrie vysokého a nízkého stavu.
.program squarewave_fast
; Like squarewave_wrap, but remove the delay cycles so we can run twice as fast.
set pindirs, 1 ; Set pin to output
.wrap_target
set pins, 1 ; Drive pin high
set pins, 0 ; Drive pin low
.wrap
Spojení front
Normálně, každý stavový automat má k dispozici dvě fronty o čtyřech záznamech: jednu pro přenos dat z systému do statovoého automatu (TX) a druhou k obrácenému přenosu dat ze stavového automatu do systému (RX). Avšak mnoho aplikací nepotřebuje obousměrnou komunikaci mezi systémem a stavových automatem, ale muže pro ně být výhodnější používat jednu frontu s více záznamy, což potřebují třeba rozhraní s vysokou propustností dat, jako např DPI. Pro tyto případy, SHIFTCTRL_FJOIN vytvoří ze dvou čtyřzáznamových front jednu osmizáznamovou frontu.

Autopush a autopull
S každou instrukcí OUT, registr OSR se postupně vyprazdňuje, protože jsou data vystrkovány ven. Jakmile je OSR prázdný, OSR musí být znovu naplněn: například přenos pomocí PULL jednoho slova dat z TX FIFO do OSR. Podobně, ISR musí být vyprázdněn jakmile je plný.
Jedním z přístupů k tomu je smyčka, která provádí PULL poté, co bylo vysunuto určité množství dat:
1
2
3
4
5
6
7
8
9
10
11
.program manual_pull
.side_set 1 opt
.wrap_target
set x, 2 ; X = bit count - 2
pull side 1 [1] ; Stall here if no TX data
bitloop:
out pins, 1 side 0 [1] ; Shift out data bit and toggle clock low
jmp x-- bitloop side 1 [1] ; Loop runs 3 times
out pins, 1 side 0 ; Shift out last bit before reloading X
.wrap
Tento program vezme 4 bity z každého slova FIFO, s doprovodným bitovým tikem, s konstantní rychlostí 1 bit na 4 cykly. Když je TX FIFO prázdné, zastaví se s vysokým taktem (všimněte si, že side-set stále probíhá v cyklech, kde se instrukce zastaví). Obrázek ukazuje jak stavový automat provádí tento program.

Tento program má některá omezení:
-
Zabírá pět instrukčních slov v paměti, ale jenom dvě jsou ve skutečnosti užitečné (
out pins, 1 set 0aout pins, 1 set 1) k výstupu sériových dat a hodin. -
Jeho výstupní propustnost je omezena systémovými hodinami děleno čtyřmi, protože tiky navíc jsou potřeba na natažení nových dat a znovu nastavení počítadla smyčky.