キーボードファームウェアをRustからZigに書き直した理由:一貫性、職人技、そして楽しさ

昨年、私はさまざまなキーボードを収集してきました。これには、さまざまな制御スキームのファームウェアの作成が含まれます。





当初はRustで書いていましたが、長年の開発経験にもかかわらず、戦わなければなりませんでした。時間が経つにつれて、キーボードが機能するようになりましたが、わいせつな時間がかかり、喜びを感じませんでした。





錆びて計算に精通した友人のジェイミー・ブランドンから何度も提案を受けた後、ファームウェアをジグに書き直しましたが、非常にうまくいきました。





これは、これまでZigを見たことがないことを考えると、驚くべきことでした。これは、ポートランド大学の流行に敏感な人によって作成された、バージョン1.0でさえない言語であり、実際、1ページのドキュメントで説明されています





経験はとてもうまくいったので、私は今ではZig(12時間使用した)とRust(少なくとも1000時間使用した)を理解しています。





もちろん、これは私と私の興味を反映しているだけでなく、これらの各言語にも当てはまります。したがって、そもそもシステムプログラミング言語に何が欲しいのかを説明する必要があります。





また、なぜ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: , .





埋め込み可能な趣味のプロジェクト、1回限りのWASMヘルパー、C APIへの必要なバインディングにZigを正常に使用するか、これらのタスクを完了するのに苦労して、Rustが私を保護する問題を最終的に理解して理解し始めます。 。





とにかく、とても嬉しいです!





謝辞

おかげジュリア・エヴァンス、ピエール・イヴ・BACC、ローラ・リンゼイジェイミーブランドン、そしてボートこの記事に彼らの錆/ジグの思慮深い議論と建設的なフィードバックのために!








All Articles