avr-oxide 0.4.0

An extremely simple Rusty operating system for AVR microcontrollers
/* lib.rs
 *
 * Developed by Tim Walls <tim.walls@snowgoons.com>
 * Copyright (c) All Rights Reserved, Tim Walls
 */
#![allow(clippy::needless_doctest_main)]
//! 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.
//! * Pre-emptive multithreading supervisor with basic synchronisation
//!   primitives.
//!
//! # 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 Feature | Clockspeed features (pick 1) |
//! | ----------- | ---------------------------- |
//! | `atmega4809` | `16MHz`, `20MHz` |
//! | `atmega328p` | `16MHz` |
//!
//! ## Optional Features
//! ### Board Compatibility
//! Feature flags can be used to enable a board-compatibility module that
//! you can then use to access pins via "board-specific" aliases.  For example,
//! if you enable on of the Arduino board features, you can access the
//! A and D pins using the Arduino naming conventions using aliases like so:
//!
//! ```rust,no_run
//! # #![no_std]
//! # #![no_main]
//! #
//! # use avr_oxide::alloc::boxed::Box;
//! # use avr_oxide::devices::UsesPin;
//! # use avr_oxide::devices::debouncer::Debouncer;
//! # use avr_oxide::devices::{ OxideLed, OxideButton, OxideSerialPort };
//! # use avr_oxide::hal::generic::serial::{BaudRate, DataBits, Parity, SerialPortMode, StopBits};
//! # use avr_oxide::io::Write;
//! # use avr_oxide::StaticWrap;
//! use avr_oxide::boards::board;
//!
//! #[avr_oxide::main(chip="atmega4809",stacksize=512)]
//! pub fn main() {
//! #   let supervisor = avr_oxide::oxide::instance();
//!   let green_led = OxideLed::with_pin(board::pin_d(7));
//! # supervisor.run()
//! }
//! ```
//!
//! The relevant feature flags are:
//!
//! | Feature name |         |
//! | ------------ | ------- |
//! | `arduino_nanoevery`       | Arduino Nano Every with ATmega4809 CPU |
//! | `arduino_uno`             | Arduino Uno with ATmega328P CPU |
//! | `atmega4809_xplained_pro` | AVR ATMega4809-XPlained-Pro board |
//!
//! ### 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 Processor  | Available panic output Features (pick 1) |
//! | -------------- | ---------------------------------------- |
//! | `atmega4809`   | `panicout_usart0`, `panicout_usart1`, `panicout_usart2`, `panicout_usart3` |
//! | `atmega328p`   | `panicout_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!
//!
//! Often, we need to disable Rust's built-in panic handling entirely in
//! `.cargo/config.toml`, like so:
//!
//! ```toml
//! build-std-features = ["compiler-builtins-mangled-names","panic_immediate_abort"]
//! ```
//!
//! In this case, there is still value to nominating a `panicout` port;
//! avr-oxide will still use this serial port to dump thread dumps, or
//! for use with the built-in serial debug methods in the [`avr_oxide::util::debug`] module.
//!
//! ### 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.  The size of the heap will be automatically calculated after
//! taking into account space used by statics/global variables.
//!
//! A certain minimum amount of memory allocatable to heap is required by
//! AVRoxide.  If this minimum amount of RAM is not available, the system
//! will halt on boot with an [`avr_oxide::oserror::OsError::NotEnoughRam`] error.
//!
//! The minimum heap size requirement is dependent on the processor:
//!
//! | For Processor | Minimum memory for heap |
//! | ------------- | ----------------------- |
//! | `atmega4809`  | 2048 bytes |
//! | `atmega328p`  | 512 bytes  |
//!
//! #### Stack size
//! Note that the thread stack is allocated from the *heap* when a thread
//! is created (including your initial main thread.)
//!
//! #### Rust memory use with panics
//! The way Rust currently handles panics results in inordinate use of
//! not just EEPROM, but also RAM.  You can help significantly by adding the
//! `panic_immediate_abort` feature to your `.cargo/config.toml`:
//!
//! ```toml
//! build-std-features = ["compiler-builtins-mangled-names","panic_immediate_abort"]
//! ```
//!
//! If your application will not run, with an `OsError::NotEnoughRam` error,
//! you may need to do this.
//!
//! ### 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 Processor | Optional Device Feature Flags |
//! | ------------- | ----------------------------- |
//! | `atmega4809`  | `usart0`,`usart1`,`usart2`,`usart3`,`tcb0`,`tcb1`,`tcb2`,`tcb3`,`rtc`, `twi0` |
//! | `atmega328p`  | `usart0` |
//!
//! ### 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 80Hz (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 flag | Processor clock speed | Oxide clock frequency |
//! | --------------- | --------------------- | --------------------- |
//! | None set        | 1:1 (equal to hardware clock source) | 80 Hz |
//! | `power_med`     | 1/4th (0.25) of hardware clock source | 40 Hz |
//! | `power_low`     | 1/8th (0.125) of hardware clock source | 10 Hz |
//!
//! ### Pre-Emptive Multithreading
//! AVRoxide supports multithreading, with threads created/joined using a
//! simplified version of the standard Rust `thread::spawn()` and
//! `thread::yield_now()` methods (provided through the [`avr_oxide::thread`]
//! module.)  Familiar synchronisation primitives are provided in [`avr_oxide::sync`].
//!
//! Cooperative threading (using `yield_now()` and/or syncorhonisation
//! primitives which yield when blocked as and when necessary) requires no
//! further configuration.
//!
//! Pre-emptive multithreading is supported as well, but does require one
//! further step - a timer that will drive thread preemption.  This means
//! at least one MasterClock device must be created and running:
//!
//! ```rust,no_run
//! #![no_std]
//! #![no_main]
//!
//! use avr_oxide::devices::{ UsesPin, OxideLed, OxideMasterClock };
//! use avr_oxide::boards::board;
//! use avr_oxide::thread;
//! use avr_oxide::hardware;
//! use avr_oxide::StaticWrap;
//!
//! #[avr_oxide::main(chip="atmega4809",stacksize=512)]
//! pub fn main() {
//!   let supervisor = avr_oxide::oxide::instance();
//!
//!   // Configure a 50Hz master clock device on TCB0
//!   let master_clock = StaticWrap::new(OxideMasterClock::with_timer::<50>(hardware::timer::tcb0::instance()));
//!   supervisor.listen(master_clock.borrow());
//!
//!   // The MasterCLock will by default drive context switches, thus threads
//!   // will now be pre-emptively multitasked, and this code will work without
//!   // locking up:
//!   let _jh = thread::Builder::new().stack_size(32).spawn(||{
//!     let white_led = OxideLed::with_pin(board::pin_d(10));
//!
//!     loop {
//!       white_led.toggle();
//!     }
//!   });
//!
//!   // Note that all the usual functionality of the MasterClock device
//!   // (delay timers, regular Oxide event handlers) remains available -
//!   // it will be used for thread preemption *in addition* to its usual
//!   // function, not instead.
//!
//!   supervisor.run();
//! }
//! ```
//!
//! Note that by default all MasterClock devices trigger context switches.
//! You can disable this behaviour with the [`disable_preemption()`] method
//! on a device that you don't wish to trigger context switches.
//!
//! [`disable_preemption()`]: avr_oxide::devices::MasterClock::disable_preemption
//!
//! ### Runtime checks
//! The kernel performs various sanity checks during runtime, if enabled
//! with the `runtime_checks` feature.  It is strongly encouraged that you
//! *do* use this feature, however it does imply a certain amount of
//! overhead (in particular during the context-switch interrupt routine, which
//! is when most of these are executed), so if you need to squeeze every last
//! ounce of performance out of the processor you may disable them.
//!
//! The table below summarises the checks performed:
//!
//! | Test | With `runtime_checks` | Without `runtime_checks` |
//! | ---- | --------------------- | ------------------------ |
//! | Thread stack overflow | Halt with [`OsError::StackOverflow`] | Undefined behaviour |
//! | Kernel structure consistency | Halt with [`OsError::KernelGuardCrashed`] | Undefined behaviour |
//! | Event queue overflow | Halt with [`OsError::OxideEventOverflow`] | Events silently discarded |
//! | Attempt to block inside an ISR | Halt with [`OsError::BadThreadState`] | Undefined behaviour |
//! | A StaticWrap dropped | Halt with [`OsError::StaticDropped`] | Memory leaked |
//!
//! [`OsError::StackOverflow`]: avr_oxide::oserror::OsError::StackOverflow
//! [`OsError::KernelGuardCrashed`]: avr_oxide::oserror::OsError::KernelGuardCrashed
//! [`OsError::OxideEventOverflow`]: avr_oxide::oserror::OsError::OxideEventOverflow
//! [`OsError::BadThreadState`]: avr_oxide::oserror::OsError::BadThreadState
//! [`OsError::StaticDropped`]: avr_oxide::oserror::OsError::StaticDropped
//!
//! # 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_run
//! #![no_std]
//! #![no_main]
//!
//! use avr_oxide::alloc::boxed::Box;
//! use avr_oxide::devices::UsesPin;
//! use avr_oxide::devices::debouncer::Debouncer;
//! use avr_oxide::devices::{ OxideLed, OxideButton, OxideSerialPort };
//! use avr_oxide::hal::generic::serial::{BaudRate, DataBits, Parity, SerialPortMode, StopBits};
//! use avr_oxide::io::Write;
//! use avr_oxide::boards::board;
//! use avr_oxide::StaticWrap;
//!
//! #[avr_oxide::main(chip="atmega4809")]
//! pub fn main() {
//!   let supervisor = avr_oxide::oxide::instance();
//!
//!   // Configure the serial port early so we can get any panic!() messages
//!   let mut serial= OxideSerialPort::using_port_and_pins(board::usb_serial(),
//!                                                        board::usb_serial_pins().0,
//!                                                        board::usb_serial_pins().1).mode(SerialPortMode::Asynch(BaudRate::Baud9600, DataBits::Bits8, Parity::None, StopBits::Bits1));
//!   serial.write(b"Welcome to AVRoxide\n");
//!
//!   let green_led = OxideLed::with_pin(board::pin_d(7));
//!   let mut green_button = StaticWrap::new(OxideButton::using(Debouncer::with_pin(board::pin_a(2))));
//!
//!   green_button.borrow().on_click(Box::new(move |_pinid, _state|{
//!     green_led.toggle();
//!   }));
//!
//!   // Tell the supervisor which devices to listen to
//!   supervisor.listen(green_button.borrow());
//!
//!   // Now enter the event loop
//!   supervisor.run();
//! }
//! ```
//!
//! # Examples and Templates
//! A more comprehensive example program can be found [here](https://avroxi.de/post/an-example-program/).
//!
//! 'Empty' templates can be found at the [AVRoxide Templates](https://gitlab.com/snowgoonspub/avr-oxide-templates) gitlab repo,
//! with all the necessary bits and pieces to get up and running quickly.
//!
//! The project homepage is at [avroxi.de](https://avroxi.de), where you'll find
//! more getting-started style guides and documentation.
//!
#![feature(abi_avr_interrupt)]
#![feature(naked_functions)]
#![feature(lang_items)]
#![feature(concat_idents)]
#![feature(const_maybe_uninit_assume_init)]
#![feature(maybe_uninit_uninit_array)]
#![feature(maybe_uninit_array_assume_init)]
#![feature(alloc_error_handler)]
#![feature(dropck_eyepatch)]
#![feature(trait_alias)]
#![feature(optimize_attribute)]
#![feature(generic_associated_types)]
#![feature(asm_experimental_arch)]
#![feature(asm_const)]
#![feature(asm_sym)]
#![feature(const_mut_refs)]
#![feature(negative_impls)]
#![feature(step_trait)]
#![feature(try_trait_v2)]
#![feature(try_trait_v2_residual)]
#![feature(const_trait_impl)]
#![feature(const_precise_live_drops)]

// no_std if built for AVR, if built for anything else it'll be for testing
// and then we do want std
#![cfg_attr(target_arch="avr", no_std)]

extern crate self as avr_oxide;

pub mod io;
pub mod devices;
pub mod hal;
pub mod concurrency;
pub mod event;
pub mod oxide;
pub mod deviceconsts;
pub mod util;
pub(crate) mod private;
mod mutstatic;
pub mod boards;

#[cfg(feature="bootable")]
mod boot;

pub mod alloc;

mod staticwrap;
pub use staticwrap::{StaticWrap,StaticRef,StaticRefMut,AsStaticRef,AsStaticRefMut};

pub mod time;
pub mod stdout;
pub mod oserror;
pub use oserror::OxideResult;

pub use oxide_macros::main as main;

// For convenience let's re-export the AVR device as a generic name
#[cfg(feature="atmega4809")]
pub use avr_oxide::hal::atmega4809 as hardware;
#[cfg(feature="atmega328p")]
pub use avr_oxide::hal::atmega328p as hardware;

// Re-export a few well buried modules at the top level
pub use avr_oxide::concurrency::sync as sync;
pub use avr_oxide::concurrency::thread as thread;