Pourquoi j'ai réécrit le micrologiciel du clavier de Rust en Zig: cohérence, savoir-faire et plaisir

Depuis un an, je collectionne divers claviers , notamment l'écriture de micrologiciels pour divers schémas de contrôle.





Au départ, je les ai écrits en Rust, mais malgré des années d'expérience en développement, j'ai dû me battre. Au fil du temps, j'ai fait fonctionner mes claviers, mais cela a pris un temps obscène et ne m'a pas apporté de plaisir.





Après les suggestions répétées de mon ami Jamie Brandon , plus averti en rouille et en calcul , j'ai réécrit le firmware en Zig et cela a très bien fonctionné.





J'ai trouvé cela étonnant, étant donné que je n'avais jamais vu Zig auparavant, et qu'il s'agit d'un langage, pas encore de la version 1.0, créé par un hipster de l'Université de Portland , et qui est décrit, en fait, avec une seule page de documentation .





L'expérience s'est si bien déroulée que je comprends maintenant Zig (qui a utilisé une dizaine d'heures) ainsi que Rust (que j'ai utilisé au moins mille heures).





Ceci, bien sûr, ne reflète pas seulement moi et mes intérêts, mais s'applique également à chacune de ces langues. Par conséquent, je devrai expliquer ce que je veux d'un langage de programmation système en premier lieu.





Aussi, pour expliquer pourquoi je me débattais avec Rust, je dois montrer beaucoup de code complexe que je n'aime vraiment pas. Mon but ici n'est pas de faire des reproches à Rust, mais de montrer ma réputation (insuffisante): c'est pour que vous puissiez juger par vous-même si j'utilise Rust de manière rationnelle, ou si j'ai complètement perdu mon chemin.





Enfin, alors que le blog court le risque de tomber dans le terriblement ennuyeux "le langage X est meilleur que le langage Y", je pense qu'il serait plus utile pour certains lecteurs de comparer explicitement Rust et Zig au lieu d'écrire en entier. Article positif "Zig est génial!". (Après tout, j'ai ignoré sans relâche les six mois que Jamie a parlé de Zig parce que "c'est super copain, mais je connais déjà Rust et je veux juste finir mon clavier, d'accord?").





, . PostScript Ruby (, ), JavaScript, . Clojure ( ClojureScript ), .





2017 . - , , , , , -, . , , :





  1. , ; , , .





  2. , , web assembly, ( ) , ..





( ) , ( , , ..).





.





, Rust 1.18.





Rust, , , : WASM , (Rust Electron), Rust- stm32g4, - ( ; !).





, Rust. - , , Rust , , /. : , .





, , Rust .





, , , . .





, Rust, , , 4- dev-kit' / Atreus'a:





" ". ( , , , 10-100 ). Rust "features", Cargo.toml



:





[dependencies]
cortex-m = "0.6"
nrf52840-hal = { version = "0.11", optional = true, default-features = false }
nrf52833-hal = { version = "0.11", optional = true, default-features = false }
arraydeque = { version = "0.4", default-features = false }
heapless = "0.5"

[features]
keytron = ["nrf52833"]
keytron-dk = ["nrf52833"]
splitapple = ["nrf52840"]
splitapple-left = ["splitapple"]
splitapple-right = ["splitapple"]

# specify a default here so that rust-analyzer can build the project; when building use --no-default-features to turn this off
default = ["keytron"]

nrf52840 = ["nrf52840-hal"]
nrf52833 = ["nrf52833-hal"]
      
      



, keytron



. nrf52833



( ), nrf52833-hal



( , , Rust). Rust . , , :





#[cfg(feature = "nrf52833")]
pub use nrf52833_hal::pac as hw;

#[cfg(feature = "nrf52840")]
pub use nrf52840_hal::pac as hw;
      
      



:





fn read_keys() -> Packet {
    let device = unsafe { hw::Peripherals::steal() };

    #[cfg(any(feature = "keytron", feature = "keytron-dk"))]
    let u = {
        let p0 = device.P0.in_.read().bits();
        let p1 = device.P1.in_.read().bits();

        //invert because keys are active low
        gpio::P0::pack(!p0) | gpio::P1::pack(!p1)
    };

    #[cfg(feature = "splitapple")]
    let u = gpio::splitapple::read_keys();

    Packet(u)
}
      
      



, :





  • - (any



    #[cfg(any(feature = "keytron", feature = "keytron-dk"))]



    ).





  • optional = true



    , Cargo.toml



    ( !).





  • (cargo build --release --no-default-features --features "keytron"



    )





!





- , , - "" :





fn read_keys(port: #[cfg(feature = "splitapple")]
                   nrf52840_hal::pac::P1
                   #[cfg(feature = "keytron")]
                   nrf52833_hal::pac::P0) -> Packet {}
      
      



, RTIC, app



, , , :





#[app(device = nrf52833)]
const APP: () = {
  //your code here...
};
      
      



? .





Rust .





: , ( ) :





, . , 1.10 (col0) 0.13 ( 1) , , K8 . , Rust :





  1. .





  2. Rust.





, .





, , P0's pin 10, :





P0.pin_cnf[10].write(|w| {
    w.input().disconnect();
    w.dir().output();
    w
});
      
      



, :





for (port, pin) in &[(P0, 10), (P1, 7), ...] {
    port.pin_cnf[pin].write(|w| {
        w.input().disconnect();
        w.dir().output();
        w
    });
}
      
      



, - (P0, usize) (P1, usize) - .





, :





type PinIdx = u8;
type Port = u8;

const COL_PINS: [(Port, PinIdx); 7] =
    [(1, 10), (1, 13), (1, 15), (0, 2), (0, 29), (1, 0), (0, 17)];

pub fn init_gpio() {
    for (port, pin_idx) in &COL_PINS {
        match port {
            0 => {
                device.P0.pin_cnf[*pin_idx as usize].write(|w| {
                    w.input().disconnect();
                    w.dir().output();
                    w
                });
            }
            1 => {
                device.P1.pin_cnf[*pin_idx as usize].write(|w| {
                    w.input().disconnect();
                    w.dir().output();
                    w
                });
            }
            _ => {}
        }
    }
}
      
      



, .





, , , ? , , :





pub fn read_keys() -> u64 {
    let device = unsafe { crate::hw::Peripherals::steal() };

    let mut keys: u64 = 0;

    macro_rules! scan_col {
        ($col_idx: tt; $($row_idx: tt => $key:tt, )* ) => {
            let (port, pin_idx) = COL_PINS[$col_idx];

            ////////////////
            //set col high
            unsafe {
                match port {
                    0 => {
                        device.P0.outset.write(|w| w.bits(1 << pin_idx));
                    }
                    1 => {
                        device.P1.outset.write(|w| w.bits(1 << pin_idx));
                    }
                    _ => {}
                }
            }

            cortex_m::asm::delay(1000);

            //read rows and move into packed keys u64.
            //keys are 1-indexed.
            let val = device.P0.in_.read().bits();
            $(keys |= ((((val >> ROW_PINS[$row_idx]) & 1) as u64) << ($key - 1));)*

            ////////////////
            //set col low
            unsafe {
                match port {
                    0 => {
                        device.P0.outclr.write(|w| w.bits(1 << pin_idx));
                    }
                    1 => {
                        device.P1.outclr.write(|w| w.bits(1 << pin_idx));
                    }
                    _ => {}
                }
            }

        };
    };

    //col_idx; row_idx => key ID
    #[cfg(feature = "splitapple-left")]
    {
        scan_col!(0; 0 => 1 , 1 => 8  , 2 => 15 , 3 => 21 , 4 => 27 , 5 => 33 ,);
        scan_col!(1; 0 => 2 , 1 => 9  , 2 => 16 , 3 => 22 , 4 => 28 , 5 => 34 ,);
        scan_col!(2; 0 => 3 , 1 => 10 , 2 => 17 , 3 => 23 , 4 => 29 , 5 => 35 ,);
        scan_col!(3; 0 => 4 , 1 => 11 , 2 => 18 , 3 => 24 , 4 => 30 , 5 => 36 ,);
        scan_col!(4; 0 => 5 , 1 => 12 , 2 => 19 , 3 => 25 , 4 => 31 , 5 => 37 ,);
        scan_col!(5; 0 => 6 , 1 => 13 , 2 => 20 , 3 => 26 , 4 => 32 , 5 => 38 ,);
        scan_col!(6; 0 => 7 , 1 => 14 ,);
    }

    #[cfg(feature = "splitapple-right")]
    {
        scan_col!(0; 0 => 1 , 1 => 8  , 2 => 15 , 3 => 23 , 4 => 30 , 5 => 37 ,);
        scan_col!(1; 0 => 2 , 1 => 9  , 2 => 16 , 3 => 24 , 4 => 31 , 5 => 38 ,);
        scan_col!(2; 0 => 3 , 1 => 10 , 2 => 17 , 3 => 25 , 4 => 32 , 5 => 39 ,);
        scan_col!(3; 0 => 4 , 1 => 11 , 2 => 18 , 3 => 26 , 4 => 33 , 5 => 40 ,);
        scan_col!(4; 0 => 5 , 1 => 12 , 2 => 19 , 3 => 27 , 4 => 34 , 5 => 41 ,);
        scan_col!(5; 0 => 6 , 1 => 13 , 2 => 20 , 3 => 28 , 4 => 35 , 5 => 42 ,);
        scan_col!(6; 0 => 7 , 1 => 14 , 2 => 21 , 3 => 29 , 4 => 36 , 5 => 22 ,);
    }

    keys
}
      
      



!





, scan_col!



, , keys



: u64 .





, Rust.





, , , , . Google " Rust" , :





  • usize ; ( ), / , / , .





  • ( ) ; , .





  • ; , .





, , , , Rust , , . C (#define, #ifdef



..), , Rust . ( !). Rust - Rust Analyzer , , , C.





, Rust - , RFC - , , , .





, , ?





, Zig , - , -- - .





Zig,

Zig. (. Rust Zig).





: , - Zig, .





.





, dk.zig







usingnamespace @import("register-generation/target/nrf52833.zig");
usingnamespace @import("ztron.zig");

pub const led = .{ .port = p0, .pin = 13 };
      
      



atreus.zig







usingnamespace @import("register-generation/target/nrf52840.zig");
usingnamespace @import("ztron.zig");

pub const led = .{ .port = p0, .pin = 11 };
      
      



.





ztron.zig



@import("root")



("root" - , ; !) :





usingnamespace @import("root");

export fn setup() void {

    led.port.pin_cnf[led.pin].modify(.{
        .dir = .output,
        .input = .disconnect,
    });

}
      
      



"feature" , Cargo.toml



, . Cargo.toml !





, , : devkit, zig build-obj dk.zig



; Atreus - zig build-obj atreus.zig



.





, Zig , . ( - , , ).





- ? , , ... :





const rows = .{
    .{ .port = p1, .pin = 0 },
    .{ .port = p1, .pin = 1 },
    .{ .port = p1, .pin = 2 },
    .{ .port = p1, .pin = 4 },
};

const cols = .{
    .{ .port = p0, .pin = 13 },
    .{ .port = p1, .pin = 15 },
    .{ .port = p0, .pin = 17 },
    .{ .port = p0, .pin = 20 },
    .{ .port = p0, .pin = 22 },
    .{ .port = p0, .pin = 24 },
    .{ .port = p0, .pin = 9 },
    .{ .port = p0, .pin = 10 },
    .{ .port = p0, .pin = 4 },
    .{ .port = p0, .pin = 26 },
    .{ .port = p0, .pin = 2 },
};

pub fn initKeyboardGPIO() void {
    inline for (rows) |x| {
        x.port.pin_cnf[x.pin].modify(.{
            .dir = .input,
            .input = .connect,
            .pull = .pulldown,
        });
    }

    inline for (cols) |x| {
        x.port.pin_cnf[x.pin].modify(.{
            .dir = .output,
            .input = .disconnect,
        });
    }
}
      
      



inline for .





, - , , "" - .





:





const col2row2key = .{
    .{ .{ 0,  1 }, .{ 1, 11 }, .{ 2, 21 }, .{ 3, 32 } },
    .{ .{ 0,  2 }, .{ 1, 12 }, .{ 2, 22 }, .{ 3, 33 } },
    .{ .{ 0,  3 }, .{ 1, 13 }, .{ 2, 23 }, .{ 3, 34 } },
    .{ .{ 0,  4 }, .{ 1, 14 }, .{ 2, 24 }, .{ 3, 35 } },
    .{ .{ 0,  5 }, .{ 1, 15 }, .{ 2, 25 }, .{ 3, 36 } },
    .{                         .{ 2, 26 }, .{ 3, 37 } },
    .{ .{ 0,  6 }, .{ 1, 16 }, .{ 2, 27 }, .{ 3, 38 } },
    .{ .{ 0,  7 }, .{ 1, 17 }, .{ 2, 28 }, .{ 3, 39 } },
    .{ .{ 0,  8 }, .{ 1, 18 }, .{ 2, 29 }, .{ 3, 40 } },
    .{ .{ 0,  9 }, .{ 1, 19 }, .{ 2, 30 }, .{ 3, 41 } },
    .{ .{ 0, 10 }, .{ 1, 20 }, .{ 2, 31 }, .{ 3, 42 } },
};

pub fn readKeys() PackedKeys {
    var pk = PackedKeys.new();

    inline for (col2row2key) |row2key, col| {
        // set col high
        cols[col].port.outset.write_raw(1 << cols[col].pin);

        delay(1000);

        const val = rows[0].port.in.read_raw();
        inline for (row2key) |row_idx_and_key| {
            const row_pin = rows[row_idx_and_key[0]].pin;
            pk.keys[(row_idx_and_key[1] - 1)] = (1 == ((val >> row_pin) & 1));
        }

        // set col low
        cols[col].port.outclr.write_raw(1 << cols[col].pin);
    }

    return pk;
}
      
      



, Ziginline for



, Rust ( , , ), / .





, // const-, . , ( ) :





pub const switch_count = comptime {
    var n = 0;
    for (col2row2key) |x| n += x.len;
    return n;
};
      
      



, Rust:





scan_col!(0; 0 => 1 , 1 => 8  , 2 => 15 , 3 => 21 , 4 => 27 , 5 => 33 ,);
      
      



( , - , Rust 500 , , ).





Rust'?

Zig Rust, . , , - " " - Rust.





, , Rust - , . , Rust , , , , , .





, , :





fn main() {
    let message = "hello world"; // a regular immutable variable definition
}

let message = "hello world"; // doesn't work at toplevel

const message: &str = "hello world"; // you have to write `const` and declare the type yourself.
      
      



, , . , :





  • , , , .





  • , , " " , .





  • , const



    , let



    , , let



    , consts data- .





  • - 100 , , , , ..





, Rust - --, . (. " " ).





: , , , , . ( , : , , , , , , , , ..).





, , Rust?





"" :





: , ?





Rust , , .





, if



/, , ( ). , , .





, "" , Zig - . , , : comptime



inline for



, , , , , , , - Zig!





Zig?

- , , , , - . , ; =D





, Zig .





- , , : , .





"" : Rust, Emacs , MacBook Air 2013 :





Rust 1.50 70 (, 90 ), target/



450 .





Zig 0.7.1, , 5 , zig-cache/



1.4. !





"" - ; , , . Zig:





, .





Zig, , , . . . .





, , , - - "".





: ", , while



", .





, , Zig, . //.





Zig, Zig.





, , .





, !





-Zig- WASM, ! (zig build-lib -target wasm32-freestanding -O ReleaseSmall foo.zig



foo.wasm



, !).





, , , Zig . , Zig - , ; , , . .





, . , , Rust, . ; XML Zig- ( continue comptime).





, Zig ; , , , , . . , .





, , , Zig: , .





Soit j'utilise Zig avec succès pour mes projets de passe-temps intégrables, les aides WASM ponctuelles et les liaisons nécessaires à l'API C, soit, dans la lutte pour accomplir ces tâches, je vais enfin commencer à comprendre et à apprécier davantage les problèmes contre lesquels Rust me protège. .





Bref, je suis très content!





Remerciements

Merci à Julia Evans , Pierre Yves Bacc, Laura Lindsey , Jamie Brandon et Boats pour leur discussion approfondie sur Rust / Zig et leurs commentaires constructifs sur cet article!








All Articles