To gain better understanding of both Rust and Arduino (Uno), I’m trying to write direct hardware code for Arduino in Rust. Here’s a very simple LED blink example, that I’ve tried to write.
I’ve made use of one library (crate) called avrd
which only provides address mapping for ATMega328 microcontroller.
#![no_std]
#![no_main]
use core::{hint::black_box, panic::PanicInfo};
use avrd::atmega328;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
fn delay(x: u32) {
let mut i = 0;
while i < x {
i += 1;
black_box(i);
}
}
unsafe fn write_reg(reg: *mut u8, val: u8, mask: u8) {
let reg_val = reg.read_volatile();
reg.write_volatile((reg_val & !mask) | val);
}
#[no_mangle]
fn main() {
const LED_BUILTIN: u8 = 5;
unsafe {
let portB_data_direction = atmega328::DDRB;
// set it to output mode
write_reg(portB_data_direction, 1 << LED_BUILTIN, 1 << LED_BUILTIN);
let portB = atmega328::PORTB;
// switch it on, hopefully..
write_reg(portB, 1 << LED_BUILTIN, 1 << LED_BUILTIN);
delay(2);
write_reg(portB, 0, 1 << LED_BUILTIN);
}
}
Now for some reason, if this delay value is 2 or greater, the LED never stops blinking. I think this delay function might be at fault, since putting the delay(2)
above the line of code switching on the LED, makes the LED never switch on. If I don’t use the black_box
function to force the delay function to execute. Another bizarre thing, is if I change the code up a bit like so:
#[no_mangle]
fn main() {
const LED_BUILTIN: u8 = 5;
unsafe {
let portB_data_direction = atmega328::DDRB;
// set it to output mode
write_reg(portB_data_direction, 1 << LED_BUILTIN, 1 << LED_BUILTIN);
let portB = atmega328::PORTB;
// switch it on, hopefully..
let mut i = 0;
loop {
while i < 1000000 {
i += 1;
write_reg(portB, 1 << LED_BUILTIN, 1 << LED_BUILTIN);
}
i = 0;
while i < 1000000 {
i += 1;
write_reg(portB, 0, 1 << LED_BUILTIN);
}
i = 0;
}
}
}
Then though this time the LED switches on, and switches off, but only once (??!!). The infinite loop becomes finite and runs only once. I’m not sure if the code being generated is wrong or what.
Here’s the .cargo/config.toml file:
[build]
target = "avr-unknown-gnu-atmega328"
[unstable]
build-std = ["core"]
[target.'cfg(target_arch = "avr")']
runner = "ravedude uno --baudrate 57600"
Note that the microcontroller inside Arduino Uno is ATMega328P, while the MCU I’ve selected in config.toml is ATMega328. But looking it up online seems to suggest that both the chips are mostly the same, and ATMega328P might have more instructions available. Hence it’s a superset of the chip I’m generating the code for.