Warum ich die Tastatur-Firmware von Rust auf Zick umgeschrieben habe: Kohärenz, Handwerkskunst und Spaß

Im letzten Jahr habe ich verschiedene Tastaturen gesammelt , darunter das Schreiben von Firmware für verschiedene Steuerungsschemata.





Anfangs habe ich sie in Rust geschrieben, aber trotz jahrelanger Entwicklungserfahrung musste ich kämpfen. Mit der Zeit habe ich meine Tastaturen zum Laufen gebracht, aber es hat eine obszöne Zeit in Anspruch genommen und mir kein Vergnügen bereitet.





Nach wiederholten Vorschlägen meines rust- und rechnerisch versierten Freundes Jamie Brandon habe ich die Firmware in Zig umgeschrieben und es hat sehr gut geklappt .





Ich fand das erstaunlich, da ich Zig noch nie zuvor gesehen hatte, und dies ist eine Sprache, noch nicht einmal Version 1.0, die von einem Hipster an der Universität von Portland erstellt wurde und tatsächlich mit nur einer Seite Dokumentation beschrieben wird .





Die Erfahrung lief so gut, dass ich jetzt Zig (das ein Dutzend Stunden gebraucht hat) sowie Rust (das ich mindestens tausend Stunden gebraucht habe) verstehe.





Dies spiegelt natürlich nicht nur mich und meine Interessen wider, sondern gilt auch für jede dieser Sprachen. Daher muss ich zunächst erklären, was ich von einer Systemprogrammiersprache will.





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





, , " X , Y", , , Rust Zig, , "Zig's !". ( , , Zig, " , , Rust, , ?").





, . 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: , .





Entweder verwende ich Zig erfolgreich für meine einbettbaren Hobbyprojekte, einmaligen WASM-Helfer und die erforderlichen Bindungen an die C-API, oder im Kampf um die Erfüllung dieser Aufgaben werde ich endlich verstehen und schätzen, vor welchen Problemen Rust mich schützt .





Jedenfalls bin ich sehr glücklich!





Danksagung

Vielen Dank an Julia Evans , Pierre Yves Bacc, Laura Lindsey , Jamie Brandon und Boats für die nachdenkliche Diskussion über Rust / Zig und das konstruktive Feedback zu diesem Artikel!








All Articles