Git Product home page Git Product logo

Comments (12)

Rahix avatar Rahix commented on August 18, 2024 6

Before divining into this, a fair warning: Dealing with interrupts is hard. Even in rust, where shooting yourself in the foot is prevented in most cases, you have to be careful. And you will need unsafe in places so you need to understand very well what you are doing.

Okay, that said, first of all the entire code (this one is for Arduino Leonardo, but I'm sure you can adapt it to whatever board you're using):

Click to expand entire listing
#![no_std]
#![no_main]
#![feature(abi_avr_interrupt)]

use arduino_leonardo::hal::port;
use arduino_leonardo::prelude::*;
use core::mem;
use panic_halt as _;

static mut LED: mem::MaybeUninit<port::portc::PC7<port::mode::Output>> = mem::MaybeUninit::uninit();

#[arduino_leonardo::entry]
fn main() -> ! {
    let dp = arduino_leonardo::Peripherals::take().unwrap();

    let mut pins = arduino_leonardo::Pins::new(dp.PORTB, dp.PORTC, dp.PORTD, dp.PORTE, dp.PORTF);

    let led = pins.d13.into_output(&mut pins.ddr);
    unsafe {
        // SAFETY: Interrupts are not enabled at this point so we can safely write the global
        // variable here.  A memory barrier afterwards ensures the compiler won't reorder this
        // after any operation that enables interrupts.
        LED = mem::MaybeUninit::new(led);
        core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
    }

    // Timer Configuration:
    // - WGM = 4: CTC mode (Clear Timer on Compare Match)
    // - Prescaler 256
    // - OCR1A = 15624
    //
    // => F = 16 MHz / (256 * (1 + 15624)) = 4 Hz
    //     (^ this formula I deduced from reading the datasheet)
    //
    // => The LED will toggle at 4 Hz, thus it blinks at 2 Hz
    let tmr1 = dp.TC1;
    tmr1.tccr1a.write(|w| w.wgm1().bits(0b00));
    tmr1.tccr1b.write(|w| w.cs1().prescale_256().wgm1().bits(0b01));
    tmr1.ocr1a.write(|w| unsafe { w.bits(15624) });

    // Enable the timer interrupt
    tmr1.timsk1.write(|w| w.ocie1a().set_bit());

    // In theory this should not be necessary ... But if you previously had
    // a sketch from Arduino loaded, the USB device will not have been reset.
    // Because of this we will be spammed with interrupts which will never
    // stop because they are never handled.
    //
    // (only for ATmega32U4)
    dp.USB_DEVICE.usbcon.reset();

    // Enable interrupts globally
    unsafe {
        // SAFETY: Not inside a critical section and any non-atomic operations have been completed
        // at this point.
        avr_device::interrupt::enable();
    }

    loop {
        avr_device::asm::sleep();
    }
}

#[avr_device::interrupt(atmega32u4)]
fn TIMER1_COMPA() {
    let led = unsafe {
        // SAFETY: We _know_ that interrupts will only be enabled after the LED global was
        // initialized so this ISR will never run when LED is uninitialized.
        &mut *LED.as_mut_ptr()
    };

    led.toggle().void_unwrap();
}

Let's take it apart:

#![feature(abi_avr_interrupt)]

this is needed because ISRs on AVR are an unstable feature at this point.

static mut LED: mem::MaybeUninit<port::portc::PC7<port::mode::Output>> = mem::MaybeUninit::uninit();

To move data between an ISR and the main program, you have to use global variables. This is where things get ugly because using globals is very unsafe for the most part (not just in Rust!). Here we get away without using any synchronization because there is no sharing happening. The LED is solely moved into the global after initialization and from then on only used in the ISR.

If you need to access a variable from both the main program and the ISR, this gets complicated: You'll have to ensure it is only accessed inside critical sections and the relevant compiler barriers are in place. Be careful, it is super easy to do this wrong.

// Timer Configuration:
// - WGM = 4: CTC mode (Clear Timer on Compare Match)
// - Prescaler 256
// - OCR1A = 15624
//
// => F = 16 MHz / (256 * (1 + 15624)) = 4 Hz
//     (^ this formula I deduced from reading the datasheet)
//
// => The LED will toggle at 4 Hz, thus it blinks at 2 Hz
let tmr1 = dp.TC1;
tmr1.tccr1a.write(|w| w.wgm1().bits(0b00));
tmr1.tccr1b.write(|w| w.cs1().prescale_256().wgm1().bits(0b01));
tmr1.ocr1a.write(|w| unsafe { w.bits(15624) });

// Enable the timer interrupt
tmr1.timsk1.write(|w| w.ocie1a().set_bit());

The comment pretty much explains what I'm doing, for more details there are tons of guides online and the datasheet also explains all the details. The most important part is actually enabling the relevant timer interrupt here.

// Enable interrupts globally
avr_device::interrupt::enable();

When the CPU resets, interrupts are globally disabled. Here we enable them after all setup is done. From this point onwards, at any point in the program an interrupt could fire so any shared data access needs to be done very carefully.

loop {
    avr_device::asm::sleep();
}

Because the main program has nothing to do in this case, I just let it sleep until any interrupt happens (and then make it immediately go back to sleep again).

#[avr_device::interrupt(atmega32u4)]
fn TIMER1_COMPA() {
    // ...
}

This is how to actually define an ISR, similar to the ISR() C macro. The function name must be the name of a know interrupt. You can look them up here: for ATmega32U4 (Arduino Leonardo) or for ATmega328P (Arduino Uno).

The avr_device::interrupt macro also needs the name of the CPU and its important to use the right one here. Otherwise you'll get strange results ...

There are a few important things to remember when writing ISRs:

  • As long as the ISR is running, nothing else can happen. The main program is stopped and no other interrupt can trigger. Thus, do as little as possible; try to offload any actual work into the main program and just set a signal flag in the ISR.
  • The ISR can run at any time when interrupts are enabled. You absolutely must synchronize access to shared data (using critical sections).
  • An interrupt firing can usually mean that multiple hardware events might have occured. You should be prepared to handle them all instead of assuming you'll get one interrupt for one event. For example, if interrupts were disabled for a long time, a timer might have overflown multiple times.

from avr-hal.

ToddG avatar ToddG commented on August 18, 2024 1

I have gotten multiple timers working by manually doing the compare within the loop like so:

https://github.com/ToddG/learn-electronics/blob/9d4c7b32a10a9d2063966cbe76da276b1274d1b4/posts/hello-world/07-multisegment-led-rust-arduino/code/multisegment-led/avr/mseg-bin/src/main.rs#L183

// enter loop
    loop {
        // manual compare timer1 : 1Hz
        if timer1.tcnt1.read().bits() >= 62499 {
            // --------------------------------------------------
            // DO SOMETHING AS IF THIS WERE AN ISR FOR TIMER1
            // --------------------------------------------------
            hardware = handle_timer1_1hz(hardware);
            // reset timer
            timer1.tcnt1.write(|w| unsafe { w.bits(0) });
        }
        // manual compare timer0 : 1kHz
        if timer0.tcnt0.read().bits() >= 249 {
            // --------------------------------------------------
            // DO SOMETHING AS IF THIS WERE AN ISR FOR TIMER0
            // --------------------------------------------------
            hardware = handle_timer0_1k_hz(hardware);
            // reset timer
            timer0.tcnt0.write(|w| unsafe { w.bits(0) });
        }
    }
}

This strategy works fine, though I still wonder if there isn't a more elegant way to configure the ISRs.

from avr-hal.

kallemooo avatar kallemooo commented on August 18, 2024

This is a quite new RUST HAL-layer for AVR and all features is not yet implemented.
Help is always wanted.

For delays using timers the issue #9 is planned.

from avr-hal.

ToddG avatar ToddG commented on August 18, 2024

Thank you for the detailed write up. I'm beginning to see what you mean about this being complicated. If nothing else, this is useful to force me to learn various aspects of Rust and embedded development.

I have extended my example to follow your advice w/respect to guarding state, etc. See full example for context, but here's the snippet that is currently blocking me. Basically, I want to encapsulate the board/device pins in a global struct that guards access with a critical section. However, I am having a tough time understanding the macro expansion of the Pins.

I'm following a pattern of encapsulating shared state in an UnsafeCell and then guarding access with critical sections. I'm doing this for: such as LedToStrobe, DisplayCounter, and MultiplexedLEDArray.

Here are my specific comments from this code

/*
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
This does not work. The problem is...how do I create a default Output Pin
to populate this global/static variable? I can set it in the entrypoint,
but I need to allocate the space here, as I do for the other globals.

Specifically:
    Pin::<mode::Output>::new()
I don't see how to create a new generic output pin.
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
struct WrappedHardware(UnsafeCell<Hardware>);
unsafe impl Sync for WrappedHardware {}
static WRAPPED_HARDWARE: WrappedHardware =
    WrappedHardware(
        UnsafeCell::new(
            Hardware{
                    onboard_led_pin: Pin::<mode::Output>::new(),
                    pins: arr![Pin::<mode::Output>::new(); 10] }));
 */

The example readme shows the path and the resources that got me here.

from avr-hal.

Rahix avatar Rahix commented on August 18, 2024

I'm following a pattern of encapsulating shared state in an UnsafeCell and then guarding access with critical sections. I'm doing this for: such as LedToStrobe, DisplayCounter, and MultiplexedLEDArray.

First of all, you're missing compiler fences here. You should add a

core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);

to the end of each critical section to ensure that operations won't get reordered.

I don't see how to create a new generic output pin.

You can't. The pins must be taken from the Pins struct and moved into your global. If there was some kind of Pin::new() function, this would be unsound, as it would allow creating multiple variables for the same pin.

To create generic pins from the concrete pin types you find in the Pins struct, you should use the .downgrade() method. This will give you a variable of the generic Pin type. Read more about this in the Digital I/O Documentation. As an example, leonardo-blink.rs uses this to store three pins in an array.

from avr-hal.

ToddG avatar ToddG commented on August 18, 2024

@Rahix Thank you for the additional comments.

I spent 2 full days trying to get timers to work. For anyone following, let me just state that this will drive you insane. The timers are incredibly fragile. I have lost count of the ways that I have broken them... and debugging by just looking at a few blinking leds is pretty draining as well.

For anyone interested, I'll paste in my code but I'm not going to spend anymore time on this. Keeping the code in the main thread and just checking the timer values worked well enough.

main.rs

#![no_std]
#![no_main]
#![feature(abi_avr_interrupt)]

use arduino_uno::hal::port;
use arduino_uno::prelude::*;
use arr_macro::arr;
use core::cell::UnsafeCell;
use core::mem;
use panic_halt as _;
use avr_device::interrupt;
use mseg_lib::{cmap, bits};

/// ------------------------------------------------------------------------
/// Consts
/// Modify these values based on the number of LEDs:
/// ------------------------------------------------------------------------
/// MAX_VALUE : if 1 LED, then this would be 10, 2 leds then 100, 3 leds then 1000.
/// This is obviously pow(10, NUM_LEDS).
const MAX_VALUE: usize = 100;

/// The number of LEDS in the display. Common numbers are 1, 2, 6, etc. Whatever
/// you have wired up.
const NUM_LEDS: usize = 2;

/// Always gonna be 10
const BASE: usize = 10usize;

/// This is where you wire up the LED segments to the GPIO pins.
const LEDS_COMMON_CATHODES: [usize; 8] = [2, 3, 4, 5, 6, 7, 8, 9];
const LED_ANODES: [usize; 2] = [10, 11];

/// ------------------------------------------------------------------------
/// State
/// ------------------------------------------------------------------------
#[derive(Debug, Clone, Copy)]
struct State {
    _led_to_strobe_index: usize,
    _display_counter: usize,
}

impl State {
    pub fn increment_led_to_strobe(&self) -> usize {
        (self._led_to_strobe_index + 1) % NUM_LEDS
    }
    pub fn increment_counter(&self) -> usize {
        (self._display_counter + 1) % MAX_VALUE
    }
    pub fn led_to_strobe(&self) -> usize {
        self._led_to_strobe_index
    }
    pub fn counter(&self) -> usize {
        self._display_counter
    }
    pub fn digits(&self) -> [u8; NUM_LEDS] {
        let mut num = self._display_counter;
        let mut result = [0; NUM_LEDS];
        // extract each digit from the count up counter and update the display value accordingly
        for i in 0..NUM_LEDS {
            result[i] = (num % BASE) as u8;
            num /= BASE
        }
        result
    }
}

struct GlobalState(UnsafeCell<State>);

impl GlobalState {
    pub fn get(&self, _cs: &avr_device::interrupt::CriticalSection) -> State {
        unsafe {
            let state = *self.0.get();
            core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
            state
        }
    }
    pub fn update(&self, new_state: State, _cs: &avr_device::interrupt::CriticalSection) {
        unsafe {
            *self.0.get() = new_state;
            core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
        };
    }
}

const GLOBAL_STATE_INIT: GlobalState = GlobalState(
    UnsafeCell::new(
        State {
            _led_to_strobe_index: 0,
            _display_counter: 0,
        }));

unsafe impl Sync for GlobalState {}

/// ------------------------------------------------------------------------
/// Statics
/// ------------------------------------------------------------------------
static mut LED: mem::MaybeUninit<port::Pin<port::mode::Output>> = mem::MaybeUninit::uninit();
static mut PINS: mem::MaybeUninit<[port::Pin<port::mode::Output>; 10]> = mem::MaybeUninit::uninit();
static GLOBAL_STATE: GlobalState = GLOBAL_STATE_INIT;


#[arduino_uno::entry]
fn main() -> ! {
    configure_hardware();

    unsafe {
        // !!!COMPILER FENCE!!!
        core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
        // Enable interrupts globally
        avr_device::interrupt::enable();
    }

    loop {
        avr_device::asm::sleep();
    }
}

fn configure_hardware() {
    let peripherals = arduino_uno::Peripherals::take().unwrap();

    let mut pins = arduino_uno::Pins::new(
        peripherals.PORTB,
        peripherals.PORTC,
        peripherals.PORTD,
    );


    let led = pins.d13.into_output(&mut pins.ddr).downgrade();
    // let mut pd0 = pins.d0.into_output(&mut pins.ddr); // rx
    // let mut pd1 = pins.d1.into_output(&mut pins.ddr); // tx
    let pd02 = pins.d2.into_output(&mut pins.ddr).downgrade();
    let pd03 = pins.d3.into_output(&mut pins.ddr).downgrade();
    let pd04 = pins.d4.into_output(&mut pins.ddr).downgrade();
    let pd05 = pins.d5.into_output(&mut pins.ddr).downgrade();
    let pd06 = pins.d6.into_output(&mut pins.ddr).downgrade();
    let pd07 = pins.d7.into_output(&mut pins.ddr).downgrade();
    let pd08 = pins.d8.into_output(&mut pins.ddr).downgrade();
    let pd09 = pins.d9.into_output(&mut pins.ddr).downgrade();
    let pd10 = pins.d10.into_output(&mut pins.ddr).downgrade();
    let pd11 = pins.d11.into_output(&mut pins.ddr).downgrade();
    unsafe {
        // SAFETY: Interrupts are not enabled at this point so we can safely write the global (static)
        // variable here.  A memory barrier afterwards ensures the compiler won't reorder this
        // after any operation that enables interrupts.
        LED = mem::MaybeUninit::new(led);
        PINS = mem::MaybeUninit::new([pd02, pd03, pd04, pd05, pd06, pd07, pd08, pd09, pd10, pd11]);
        // !!!COMPILER FENCE!!!
        core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
    }

    // // TIMER0 : 1kHz
    // // # ------------------------------------
    // // # interrupt frequency: f
    // // # prescaler: p
    // // # compare match register value: cmr
    // // # timers: t
    // // # ------------------------------------
    // // "f: 1000, p: 64, cmr: 249.0, t: [0, 2]"
    let timer0 = peripherals.TC0;
    timer0.tccr0a.write(|w| unsafe { w.bits(0) });
    timer0.tccr0b.write(|w| w.cs0().prescale_64());
    timer0.tcnt0.write(|w| unsafe { w.bits(0) });
    // Enable the timer interrupt
    timer0.timsk0.write(|w| w.ocie0a().set_bit());

    // TIMER1 : 1Hz
    // # ------------------------------------
    // # interrupt frequency: f
    // # prescaler: p
    // # compare match register value: cmr
    // # timers: t
    // # ------------------------------------
    // "f: 1, p: 256, cmr: 62499.0, t: [1]"
    let timer1 = peripherals.TC1;
    timer1.tccr1a.write(|w| unsafe { w.bits(0) });
    timer1.tccr1b.write(|w| w.cs1().prescale_256());
    timer1.ocr1a.write(|w| unsafe { w.bits(62499) });
    timer1.tcnt1.write(|w| unsafe { w.bits(0) });
    // Enable the timer interrupt
    timer1.timsk1.write(|w| w.ocie1a().set_bit());
}

#[avr_device::interrupt(atmega328p)]
fn TIMER0_COMPA() {
    let pins = unsafe {
        &mut *PINS.as_mut_ptr()
    };
    let state = interrupt::free(|cs| GLOBAL_STATE.get(cs));
    let strobe_idx = state.led_to_strobe();

    // set COM HIGH for the led to strobe
    let target_led_com = LED_ANODES[strobe_idx];
    for com in LED_ANODES.iter() {
        let xcom = *com - 2;
        if xcom == target_led_com {
            if pins[xcom].is_set_low().void_unwrap() {
                pins[xcom].set_high().void_unwrap();
            }
        } else {
            if pins[xcom].is_set_high().void_unwrap() {
                pins[xcom].set_low().void_unwrap();
            }
        }
    }

    // set the segments to high and low for this digit
    let digit = state.digits()[strobe_idx];
    let segments = cmap::segments(digit);
    for x in 0..8{
        let segment = bits::get(segments, x);
        let cathode = LEDS_COMMON_CATHODES[x];
        let xcat = cathode - 2;
        if segment && pins[xcat].is_set_low().void_unwrap(){
            pins[xcat].set_high().void_unwrap();
        }else if !segment && pins[xcat].is_set_high().void_unwrap() {
            pins[xcat].set_low().void_unwrap();
        }
    }
    let next_state = State { _led_to_strobe_index: state.increment_led_to_strobe(), ..state };
    interrupt::free(|cs| GLOBAL_STATE.update(next_state, cs));
}

#[avr_device::interrupt(atmega328p)]
fn TIMER1_COMPA() {
    let state = interrupt::free(|cs| GLOBAL_STATE.get(cs));
    let next_state = State { _display_counter: state.increment_counter(), ..state };
    interrupt::free(|cs| GLOBAL_STATE.update(next_state, cs));
    let led = unsafe {
        &mut *LED.as_mut_ptr()
    };
    led.toggle().void_unwrap();
}

bits.rs

///! TODO: should I put in bounds checking? Right now the index could
///! TODO: rollover if we leftshift more than the number bits in T.
///!
///! Notes/Links:
///! * https://immunant.com/blog/2020/01/bitfields/
///! * https://doc.rust-lang.org/reference/type-layout.html
///! * https://stackoverflow.com/questions/36061560/can-i-take-a-byte-array-and-deserialize-it-into-a-struct

/// Set a single bit in a bit array.
///
/// Examples:
///
/// ```
/// use mseg_lib::bits::set;
///
/// let b1 = set(0b00000000, 0, true);
/// assert_eq!(0b00000001, b1);
///
/// let b2 = set(b1, 7, true);
/// assert_eq!(0b10000001, b2);
///
/// let b3 = set(b2, 7, true);
/// assert_eq!(0b10000001, b3);
///
/// let b4 = set(b2, 7, false);
/// assert_eq!(0b00000001, b4);
/// ```
pub fn set(input: u8, index: usize, value: bool) -> u8 {
    let mask: u8 = 1 << index;
    return if value {
        input | mask
    } else {
        input & !(mask)
    };
}

/// Retrieve a single bit from a bit array.
///
/// Examples:
///
/// ```
/// use mseg_lib::bits::get;
///
/// let b1 = get(0b00000001, 0);
/// assert_eq!(b1, true);
///
/// let b2 = get(0b00000001, 1);
/// assert_eq!(b2, false);
/// ```
pub fn get(input: u8, index: usize) -> bool {
    let mask: u8 = 1 << index;
    let mut result = input & mask;
    result >>= index;
    return if result == 1 { true } else { false };
}

#[cfg(test)]
mod tests {
    #[test]
    /// set each bit on, one at a time, and verify that all the other bits are off.
    fn test_bits_set() {
        let b: u8 = 0b00000000;
        use super::*;

        for i in 0..8 {
            for j in 0..8 {
                let actual = get(set(b, i, true), j);
                if i == j {
                    assert_eq!(true, actual);
                } else {
                    assert_eq!(false, actual);
                }
            }
        }
    }
}

cmap.rs

///! `cmap` module handles character maps.
///!
///!
///! Generic 8 Segment LED
///!
///!  +--------------------------------------------------------------+
///! |                                                              |
///! |                                                              |
///! |                        A                                     |
///! |            |----------------------------|                    |
///! |            |----------------------------|                    |
///! |            +--+                      +--+                    |
///! |            |  |                      |  |                    |
///! |            |  |                      |  |                    |
///! |            |  |                      |  |                    |
///! |            |  |                      |  | B                  |
///! |      F     |  |                      |  |                    |
///! |            |  |                      |  |                    |
///! |            |  |                      |  |                    |
///! |            +--+          G           +--+                    |
///! |            |----------------------------|                    |
///! |            |----------------------------|                    |
///! |            +--+                      +--+                    |
///! |            |  |                      |  |                    |
///! |            |  |                      |  |                    |
///! |            |  |                      |  |                    |
///! |       E    |  |                      |  | C                  |
///! |            |  |                      |  |                    |
///! |            |  |                      |  |                    |
///! |            |  |                      |  |                    |
///! |            +--+                      +--+                    |
///! |            |----------------------------|      +-+           |
///! |            |----------------------------|      +-+   DP      |
///! |                        D                                     |
///! |                                                              |
///! +--------------------------------------------------------------+
///!

// segment indexes
pub const SEGMENT_INDEX_A: u8 = 0;
pub const SEGMENT_INDEX_B: u8 = 1;
pub const SEGMENT_INDEX_C: u8 = 2;
pub const SEGMENT_INDEX_D: u8 = 3;
pub const SEGMENT_INDEX_E: u8 = 4;
pub const SEGMENT_INDEX_F: u8 = 5;
pub const SEGMENT_INDEX_G: u8 = 6;
pub const SEGMENT_INDEX_DP: u8 = 7;


/// led segments
///
/// remember these are pulled in reverse order, so 
/// the mapping is:
///
/// DP G F E D C B A
pub fn segments(n: u8) -> u8 {
    return match n {
        0 => 0b00111111,
        1 => 0b00000110,
        2 => 0b01011011,
        3 => 0b01001111,
        4 => 0b01100110,
        5 => 0b01101101,
        6 => 0b01111101,
        7 => 0b00000111,
        8 => 0b01111111,
        9 => 0b01100111,
        _ => 0b11111111,
    }
}

led.rs

///! led module
///!
///! # Links:
///! * https://doc.rust-lang.org/book/ch10-02-traits.html
///! * https://doc.rust-lang.org/rust-by-example/trait.html
///! * https://doc.rust-lang.org/stable/rust-by-example/trait/impl_trait.html
///! * https://doc.rust-lang.org/std/keyword.impl.html

/// SM4105W6 Eight Segment LED
    /// https://www.velleman.eu/downloads/29/infosheets/vmp502_sma42056etc.pdf
    ///
    ///  Hardware Pins
    ///      A  B   C   D   E   F   G   DP  COM
    ///      7  6   4   2   1   9   10  5   3/8
    ///
    /// In the array, the pins are indexed as:
    ///      A  B   C   D   E   F   G   DP  COM
    ///      0  1   2   3   4   5   6   7   8
#[derive(Clone, Copy, Debug)]
pub struct EightSegmentLEDCommonAnode {
    // pins (0-7 are A-DP, 8 is com)
    pub pins: [usize; 9],
}

impl EightSegmentLEDCommonAnode {
    pub fn pins(&self) -> [usize;9] {
        self.pins
    }
    pub fn com(&self) -> usize {
        self.pins[8]
    }
}

Links

from avr-hal.

Rahix avatar Rahix commented on August 18, 2024

I spent 2 full days trying to get timers to work. For anyone following, let me just state that this will drive you insane. The timers are incredibly fragile. I have lost count of the ways that I have broken them... and debugging by just looking at a few blinking leds is pretty draining as well.

Oh, I know this all too well! Props to you for going this far though!

For anyone interested, I'll paste in my code but I'm not going to spend anymore time on this. Keeping the code in the main thread and just checking the timer values worked well enough.

Just a few comments about your code and regarding this: Doing all the work in ISRs is really not ideal. AVR does not have interrupt nesting so during an ISR absolutely nothing can happen in the system. Ideally, ISRs should be as short as possible and any work should happen in the main thread. This also helps avoid all the nasty synchronization issues ... A common pattern is a global flag marker that is set by the ISR and reset by the main thread:

(not tested)
use core::sync::atomic;

static TMR_OVERFLOW: atomic::AtomicBool = atomic::AtomicBool::new(false);

#[avr_device::interrupt(atmega328p)]
fn TIMER1_COMPA() {
    TMR_OVERFLOW.store(true, atomic::Ordering::SeqCst);
}

#[arduino_uno::entry]
fn main() -> ! {
    // ...

    loop {
        let tmr_overflow = avr_device::interrupt::free(|_cs| {
            // With interrupts disabled, check and reset the flag.  If interrupts
            // were enabled, the ISR could run in between the check and reset, leading to
            // us loosing a timer overflow.
            if TMR_OVERFLOW.load(atomic::Ordering::SeqCst) {
                TMR_OVERFLOW.store(false, atomic::Ordering::SeqCst);
                true
            } else {
                false
            }
        });

        if tmr_overflow {
            // the code you currently have in the ISR goes here.
        }
    }
}

from avr-hal.

ToddG avatar ToddG commented on August 18, 2024

Related question, in that using I2C is going to use interrupts under the covers (I think).

Q1: How to write the code for a slave that listens on an address?

Both the sample code and the I2C API docs seem to be from the perspective of the master:

For context, I am attempting to implement a simple master/slave example from here: http://gammon.com.au/i2c

from avr-hal.

Rahix avatar Rahix commented on August 18, 2024

Related question, in that using I2C is going to use interrupts under the covers (I think).

No, the I2C (also called TWI an AVR MCUs) master driver currently does not enable any interrupts at all. Slave operation should also be possible without interrupts. Though in practice, usually you'd want the core to go to sleep while waiting for a bus transaction, which of course will need to make use of an interrupt.

Both the sample code and the I2C API docs seem to be from the perspective of the master:

There is no I2C slave driver in avr-hal as of now. You'd need to manually do this with direct register accesses (or write your own I2C slave driver).

from avr-hal.

Rahix avatar Rahix commented on August 18, 2024

There is no I2C slave driver in avr-hal as of now. You'd need to manually do this with direct register accesses (or write your own I2C slave driver).

If you are really interested in this we could work together on adding an I2C slave driver to avr-hal though. Just let me know!

from avr-hal.

ToddG avatar ToddG commented on August 18, 2024

Absolutely, let's do this! Ok, so I'll start brainstorming:

  • Are there other platforms that we could look at and model after?
  • What sections of the AVR spec should I look at closely?
  • What's the overall game plan?

My sense of this is that the slave needs to:

  • set an interrupt on the SDA pin being pulled low
  • the interrupt wakes the CPU and sets a flag
  • main thread loop checks flag, if set, invokes reader function with user defined callback
  • callback is given an iterator over a stream

Conceptually, it looks simple enough when looking at Arduino code: Wire.onReceive (receiveEvent);

from avr-hal.

Rahix avatar Rahix commented on August 18, 2024

Right, let's move this to a separate issue though: #90

from avr-hal.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.