In November last year[1], I wrote about my early efforts to get Rust to compile on the Arduino Nano Every[2] - the latest, and cheapest/most convenient as an embedded controller - iteration of the Arduino family of Microchip/Atmel AVR 8-bit microcontroller based boards.
The ATmega4809[3] chip that the Every is based on is in many ways a super nice chip, small, fast, power light, and with more memory for your code and data than chips used in older Arduino boards. Unfortunately, it's also based on a somewhat different internal architecture - `avrxmega3` - than those other boards, meaning that toolchain support is best described as "broken to non existent". The `avr-gcc` you installed from a package probably can't even compile or link properly for it, before you even begin to think about Rust[4]'s limited support for AVR.
As evidenced by my last post on this subject being in November 2020, progress is not exactly quick. That's mostly a factor of the day job presenting many other challenges in the meantime than the complexity of the task though.
I am slowly making some progress on getting useful code to build and run, though. Hopefully some of the things I've discovered will be helpful to anyone else taking the same journey...
First off, toolchain dependencies. In my last post, I had managed to get a basic LED-blinking program to run using a hacked version of the Ruduino[5] crate. That was mostly by luck rather than judgement - but it did at least prove that it was possible.
But, since then, I've realised the first thing we need to do with this board is give up on 'standard' versions of the underlying compiler/linked toolchain, `avr-gcc` and `avr-ld`. They don't understand this chip properly... Fortunately, Microchip[6] maintain their own fork of `gcc` which *does* work properly with this chip.
So, my first advice to you is to download and install the Atmel toolchain for your platform from the link below. After installing, modify your `PATH` so the Atmel version of the commands appears before any other version of `avr-gcc` you may have installed. (I am succesfully using both the Mac (on my desktop) and Linux (in my CI/CD pipeline) versions of these.)
Atmel also distribute "ATpacks" for each of their microchips. These contain a machine-readable description of the chip's capabilities and also some support libraries you need to link into your eventual binary, specific to each chip. You will need to download these too, and then you can point `avr-gcc` to the correct ATpack for the ATmega4809 using the `-B` command line flag. We'll see how to do this using Rust in the A Working Build Target section of this post.
┌────────────────────┬─────────────────────────────────────────────────────────┐ │ Resource │ Where │ ╞════════════════════╪═════════════════════════════════════════════════════════╡ │ ATmega toolchain │ https://www.microchip.com/en-us/development-tools- │ │ │ tools-and-software/gcc-compilers-avr-and-arm[7] │ ├────────────────────┼─────────────────────────────────────────────────────────┤ │ The ATmega ATpacks │ http://packs.download.atmel.com[8] │ └────────────────────┴─────────────────────────────────────────────────────────┘
Last time round, I used the Ruduino[9] crate, which was a great start and I'm very grateful for it. But, I have discovered that there is a somewhat more recently maintained, and cleaner, HAL crate avr-hal[10], which it was a little easier to hack 4809 support into.
Note that there *will* be official support for the ATmega4809[11] in avr-hal[12] eventually - and it'll be a great day when it comes :). But at the moment there is a lot of good work happening in that project on more general development and refactoring of the HAL, so in the meantime I am using a slightly older version of the crate which I have *hacked* to provide basic support for the 4809.
Because this is a hack, and eventually will be deprecated in favour of the 'official' suppoort when it arrives, I am not publishing it on the official `crates.io` Rust crate repo. However, I *am* publishing it publicly for anyone who does wish to use it on a public Cloudsmith[13] Rust repo.
To use my packaged versions, add the following to the `.cargo/config.toml` file in your Rust project:
[registries] snowgoons-crates = { index = "https://dl.cloudsmith.io/public/snowgoons/crates/cargo/index.git" }
You can then reference my 4809-compatible versions of the HAL for the Arduino Nano Every in your `Cargo.toml` like so:
arduino-nano-every = { version = "0.1.0", registry = "snowgoons-crates" } avr-hal-generic = { version = "0.1.0", registry = "snowgoons-crates" }
The source is of course there in GitHub as well: | Crate | Github | | ----- | ------ | | arduino-nano-every, avr-hal-generic | https://github.com/timwalls/avr-hal/tree/arduino-nano-every[14] | | avr-device | https://github.com/timwalls/avr-device/tree/ATmega4809[15] |
OK, so, now we have a working toolchain, and a HAL that is known to work, we need to give Rust a build target description that tells it to use the right toolchain for the ATmega4809. We do this using a JSON file in the root of our Cargo project.
Last time round, you may remember we used an `atmega328p` build target; this worked to get basic code working (the instruction set is the same,) but would fail as soon as you tried anything more ambitious because of architecture and memory map differences between the chips. Now we have a basically functioning working description that's actually right for the ATmega4809[16].
To use this description, add this to your `.cargo/config.toml`:
[build] target = "avr-atmega4809.json"
Now you need to create the `avr-atmega4809.json` file like so:
{ "arch": "avr", "atomic-cas": false, "cpu": "avrxmega3", "data-layout": "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8", "eh-frame-header": false, "env": "", "exe-suffix": ".elf", "executables": true, "late-link-args": { "gcc": [ "-lgcc" ] }, "linker": "avr-gcc", "linker-flavor": "gcc", "linker-is-gnu": true, "llvm-target": "avr-unknown-unknown", "max-atomic-width": 8, "no-default-libraries": false, "os": "unknown", "pre-link-args": { "gcc": [ "-mmcu=atmega4809", "-Wl,--as-needed", "-Wl,-Map=target/memory.map", "-L","./atmel-atpack/atmega4809/avrxmega3", "-B","./atmel-atpack/atmega4809/" ] }, "target-c-int-width": "16", "target-endian": "little", "target-pointer-width": "16", "vendor": "unknown" }
There are a couple of important things to note:
Last time round I gave you a script[17] to upload compiled ELF code to your Arduino. This still works; the only thing to add here is that by adding a couple of lines to your `.cargo/config.toml` you can have Cargo automatically use this script when you use `cargo run`, which is a nice convenience:
[target.'cfg(target_arch = "avr")'] runner = "bin/arduino-nano-every-upload.sh"
If you got everything right, you should be able to test it's working with a test programme that looks something like this:
#![no_std] #![no_main] use arduino_nano_every::prelude::*; #[arduino_nano_every::entry] fn main() -> ! { let dp = arduino_nano_every::Peripherals::take().unwrap(); let mut pins = arduino_nano_every::Pins::new(dp.PORTA, dp.PORTB, dp.PORTC, dp.PORTD, dp.PORTE, dp.PORTF); // On the Nano Every, the LED is on pin D13 // Note - these are the *Arduino* pin references, not ATmega port references // it's the avr_hal:arduino_nano_every crate's job to map the Arduino pin refs // to the actual port used on the ATmega4809. let mut led = pins.d13.into_output(&mut pins.ddr); loop { led.toggle().void_unwrap(); arduino_nano_every::delay_ms(500); } } #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} }
The `Cargo.toml` would look like:
[package] name = "blinky" version = "0.1.0" edition = "2018" [dependencies] arduino-nano-every = { version = "0.1.0", registry = "snowgoons-crates" } avr-hal-generic = { version = "0.1.0", registry = "snowgoons-crates" } [profile.dev] panic = "abort" lto = true opt-level = "s" [profile.release] panic = "abort" codegen-units = 1 debug = true lto = true opt-level = "s"
Your `.cargo/config.toml` will look like this:
[build] target = "avr-atmega4809.json" [unstable] build-std = ["core"] [registries] snowgoons-crates = { index = "https://dl.cloudsmith.io/public/snowgoons/crates/cargo/index.git" } [target.'cfg(target_arch = "avr")'] runner = "bin/arduino-nano-every-upload.sh"
You will also need a specific version of the Rust toolchain - `nightly-2021-01-07` - because later versions introduce AVR-specific bugs. You can configure this using the `rustup` command, but you can also just put a config file `rust-toolchain.toml` into the root of your project as well:
[toolchain] channel = "nightly-2021-01-07" components = ["rust-src"]
Don't forget to include the `avr-atmega4809.json` file described above, and to include the ATpacks in an appropriate place, and then, fingers crossed, you should be able to build and run!
Good luck :-).
1: /posts/2020-11-11-compiling-rust-for-arduino-nano-every-part-one.gmi
2: https://docs.arduino.cc/hardware/nano-every
4: https://www.rust-lang.org/what/embedded
5: https://crates.io/crates/ruduino
7: https://www.microchip.com/en-us/development-tools-tools-and-software/gcc-compilers-avr-and-arm
8: http://packs.download.atmel.com
9: https://crates.io/crates/ruduino
10: https://github.com/Rahix/avr-hal
12: https://github.com/Rahix/avr-hal
14: https://github.com/timwalls/avr-hal/tree/arduino-nano-every
15: https://github.com/timwalls/avr-device/tree/ATmega4809
17: /posts/2020-11-11-compiling-rust-for-arduino-nano-every-part-one.gmi
--------------------