simavr-section 0.1.1

Add simavr-compatible metadata to your binary.
// This file is part of simavr-section, a Rust port of the *simavr*
// header `avr_mcu_section.h`.
//
// Copyright 2021 Andrew Dona-Couch
//
// simavr-section is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// simavr-section is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

//! Add simavr-compatible metadata to your binary.
//!
//! The [`simavr`] library specifies a format for including AVR-related
//! metadata in a firmware ELF file.  This crate ports that format to Rust,
//! to allow AVR binaries authored entirely in Rust to specify `simavr`-compliant
//! metadata.
//!
//! The metadata is used to pass various "parameters" to a simulator, such as
//! what microcontroller the code was built for, the expected clock frequency, fuses,
//! and so forth.  The same metadata could be read by a programmer to verify the
//! chip being programmed and update fuses as appropriate.
//!
//! # Example
//!
//! Start by adding the `avr_mcu` macro to your `main.rs`.  This metadata specifies
//! the core and frequency and is always required.
//!
//! ```
//! use simavr_section::avr_mcu;
//! avr_mcu!(8000000, b"atmega88");
//! ```
//!
//! You can then add additional metadata, such as configuring a VCD trace file
//! that the simulator should export:
//!
//! ```
//! use simavr_section::{avr_mcu_vcd_file, avr_mcu_vcd_port_pin};
//! avr_mcu_vcd_file!(b"gtkwave_trace.vcd", 1 /* param ignored due to a bug in simavr */);
//! avr_mcu_vcd_port_pin!(b'B', 5, b"PORTB5");
//! ```
//!
//! # Build Flags
//!
//! To ensure the metadata passes through to the final ELF, you need to add some link
//! args to the `rustc` call.  The easiest way to do this is to add or update your
//! `config.toml` file with these lines:
//!
//! ```toml
//! [target.'cfg(target_arch = "avr")']
//! rustflags = ["-C", "link-arg=-Wl,--undefined=_mmcu,--section-start=.mmcu=0x910000"]
//! ```
//!
//! [`simavr`]: https://github.com/buserror/simavr

#![no_std]
#![allow(non_camel_case_types, non_upper_case_globals)]

#[doc(hidden)]
pub use simavr_section_sys as bindings;

pub use simavr_section_macro::{avr_mcu_long, avr_mcu_string, avr_mcu_vcd_trace};

/// Specify the AVR device and frequency to use.
///
/// Name the device starting with `at`, such as `attiny10` or `atmega328p`.
/// The frequency needs to be a `const`-valued `u32`.
///
/// This tag must always be included.
///
/// # Example
///
/// ```
/// # use simavr_section::avr_mcu;
/// avr_mcu!(8000000, b"atmega88");
/// ```
#[macro_export]
macro_rules! avr_mcu {
    (
        $freq:expr, $mmcu:literal
    ) => {
        $crate::avr_mcu_string!(AVR_MMCU_TAG_NAME, $mmcu);
        $crate::avr_mcu_long!(AVR_MMCU_TAG_FREQUENCY, $freq);

        #[used]
        #[no_mangle]
        #[link_section = ".mmcu"]
        pub static _mmcu: [u8; 2] = [0, 0];
    };
}

/// Specify the AVR device voltages.
///
/// This tag allows you to specify the voltages used by your board
/// It is optional in most cases, but you will need it if you use
/// ADC module's IRQs. Not specifying it in this case might lead
/// to a divide-by-zero crash.
/// The units are Volts*1000 (millivolts)
///
/// All three voltages must be literal values.
///
/// # Example
///
/// ```
/// # use simavr_section::avr_mcu_voltages;
/// // Vcc & AVcc are 5V, Aref is 2.5V
/// avr_mcu_voltages!(5000, 5000, 2500);
/// ```
#[macro_export]
macro_rules! avr_mcu_voltages {
    (
        $vcc:literal, $avcc:literal, $aref:literal
    ) => {
        $crate::avr_mcu_long!(AVR_MMCU_TAG_VCC, $vcc);
        $crate::avr_mcu_long!(AVR_MMCU_TAG_AVCC, $avcc);
        $crate::avr_mcu_long!(AVR_MMCU_TAG_AREF, $aref);
    };
}

/// Write to the VCD tracefile at the given period.
///
/// Specifies the name and wanted period (in usec) for a VCD file.
/// Both must be literal values.
///
/// Note that due to a [bug in `simavr`], the period is ignored.
///
/// [bug in `simavr`]: https://github.com/buserror/simavr/issues/325
///
/// # Example
///
/// ```
/// # use simavr_section::avr_mcu_vcd_file;
/// avr_mcu_vcd_file!(b"gtkwave_trace.vcd", 1 /* param ignored due to a bug in simavr */);
/// ```
#[macro_export]
macro_rules! avr_mcu_vcd_file {
    (
        $name:literal, $period:literal
    ) => {
        $crate::avr_mcu_string!(AVR_MMCU_TAG_VCD_FILENAME, $name);
        $crate::avr_mcu_long!(AVR_MMCU_TAG_VCD_PERIOD, $period);
    };
}

/// Add this port/pin to the VCD file.
///
/// The syntax uses the name of the port as a character,
/// and not a pointer to a register.
///
/// The port character and pin number must be constant-valued expressions.
/// The trace name must be a literal byte string.
///
/// # Example
///
/// ```
/// # use simavr_section::avr_mcu_vcd_port_pin;
/// avr_mcu_vcd_port_pin!(b'B', 5, b"PORTB5");
/// ```
#[macro_export]
macro_rules! avr_mcu_vcd_port_pin {
    (
        $port:expr, $pin:expr, $name:literal
    ) => {
        $crate::avr_mcu_vcd_trace!(AVR_MMCU_TAG_VCD_PORTPIN, $port, $pin, $name);
    };
}

/// Add an IRQ vector to the VCD file.
///
/// These allows you to add a trace showing how long an IRQ vector is pending,
/// and also how long it is running. You can specify the IRQ as a vector name
/// straight from the firmware file, and it will be named properly in the trace
///
/// The vector number and what byte must be `const`-valued expressions.
/// The trace name must be a literal byte string.
///
/// # Example
///
/// ```
/// # use simavr_section::avr_mcu_vcd_irq_trace;
/// avr_mcu_vcd_irq_trace!(1, 1, b"INT0");
/// ```
#[macro_export]
macro_rules! avr_mcu_vcd_irq_trace {
    (
        $vect_number:expr, $what:expr, $trace_name:literal
    ) => {
        $crate::avr_mcu_vcd_trace!(AVR_MMCU_TAG_VCD_IRQ, $vect_number, $what, $trace_name);
    };
}

/// Configure pins which have external pullup or pulldown resistors.
///
/// Allows the firmware to hint simavr as to wether there are external
/// pullups/down on PORT pins. It helps if the firmware uses "open drain"
/// pins by toggling the DDR pins to switch between an output state and
/// a "default" state.
///
/// The value passed here will be output on the PORT IRQ when the DDR
/// pin is set to input again
///
/// # Example
///
/// ```
/// # use simavr_section::avr_mcu_external_port_pull;
/// // Expect an external pull-up on PINB5 and an external pull-down
/// // on PINB0.
/// avr_mcu_external_port_pull!(b'B', 0x11, 0x10);
/// ```
#[macro_export]
macro_rules! avr_mcu_external_port_pull {
    (
        $port:expr, $mask:expr, $val:expr
    ) => {
        $crate::avr_mcu_long!(
            AVR_MMCU_TAG_PORT_EXTERNAL_PULL,
            ((($port as u32) & 0xff) << 16)
                | ((($mask as u32) & 0xff) << 8)
                | (($val as u32) & 0xff)
        );
    };
}