PIO je zkratka z programovatelného vstupně výstupního zařízení (Programmable I/O). PIO je v podstatě velmi rychlý koprocesor se svými vlastními registry, pamětí, DMA a dokonce s vlastním programovacím jazykem (PIO Assembler). PIO pracuje nezávisle na hlavním procesoru. Kompletní popis funkce PIO s programátorskými příklady je popsán v 3. kapitole C SDK book.
Úvod
Na většině mikrokontrolérů používáte „bit-bang“, což znamená použití hlavního procesorového jádra k přímému zapínání a vypínání pinů. To může fungovat, ale může to způsobit problémy s načasováním (zejména pokud používáte přerušení) a může to vyžadovat spoustu prostředků na zpracování, které můžete potřebovat pro jiné věci. Pico má v rukávu malý trik: Programmable I/O (PIO). Kromě dvou hlavních procesorových jader Cortex-M0+ existují dva bloky PIO, z nichž každý má čtyři stavové stroje. Jedná se o skutečně okleštěná procesní jádra, která lze použít ke zpracování dat přicházejících do mikrokontroléru nebo z něj a odlehčit některé požadavky na zpracování pro implementaci komunikačních protokolů.

Pro každý jednoduchý procesor (nazývaný stavový stroj) existují dvě struktury First-In-First-Out (FIFO): jedna pro data přicházející a jedna pro data odcházející. Ty se používají k propojení stavového automatu se vším ostatním. V podstatě se jedná pouze o typ paměti, kde jsou data načítána ve stejném pořadí, v jakém jsou zapsána – z tohoto důvodu se jim často říká fronty, protože fungují stejným způsobem jako fronta lidí. První část dat ve frontě (nebo první osoba ve frontě) je první, která se dostane na druhý konec. Když potřebujete odeslat data přes stavový automat PIO, vložíte je do FIFO, a když je váš stavový automat připraven na další část dat, stáhne je. Tímto způsobem váš stavový stroj PIO a program běžící na hlavním jádru nemusí být dokonale synchronizované. FIFO jsou pouze čtyři slova (32 bitů) dlouhá, ale můžete je propojit s přímým přístupem do paměti (DMA) a odesílat větší množství dat do stavového automatu PIO, aniž byste je museli neustále zapisovat z hlavního programu. FIFO se spojují se stavovým automatem PIO přes vstupní posuvný registr a výstupní posuvný registr. Existují také dva stírací registry zvané X a Y. Ty slouží k ukládání dočasných dat.

-
Máme dva identické PIO bloky
-
Každý blok má 4 stavové stroje (state machines)
-
Stavový stroj je programovatelný v asembleru
-
Každý stavový stroj může nezávisle na jiném vykonávat sekvenční program k manipulaci s GPIO a přenosu dat
-
Používá se hlavně pro vstupně výstupní operace, je deterministický s přesným časováním.
-
Se systémem komunikuje pomocí FIFO (které mohou být připojeny k DMA kanálu) a přerušení (interrupt)

-
Dva 32 bitové posuvné registry, které se mohou posouvat doleva nebo doprava o libovolný počet bitů (1 až 31)
-
Dva 32 bitové "scratch" registry
-
Dvě fronty (FIFO) o rozměrech 4x32 bitů v každém směru (TX,RX), které mohou být rekonfigurovány na jednu frontu 8x32 bitů v jednom směru.
-
Děličku hodin (clock divider)
-
Flexibilní mapování GPIO
-
DMA (direct memory access) rozhraní
-
Přerušení (IRQ) s flagy set, clear, status
-
Všechny stavové stroje mají k dispozici paměť, která může odsahovat až 32 instrukcí (instrukce má 16 bitů)
K programování stavového stroje (state machine) máme k dispozici devět instrukcí:
-
in přesune 1–32 bitů najednou do vstupního posuvného registru odněkud (jako je sada kolíků nebo škrábací registr)
-
out přesune 1–32 bitů z výstupního posuvného registru někam
-
push odesílá data do RX FIFO
-
pull získává data z TX FIFO
-
mov přesouvá data ze zdroje do cíle
-
irq nastavuje nebo ruší příznak přerušení
-
set zapíše data do cíle
-
wait pozastaví program, dokud se nestane konkrétní akce
-
jmp skok, který se přesune na jiné místo v kódu
V rámci nich existují možnosti, jak změnit chování a umístění dat. Úplný přehled najdete v datovém listu, kapitola 3. strana 309
Při každém tiku systémových hodin, každý staový stroj načte, dekóduje a vykoná jednu instrukci.

Kromě toho existuje několik funkcí, které vám usnadní život:
Side-setting je místo, kde můžete nastavit jeden nebo více pinů ve stejnou dobu, kdy běží další instrukce.
Ke každé instrukci lze přidat zpoždění (počet zpoždění hodinových cyklů v hranatých závorkách).
Podívejme se na opravdu jednoduchý příklad, obdélníkovou vlnu:
.wrap_target
set pins, 1 [1]
set pins, 0 [1]
.wrap
To využívá vnější smyčku.
Návěští .wrap_target je speciální případ, protože na něj nepotřebujeme explicitně přeskakovat: můžeme použít .wrap ke skoku přímo na .wrap_target.
Na rozdíl od explicitní instrukce skoku to nezabere hodinový cyklus, takže v tomto případě vytváříme perfektní 50% obdélníkovou vlnu.
Štítky .wrap a .wrap_target lze vynechat.
Každá instrukce má také zpoždění jednoho hodinového cyklu.
Hodnota pinů je nastavena při vytváření programu PIO, takže to může vytvořit obdélníkovou vlnu na libovolném I/O.
Příklad v C a PIO assembleru
Následující program bude generovat obdélníkovou vlnu o kmitočtu 12.5 MHz na pinu č.1 (GP0).

/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
// Output a 12.5 MHz square wave (if system clock frequency is 125 MHz).
//
// Note this program is accessing the PIO registers directly, for illustrative
// purposes. We pull this program into the datasheet so we can talk a little
// about PIO's hardware register interface. The `hardware_pio` SDK library
// provides simpler or better interfaces for all of these operations.
//
// _*This is not best practice! I don't want to see you copy/pasting this*_
//
// For a minimal example of loading and running a program using the SDK
// functions (which is what you generally want to do) have a look at
// `hello_pio` instead. That example is also the subject of a tutorial in the
// SDK book, which walks you through building your first PIO program.
#include "pico/stdlib.h"
#include "hardware/pio.h"
// Our assembled program:
#include "squarewave.pio.h"
int main() {
// Pick one PIO instance arbitrarily. We're also arbitrarily picking state
// machine 0 on this PIO instance (the state machines are numbered 0 to 3
// inclusive).
PIO pio = pio0;
/// \tag::load_program[]
// Load the assembled program directly into the PIO's instruction memory.
// 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];
/// \end::load_program[]
/// \tag::clock_divider[]
// Configure state machine 0 to run at sysclk/2.5. The state machines can
// 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));
/// \end::clock_divider[]
/// \tag::setup_pins[]
// 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);
/// \end::setup_pins[]
/// \tag::start_sm[]
// 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));
/// \end::start_sm[]
return 0;
}
V našem Céčkovém programu si všimněte, že program neobsahuje žádnou nekonečnou smyčku a normálně skončí, avšak Pico bude kmitat na frekvenci 12.5 MHz na pinu GP0 donekonečna. Je to důsledek toho, že PIO je samostatný koprocesor.
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program squarewave
set pindirs, 1 ; Set pin to output
again:
set pins, 1 [1] ; Drive pin high and then delay for one cycle
set pins, 0 ; Drive pin low
jmp again ; Set PC to label `again`
add_executable(pio_squarewave)
pico_generate_pio_header(pio_squarewave ${CMAKE_CURRENT_LIST_DIR}/squarewave.pio)
pico_generate_pio_header(pio_squarewave ${CMAKE_CURRENT_LIST_DIR}/squarewave_wrap.pio)
pico_generate_pio_header(pio_squarewave ${CMAKE_CURRENT_LIST_DIR}/squarewave_fast.pio)
target_sources(pio_squarewave PRIVATE squarewave.c)
target_link_libraries(pio_squarewave PRIVATE pico_stdlib hardware_pio)
pico_add_extra_outputs(pio_squarewave)
# add url via pico_set_program_url
example_auto_set_url(pio_squarewave)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/generated)
# generate .hex file and .pio.h file for the RP2040 datasheet (to make sure
# the datasheet always shows the output of the latest pioasm version)
add_custom_target(pio_squarewave_datasheet DEPENDS
${CMAKE_CURRENT_LIST_DIR}/generated/squarewave.hex
${CMAKE_CURRENT_LIST_DIR}/generated/squarewave.pio.h
${CMAKE_CURRENT_LIST_DIR}/generated/squarewave_wrap.pio.h
)
add_custom_command(OUTPUT ${CMAKE_CURRENT_LIST_DIR}/generated/squarewave.hex
DEPENDS ${CMAKE_CURRENT_LIST_DIR}/squarewave.pio
COMMAND Pioasm -o hex ${CMAKE_CURRENT_LIST_DIR}/squarewave.pio ${CMAKE_CURRENT_LIST_DIR}/generated/squarewave.hex
VERBATIM)
add_custom_command(OUTPUT ${CMAKE_CURRENT_LIST_DIR}/generated/squarewave.pio.h
DEPENDS ${CMAKE_CURRENT_LIST_DIR}/squarewave.pio
COMMAND Pioasm ${CMAKE_CURRENT_LIST_DIR}/squarewave.pio ${CMAKE_CURRENT_LIST_DIR}/generated/squarewave.pio.h
VERBATIM)
add_custom_command(OUTPUT ${CMAKE_CURRENT_LIST_DIR}/generated/squarewave_wrap.pio.h
DEPENDS ${CMAKE_CURRENT_LIST_DIR}/squarewave_wrap.pio
COMMAND Pioasm ${CMAKE_CURRENT_LIST_DIR}/squarewave_wrap.pio ${CMAKE_CURRENT_LIST_DIR}/generated/squarewave_wrap.pio.h
VERBATIM)
add_dependencies(pio_squarewave pio_squarewave_datasheet)

Z obrázku je vidět, že výsledná vlna není moc obdélníková. 12.5 MHz je už docela vysoká frekvence a meření osciloskopen zkresluje.
Zkusíme snížit frekvenci na 625KHz:
pio->sm[0].clkdiv = (uint32_t) (50.0f * (1 << 16)); // frekvence bude 625 kHz

Tady už to je poměrně hezký obdélník.
Céčkový program je velmi jednoduchý, ale není moc hezký. Zatím nic nevíme o struktuře PIO.
Problémy měření
Parazitní kapacita osciloskopické sondy ovlivňuje měřený signál. Je potřeba použít co nejmenší zemní smyčku, potom je měření přesné.

Jednoduchou úpravou (odebereme dlouhý drát s krokodýlem) uděláme malou zemní smyčku a budeme dostávat mnohem lepší výsledky měření.
