Crate avr_oxide[][src]

Expand description

A simple Rust runtime for AVR microcontrollers.

AVRoxide is a simple Hardware Abstraction Layer and runtime for developing software for ATmega AVR microcontrollers.

It was born out of frustration of the lack of options available for Rust development using the ATmega4809 microcontroller present in Arduino Nano Every embedded processor boards, but is intended to grow to support multiple target devices.

Key features include:

  • Abstractions of the key hardware devices provided by the controller, like USARTs, timers, and GPIO ports.
  • Higher-level abstractions - like ‘clock’ and ‘button’ - for simple application programming.
  • An “Arduino Familiar” way of referring to hardware components, while also offering complete access to the underlying chip for regular, non-Arduino, embedded solutions.
  • A simple runtime for event-based application development, using interrupt-driven IO for power-efficient operation.

Feature Flags

Feature flags are used so you can ensure minimal code size by disabling features that are not required, and also to configure some of those features at compile-time (important in an embedded system.) Some of these features are mandatory, and describe the type of hardware you are building for. Others describe optional functionality.

Mandatory Features

It is necessary to tell AVRoxide what device you are supporting by providing both a processor feature, and a clockspeed feature, from the list below. The CPU feature flag determines which hardware devices are exposed by the HAL, while the clockspeed feature is used to calculate certain constants for things like timer and baud rate calculations.

CPU FeatureClockspeed features (pick 1)
atmega480916MHz, 20MHz
atmega328p16MHz

Optional Features

Boot segment

If the bootable feature flag is set, AVRoxide will link in a small boot sector library including the interrupt vector table.

If you do not specify the bootable feature, you must provide your own:

  • Interrupt Vector Table
  • Inititalisation code to clear BSS and copy static initialisation data to RAM
  • Inititalise the global allocator (if enabled with one of the alloc_ features (see [Dynamic allocator]) by calling avr_oxide::alloc::initialise()
  • Jump to the _oxide_main() function to initialise the runtime.

Arduino Compatibility

If the arduino feature flag is set, the avr_oxide::arduino module will be exported, enabling access to hardware devices (like GPIO pins) using Arduino naming/numbering conventions.

Panic handler

If the panic_handler feature is enabled, a default panic handler will be provided.

Panic to serial port support

You can configure a serial port which will be used by the AVRoxide [panic handler] to output error line information on panic.

It is your responsibility to initialise that port with a suitable baud rate/serial protocol as early as possible in your program.

For ProcessorAvailable panic output Features (pick 1)
atmega4809panicout_usart0, panicout_usart1, panicout_usart2, panicout_usart3
atmega328ppanicout_usart0

Important: Because of the way Rust embeds panic information in your binary, enabling this feature can take up a fair amount of Flash memory space. For example, a simple test app that uses 37882 bytes of Flash without this feature uses 44330 bytes with it.

Note also that because Rust embeds absolute pathnames for source files into the binary, the size of your binary can change depending on where (the directory path) you compile it on your development machine!

Dynamic Allocator

A dynamic allocator implementation is provided; this will allocate a certain amount of the device’s memory as a heap for dynamic data allocation. More memory allocated to heap means less for stack and mutable constants, so three different memory models (small, medium, large) are available.

For ProcessorAllocator flagRAM allocated to heap
atmega4809alloc_small512 bytes
alloc_medium1024 bytes
alloc_large3072 bytes
atmega328palloc_small256 bytes
alloc_medium512 bytes
alloc_large1024 bytes

Device Features

Some individual device ‘drivers’ are enabled by feature flags. If you don’t need one, don’t enable it and maybe you can save a few precious bytes of RAM…

For ProcessorOptional Device Feature Flags
atmega4809usart0,usart1,usart2,usart3,tcb0,tcb1,tcb2,tcb3,rtc
atmega328pusart0

Low-Power Modes

By default, Oxide configures the chip to run without a clock prescaler (i.e. it runs at the full configured clockspeed), and a basic internal clock interrupt frequency of 1KHz (this is the maximum frequency for which you can configure MasterClock device events.)

It is possible however to enable lower-power-use modes through either the power_med or power_low feature flags; these have the following effect:

Power mode flagProcessor clock speedOxide clock frequency
None set1:1 (equal to hardware clock source)1000 KHz
power_med1/4th (0.25) of hardware clock source500 KHz
power_low1/8th (0.125) of hardware clock source500 KHz

A Minimal AVRoxide Program

This program shows how to access the devices using the Arduino aliases for an Arduino Nano Every, and how to listen to button events from a button attached to pin A2 to toggle an LED attached to pin D7 every time the button is pressed.

We also show the use of a software debouncer on the pin, and configuring a serial port.

#![no_std]
#![no_main]

use avr_oxide::hal::atmega4809::hardware;
use avr_oxide::alloc::boxed::Box;
use avr_oxide::devices::UsesPin;
use avr_oxide::devices::debouncer::Debouncer;
use avr_oxide::devices::{ Handle, OxideLed, OxideButton, OxideSerialPort };
use avr_oxide::hal::generic::serial::{BaudRate, DataBits, Parity, SerialPortMode, StopBits};
use avr_oxide::io::Write;
use avr_oxide::arduino;

#[avr_oxide::main(chip="atmega4809")]
pub fn main() {
  let supervisor = avr_oxide::oxide::instance();
  let hardware = hardware::instance();
  let arduino = arduino::nanoevery::Arduino::from(hardware);

  // Configure the serial port early so we can get any panic!() messages
  let mut serial= OxideSerialPort::using_port_and_pins(arduino.usb_serial,
                                                       arduino.usb_serial_tx,
                                                       arduino.usb_serial_rx).mode(SerialPortMode::Asynch(BaudRate::Baud9600, DataBits::Bits8, Parity::None, StopBits::Bits1));
  serial.write(b"Welcome to AVRoxide\n");

  let green_led = OxideLed::with_pin(arduino.d7);
  let mut green_button = Handle::new(OxideButton::using(Debouncer::with_pin(arduino.a2)));

  green_button.on_click(Box::new(move |_pinid, _state|{
    green_led.toggle();
  }));

  // Tell the supervisor which devices to listen to
  supervisor.listen_handle(green_button);

  // Now enter the event loop
  supervisor.run();
}

Examples and Templates

A more comprehensive example program can be found here.

‘Empty’ templates can be found at the AVRoxide Templates gitlab repo, with all the necessary bits and pieces to get up and running quickly.

The project homepage is at avroxi.de, where you’ll find more getting-started style guides and documentation.

Modules

A simple dynamic allocator implementation for our embedded devices.

A helper module that maps standard ATmega pins into equivalent Arduino names.

Device-specific constants. These are determined by the feature flags configued in your Cargo.toml.

Higher-level device abstractions for things like LEDs, system clocks, buttons.

Oxide system events; these are generated by the low-level device driver interrupt handlers and passed to the OxideSupervisor, which is responsible for then scheduling the appropriate userland code callbacks to handle the events.

Abstraction of the hardware devices attached to an AVR microcontroller

Simple IO traits (a very, very, very cutdown version of std::io). In due course hopefully somewhat /less/ cutdown, and maybe even derived from std::io in the style of the core_io crate, but right now this is what we’ve got.

Simple supervisor implementation for the AVRoxide runtime.

Simple macros and container for a static reference to a global SerialPort instance, so we can use print!()/println!() style macros.

Simplified datastructures for dealing with time, meant to broadly mirror the functionality provided by std::time where possible (and is derived directly from that source.)

General utility types/functions.

Macros

Obtain a reference to the CPU controller instance

A macro you can use as a substitute for .unwrap() on Results that will simply panic if the result is an error. You will want this to avoid hauling in all of the garbage associated with the Debug and Formatter types that comes along with the ‘real’ unwrap() implementation. With small EEPROMs/Flash we don’t have the room for that luxury.

A macro you can use as a substitute for .unwrap() on Options that will simply panic if the result is None. You will want this to avoid hauling in all of the garbage associated with the Debug and Formatter types that comes along with the ‘real’ unwrap() implementation. With small EEPROMs/Flash we don’t have the room for that luxury.

Same as panic_if_error!, but less typing!

Print the given output to the standard out writer device previously set with the set_stdout() function.

Print the given output to the standard out writer device previously set with the set_stdout() function, followed by a newline character.

Obtain a references to the Sleep controller instance

Volatile read from a variable by reference

Volatile write to a variable by reference

Attribute Macros