uf2 Binary Serialization and Cargo Subcommands
Topics:
- Binary serialization for bootloader protocols.
- Rust and WASM in a Chrome extension
- Elixer and Rust and OpenCL
- Command line argument processing
- Handy tools like clippy, cargo-expand, cargo
Binary Serialization via @jacobrosenthal
Im trying to simplify uploading code to the pygamer. Theres an existing uf2 converter and you can just manually copy the file to the disk, but its kind of a lot of lines of setup and libraries we need installed. Honestly it works fine, I just like the idea of learning some more about Rust while cleaning up the workflow. This is the current state of things
#only needed one time
cargo install --git https://github.com/sajattack/uf2conv-rs
cargo install cargo-binutils
rustup component add llvm-tools-preview
#each compile, objcopy actually already calls cargo under the hood here saving 1 line
cargo objcopy --example ferris_img --release -- -O binary ferris_img.bin
uf2conv-rs ferris_img.bin --base 0x4000 --output ferris_img.uf2
cp ferris_img.uf2 /Volumes/PYGAMERBOOT/
But its a lot of dependencies. Instead talking to the device over the HID protocol using the microsoft bootloader protocol called hf2 which adafruit uses heavily. My work is at uf2-rs where I use live upload over usb instead of a copy
cargo install --git https://github.com/jacobrosenthal/uf2
cargo install cargo-binutils
rustup component add llvm-tools-preview
cargo objcopy --example ferris_img --release -- -O binary ferris_img.bin
uf2 flash -f ferris.bin 0x4000
My next step not yet done yet is to turn it into a cargo command using cargo-project reducing it into something like:
cargo install --git https://github.com/jacobrosenthal/uf2
cargo install cargo-binutils
rustup component add llvm-tools-preview
cargo uf2 --example ferris_img --release 0x4000
Ideally I can figure out how to convert the cargo elf file to a bin internally and remove the need for even llvm dependencies
cargo install --git https://github.com/jacobrosenthal/uf2
cargo uf2 --example ferris_img --release 0x4000
The big thing this library needs to solve is how binary serialization to turn this c style struct
pub(crate) struct Command {
///Command ID
id: u32,
///arbitrary number set by the host, for example as sequence number. The response should repeat the tag.
tag: u16,
///reserved bytes in the command should be sent as zero and ignored by the device
_reserved0: u8,
///reserved bytes in the command should be sent as zero and ignored by the device
_reserved1: u8,
}
into a buffer to send over the wire. In std rust you might use the std cursor and The core available from_le_bytes to help us turn bytes into primitives, and not mess up indexing, but but I like to write no_std code so I use a library called scroll like this
//Packets are up to 64 bytes long
let buffer = &mut [0_u8; 64];
let mut offset = 1;
buffer.gwrite_with(cmd.id, &mut offset, LE)?;
buffer.gwrite_with(cmd.tag, &mut offset, LE)?;
buffer.gwrite_with(cmd._reserved0, &mut offset, LE)?;
buffer.gwrite_with(cmd._reserved1, &mut offset, LE)?;
Other topics discussed:
Using +nightly
For those not in the know say someone says you need the nightly build of a cargo crate. So you have to rustup default nightly first then cargo install blah-thing and then rustup default stable Instead you can add +nightly onto most commands to do something in nightly for just one command so all that becomes cargo +nightly install blah-thing
cargo-asm to see what assembly language is generated
cargo new helloworld && cd helloworld
$ cargo asm helloworld::main --rust
fn main() {
push rbp
mov rbp, rsp
sub rsp, 48
lea rax, [rip, +, l___unnamed_2]
mov qword, ptr, [rbp, -, 48], rax
mov qword, ptr, [rbp, -, 40], 1
mov qword, ptr, [rbp, -, 32], 0
lea rax, [rip, +, l___unnamed_3]
mov qword, ptr, [rbp, -, 16], rax
mov qword, ptr, [rbp, -, 8], 0
lea rdi, [rbp, -, 48]
println!("Hello, world!");
call std::io::stdio::_print
}
add rsp, 48
pop rbp
ret
cargo-expand to expand macros to the console so you can see what you're generating.
You may have seen by now you can implement Debug on a type for free if underlying types support it by using the #[derive(Debug)] attribute macro. Using cargo expand we can see what that did for us:
#[derive(Debug)]
struct S;
fn main() {
}
#![feature(prelude_import)]
#![no_std]
#[prelude_import]
use ::std::prelude::v1::*;
#[macro_use]
extern crate std as std;
struct S;
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::fmt::Debug for S {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match *self {
S => {
let mut debug_trait_builder = f.debug_tuple("S");
debug_trait_builder.finish()
}
}
}
}
fn main() { }
cargo clippy for more help after it compiles
The compiler makes sure things compile, clippy tries to tell you there might be a better way to do things. You can replace 'check' in your ide with 'clippy' or run it on the command line.
$ cargo clippy
warning: casting u32 to f64 may become silently lossy if you later change the type
--> src/bin.rs:166:51
|
166 | let padded_num_pages = (binary.len() as f64 / bininfo.flash_page_size as f64).ceil() as u32;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `f64::from(bininfo.flash_page_size)`
|
= note: `#[warn(clippy::cast_lossless)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless
Finished dev [unoptimized + debuginfo] target(s) in 12.93s
$
Note you have it installed on nightly, it can also apply the suggestion automatically.
$ cargo +nightly fix -Z unstable-options --clippy
$ cargo clippy
Finished dev [unoptimized + debuginfo] target(s) in 0.57s
$
There is a list of ALL the Clippy Lints which shows you all the rules in one place.
CLI Apps with clap.rs via @danielbank
Demo Repo
https://github.com/danielbank/next-prime-rs
Builder Pattern for Making a CLI
let matches = App::new("next-prime")
.version("1.0")
.about("Calculate the next prime after a given integer")
.author("Daniel Bank")
.arg(
Arg::with_name("number")
.help("a number after which the next prime will occur")
.index(1)
.required(true),
)
.get_matches();
Crates You Should Know
Learnings
Return Values
-
Forgot the return type (
-> u64) -
I was trying to return using a statement:
n;vs an expression:n
Statements are instructions that perform some action and do not return a value. Expressions evaluate to a resulting value.
error[E0308]: mismatched types
--> src/main.rs:4:26
|
4 | fn next_prime(x: u64) -> u64 {
| ---------- ^^^ expected u64, found ()
| |
| implicitly returns `()` as its body has no tail or `return` expression
...
7 | n;
| - help: consider removing this semicolon
|
= note: expected type `u64`
found type `()`