Využití TinyGo při programování Raspberry Pi Pico: od GPIO až k PWM

Včera
Doba čtení: 32 minut

Sdílet

Autor: Raspberry Pi Foundation
Jednou z užitečných vlastností překladače TinyGo je podpora mnoha platforem, mezi které patří i Raspberry Pi Pico. Ukážeme si, jak naprogramovat ovládání GPIO, analogově-digitálního převodníku či použít pulsní šířkovou modulaci.

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

16. Pulsně šířková modulace

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

20. Odkazy na Internetu

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
Poznámka: samotnou verzi TinyGo opět získáme jednoduše:
$ 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.

Y Soft logo

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 XX:

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. 

Autor: tisnik, podle licence: Rights Managed
Obrázek XX:

Obrázek 2: Zadní strana desky, která naznačuje, že Pico lze provozovat i s napájením 4,5V 

Autor: tisnik, podle licence: Rights Managed

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
}
Poznámka: V tomto zdrojovém kódu jsou definice jen dvou metod High a Low, ovšem později se seznámíme i s dalšími metodami datového typu 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)
    }
}
Poznámka: v MicroPythonu je tento program sice mnohem kratší (pokud spočteme počet řádků zdrojového kódu), ale vyžaduje vlastní MicroPython, který zabírá poměrně velkou část Flash i RAM na Pi Picu.
Obrázek XX:

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). 

Autor: tisnik, podle licence: Rights Managed

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
Poznámka: k Picu se nyní můžeme připojit například přes minicom, což je pochopitelně téma, kterému se budeme taktéž věnovat.

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()
        }
    }
}
Poznámka: pochopitelně je možné tento program dále modifikovat, například naprogramovat zhasnutí LED jiným tlačítkem nebo automaticky po uplynutí určitého časového intervalu.

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()
        }
}
Poznámka: je zajímavé, že metody High a Low volají právě metodu Set, ve které se opět provádí rozeskok.

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 XX:

Obrázek 4: Zapojení čtveřice sedmisegmentových dispejů. DIG1–4 řídí, která číslice se právě zobrazuje. 

Autor: tisnik, podle licence: Rights Managed

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()
        }
    }
}
Poznámka: samozřejmě lze volat pin.Set(), což již ponechávám na laskavém čtenáři.

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})
}
Obrázek XX:

Obrázek 5: Všechny segmenty vybrané číslice svítí. 

Autor: tisnik, podle licence: Rights Managed

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})
}
Obrázek XX:

Obrázek 6: Všechny segmenty vybraných číslic svítí.  

Autor: tisnik, podle licence: Rights Managed

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}})
}
Obrázek XX:

Obrázek 7: Simulace současného svícení všech čtyř sedmisegmentových displejů. 

Autor: tisnik, podle licence: Rights Managed

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()
        }
    }
}
Poznámka: zobrazený obsah je nutné neustále obnovovat, protože již víme, že v jeden okamžik vlastně „svítí“ pouze jediná číslice.

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/do­cumentation/microcontroller­s/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:

Obrázek XX:

Obrázek 8: Zapojení potenciometru do GPIO nastaveného do režimu ADC. 

Autor: tisnik, podle licence: Rights Managed

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ě:

Obrázek XX:

Obrázek 9: LED ovládaná přes PWM. 

Autor: tisnik, podle licence: Rights Managed
Obrázek XX:

Obrázek 10: LED ovládaná přes PWM.  

Autor: tisnik, podle licence: Rights Managed
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í:

  1. Použijeme standardní vestavěnou funkci println, což ovšem vyžaduje přístup ke konzoli (popíšeme si příště)
  2. Zobrazíme informaci na sedmisegmentovém displeji, například číslo chyby (nepatrně komplikované, opět využijeme příště)
  3. 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:

Školení Linux

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

  1. TinyGo – A Go Compiler For Small Places
    https://tinygo.org/
  2. Getting started
    https://tinygo.org/getting-started/
  3. Go.dev (klasická varianta překladače jazyka Go)
    https://go.dev/
  4. gccgo
    https://gcc.gnu.org/onlinedocs/gccgo/
  5. Setting up and using gccgo
    https://go.dev/doc/install/gccgo
  6. Awesome Go
    https://github.com/avelino/awesome-go
  7. TinyGo: Inline assembly
    https://tinygo.org/docs/con­cepts/compiler-internals/inline-assembly/
  8. 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
  9. Optimizing Go code with GCCGO for improved performance
    https://dev.to/parmcoder/optimizing-go-code-with-gccgo-for-improved-performance-2d3d
  10. 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
  11. 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
  12. Optimizing binaries
    https://tinygo.org/docs/gu­ides/optimizing-binaries/
  13. 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/
  14. TinyGo na GitHubu
    https://github.com/tinygo-org/tinygo
  15. 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/
  16. Do you use gccgo?
    https://www.reddit.com/r/go­lang/comments/j1g1z6/do_y­ou_use_gccgo/
  17. 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
  18. Go Wiki: GccgoCrossCompilation
    https://go.dev/wiki/Gccgo­CrossCompilation
  19. Oficiální stránky Gccgo
    https://gcc.gnu.org/online­docs/gccgo/index.html
  20. What are the primary differences between ‚gc‘ and ‚gccgo‘?
    https://stackoverflow.com/qu­estions/25811445/what-are-the-primary-differences-between-gc-and-gccgo
  21. Setting up and using gccgo
    https://go.dev/doc/install/gccgo
  22. Go (Arch Linux)
    https://wiki.archlinux.or­g/title/Talk:Go
  23. Why are binaries built with gccgo smaller (among other differences?)
    https://stackoverflow.com/qu­estions/27067112/why-are-binaries-built-with-gccgo-smaller-among-other-differences
  24. 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/
  25. Go (Golang) GOOS and GOARCH
    https://gist.github.com/a­sukakenji/f15ba7e588ac42795f421­b48b8aede63
  26. Externí sériové sběrnice SPI a I²C
    https://www.root.cz/clanky/externi-seriove-sbernice-spi-a-i2c/
  27. Sedmisegmentový displej (Wikipedie)
    https://cs.wikipedia.org/wi­ki/Sedmisegmentov%C3%BD_dis­plej
  28. A/D převodník (Wikipedie)
    https://cs.wikipedia.org/wi­ki/A/D_p%C5%99evodn%C3%ADk
  29. D/A převodník (Wikipedie)
    https://cs.wikipedia.org/wi­ki/D/A_p%C5%99evodn%C3%ADk
  30. Pulzně šířková modulace
    https://cs.wikipedia.org/wi­ki/Pulzn%C4%9B_%C5%A1%C3%AD%C5­%99kov%C3%A1_modulace
  31. TinyGo: Using PWM
    https://tinygo.org/docs/tu­torials/pwm/
  32. General-purpose input/output
    https://en.wikipedia.org/wiki/General-purpose_input/output

Autor článku

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