Mengapa Saya Menulis Ulang Firmware Keyboard dari Rust ke Zig: Koherensi, Pengerjaan, dan Kesenangan

Selama setahun terakhir saya telah mengumpulkan berbagai keyboard , termasuk menulis firmware untuk berbagai skema kontrol.





Awalnya, saya menulisnya di Rust, tetapi terlepas dari pengalaman pengembangan bertahun-tahun dengannya, saya harus berjuang. Seiring waktu, keyboard saya berfungsi, tetapi itu membutuhkan waktu yang lama dan tidak membuat saya senang.





Setelah saran berulang kali dari teman saya yang lebih paham Rust-and-compute-savvy Jamie Brandon , saya menulis ulang firmware ke Zig dan itu bekerja dengan sangat baik.





Saya menemukan ini mengherankan, mengingat saya belum pernah melihat Zig sebelumnya, dan ini adalah bahasa, bahkan belum versi 1.0, dibuat oleh seorang hipster di University of Portland , dan dijelaskan, pada kenyataannya, hanya dengan satu halaman dokumentasi .





Pengalaman berjalan sangat baik sehingga saya sekarang mengerti Zig (yang telah menggunakan belasan jam) serta Rust (yang telah saya gunakan setidaknya seribu jam).





Ini, tentu saja, tidak hanya mencerminkan saya dan minat saya, tetapi juga berlaku untuk masing-masing bahasa ini. Oleh karena itu, saya harus menjelaskan apa yang saya inginkan dari bahasa pemrograman sistem.





Juga, untuk menjelaskan mengapa saya berjuang dengan Rust, saya harus menunjukkan banyak kode kompleks yang pasti tidak saya sukai. Tujuan saya di sini bukan untuk mencela Rust, tetapi untuk menunjukkan reputasi saya (tidak cukup): ini agar Anda dapat menilai sendiri apakah saya menggunakan Rust dengan cara yang rasional, atau jika saya benar-benar tersesat.





Akhirnya, meskipun blog berisiko jatuh ke dalam "bahasa X lebih baik daripada bahasa Y" yang sangat membosankan, saya merasa bahwa akan lebih membantu bagi beberapa pembaca jika saya secara eksplisit membandingkan Rust dan Zig daripada menulis secara lengkap. Artikel positif "Zig hebat!". (Lagi pula, saya tanpa henti mengabaikan enam bulan Jamie berbicara tentang Zig karena "itu sobat hebat, tapi saya sudah tahu Rust dan saya hanya ingin menyelesaikan keyboard saya, oke?").





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





Saya berhasil menggunakan Zig untuk proyek hobi saya yang dapat disematkan, pembantu WASM satu kali dan pengikatan yang diperlukan ke C API, atau, dalam perjuangan untuk menyelesaikan tugas-tugas ini, saya akhirnya akan mulai memahami dan menghargai lebih banyak masalah apa yang melindungi saya dari Rust .





Bagaimanapun, saya sangat senang!





Ucapan Terima Kasih

Terima kasih kepada Julia Evans , Pierre Yves Bacc, Laura Lindsey , Jamie Brandon, dan Boats atas diskusi bijaksana mereka tentang Rust / Zig dan umpan balik yang membangun pada artikel ini!








All Articles