Baby steps into abstraction in Rust

The aim of this tutorial is to introduce abstraction. Abstraction are used to hide some complexity and compartimentalize the writing of a program.

The example will be to toggle a led on and off at each press of a button.

While this example sound simple, it requires two functionalities to be added:

  • the led needs to remember its previous state to know what state to toggle to
  • the button read function neads to be secured against bouncing. Bouncing is when a button generates spurious open/close transitions when pressed, due to mechanical defaults.

Instead of adding these functionalities inside the bulk of the main loop, it is a much better practice to compartementalize each problem.

We will create a Led structure with the associated method and state memory for toggling. And a Button structure with the associated method for debouncing the state read.

Step 1: A look at the code

The program is called abstraction_led_button_toggle.rs and can be found in the examples folder at the root of the Luos repository repository.

// This example starts introducing abstractions
// We will use the button to toggle on/off the led
// This requires additional functionalities for both Led and Button
// The led need to remember its previous state
// And the button need to implement a debounced read method to avoid spurious on/off transitions when pressed, due to mechanical and physical issues
// For that we will create a Led and Button structure with specific method associated to them
//
// Board: STM32F072B-DISCO
// Tested on: 21/02/2018

#![no_std]

extern crate luos;
use luos::hal::{gpio, rcc};

// LED iplementation

// First, we need to create a structure to hold the necessary elements of our augmented Led
pub struct Led {
    pin: gpio::Output, // ouput pin to control the led on and off
    state: bool, // state variable to store the current state of the led (on -> true, off -> false)
}

// Second we associate some methods to this structure
impl Led {
    // Using the new function is a convention to make a sort of constructor
    // It is used to initialize the Led structure with the attached `Pin` and initialize its state.
    // The user needs to provide a GPIO pin and an init_state for that
    pub fn new(pin: gpio::Pin, init_state: bool) -> Led {
        // we create a led_pin variable as an Ouptut pin
        let mut led_pin = gpio::Output::setup(pin);
        // depending on the initial state requested, we set that pin high or low
        match init_state {
            true => led_pin.high(),
            false => led_pin.low(),
        }
        // finally we return a Led structure with those elements all set
        Led {
            pin: led_pin,
            state: init_state,
        }
    }

    // We now implement a toggle method associated to a Led structure
    // It takes as an input a reference of itself as per convention
    // Notice that we use &mut wich indicates that elements of the structure may be changed
    // Indeed the pin state and led state will be affected by toggle
    pub fn toggle(&mut self) {
        // we read the current stored state
        match self.state {
            true => self.pin.low(),   // if true/high -> toggle and set to low
            false => self.pin.high(), // if false/low -> toggle and set to high
        }
        // finally update the current state as the opposite of the previous state
        self.state = !self.state;
    }
}
// At this point we have a structure with associated methods that define an inhenced version of a Led
// This Led can remember its state and toggle from on to off appropriately
// We will see how to initialize this in the main function, but let's see the button before

// Button implementation

// First, we need to create a structure to hold the necessary elements of our augmented Button
pub struct Button {
    pin: gpio::Input,      // input pin to read the button state
    debouce_delay_ms: u32, // this is a fixed timing used to debounced the reading of the button state
}

// Second we associate some methods to this structure
impl Button {
    // We initialize the Button structure with the attached `Pin`, here a GPIO::Pin
    pub fn new(pin: gpio::Pin) -> Button {
        // we return a Button structure
        Button {
            pin: gpio::Input::setup(pin), // with pin holding a gpio input
            debouce_delay_ms: 10,         // and a fixed value for the delay
        }
    }

    // We now implement a debounce_read method associated to a Button structure
    // This debounce read is needed to avoid spurious on/off transitions when pressed due to mechanical issues
    // It leads the programs to read multiple presses in a very short time
    // The method to debounce is to read the value twice in a short period of time
    // If we read twice the same value, we assume thare is not bouncing effect
    // If we don't, then we repeat the process until we get a good reading

    // The debounce_read method does not need to use &mut because no modification to the structure elements is done
    // Note that the function return a bool, that is the state of the button
    pub fn debounce_read(&self) -> bool {
        // loop until we return a value
        loop {
            let first_read = self.pin.read(); // read button state
            rcc::ms_delay(self.debouce_delay_ms); // pause the program for self.debouce_delay_ms (here 10 ms)
            let second_read = self.pin.read(); // read button state again

            // if both reading agrees, we consider there was no bouncing and return the button state
            if first_read == second_read {
                return second_read;
            }
            // else we try again
        }
    }
}

// Let put it all together in the main
fn main() {
    // initialize rcc for the time related functionalities (rcc::ms_delay)
    rcc::init();

    // create a new Led on PC7 with a default state of false/off (the blue led)
    let mut led = Led::new(gpio::Pin::PC7, false);
    // create a new Button on PA0 (the blue user button)
    let button = Button::new(gpio::Pin::PA0);

    // forever
    loop {
        // read the debounced button state
        if button.debounce_read() {
            // toggle the led when the button is pressed
            led.toggle();
            // wait for the button to be released
            while button.debounce_read() {}
        }
    }
    // each time you press the button now, the led with toggle its state
    // this could be done without abstraction but would lead to a much more complex code in the main loop
}

// Is this example not working? have you seen a mistake or a typo?
// You can contribute by raising an issue of the problem
// Or directly submit a pull request to fix this code

You can see that we introduced the Led struture, which contains a pin and state element to keep the current state in memory. The Button structure and associated methods also enable to hide the complexity of the debouncing operation to the user when writing the main program.

Let's compile this program.

Note: To compile and upload the program to the board, we will follow the same steps as introduced in the hello world tutorial. For more details please refer to 01_your_first_program.md.

Step 2: Compiling the code

To compile abstraction_led_button_toggle.rs for the STM32F072B-DISCO board run:

xargo build --target thumbv6m-none-eabi --example abstraction_led_button_toggle

Tips: if you get problem compiling, you might have run into a Cargo.lock problem. Try running the following command:

rm -f Cargo.lock && cargo clean && touch Xargo.toml

This should clean all build history and compile everything from scratch again. We will explain more about this issue at a later time.

Step 3: Uploading and running the code

On a first terminal, run:

openocd -f $OPENCD_ROOT/interface/stlink-v2.cfg -f $OPENCD_ROOT/target/stm32f0x.cfg

On a second terminal, run:

arm-none-eabi-gdb target/thumbv6m-none-eabi/debug/examples/abstraction_led_button_toggle

Where target/thumbv6m-none-eabi/debug/examples/abstraction_led_button_toggle is the folder containing the compiled code to be uploaded to the board.

Note: This command as written assumes you are currently at luos root folder. Change the path argument accordingly if it's not the case.

To start executing the code on the board, simply type continue and press enter.

And voila, you can toggle the led state with each press of a button.

The debouncing algorithm is quite primitive and we encourage you to improve it to familiarize yourself with using abstraction

The idea of abstraction is very powerful. The Rust programming language has abstraction mechanisms perfectly suited for robotics that we will present. More specificially, we will introduce Luos own librabry of drivers.


Troubleshooting

If you encountered any issues following these steps, please let us know.

results matching ""

    No results matching ""