Kromě jazyka C a MicroPythonu se dá používat při vývoji na Raspberry Pi Pico i programovací jazyk Zig, konkrétně v předpřipraveném prostředí MicroZig. Programovací jazyk Zig je koncipován autorem Andrewem Kelley jako nástupce Céčka a je určen pro multiplatformní vývoj. Není to dosud hotový jazyk, ale dá se dost dobře používat. Zig je vyvíjen neziskovou organizací Zig Software Foundation, což je velká výhoda. Další výhodou při programování v Zigu je to, že k vývoji nepotřebujeme žádná složitá IDE nebo cmake, vystačíme si s textovým editorem a kompilátorem jazyka. Rozchodím jednoduchý příklad, budeme si vypisovat informace na UART (sériovou linku).

Mimochodem, pomocí MikroZigu se dá vyvíjet nejenom na Rasperry Pi Pico, ale i na další mikro počítače, jako třeba:

Předběžné práce

Schéma zapojení

Připojení USB - UART převodníku k Picu

pico uart

IMG 20240817 030138

Vodiče jsou zapojeny takto:

  • červený (+5V) — Pico VBUS (pin 40), budeme Pico zároveň napájet kabelem

  • černý (zem) — Pico GND (třeba pin 38)

  • bílý (TX) — Pico GP0 (pin 1)

  • zelený (RX) — Pico GP1 (pin 2)

Rozmístění pinů na Picu

pico pinout

Instalace kompilátoru Zig

Instalace kompilátoru Zig je velmi jednoduchá, stáhnete si pro váš systém balík s kompilátorem, rozbalíte ho a nastavíte cestu. Já programuji na Linuxu, použil jsem verzi Zigu 0.13.0

Instalace a nastavení kompilátoru
$ cd
$ tar xf zig-linux-x86_64-0.13.0.tar.xz
$ ln -s zig-linux-x86_64-0.13.0 zig
$ echo 'export PATH=$PATH:/home/jirka/zig' >> ~/.bashrc
$ source ~/.bashrc
$ zig version
0.13.0
$

Obrovskou výhodou Zigu je to, že kompilátor nejen kompiluje, ale i sestavuje projekt. Nyní stačí naklonovat z GitHubu MicroZig a můžeme začít.

Já jsem si nainstaloval vlastní git server kam si budu ukládat výsledky své práce, protože nechci používat GitHub. Jak jsem to udělal si můžete přečíst zde.

Instalace utilitky elf2uf2

Kompilátor Zigu dělá elf soubory, ty se musí konvertovat na soubory uf2, které můžeme snadno nahrát na Raspberry Pi Pico třeba pomocí USB. Utilita na konverzi elf → uf2 se jmenuje elf2uf2. Je dobré si tuto utilitu nainstalovat třeba do adresáře /usr/local/bin/.

Instalace elf2uf2
$ git clone https://github.com/rej696/elf2uf2.git
$ make build # or run your native compiler command
$ sudo cp elf2uf2 /usr/local/bin
$ elf2uf2 <input ELF file> <output UF2 file>

Instalace Micro Zigu

Klonování MicroZigu
jirka@jirka-Precision-T3610:~/pokusy$ git clone https://github.com/ZigEmbeddedGroup/microzig/
Cloning into 'microzig'...
remote: Enumerating objects: 9504, done.
remote: Counting objects: 100% (1037/1037), done.
remote: Compressing objects: 100% (300/300), done.
remote: Total 9504 (delta 825), reused 793 (delta 719), pack-reused 8467 (from 1)
Receiving objects: 100% (9504/9504), 12.20 MiB | 6.40 MiB/s, done.
Resolving deltas: 100% (4114/4114), done.
jirka@jirka-Precision-T3610:~/pokusy$

Sestavím celý MicroZig, protože budu používat příklady pro procesor RP2040 (Pico).

Sestavení MicroZigu
$ zig build

Sestavení trvá delší dobu. Může se stát, že MicroZig nepůjde přeložit, protože na kompilátoru i na samotném projektu MicroZig se neustále pracuje. Můžete si stáhnout odsud microzig.tar.gz, který půjde přeložit kompilátorem verze 0.13.0. Mám i uložen tento projekt na svém Gitu, ale je neveřejný.

Poté skočíme do adresáře ./examples/raspberrypi/rp2040 a přeložíme si příklady autorů MicroZigu pro Raspberry Pi Pico (procesor RP2040)

Překlad MicroZigu s příklady pro RPi Pico
jirka@jirka-Precision-T3610:~/pokusy/microzig$ date
Pá 16. srpna 2024, 22:41:05 CEST
jirka@jirka-Precision-T3610:~/pokusy/microzig$ zig version
0.13.0
jirka@jirka-Precision-T3610:~/pokusy/microzig$ zig build
jirka@jirka-Precision-T3610:~/pokusy/microzig$ cd examples/raspberrypi/rp2040
jirka@jirka-Precision-T3610:~/pokusy/microzig/examples/raspberrypi/rp2040$ zig build
jirka@jirka-Precision-T3610:~/pokusy/microzig/examples/raspberrypi/rp2040$
Výsledné přeložené příklady najdeme v adresáři zig-out/firmware/
jirka@jirka-Precision-T3610:~/pokusy/microzig/examples/raspberrypi/rp2040$ ls -l zig-out/firmware/*
-rwxrwxr-x 1 jirka jirka 1024008 srp 16 22:36 zig-out/firmware/pico_adc.elf
-rw-rw-r-- 1 jirka jirka   63488 srp 16 22:36 zig-out/firmware/pico_adc.uf2
-rwxrwxr-x 1 jirka jirka  944956 srp 16 22:36 zig-out/firmware/pico_blinky.elf
-rw-rw-r-- 1 jirka jirka   39936 srp 16 22:36 zig-out/firmware/pico_blinky.uf2
-rwxrwxr-x 1 jirka jirka 1013020 srp 16 22:36 zig-out/firmware/pico_flash-id.elf
-rw-rw-r-- 1 jirka jirka   46592 srp 16 22:36 zig-out/firmware/pico_flash-id.uf2
-rwxrwxr-x 1 jirka jirka 1030592 srp 16 22:36 zig-out/firmware/pico_flash-program.elf
-rw-rw-r-- 1 jirka jirka   63488 srp 16 22:36 zig-out/firmware/pico_flash-program.uf2
-rwxrwxr-x 1 jirka jirka  930240 srp 16 22:36 zig-out/firmware/pico_gpio-clk.elf
-rw-rw-r-- 1 jirka jirka   38912 srp 16 22:36 zig-out/firmware/pico_gpio-clk.uf2
-rwxrwxr-x 1 jirka jirka 1043348 srp 16 22:36 zig-out/firmware/pico_i2c-bus-scan.elf
-rw-rw-r-- 1 jirka jirka   72704 srp 16 22:36 zig-out/firmware/pico_i2c-bus-scan.uf2
-rwxrwxr-x 1 jirka jirka  999244 srp 16 22:36 zig-out/firmware/pico_multicore.elf
-rw-rw-r-- 1 jirka jirka   41984 srp 16 22:36 zig-out/firmware/pico_multicore.uf2
-rwxrwxr-x 1 jirka jirka  951856 srp 16 22:36 zig-out/firmware/pico_pwm.elf
-rw-rw-r-- 1 jirka jirka   40960 srp 16 22:36 zig-out/firmware/pico_pwm.uf2
-rwxrwxr-x 1 jirka jirka 1132996 srp 16 22:36 zig-out/firmware/pico_random.elf
-rw-rw-r-- 1 jirka jirka  205312 srp 16 22:36 zig-out/firmware/pico_random.uf2
-rwxrwxr-x 1 jirka jirka  931224 srp 16 22:36 zig-out/firmware/pico_spi-master.elf
-rw-rw-r-- 1 jirka jirka   52224 srp 16 22:36 zig-out/firmware/pico_spi-master.uf2
-rwxrwxr-x 1 jirka jirka 1074716 srp 16 22:36 zig-out/firmware/pico_squarewave.elf
-rw-rw-r-- 1 jirka jirka   55296 srp 16 22:36 zig-out/firmware/pico_squarewave.uf2
-rwxrwxr-x 1 jirka jirka 1007344 srp 16 22:36 zig-out/firmware/pico_uart.elf
-rw-rw-r-- 1 jirka jirka   52224 srp 16 22:36 zig-out/firmware/pico_uart.uf2
-rwxrwxr-x 1 jirka jirka 1291080 srp 16 22:36 zig-out/firmware/pico_usb-cdc.elf
-rw-rw-r-- 1 jirka jirka  133632 srp 16 22:36 zig-out/firmware/pico_usb-cdc.uf2
-rwxrwxr-x 1 jirka jirka 1220436 srp 16 22:36 zig-out/firmware/pico_usb-hid.elf
-rw-rw-r-- 1 jirka jirka  128512 srp 16 22:36 zig-out/firmware/pico_usb-hid.uf2
-rwxrwxr-x 1 jirka jirka 1080580 srp 16 22:36 zig-out/firmware/pico_ws2812.elf
-rw-rw-r-- 1 jirka jirka   57344 srp 16 22:36 zig-out/firmware/pico_ws2812.uf2
-rwxrwxr-x 1 jirka jirka 1087056 srp 16 22:36 zig-out/firmware/rp2040-matrix_tiles.elf
-rw-rw-r-- 1 jirka jirka   64000 srp 16 22:36 zig-out/firmware/rp2040-matrix_tiles.uf2
jirka@jirka-Precision-T3610:~/pokusy/microzig/examples/raspberrypi/rp2040$

Máme všechno hotovo a můžeme začít programovat v Zigu. Základem pro nás budou příklady z adresáře ./src/

jirka@jirka-Precision-T3610:~/pokusy/microzig/examples/raspberrypi/rp2040$ ls -l src/*
-rw-rw-r-- 1 jirka jirka 1043 srp 16 18:52 src/adc.zig                  (1)
-rw-rw-r-- 1 jirka jirka  524 srp 16 18:52 src/blinky_core1.zig         (2)
-rw-rw-r-- 1 jirka jirka  394 srp 16 18:52 src/blinky.zig               (3)
-rw-rw-r-- 1 jirka jirka 1047 srp 16 18:52 src/flash_id.zig             (4)
-rw-rw-r-- 1 jirka jirka 2333 srp 16 18:52 src/flash_program.zig        (5)
-rw-rw-r-- 1 jirka jirka  402 srp 16 18:52 src/gpio_clk.zig             (6)
-rw-rw-r-- 1 jirka jirka 1268 srp 16 18:52 src/i2c_bus_scan.zig         (7)
-rw-rw-r-- 1 jirka jirka  767 srp 16 18:52 src/pwm.zig                  (8)
-rw-rw-r-- 1 jirka jirka 1792 srp 16 18:52 src/random.zig               (9)
-rw-rw-r-- 1 jirka jirka 1405 srp 16 18:52 src/spi_master.zig           (10)
-rw-rw-r-- 1 jirka jirka 3089 srp 16 18:52 src/squarewave.zig           (11)
-rw-rw-r-- 1 jirka jirka 4147 srp 16 18:52 src/tiles.zig                (12)
-rw-rw-r-- 1 jirka jirka 1084 srp 16 18:52 src/uart.zig                 (13)
-rw-rw-r-- 1 jirka jirka 3312 srp 16 18:52 src/usb_cdc.zig              (14)
-rw-rw-r-- 1 jirka jirka 3145 srp 16 18:52 src/usb_hid.zig              (15)
-rw-rw-r-- 1 jirka jirka 2984 srp 16 18:52 src/ws2812.zig               (16)
jirka@jirka-Precision-T3610:~/pokusy/microzig/examples/raspberrypi/rp2040$
1 Příklad pro použití ADC
2 Příklad blikání vestavěnou LEDkou s pomocí druhého jádra procesoru RP2040
3 První příklad pro nováčky, blikání vestavěnou LEDkou.
4 Nevím
5 Nevím
6 Hodiny pomocí GPIO
7 Skenování I2C sběrnice (užitečná věc)
8 Použití pulsně šířkové modulace PWM
9 Použití vestavěného generátoru náhodných čísel
10 Použití SPI sběrnice
11 Vytváření obdélníkové vlny pomocí PIO
12 Neopixel matice 5x5 LED
13 Použití rozhraní UART
14 Použití UART sériové linky (třeba přes USB)
15 USB HID zařízení
16 Příklad na použití Neopixel LED pásku — tím se budeme zabývat.

Zig build system

Genialitou jazyka Zig je to, že sestavovací program, který nezbytně potřebujeme abychom zkompilovali a sestavili náš projekt, je napsán přímo v jazyce Zig. Nemusíme se učit nijaké make nebo cmake a bojovat s nastavením Makefile a CMakeFile.txt.

Podíváme se na soubor build.zig, který slouží pro sestavení příkladů.

const std = @import("std");
const MicroZig = @import("microzig/build");
const rp2040 = @import("microzig/bsp/raspberrypi/rp2040");

const available_examples = [_]Example{ (1)
    // RaspberryPi Boards:
    .{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_adc", .file = "src/adc.zig" },
    .{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_blinky", .file = "src/blinky.zig" },
    .{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_flash-program", .file = "src/flash_program.zig" },
    .{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_flash-id", .file = "src/flash_id.zig" },
    .{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_gpio-clk", .file = "src/gpio_clk.zig" },
    .{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_i2c-bus-scan", .file = "src/i2c_bus_scan.zig" },
    .{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_pwm", .file = "src/pwm.zig" },
    .{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_random", .file = "src/random.zig" },
    .{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_spi-master", .file = "src/spi_master.zig" },
    .{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_squarewave", .file = "src/squarewave.zig" },
    .{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_uart", .file = "src/uart.zig" }, (2)
    .{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_usb-hid", .file = "src/usb_hid.zig" },
    .{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_usb-cdc", .file = "src/usb_cdc.zig" },
    .{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_ws2812", .file = "src/ws2812.zig" },
    .{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_multicore" , .file = "src/blinky_core1.zig" },

    // WaveShare Boards:
    .{ .target = rp2040.boards.waveshare.rp2040_matrix, .name = "rp2040-matrix_tiles", .file = "src/tiles.zig" },
    // .{ .target = "board:waveshare/rp2040_eth", .name = "rp2040-eth" },
    // .{ .target = "board:waveshare/rp2040_plus_4m", .name = "rp2040-plus-4m" },
    // .{ .target = "board:waveshare/rp2040_plus_16m", .name = "rp2040-plus-16m" },
};

pub fn build(b: *std.Build) void {  (3)
    const mz = MicroZig.init(b, .{});
    const optimize = b.standardOptimizeOption(.{});

    for (available_examples) |example| {

        // `add_firmware` basically works like addExecutable, but takes a
        // `microzig.Target` for target instead of a `std.zig.CrossTarget`.
        //
        // The target will convey all necessary information on the chip,
        // cpu and potentially the board as well.
        const firmware = mz.add_firmware(b, .{
            .name = example.name,
            .target = example.target,
            .optimize = optimize,
            .root_source_file = b.path(example.file),
        });

        // `install_firmware()` is the MicroZig pendant to `Build.installArtifact()`
        // and allows installing the firmware as a typical firmware file.
        //
        // This will also install into `$prefix/firmware` instead of `$prefix/bin`.
        mz.install_firmware(b, firmware, .{});

        // For debugging, we also always install the firmware as an ELF file
        mz.install_firmware(b, firmware, .{ .format = .elf });
    }
}

const Example = struct {        (4)
    target: MicroZig.Target,    (5)
    name: []const u8,           (6)
    file: []const u8,           (7)
};
1 Seznam předpřipravených příkladů (pole struktur Example)
2 Náš program, který bude komunikovat přes UART
3 Funkce pro sestavení programu (spustí se příkazem zig build)
4 Definice struktury Example
5 Pro jakou desku děláme program.
6 Jak se program bude jmenovat.
7 Kde se nachází zdrojový soubor s programem.

Pokus si budeme chtít hrát, tak si do pole available_examples přidáme další program a bude to fungovat. Příkazem zig build se nám přeloží všechny příklady.

Rozbor programu uart.zig

Program dělá dvě věci:

  • bliká vestavěnou LED (na GPIO25), 0.5 sekundy svítí a 0.5 sekundy nesvítí.

  • vytvoří standardní log připojený k UARTU a vypisuje nám logovací informace (tyto informace snadno zachytíme třeba pomocí sudo minicom -b 115200 -c on -D /dev/ttyUSB0 )

Na zdrojový kód se podíváme po kouskách.

Import knihoven
const std = @import("std");             (1)
const microzig = @import("microzig");   (2)
1 Importujeme standardní knihovnu Zigu.
2 Importujeme knihovnu MicroZig
Pojmenování toho, co budeme používat
const rp2040 = microzig.hal;        (1)
const time = rp2040.time;           (2)
const gpio = rp2040.gpio;           (3)
const clocks = rp2040.clocks;       (4)

const led = gpio.num(25);           (5)
const uart = rp2040.uart.num(0);    (6)
const baud_rate = 115200;           (7)
const uart_tx_pin = gpio.num(0);    (8)
const uart_rx_pin = gpio.num(1);    (9)
1 Zkrácení microzig.hal
2 Budeme používat funkce s měřením času
3 Budeme používat GP0
4 Budeme používat hodiny
5 GPIO25 je na Picu vestavěná LED.
6 Budeme používat UART 0
7 Rychlost UARTU bude 115200 baudů.
8 TX pin UARTu bude GP0
9 RX pin UARTU bude GP1
Funkce panic
pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
    std.log.err("panic: {s}", .{message});
    @breakpoint();
    while (true) {}
}

Funkci panic() potřebujeme na ukončení programu při vzniku chyby.

Volby logování
pub const microzig_options = .{
    .log_level = .debug,
    .logFn = rp2040.uart.log,
};
Funkce main
pub fn main() !void {                           (1)
    led.set_function(.sio);                     (2)
    led.set_direction(.out);                    (3)
    led.put(1);                                 (4)

    uart.apply(.{                               (5)
        .baud_rate = baud_rate,
        .tx_pin = uart_tx_pin,
        .rx_pin = uart_rx_pin,
        .clock_config = rp2040.clock_config,
    });

    rp2040.uart.init_logger(uart);              (6)

    var i: u32 = 0;
    while (true) : (i += 1) {                   (7)
        led.put(1);
        std.log.info("what {}", .{i});          (8)
        time.sleep_ms(500);                     (9)

        led.put(0);                             (10)
        time.sleep_ms(500);
    }
}
1 Funkce main() nevrací nic užitečného, ale může vrátit chybu. Proto má !void.
2 Nastavíme funkci interní LED
3 Nastavíme pin led (GP25) jako výstupní
4 Pošleme na pin led vysokou úroveň (rozsvítíme LED).
5 Aplikujeme přednastavené hodnoty do UARTu.
6 Spojíme standardní logování s UARTem a nastartujeme.
7 Nekonečná smyčka
8 Zalogujeme hlášení
9 Počkáme 500 ms.
10 Zhasneme LED, pokčáme 500 ms a smyčka se opakuje.
Výstup programu do terminálu počítače (minicom)

minicom pico

Časová značka v hranatých závorkách je počet sekund od spuštění Pica.

Zdroje a odkazy