Module avr_oxide::concurrency::thread

source ·
Expand description

Multithreading primitives.

AVRoxide allows you to create multiple threads, which will be scheduled cooperatively (through the yield_now() method, or by calling any blocking I/O routine), or pre-emptively if a suitable interrupt source is nominated for pre-emption via a crate feature flag.

§Limitations

There is a limit to the number of threads you can create, and additionally since each thread gets a stack allocated from the heap, the available heap memory is a limit.

The initial (main()) thread is allocated a relatively large default stack size, in recognition that it may be the only thread in the program and that it is also likely to allocate a lot of ‘global’ data on its own stack. This stack size can be changed by passing the stacksize attribute to the avr_oxide::main macro.

Subsequent threads are given a smaller default stack size, although this can be overridden using the Builder::stack_size() method to create the thread.

The following table summarises the default thread limits:

For processorMax threadsdefault main() stackdefault new thread stack
atmega48093512 bytes128 bytes
atmega328p3384 bytes64 bytes

Note: The maximum number of threads in the table includes the main() thread, but not the default Idle thread that is created by the kernel and must always exist.

§Thread completion and cleanup

When a thread completes, it will enter a Zombie state. It will remain in this state - and the thread context and stack will not be cleaned up, releasing any associated memory - until another thread joins it using the JoinHandle::join() method.

§Pre-Emptive Multithreading

Thread pre-emption depends on one (or more) interrupt sources being nominated to drive the scheduler. This is done by enabling a pmt_<interrupt_name> feature when including the AVRoxide crate in your cargo.toml.

Typically, you would nominate a timer interrupt; which interrupt will depend on the device:

For processorTypical Feature flagEffect
atmega4809pmt_tcb0_intThreads will be rescheduled every time TimerControlBlock 0 generates an interrupt

§Example

#![no_std]
#![no_main]

use avr_oxide::devices::{ UsesPin, OxideLed, OxideMasterClock };
use avr_oxide::thread;
use avr_oxide::hardware;
use avr_oxide::boards::board;
use avr_oxide::StaticWrap;

#[avr_oxide::main(chip="atmega4809",stacksize=1024)]
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());

  // If our code builds AVRoxide using the `pmt_tcb0_int` feature, 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();
}

Structs§

  • A thread factory that allows us to set the stack size for the thread that we create.
  • Handle that allows us to join a thread
  • The ‘userland facing’ representation of a Thread

Functions§

  • Spawn a new thread, returning a JoinHandle for it. Panics if the thread cannot be spawned.
  • Cooperatively have the current thread yield to another.