Rookie's question: How shall I handle ownership with this embedded code (singletons)?

Hello and my apologies for the bad summary. I didn't find a good way to summarize my problem, which is:

As a rust rookie, I'm trying to write some code for an attiny85, mainly using attiny_hal. I want to write some code for communication via RS485. For this, I need (at least) access to a timer, the serial interface and one pin that changes the direction of the communication (half duplex).

I get access to the peripherals like this:

    let peripherals = attiny_hal::Peripherals::take().unwrap();

    let timer0 = peripherals.TC0;
    let usi = peripherals.USI;
    let portb = peripherals.PORTB; // that's bad :(

    let mut uart = usiuart::new(timer0, usi, portb);


Then I initialise my struct and give it ownership of the peripherals. After all, timer0 and usi should only be handled by my communication code, but no one else:

pub struct UsiUart {
    timer0: TC0,  // Access to the timer 0 device
    usi: USI,     // Access to the Universal Serial Interface
    portb: PORTB, // TODO: I'm passing the whole PORTB while I only need ddrb because I don't know how to do only the latter.
    // more internal stuff

pub fn new(timer0: TC0, usi: USI, portb: PORTB) -> UsiUart {
    UsiUart {
        // more internal stuff

So I'm currently giving the whole port B to UsiUart which is of course complete nonsense: I only need control over a few pins (PB0, PB1 and PB3) and I need the other pins to access other hardware. Does it make sense to borrow PORTB / store a reference to it in UsiUart? How else can I handle it?

There are much more problems for me to solve, but this is the most imminent. I think, for example, that UsiUart should be a singleton itself since it doesn't make sense to have two of them (at least on the attiny85). But that's a different problem.

Thanks a lot for your help!

The Embedded Rust Book has some advice on using a singleton to access peripheral ports.

It might be useful to you.

Great :slight_smile: Thank you!

first of all, you should really use the abstract types (that's the whole point of use HAL) instead of concrete peripherals types from PAC. the PAC (peripheral access crate) is automatically generated from SVD data by manufacture, it's just register definitions, while the HAL provide meaningful operations for the device.

as for the port problem, just store individual pins you actually use, the types are provided by the HAL.

DISCLAIMER: I'm familar with arm cortex-m devices but I have not used attiny before, the following code is only for illustration purpose, the pin mapping is definitely incorrect, you should check the your hardware yourself. (the PinMode is probably wrong too, change to Input<Floating>/OpenDrain according to your hardware)

struct MyConfiguredPins {
    tx_pin: Pin<Output, PB0>,
    rx_pin: Pin<Input<Pullup>, PB1>,
    ctrl_pin: Pin<Output, PB2>,
let pins = Pin::new(portb);
let my_pins = MyConfiguredPins {
    tx_pin: pins.pb0.into_output(),
    rx_pin: pins.pb1.into_pullup_input(),
    ctrl_pin: pins.pb2.into_output(),

Thank you @nerditation! That makes a lot of sense. I'm not used to this HAL (and my last embedded experience is several years old), so I guess I missed a lot...
I tried to find an abstraction for the timer, external interrupts and the universal serial interface but didn't find anything. Did I again miss something? Or do I have to use the PAC for these peripherals?

again, I'm not familiar with avr, but typically two situations for lacking of certain hal types, either:

a) the pac doesn't have the register definitions, or:
b) the hal implementation is incomplete.

you can check the pac crate: if the register definitions do not exists, it's highly likely the chip doesn't have the specific peripherals, since the pac is usually generated from vendor specifications, (or, although less likely, the conversion tool could have failed to parse the vendor spec partially); otherwise, the hal is simply incomplete, and you'll have to work with the lower level pac directly, e.g. by implementing your own abstraction wrapper types.

I took a quick glimpse of the pac documents, it seems there are register block definitions for external interrupts and timer/counter 0 and timer/counter 1, but I don't see a uart specific type, altough there's a USI definition.

so I'm afraid the hal is incomplete in this case and I'm afraid you probably have to work with the low level register blocks instead. if you come up with useful abstraction types, maybe you could make a PR for the project.

just a reminder, I see there's an interrupt definition enum type in the pac, there's a chance the interrupt API is supported, which means you can write your interrupt handler in rust (no need for manual assembly shims), check the rt feature flag, there's a mention of avr-device at the top of the pac documentation, I assume that is the corresponding rt crate.

though you still need to go though the registers directly, as there's no interrupt controller in the hal crate.