5.4. Choosing an input/output library

To communicate with the outside world, you will need to use the AVR's built in IO registers to configure and access the electrical pins on the outside of the chip.

External resources

The problem space of IO on AVR

  • Reading from or writing to an IO pin is as easy as reading a byte from or writing a byte to the correct location in memory
  • The main issue is identifying which memory locations assigned to which physical pins and peripherals for each chip

Rather than using bare-metal IO tied to specific AVR chips, it is recommended to use an abstraction layer over the actual I/O register manipulation.

A bare-metal example of IO

This example uses hardcoded IO register locations for the ATMega328p. It may work for some other AVR devices in the same family, but others will use different IO register memory mappings and so will not produce the expected output when ran on these devices.

NOTE: Executables should prefer using an abstraction layer over the actual I/O such as embedded-hal rather than writing to microcontroller-specific I/O registers directly.

#![no_std]
#![no_main]

extern crate avr_std_stub;

/// The data direction register for PORT B, which is mapped to 0x24 in memory on the atmega328.
const DDRB: *mut u8 = 0x24 as *mut u8;
/// The pin status register for PORT B, which is mapped to 0x25 in memory on the atmega328.
const PORTB: *mut u8 = 0x25 as *mut u8;

#[no_mangle]
pub extern fn main() {
    unsafe {
        // Set the upper four physical pins on PORT B to inputs, the lower four to outputs.
        // The AVR interprets '1' in the data direction register as 'output', '0' input
        // for the corresponding pin.
        core::ptr::write_volatile(DDRB, core::ptr::read_volatile(DDRB) | 0b00001111);

        // Write half of the output pins as high, the other half low.
        core::ptr::write_volatile(PORTB, 0b00001010);
    }
}

High level libraries for IO on AVR

The embedded-hal Hardware abstraction layer

The embedded-hal project exposes a device-independent set of traits that can be implemented by crates for specific devices. In this manner, one crate can be written to target multiple different embedded device architectures like ARM, MSP430, and AVR.

The embedded-hal project

embedded-hal implementations for AVR:

  • Rahix/avr-hal
    • Supports atmega32u4, attiny85, atmega328p, atmega1280

The AVR-specific avr-rust/ruduino crate

Caveat: This crate can and will only work for AVR. Depending on this crate directly will lock you out of targeting architectures other than AVR.

The ruduino library provides a high-level type-safe API for interacting with the AVR IO registers.

Driven from the official .atdf AVR device specification files, this crate exposes all available IO registers for any AVR device you wish to target.

At the moment, the high level bindings in ruduino are limited. You have access to all of the peripherals, but you will need to write to the expected IO registers manually.

Others?

Pull requests to github.com/avr-rust/book.avr-rust.com welcome!