Obsah
1. Využití TinyGo při programování Raspberry Pi Pico
2. Hardware, na kterém otestujeme možnosti TinyGo
3. Základy práce s GPIO a dalšími periferními zařízeními: modul machine
4. Klasický školní příklad: blikání LED připojenou na zvolený GPIO pin
5. Překlad demonstračního příkladu pro Raspberry Pi Pico
6. Kopie programu do RPi Pica s jeho spuštěním
7. Čtení stavu tlačítka přes GPIO
8. Zjednodušení zápisu programu s využitím GPIO pojmenovaných v modulu machine
9. Přímá specifikace hodnoty na výstupním GPIO
10. Ovládání sedmisegmentového displeje (jedna číslice)
11. Řízení zobrazení na sedmisegmentovém displeji s více číslicemi
12. Zobrazení numerické hodnoty 0–9999 na čtveřici sedmisegmentových displejů
13. Analogově-digitální převodník (ADC)
14. Čtení hodnot z analogově-digitálního převodníku
15. ADC a čtení hodnoty potenciometru
17. Ovládání jasu diody přes PWM
18. Kontroly prováděné při konfiguraci PWM
19. Repositář s demonstračními příklady
1. Využití TinyGo při programování Raspberry Pi Pico
V článku TinyGo: alternativní překladač a runtime systém programovacího jazyka Go jsme se seznámili s alternativním překladačem jazyka Go nazvaným TinyGo. Jednou z velmi užitečných vlastností, kterou TinyGo nabízí, je podpora poměrně velkého množství cílových platforem, mezi něž patří i populární Raspberry Pi Pico (založené buď na čipu RP2040 nebo nověji i na RP2035).
V dnešním článku si ukážeme, jakým způsobem je možné s využitím TinyGo naprogramovat ovládání GPIO (tedy obecně použitelných vstupně-výstupních pinů), kterými je Raspberry Pi Pico vybaveno, jak se čtou hodnoty z analogově-digitálního převodníku, zmíníme se i o způsobu použití pulsně šířkové modulace (PWM) atd. Všechny dále uvedené demonstrační příklady jsou přitom otestovány na reálném hardware, konkrétně na vývojové desce, jejímž ústředním prvkem je právě Raspberry Pi Pico.
Pokud již máte TinyGo nainstalováno, je zjištění, jaké platformy (kombinace základní desky a CPU nebo MCU) jsou podporovány, velmi snadné. Postačuje pouze zadat příkaz:
$ tinygo targets
V případě TinyGo verze 0.40.1 (kterou v dnešním článku použiji) by se měl vypsat tento seznam:
adafruit-esp32-feather-v2 ae-rp2040 arduino arduino-leonardo arduino-mega1280 arduino-mega2560 arduino-mkr1000 arduino-mkrwifi1010 arduino-nano arduino-nano-new arduino-nano33 arduino-zero atmega1284p atsame54-xpro attiny1616 badger2040 badger2040-w bluemicro840 bluepill bluepill-clone btt-skr-pico challenger-rp2040 circuitplay-bluefruit circuitplay-express clue clue-alpha cortex-m-qemu d1mini digispark elecrow-rp2040 elecrow-rp2350 esp-c3-32s-kit esp32 esp32-c3-devkit-rust-1 esp32-coreboard-v2 esp32-mini32 esp32c3 esp32c3-12f esp32c3-supermini esp32s3 esp8266 feather-m0 feather-m0-express feather-m4 feather-m4-can feather-nrf52840 feather-nrf52840-sense feather-rp2040 feather-stm32f405 gameboy-advance gemma-m0 gnse gobadge gopher-arcade gopher-badge gopherbot gopherbot2 grandcentral-m4 hifive1b hw-651 hw-651-s110v8 itsybitsy-m0 itsybitsy-m4 itsybitsy-nrf52840 kb2040 lgt92 lorae5 m5paper m5stack m5stack-core2 m5stamp-c3 m5stick-c macropad-rp2040 maixbit makerfabs-esp32c3spi35 matrixportal-m4 mch2022 mdbt50qrx-uf2 metro-m4-airlift metro-rp2350 microbit microbit-s110v8 microbit-v2 microbit-v2-s113v7 microbit-v2-s140v7 mksnanov3 nano-33-ble nano-33-ble-s140v6-uf2 nano-33-ble-s140v7 nano-33-ble-s140v7-uf2 nano-rp2040 nicenano nodemcu nrf52840-mdk nrf52840-mdk-usb-dongle nrf52840-s140v6-uf2 nrf52840-s140v6-uf2-generic nrf52840-s140v7-uf2 nucleo-f103rb nucleo-f722ze nucleo-l031k6 nucleo-l432kc nucleo-l476rg nucleo-l552ze nucleo-wl55jc p1am-100 particle-3rd-gen particle-argon particle-boron particle-xenon pca10031 pca10040 pca10040-s132v6 pca10056 pca10056-s140v6-uf2 pca10056-s140v7 pca10059 pca10059-s140v7 pga2350 pico pico-plus2 pico-w pico2 pico2-ice pico2-w pinetime pybadge pygamer pyportal qtpy qtpy-esp32c3 qtpy-rp2040 rak4631 reelboard reelboard-s140v7 riscv-qemu rp2040 rp2350 rp2350b simavr stm32f469disco stm32f4disco stm32f4disco-1 stm32l0x1 swan teensy36 teensy40 teensy41 thingplus-rp2040 thumby tiny2350 tkey trinket-m0 trinkey-qt2040 tufty2040 wasi wasip1 wasip2 wasm wasm-unknown waveshare-rp2040-tiny waveshare-rp2040-zero wioterminal x9pro xiao xiao-ble xiao-esp32c3 xiao-esp32s3 xiao-rp2040
$ tinygo version tinygo version 0.40.1 linux/amd64 (using go version go1.24.10 and LLVM version 20.1.1)
Programátoři, co nesnášíte BS, ale máte rádi business! Y Soft je česká firma s globálním dopadem (100+ zemí, 1M+ uživatelů a >100% meziroční růst). R&D úplně bez manažerů (130 developerů). Otevíráme 30 pozic pro Cloud a AI: Praha/Brno/Ostrava/remote. Zodpovědnost ano, mikro-management ne. Pojď někam, kde můžeš věci změnit.
2. Hardware, na kterém otestujeme možnosti TinyGo
Všechny dále uvedené demonstrační příklady byly otestovány na Raspberry Pi Pico W, ovšem měly by být funkční i na všech ostatních variantách Raspberry Pi Pico:
| Označení | Vydáno | Mikrořadič | Wifi | RAM | Flash |
|---|---|---|---|---|---|
| Raspberry Pi Pico | 2021 | RP2040 | ne | 264kB | 2MB |
| Raspberry Pi Pico W | 2022 | RP2040 | ano | 264kB | 2MB |
| Raspberry Pi Pico 2 | 2024 | RP2350 | ne | 520kB | 4MB |
| Raspberry Pi Pico 2 W | 2024 | RP2350 | ano | 520kB | 4MB |
Již z tabulky uvedené před tímto odstavcem je zřejmé, že modely Pi Pico (bez číslovky) a Pi Pico 2 se od sebe v několika ohledech odlišují, což je do značné míry způsobeno použitím odlišných mikrořadičů. Rozdílů je však ještě větší množství a vzhledem k tomu, že se dotýkají i problematiky probírané v dnešním článku (PIO), si tyto rozdíly taktéž vypíšeme. Zajímat nás budou především údaje o PIO:
| Čip | RP2040 | RP2350 | Poznámka |
|---|---|---|---|
| Jádra | 2×ARM Cortex-M0+ | 2×ARM Cortex-M33 + 2×Hazard3 RISC-V | volba jader při bootu |
| FPU | ne | ano (ARM) | pouze jednoduchá přesnost |
| Frekvence | 133 MHz | 150 MHz | |
| (S)RAM | 264 kB | 510 kB | |
| OTP paměť | × | 8 kB | používáno například pro klíče při bootu atd. |
| DMA | 12 kanálů | 16 kanálů | přímý přístup do paměti |
| IRQ pro DMA | 2 | 4 | HW přerušení používaná při DMA |
| PIO | 2 (8 stavových strojů) | 3 (12 stavových strojů) | bude popsáno dále |
| PWM | 16 | 24 | pulsní šířková modulace |
| ADC | 4 kanály, 12bitů | 4 kanály, 12 bitů (popř. 8 kanálů) | analogově-digiální převodník |
| DAC | × | × | digitálně-analogový převodník |
Použita byla vývojová deska navržená synem. Na této desce je kromě vlastního Raspberry Pi Pico W zapojena sada tlačítek (vstupní GPIO), potenciometr (připojený pochopitelně na ADC), LED (výstupní GPIO), čtyřmístný sedmisegmentový displej, relé pro ovládání dalších zařízení a taktéž maticový displej (ten dnes nevyužijeme):
Obrázek 1: Přední strana desky, kterou použijeme v dnešním článku. Vlevo dole je běžné Raspberry Pi Pico W.
3. Základy práce s GPIO a dalšími periferními zařízeními: modul machine
V TinyGo nalezneme mj. i modul nazvaný machine. V tomto modulu je definován datový typ Pin, jenž je odvozený od standardního typu uint8. Ovšem důležitější je fakt, že jsou pro typ Pin předepsány i metody určené pro čtení stavu vstupního pinu, nastavení úrovně výstupního pinu atd. A taktéž zde nalezneme typ určený pro konfiguraci pinů (tedy nastavení, zda je pin vstupní, výstupní, zda se má jednat o výstup PWM, vstup z A/D převodníku atd.):
// PinMode sets the direction and pull mode of the pin. For example, PinOutput
// sets the pin as an output and PinInputPullup sets the pin as an input with a
// pull-up.
type PinMode uint8
type PinConfig struct {
Mode PinMode
}
// Pin is a single pin on a chip, which may be connected to other hardware
// devices. It can either be used directly as GPIO pin or it can be used in
// other peripherals like ADC, I2C, etc.
type Pin uint8
// NoPin explicitly indicates "not a pin". Use this pin if you want to leave one
// of the pins in a peripheral unconfigured (if supported by the hardware).
const NoPin = Pin(0xff)
// High sets this GPIO pin to high, assuming it has been configured as an output
// pin. It is hardware dependent (and often undefined) what happens if you set a
// pin to high that is not configured as an output pin.
func (p Pin) High() {
p.Set(true)
}
// Low sets this GPIO pin to low, assuming it has been configured as an output
// pin. It is hardware dependent (and often undefined) what happens if you set a
// pin to low that is not configured as an output pin.
func (p Pin) Low() {
p.Set(false)
}
type ADC struct {
Pin Pin
}
4. Klasický školní příklad: blikání LED připojenou na zvolený GPIO pin
Úvodní pokusy s jednodeskovými mikropočítači (ať již postavenými na osmibitových nebo 32bitových mikrořadičích) jsou většinou založeny na připojení LED k mikropočítači a programovém ovládání této LED. Jelikož se v této oblasti jedná o obdobu známého „Hello world“, vytvoříme naprosto stejný příklad, ovšem naprogramovaný v jazyce Go a přeložený s využitím TinyGo.
Nejprve v programu nadefinujeme konstantu s číslem GPIO pinu, na který je LED (se sériovým odporem) připojena. V mém případě se jedná o GPIO číslo 2:
const OutputPin = 2
Dále vytvoříme datovou strukturu typu machine.Pin, která je odvozena (jak jsme si řekli v předchozí kapitole) od základního datového typu uint8, ke kterému byly přidány nové metody:
led := machine.Pin(OutputPin)
Nesmíme zapomenout na explicitní přepnutí zvoleného GPIO do výstupního režimu, což se provádí metodou Configure:
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
Nyní je již možné LED programově ovládat, a to posláním logické jedničky nebo naopak logické nuly na příslušný GPIO:
led.High()
Nastavení logické nuly:
led.Low()
Přepínáním logické úrovně v určitých časových intervalech dosáhneme blikání LED. Pro specifikaci intervalu můžeme využít standardní knihovnu time (TinyGo se v tomto ohledu neliší od standardního Go):
const SleepAmount = time.Millisecond * 200
for {
led.High()
time.Sleep(SleepAmount)
led.Low()
time.Sleep(SleepAmount)
}
Úplný zdrojový kód takto navrženého demonstračního příkladu vypadá následovně:
package main
import (
"machine"
"time"
)
const OutputPin = 2
const SleepAmount = time.Millisecond * 200
func main() {
led := machine.Pin(OutputPin)
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
for {
led.High()
time.Sleep(SleepAmount)
led.Low()
time.Sleep(SleepAmount)
}
}
Obrázek 3: Typické „školní“ blikání LED (zelených LED na maticovém displeji si prozatím nevšímejte – po resetu se jedná o náhodný stav).
5. Překlad demonstračního příkladu pro Raspberry Pi Pico
Překlad demonstračního příkladu z předchozí kapitoly lze v tom nejjednodušším případě provést následujícím příkazem:
$ tinygo build -o blink.uf2 -target=pico blink.go
Výsledkem je binární soubor s formátem UF2, jehož velikost dosahuje téměř 20kB:
$ ls -l total 191320 -rw-r--r--. 1 ptisnovs ptisnovs 303 Dec 30 15:34 blink.go -rwxr-xr-x. 1 ptisnovs ptisnovs 19968 Dec 30 15:34 blink.uf2
Velikost výsledného souboru však můžeme s využitím několika přepínačů zmenšit na rozumnější velikost. Z runtime necháme odstranit například plánovač pro gorutiny a odstraníme i ladicí symboly:
$ tinygo build -no-debug -nobounds -scheduler=none -o blink.uf2 -target=pico blink.go
Nyní bude velikost výsledného souboru menší – cca třináct kilobajtů:
$ ls -l total 191316 -rw-r--r--. 1 ptisnovs ptisnovs 303 Dec 30 15:34 blink.go -rwxr-xr-x. 1 ptisnovs ptisnovs 13312 Dec 30 15:40 blink.uf2
6. Kopie programu do RPi Pica s jeho spuštěním
Výsledný soubor vzniklý překladem běžným způsobem nahrajeme do Raspberry Pi Pico. Při zapojování Pico k počítači přes USB je nutné podržet tlačítko Bootsel. Pico se k počítači připojí v režimu mass storage device, tedy chová se jako flash disk. Toto chování si můžeme ověřit například příkazem dmesg:
[2661401.124131] usb 3-1: new full-speed USB device number 61 using xhci_hcd [2661401.249236] usb 3-1: New USB device found, idVendor=2e8a, idProduct=0003, bcdDevice= 1.00 [2661401.249263] usb 3-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 [2661401.249272] usb 3-1: Product: RP2 Boot [2661401.249279] usb 3-1: Manufacturer: Raspberry Pi [2661401.249284] usb 3-1: SerialNumber: E0C9125B0D9B [2661401.253896] usb-storage 3-1:1.0: USB Mass Storage device detected [2661401.254513] scsi host0: usb-storage 3-1:1.0 [2661402.292170] scsi 0:0:0:0: Direct-Access RPI RP2 3 PQ: 0 ANSI: 2 [2661402.293215] sd 0:0:0:0: Attached scsi generic sg0 type 0 [2661402.295776] sd 0:0:0:0: [sda] 262144 512-byte logical blocks: (134 MB/128 MiB) [2661402.296607] sd 0:0:0:0: [sda] Write Protect is off [2661402.296615] sd 0:0:0:0: [sda] Mode Sense: 03 00 00 00 [2661402.297239] sd 0:0:0:0: [sda] No Caching mode page found [2661402.297247] sd 0:0:0:0: [sda] Assuming drive cache: write through [2661402.338588] sda: sda1 [2661402.339371] sd 0:0:0:0: [sda] Attached SCSI removable disk
Nyní postačuje soubor blink.uf2 zkopírovat na připojený disk. Ihned poté dojde k odpojení Pica od počítače s novým připojením, ovšem nikoli v režimu mass storage device. A současně dojde ke spuštění našeho programu – LED začne blikat:
[2399478.090936] usb 3-1: new full-speed USB device number 106 using xhci_hcd [2399478.216206] usb 3-1: New USB device found, idVendor=2e8a, idProduct=000a, bcdDevice= 1.00 [2399478.216224] usb 3-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 [2399478.216230] usb 3-1: Product: Pico [2399478.216235] usb 3-1: Manufacturer: Raspberry Pi [2399478.220389] cdc_acm 3-1:1.0: ttyACM0: USB ACM device
7. Čtení stavu tlačítka přes GPIO
Prozatím umíme ovládat logickou úroveň na GPIO, které je přepnuté do výstupního režimu. Ovšem už z významu zkratky GPIO (General-Purpose Input/Output) je zřejmé, že piny je možné programově přepnout do vstupního režimu, které umožňují například čtení stavu tlačítka (stisknuto/pušteno). Příslušná metoda, která zjistí úroveň logické hodnoty přivedené na pin, se jmenuje Get a nalezneme ji v souboru machine_rp2.go:
//go:build rp2040 || rp2350
// Get reads the pin value.
func (p Pin) Get() bool {
return p.get()
}
V dalším demonstračním příkladu budeme tlačítkem (pochopitelně nepřímo) ovládat LED, takže nás nemusí trápit zákmity, ke kterým nutně při stisku dochází (ovšem pokud bychom například zjišťovali počet stisků atd., byly by zákmity velkým problémem, který se dá řešit buď na HW nebo SW úrovni). Nakonfigurujeme GPIO číslo 0, kam je tlačítko fyzicky připojena, do vstupního režimu:
const InputPin = 0
button := machine.Pin(InputPin)
button.Configure(machine.PinConfig{Mode: machine.PinInput})
Následně budeme číst stav tlačítka a pokud je stisknuto, rozsvítíme LED:
for {
pressed := button.Get()
if pressed {
led.High()
} else {
led.Low()
}
}
Celý zdrojový kód dnešního druhého demonstračního příkladu vypadá následovně:
package main
import (
"machine"
)
const InputPin = 0
const OutputPin = 2
func main() {
led := machine.Pin(OutputPin)
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
button := machine.Pin(InputPin)
button.Configure(machine.PinConfig{Mode: machine.PinInput})
for {
pressed := button.Get()
if pressed {
led.High()
} else {
// pokud vynecháme, LED se pouze rozsvítí
led.Low()
}
}
}
8. Zjednodušení zápisu programu s využitím GPIO pojmenovaných v modulu machine
Ve skutečnosti není nutné na základě čísel GPIO explicitně konstruovat struktury typu machine.Pin tak, jako tomu bylo v předchozím demonstračním příkladu:
const InputPin = 0 const OutputPin = 2 led := machine.Pin(OutputPin) button := machine.Pin(InputPin)
Jednodušší je použít již předpřipravené struktury definované v modulu machine (interně se opět jedná o hodnoty typu uint8, ovšem doplněné o možnost volání metod). Předchozí demonstrační příklad se tak zkrátí do následující podoby:
package main
import (
"machine"
)
const InputPin = machine.GP0
const OutputPin = machine.GP2
func main() {
led := OutputPin
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
button := InputPin
button.Configure(machine.PinConfig{Mode: machine.PinInput})
for {
pressed := button.Get()
if pressed {
led.High()
} else {
led.Low()
}
}
}
9. Přímá specifikace hodnoty na výstupním GPIO
Ve zdrojovém kódu machine_rp2.go, který je součástí modulu machine, nalezneme mj. i metodu nazvanou Set, opět definovanou pro příjemce (receiver) Pin. Definice této metody vypadá následovně:
// Set drives the pin high if value is true else drives it low.
func (p Pin) Set(value bool) {
if p == NoPin {
return
}
if value {
p.set()
} else {
p.clr()
}
}
Díky tomu je možné přímo nastavit hodnotu výstupního GPIO na základě předané pravdivostní hodnoty – není tedy nutné na základě podmínky volat buď pin.High nebo naopak pin.Low. To nám umožní zkrácení demonstračního příkladu pro detekci stisku a uvolnění tlačítka do této podoby:
package main
import (
"machine"
)
const InputPin = machine.GP0
const OutputPin = machine.GP2
func main() {
led := OutputPin
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
button := InputPin
button.Configure(machine.PinConfig{Mode: machine.PinInput})
for {
pressed := button.Get()
led.Set(pressed)
}
}
10. Ovládání sedmisegmentového displeje (jedna číslice)
Nyní, když již víme, jakým způsobem se ovládají jednotlivé GPIO piny, se pokusíme o řízení sedmisegmentového displeje, resp. přesněji řečeno jedné číslice reprezentované sedmi segmenty a desetinné tečky. Na vývojové desce je sedmisegmentový displej zapojen následujícím způsobem:
Obrázek 4: Zapojení čtveřice sedmisegmentových dispejů. DIG1–4 řídí, která číslice se právě zobrazuje.
Toto zapojení umožňuje nastavit individuálně jakýkoli segment (nebo tečku) jedné číslice nebo skupiny číslic, a to pomocí signálů A, B, C, D, E, F, G a DP (takto se většinou označují jednotlivé segmenty a desetinná tečka, viz například toto schéma). Výše uvedené signály jsou vytvářeny na GPIO 11, 9, 13, 15, 16, 10, 12 a 14:
var pins [8]machine.Pin pins[0] = machine.GP11 pins[1] = machine.GP9 pins[2] = machine.GP13 pins[3] = machine.GP15 pins[4] = machine.GP16 pins[5] = machine.GP10 pins[6] = machine.GP12 pins[7] = machine.GP14
Tyto piny musí být pochopitelně přepnuty do výstupního režimu:
for _, pin := range pins {
pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
To, která číslice nebo které číslice budou rozsvíceny, se řídí signály DIG1, DIG2, DIG3 a DIG4, které odpovídají GPIO 5, 6, 7 a 8. Prozatím si necháme rozsvítit jedinou číslici, takže nám bude stačit nastavit GPIO 5 na logickou jedničku (povolení dané číslice):
c1 := machine.GP5
c1.Configure(machine.PinConfig{Mode: machine.PinOutput})
c1.High()
Řízení segmentů je zajištěno funkcí display4Segments, které se předává řez pravdivostních hodnot (ovšem snadno si můžete provést úpravu pro předání celočíselné hodnoty 0 až 255):
func displaySegments(bits []bool) {
for i := range bits {
bit := bits[i]
pin := pins[i]
if bit {
pin.High()
} else {
pin.Low()
}
}
}
Následující demonstrační příklad by měl rozsvítit všechny segmenty první číslice a současně i její desetinou tečku:
package main
import (
"machine"
)
var pins [8]machine.Pin
func displaySegments(bits []bool) {
for i := range bits {
bit := bits[i]
pin := pins[i]
if bit {
pin.High()
} else {
pin.Low()
}
}
}
func main() {
c1 := machine.GP5
c1.Configure(machine.PinConfig{Mode: machine.PinOutput})
c1.High()
pins[0] = machine.GP11
pins[1] = machine.GP9
pins[2] = machine.GP13
pins[3] = machine.GP15
pins[4] = machine.GP16
pins[5] = machine.GP10
pins[6] = machine.GP12
pins[7] = machine.GP14
for _, pin := range pins {
pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
displaySegments([]bool{true, true, true, true, true, true, true, true})
}
11. Řízení zobrazení na sedmisegmentovém displeji s více číslicemi
Jak jsme si již řekli v předchozí kapitole, můžeme přes GPIO 5, 6, 7 a 8 řídit, které číslice mají být zobrazeny a které nikoli. V jeden okamžik může být rozsvícena jakákoli kombinace číslic, ovšem vždy se bude jednat o shodné segmenty (tedy například můžeme zobrazit čtyři dvojky atd.). Nejprve nastavíme všechny čtyři GPIO (5–8) do výstupního režimu:
var controls [4]machine.Pin
controls[0] = machine.GP5
controls[1] = machine.GP6
controls[2] = machine.GP7
controls[3] = machine.GP8
for _, control := range controls {
control.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
Pokud budeme chtít zobrazit jen první a třetí číslici, musíme příslušné GPIO nastavit na hodnotu logické jedničky:
controls[0].High() controls[2].High()
Samotná logika zapnutí či vypnutí jednotlivých segmentů a desetinné tečky zůstává stále stejná (je zcela nezávislá na GPIO 5–8):
var pins [8]machine.Pin
pins[0] = machine.GP11
pins[1] = machine.GP9
pins[2] = machine.GP13
pins[3] = machine.GP15
pins[4] = machine.GP16
pins[5] = machine.GP10
pins[6] = machine.GP12
pins[7] = machine.GP14
for _, pin := range pins {
pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
Ukažme si nyní celý zdrojový kód demonstračního příkladu, který zobrazí dvojici osmiček (další dvě místa budou zhasnuta):
package main
import (
"machine"
"time"
)
const SleepAmount = time.Millisecond * 200
var controls [4]machine.Pin
var pins [8]machine.Pin
func displaySegments(bits []bool) {
for i := range bits {
bit := bits[i]
pin := pins[i]
if bit {
pin.High()
} else {
pin.Low()
}
}
}
func main() {
controls[0] = machine.GP5
controls[1] = machine.GP6
controls[2] = machine.GP7
controls[3] = machine.GP8
for _, control := range controls {
control.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
controls[0].High()
controls[2].High()
pins[0] = machine.GP11
pins[1] = machine.GP9
pins[2] = machine.GP13
pins[3] = machine.GP15
pins[4] = machine.GP16
pins[5] = machine.GP10
pins[6] = machine.GP12
pins[7] = machine.GP14
for _, pin := range pins {
pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
displaySegments([]bool{true, true, true, true, true, true, true, true})
}
Nyní ovšem nastává otázka – jak zobrazit rozdílné (libovolné) čtyři číslice? Zde se musí použít starodávný trik: v daný okamžik je zobrazena jen jediná číslice (vybraná jedním GPIO 5–8). Ovšem zobrazení jednotlivých číslic je prováděno tak rychle, že vlivem setrvačnosti oka vidíme současně všechny čtyři číslice. Ovšem například při focení displeje nebo při analýze videa by bylo patrné, že jsou rozsvíceny vždy jen segmenty jediné číslice.
Následující funkce zobrazí každou číslici na dobu minimálně jedné milisekundy. Poté celá číslice zhasne a přejde se na číslici další:
const SleepAmount = time.Millisecond * 1
func display4Segments(bits [][]bool) {
for {
for i := range bits {
control := controls[i]
control.High()
displaySegments(bits[i])
time.Sleep(SleepAmount)
control.Low()
}
}
}
Následuje úplný zdrojový kód takto upraveného demonstračního příkladu:
package main
import (
"machine"
"time"
)
const SleepAmount = time.Millisecond * 1
var controls [4]machine.Pin
var pins [8]machine.Pin
func init() {
controls[0] = machine.GP5
controls[1] = machine.GP6
controls[2] = machine.GP7
controls[3] = machine.GP8
for _, control := range controls {
control.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
pins[0] = machine.GP11
pins[1] = machine.GP9
pins[2] = machine.GP13
pins[3] = machine.GP15
pins[4] = machine.GP16
pins[5] = machine.GP10
pins[6] = machine.GP12
pins[7] = machine.GP14
for _, pin := range pins {
pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
}
func displaySegments(bits []bool) {
for i := range bits {
bit := bits[i]
pin := pins[i]
if bit {
pin.High()
} else {
pin.Low()
}
}
}
func display4Segments(bits [][]bool) {
for {
for i := range bits {
control := controls[i]
control.High()
displaySegments(bits[i])
time.Sleep(SleepAmount)
control.Low()
}
}
}
func main() {
display4Segments([][]bool{
{false, true, true, false, false, false, false, false},
{true, true, false, true, true, false, true, false},
{true, true, true, true, false, false, true, false},
{false, true, true, false, false, true, true, false}})
}
12. Zobrazení numerické hodnoty 0–9999 na čtveřici sedmisegmentových displejů
Ve skutečnosti je explicitní ovládání jednotlivých segmentů poměrně nízkoúrovňovou operací. Výhodnější by bylo mít k dispozici funkci, která přímo zobrazí numerickou hodnotu v rozsahu 0 až 9999, popř. 0 až FFFF, pokud by se mělo jednat o hexadecimální výstup. Realizace takové funkce vlastně není příliš komplikovaná, protože pouze potřebujeme mít k dispozici převodní tabulku číslice→příznaky_segmentů (včetně desetinné tečky, která je vždy zhasnutá):
var digits [][]bool = [][]bool{
{true, true, true, true, true, true, false, false},
{false, true, true, false, false, false, false, false},
{true, true, false, true, true, false, true, false},
{true, true, true, true, false, false, true, false},
{false, true, true, false, false, true, true, false},
{true, false, true, true, false, true, true, false},
{true, false, true, true, true, true, true, false},
{true, true, true, false, false, false, false, false},
{true, true, true, true, true, true, true, false},
{true, true, true, true, false, true, true, false},
}
Vlastní vykreslení numerické hodnoty potom vypadá takto:
func displayNumber(number int) {
for {
x := number
for i := range 4 {
digit := x % 10
x /= 10
bits := digits[digit]
control := controls[3-i]
control.High()
displaySegments(bits)
time.Sleep(SleepAmount)
control.Low()
}
}
}
Následuje výpis demonstračního příkladu, který na displeji zobrazí hodnotu 1234:
package main
import (
"machine"
"time"
)
const SleepAmount = time.Millisecond * 1
var controls [4]machine.Pin
var pins [8]machine.Pin
var digits [][]bool = [][]bool{
{true, true, true, true, true, true, false, false},
{false, true, true, false, false, false, false, false},
{true, true, false, true, true, false, true, false},
{true, true, true, true, false, false, true, false},
{false, true, true, false, false, true, true, false},
{true, false, true, true, false, true, true, false},
{true, false, true, true, true, true, true, false},
{true, true, true, false, false, false, false, false},
{true, true, true, true, true, true, true, false},
{true, true, true, true, false, true, true, false},
}
func init() {
controls[0] = machine.GP5
controls[1] = machine.GP6
controls[2] = machine.GP7
controls[3] = machine.GP8
for _, control := range controls {
control.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
pins[0] = machine.GP11
pins[1] = machine.GP9
pins[2] = machine.GP13
pins[3] = machine.GP15
pins[4] = machine.GP16
pins[5] = machine.GP10
pins[6] = machine.GP12
pins[7] = machine.GP14
for _, pin := range pins {
pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
}
func displaySegments(bits []bool) {
for i := range bits {
bit := bits[i]
pin := pins[i]
if bit {
pin.High()
} else {
pin.Low()
}
}
}
func displayNumber(number int) {
for {
x := number
for i := range 4 {
digit := x % 10
x /= 10
bits := digits[digit]
control := controls[3-i]
control.High()
displaySegments(bits)
time.Sleep(SleepAmount)
control.Low()
}
}
}
func main() {
displayNumber(1234)
}
13. Analogově-digitální převodník (ADC)
Čipy RP2040 i RP2350 jsou mj. vybaveny i analogově-digitálním převodníkem. Typicky se jedná o čtyři (vstupní) kanály s rozlišením dvanácti bitů, ovšem RP2350 v pouzdře QFN-80EP nabízí celých osm kanálů (a současně i osmnáct GPIO). I když má analogově-digitální převodník rozlišení dvanácti bitů, prakticky všechna programová rozhraní vrací šestnáctibitové hodnoty, což znamená, že spodní čtyři bity přečtených výsledků je možné ignorovat. Navíc je možné u každého ze vstupů do analogově-digitálního převodníku zvolit, zda budou použity nebo zda se má příslušný pin použít pro jiné účely (GPIO, SCL). To se týká prvních tří kanálů ADC0, ADC1 a ADC2, protože čtvrtý kanál se používá pro čtení úrovně referenčního napětí (není vyveden na piny RPi). Pátý kanál čte hodnoty získané teplotním senzorem (a není tedy vyveden na piny RPi). Popis pinů naleznete na adrese https://www.raspberrypi.com/documentation/microcontrollers/pico-series.html#pico-2-family.
Na použité vývojové desce je nainstalován i potenciometr, který je připojený k ADC2, což odpovídá GPIO číslo 28:
14. Čtení hodnot z analogově-digitálního převodníku (ADC)
V dalším demonstračním příkladu je ukázán postup použitý pro čtení hodnot z analogově-digitálního převodníku s následným zobrazením výsledné hodnoty na čtveřici sedmisegmentových displejů. Nejprve je nutné ADC nakonfigurovat. Víme již, že budeme používat kanál číslo 2 (ADC2). Inicializujeme tedy subsystém ADC a nakonfigurujeme příslušný pin:
machine.InitADC()
adc2 := machine.ADC{Pin: machine.ADC2}
adc2.Configure(machine.ADCConfig{})
Samotné přečtení hodnoty je snadné. Jedná se o šestnáctibitovou hodnotu, ovšem čtyři nejnižší bity je možné ignorovat (stejně budou nulové):
value := adc2.Get()
Zobrazit ovšem můžeme jen hodnoty v rozsahu 0 až 9999, takže přečtenou hodnotu podělíme sedmi (nebo je možné použít bitový posun do rozsahu 0 až 8191). Výsledek tohoto výpočtu je již možné přímo zobrazit:
value /= 7 displayNumber(int(value))
Ukažme si nyní celý zdrojový kód tohoto demonstračního příkladu:
package main
import (
"machine"
"time"
)
const SleepAmount = time.Millisecond * 1
var controls [4]machine.Pin
var pins [8]machine.Pin
var digits [][]bool = [][]bool{
{true, true, true, true, true, true, false, false},
{false, true, true, false, false, false, false, false},
{true, true, false, true, true, false, true, false},
{true, true, true, true, false, false, true, false},
{false, true, true, false, false, true, true, false},
{true, false, true, true, false, true, true, false},
{true, false, true, true, true, true, true, false},
{true, true, true, false, false, false, false, false},
{true, true, true, true, true, true, true, false},
{true, true, true, true, false, true, true, false},
}
func init() {
controls[0] = machine.GP5
controls[1] = machine.GP6
controls[2] = machine.GP7
controls[3] = machine.GP8
for _, control := range controls {
control.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
pins[0] = machine.GP11
pins[1] = machine.GP9
pins[2] = machine.GP13
pins[3] = machine.GP15
pins[4] = machine.GP16
pins[5] = machine.GP10
pins[6] = machine.GP12
pins[7] = machine.GP14
for _, pin := range pins {
pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
}
func displaySegments(bits []bool) {
for i := range bits {
bit := bits[i]
pin := pins[i]
if bit {
pin.High()
} else {
pin.Low()
}
}
}
func displayNumber(number int) {
x := number
for i := range 4 {
digit := x % 10
x /= 10
bits := digits[digit]
control := controls[3-i]
control.High()
displaySegments(bits)
time.Sleep(SleepAmount)
control.Low()
}
}
func main() {
machine.InitADC()
adc0 := machine.ADC{Pin: machine.ADC0}
adc1 := machine.ADC{Pin: machine.ADC1}
adc2 := machine.ADC{Pin: machine.ADC2}
adc0.Configure(machine.ADCConfig{})
adc1.Configure(machine.ADCConfig{})
adc2.Configure(machine.ADCConfig{})
for {
value := adc2.Get()
value /= 7
displayNumber(int(value))
}
}
15. ADC a čtení hodnoty potenciometru
I když se nebude potenciometrem otáčet, budeme při opakovaném čtení dostávat odlišné hodnoty. Aby čísla na displeji neposkakovala, je možné nejprve změřit větší množství hodnot a poté spočítat jejich průměr. Například můžeme postupně přečíst 100 hodnot (s případným zpožděním – nutné nastavit) a podělit výsledek hodnotou 700 (tedy 7×100). Teprve tato hodnota se zobrazí na displeji:
var value int32 = 0
for range 100 {
value += int32(adc2.Get())
}
value /= 700
Takto upravený příklad bude vypadat následovně:
package main
import (
"machine"
"time"
)
const SleepAmount = time.Millisecond * 1
var controls [4]machine.Pin
var pins [8]machine.Pin
var digits [][]bool = [][]bool{
{true, true, true, true, true, true, false, false},
{false, true, true, false, false, false, false, false},
{true, true, false, true, true, false, true, false},
{true, true, true, true, false, false, true, false},
{false, true, true, false, false, true, true, false},
{true, false, true, true, false, true, true, false},
{true, false, true, true, true, true, true, false},
{true, true, true, false, false, false, false, false},
{true, true, true, true, true, true, true, false},
{true, true, true, true, false, true, true, false},
}
func init() {
controls[0] = machine.GP5
controls[1] = machine.GP6
controls[2] = machine.GP7
controls[3] = machine.GP8
for _, control := range controls {
control.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
pins[0] = machine.GP11
pins[1] = machine.GP9
pins[2] = machine.GP13
pins[3] = machine.GP15
pins[4] = machine.GP16
pins[5] = machine.GP10
pins[6] = machine.GP12
pins[7] = machine.GP14
for _, pin := range pins {
pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
}
func displaySegments(bits []bool) {
for i := range bits {
bit := bits[i]
pin := pins[i]
if bit {
pin.High()
} else {
pin.Low()
}
}
}
func displayNumber(number int) {
x := number
for i := range 4 {
digit := x % 10
x /= 10
bits := digits[digit]
control := controls[3-i]
control.High()
displaySegments(bits)
time.Sleep(SleepAmount)
control.Low()
}
}
func main() {
machine.InitADC()
adc0 := machine.ADC{Pin: machine.ADC0}
adc1 := machine.ADC{Pin: machine.ADC1}
adc2 := machine.ADC{Pin: machine.ADC2}
adc0.Configure(machine.ADCConfig{})
adc1.Configure(machine.ADCConfig{})
adc2.Configure(machine.ADCConfig{})
for {
var value int32 = 0
for range 100 {
value += int32(adc2.Get())
}
value /= 700
16. Pulsně šířková modulace (PWM)
Čipy RP2040 i RP2035 jsou vybaveny obvody, které umožňují realizovat programovatelnou pulsně šířkovou modulaci neboli PWM. Tu lze použít k mnoha účelům, například pro generování zvuků (bez nutnosti připojení digitálně-analogového převodníku), ovládání jasu LED, podsvícení připojeného LCD či VFD (samozřejmě záleží na typu) apod. Pomocí PWM lze řídit vybrané GPIO, přičemž u RP2040 lze takto řídit 16 GPIO a u RP2035 dokonce 24 GPIO.
Obrázek 9: Pulsní šířková modulace sinusové vlny (červená barva), která je porovnávána s referenčním trojúhelníkovým signálem (modrá barva). Výsledný binární signál je zobrazen žlutou barvou.
Na obrázcích uvedených v této kapitole je naznačen jeden ze způsobů převodu analogového signálu (popř. i signálu navzorkovaného) na signál binární (dvoustavový) pomocí pulsní šířkové modulace. Princip je jednoduchý – původní analogový signál je kontinuálně porovnáván s trojúhelníkovým signálem o vyšší frekvenci, jehož amplituda je shodná s amplitudou (resp. maximální absolutní hodnotou) původního analogového signálu. V případě, že je aktuální hodnota původního modulovaného signálu vyšší, než je hodnota trojúhelníkového referenčního signálu, je na výstupu vysoká úroveň (většinou kladné napětí), v opačném případě nízká úroveň (záporné napětí o stejné absolutní hodnotě).
Obrázek 10: Pulsní šířková modulace – frekvence referenčního trojúhelníkového signálu je dvojnásobná oproti signálu zobrazeném na čtvrtém obrázku, výstup by však stále nebyl (při přehrání na reproduktoru) dostatečně kvalitní.
Pokud je pulsní šířková modulace implementována v elektronickém obvodu (my ovšem budeme vše řídit čistě programově), jsou oba signály porovnávány pomocí operačního zesilovače, na nějž je zapojený Schmittův klopný obvod, který zajistí kolmost hran výsledného binárního signálu. Programová implementace je založena na výpočtu referenčního signálu v diskrétních krocích a jeho jednoduchém porovnání s původními vzorky.
Obrázek 11: Pulsní šířková modulace – frekvence referenčního trojúhelníkového signálu je dvojnásobná oproti signálu zobrazeném na pátém obrázku.
Aby byl výsledek PWM dostatečně kvalitní, je nutné, aby frekvence trojúhelníkového referenčního signálu byla mnohonásobně vyšší, než frekvence signálu vstupního. V případě, že se například pomocí PWM převádí navzorkovaný (digitální) zvuk s určitou vzorkovací frekvencí, je vhodné, aby frekvence referenčního signálu byla minimálně 10× vyšší než frekvence vzorkovací, což ovšem klade vyšší nároky na přesnost časování při přehrávání zvuku (tato situace zhruba odpovídá šestému obrázku). Pokud byl například původní zvuk navzorkován s frekvencí 22 kHz, což odpovídá spíše menší kvalitě (ztrácí se vyšší slyšitelné frekvence), tak by se měla PWM provádět s frekvencí 220 kHz.
Obrázek 12: Na tomto grafu je frekvence referenční trojúhelníkové vlny cca 10× vyšší, než frekvence převáděného signálu, což je již pro mnoho aplikací dostatečné.
17. Ovládání jasu diody přes PWM
V dalším demonstračním příkladu budeme ovládat LED přes PWM. Pokud bude frekvence PWM dostatečně velká, bude se měnit pouze intenzita svitu (resp. tak budeme blikající LED vnímat). Nejprve je nutné PWM povolit, tj. nakonfigurovat příslušný GPIO tak, aby se jeho úroveň řídila pomocí PWM:
pwm0 := machine.PWM0
pwmPin := machine.GP0
pwmPin.Configure(machine.PinConfig{Mode: machine.PinPWM})
Dále nastavíme frekvenci PWM, resp. můžeme naopak specifikovat jeho periodu a z ní se frekvence dopočítá. Základem je jedna mikrosekunda (což ve frekvenční oblasti odpovídá 1e9 Hz). V případě, že budeme chtít zajistit frekvenci PWM na 500 Hz (to je dostatečná hodnota), můžeme to provést následujícím způsobem:
pwm0.Configure(machine.PWMConfig{
Period: 1e9 / 500,
})
Získáme PWM kanál, který se bude používat:
channel, _ := pwm0.Channel(pwmPin)
Nyní již můžeme metodou pwm0.Set měnit mezní hodnoty, interního čítače PWM. Pokud čítač dosáhne této hodnoty, změní se úroveň výstupu. Rozsah těchto hodnot je od 0 to pwm0.Top (ať nemusíme používat magické konstanty).
Opakovaná změna svítivosti LED od 100% do zhruba 10% a zpět se provede takto:
for {
for divider := 1; divider <= 10; divider++ {
pwm0.Set(channel, pwm0.Top()/uint32(divider))
time.Sleep(100 * time.Millisecond)
}
for divider := 10; divider >= 1; divider-- {
pwm0.Set(channel, pwm0.Top()/uint32(divider))
time.Sleep(100 * time.Millisecond)
}
}
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
package main
import (
"machine"
"time"
)
func main() {
pwm0 := machine.PWM0
pwmPin := machine.GP0
pwmPin.Configure(machine.PinConfig{Mode: machine.PinPWM})
pwm0.Configure(machine.PWMConfig{
Period: 1e9 / 500,
})
channel, _ := pwm0.Channel(pwmPin)
for {
for divider := 1; divider <= 10; divider++ {
pwm0.Set(channel, pwm0.Top()/uint32(divider))
time.Sleep(100 * time.Millisecond)
}
for divider := 10; divider >= 1; divider-- {
pwm0.Set(channel, pwm0.Top()/uint32(divider))
time.Sleep(100 * time.Millisecond)
}
}
}
18. Kontroly prováděné při konfiguraci PWM
V demonstračním příkladu, který byl uveden v předchozí kapitole, se neprováděla žádná kontrola, jestli nastavení PWM proběhlo v pořádku, tj. zda nenastala chyba. Kontroly samozřejmě můžeme doplnit, ovšem otázka je, jak případnou chybu indikovat uživateli. Máme několik možností:
- Použijeme standardní vestavěnou funkci println, což ovšem vyžaduje přístup ke konzoli (popíšeme si příště)
- Zobrazíme informaci na sedmisegmentovém displeji, například číslo chyby (nepatrně komplikované, opět využijeme příště)
- Využijeme jednu z připojených LED, kterou budeme indikovat, zda vše proběhlo korektně (svítí/nesvítí)
V dnešním posledním demonstračním příkladu je použita poslední možnost. K indikaci chyby je použita LED připojená na GPIO 2. Pokud se PWM podaří inicializovat, bude dioda svítit, v opačném případě nikoli:
package main
import (
"machine"
"time"
)
func main() {
pwm0 := machine.PWM0
pwmPin := machine.GP0
pwmPin.Configure(machine.PinConfig{Mode: machine.PinPWM})
led := machine.GP2
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
led.Low()
err := pwm0.Configure(machine.PWMConfig{
Period: 1e9 / 500,
})
if err != nil {
return
}
channel, err := pwm0.Channel(pwmPin)
if err != nil {
return
}
led.High()
for {
for divider := 1; divider <= 10; divider++ {
pwm0.Set(channel, pwm0.Top()/uint32(divider))
time.Sleep(100 * time.Millisecond)
}
for divider := 10; divider >= 1; divider-- {
pwm0.Set(channel, pwm0.Top()/uint32(divider))
time.Sleep(100 * time.Millisecond)
}
}
}
*** image ***
19. Repositář s demonstračními příklady
Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář, můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
| # | Příklad | Stručný popis | Cesta |
|---|---|---|---|
| 1 | blink.go | blikání LED připojenou ke zvolenému GPIO | https://github.com/tisnik/go-root/blob/master/tinygo-rp2040/blink.go |
| 2 | button1.go | čtení tlačítka připojeného ke zvolenému GPIO, základní varianta | https://github.com/tisnik/go-root/blob/master/tinygo-rp2040/button1.go |
| 3 | button2.go | čtení tlačítka připojeného ke zvolenému GPIO, pojmenované konstanty s GPIO | https://github.com/tisnik/go-root/blob/master/tinygo-rp2040/button2.go |
| 4 | button3.go | čtení tlačítka připojeného ke zvolenému GPIO, vylepšení vizualizace stisku tlačítka | https://github.com/tisnik/go-root/blob/master/tinygo-rp2040/button3.go |
| 5 | 7segments1.go | ovládání sedmisegmentového displeje, základní varianta | https://github.com/tisnik/go-root/blob/master/tinygo-rp2040/7segments1.go |
| 6 | 7segments2.go | ovládání sedmisegmentového displeje s více číslicemi | https://github.com/tisnik/go-root/blob/master/tinygo-rp2040/7segments2.go |
| 7 | 7segments3.go | rozsvícení libovolné kombinace segmentů | https://github.com/tisnik/go-root/blob/master/tinygo-rp2040/7segments3.go |
| 8 | 7segments4.go | tisk hodnoty 0 až 9999 na čtyřmístném sedmisegmentovém displeji | https://github.com/tisnik/go-root/blob/master/tinygo-rp2040/7segments4.go |
| 9 | adc1.go | analogově digitální převodník: čtení stavu potenciometru s tiskem hodnoty odpovídající jeho natočení, základní varianta | https://github.com/tisnik/go-root/blob/master/tinygo-rp2040/adc1.go |
| 10 | adc2.go | čtení stavu potenciometru, varianta méně závislá na šumu ADC | https://github.com/tisnik/go-root/blob/master/tinygo-rp2040/adc2.go |
| 11 | pwm1.go | pulsně šířková modulace: příklad bez detekce chyb | https://github.com/tisnik/go-root/blob/master/tinygo-rp2040/pwm1.go |
| 12 | pwm2.go | pulsně šířková modulace: příklad s detekcí chyb (vizualizováno přes LED) | https://github.com/tisnik/go-root/blob/master/tinygo-rp2040/pwm2.go |
20. Odkazy na Internetu
- TinyGo – A Go Compiler For Small Places
https://tinygo.org/ - Getting started
https://tinygo.org/getting-started/ - Go.dev (klasická varianta překladače jazyka Go)
https://go.dev/ - gccgo
https://gcc.gnu.org/onlinedocs/gccgo/ - Setting up and using gccgo
https://go.dev/doc/install/gccgo - Awesome Go
https://github.com/avelino/awesome-go - TinyGo: Inline assembly
https://tinygo.org/docs/concepts/compiler-internals/inline-assembly/ - Getting Started with TinyGo: Bringing Go to Microcontrollers and WebAssembly
https://dev.to/ekwoster/getting-started-with-tinygo-bringing-go-to-microcontrollers-and-webassembly-2pp0 - Optimizing Go code with GCCGO for improved performance
https://dev.to/parmcoder/optimizing-go-code-with-gccgo-for-improved-performance-2d3d - The Untold Power of TinyGo: How to Run Go on Microcontrollers and Supercharge Embedded Development
https://dev.to/ekwoster/the-untold-power-of-tinygo-how-to-run-go-on-microcontrollers-and-supercharge-embedded-development-2g7d - From Arduino to Mars: Why You Should Be Using TinyGo for Embedded Web Development
https://dev.to/ekwoster/from-arduino-to-mars-why-you-should-be-using-tinygo-for-embedded-web-development-54od - Optimizing binaries
https://tinygo.org/docs/guides/optimizing-binaries/ - Why TinyGo Might Be the Future of Embedded WebAssembly & How To Get Started Today
https://ekwoster.dev/post/-why-tinygo-might-be-the-future-of-embedded-webassembly-how-to-get-started-today/ - TinyGo na GitHubu
https://github.com/tinygo-org/tinygo - Compile Go directly to WebAssembly components with TinyGo and WASI P2
https://wasmcloud.com/blog/compile-go-directly-to-webassembly-components-with-tinygo-and-wasi-p2/ - Do you use gccgo?
https://www.reddit.com/r/golang/comments/j1g1z6/do_you_use_gccgo/ - Go v/s TinyGo: Which one is the best for you?
https://blog.nonstopio.com/go-v-s-tinygo-which-one-is-the-best-for-you-73cac3c7849e - Go Wiki: GccgoCrossCompilation
https://go.dev/wiki/GccgoCrossCompilation - Oficiální stránky Gccgo
https://gcc.gnu.org/onlinedocs/gccgo/index.html - What are the primary differences between ‚gc‘ and ‚gccgo‘?
https://stackoverflow.com/questions/25811445/what-are-the-primary-differences-between-gc-and-gccgo - Setting up and using gccgo
https://go.dev/doc/install/gccgo - Go (Arch Linux)
https://wiki.archlinux.org/title/Talk:Go - Why are binaries built with gccgo smaller (among other differences?)
https://stackoverflow.com/questions/27067112/why-are-binaries-built-with-gccgo-smaller-among-other-differences - Why Everyone Is Sleeping On TinyGo: Run Go on Microcontrollers and the Web (WASM) Today!
https://ekwoster.dev/post/-why-everyone-is-sleeping-on-tinygo-run-go-on-microcontrollers-and-the-web-wasm-today/ - Go (Golang) GOOS and GOARCH
https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63 - Externí sériové sběrnice SPI a I²C
https://www.root.cz/clanky/externi-seriove-sbernice-spi-a-i2c/ - Sedmisegmentový displej (Wikipedie)
https://cs.wikipedia.org/wiki/Sedmisegmentov%C3%BD_displej - A/D převodník (Wikipedie)
https://cs.wikipedia.org/wiki/A/D_p%C5%99evodn%C3%ADk - D/A převodník (Wikipedie)
https://cs.wikipedia.org/wiki/D/A_p%C5%99evodn%C3%ADk - Pulzně šířková modulace
https://cs.wikipedia.org/wiki/Pulzn%C4%9B_%C5%A1%C3%AD%C5%99kov%C3%A1_modulace - TinyGo: Using PWM
https://tinygo.org/docs/tutorials/pwm/ - General-purpose input/output
https://en.wikipedia.org/wiki/General-purpose_input/output

