Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
dbe4aab
finished first interrupt example
BartMassey Oct 26, 2024
cf4c921
removed unused interrupt example
BartMassey Oct 26, 2024
109eedd
added second example; needs debugging
BartMassey Oct 26, 2024
259531b
added forgotten interrupt example
BartMassey Oct 26, 2024
daff3e0
added first-draft text of interrupt chapter
BartMassey Oct 26, 2024
5f74b91
finished initial edit of interrupts, added subsections
BartMassey Oct 26, 2024
529ace1
cleaned up pokier.rs
BartMassey Oct 26, 2024
977394b
fixed some text in interrupts
BartMassey Oct 28, 2024
5035fc4
added more content to sharing-data-with-globals
BartMassey Oct 28, 2024
8924ab6
added more content to interrupts chapter
BartMassey Nov 8, 2024
b9919d4
added count.rs
BartMassey Nov 13, 2024
3848fb3
finished interrupt count example
BartMassey Nov 16, 2024
0fc56a7
finished sharing data with globals subsection
BartMassey Nov 16, 2024
78df7df
started on interrupt button debounce stuff
BartMassey Nov 18, 2024
9046efc
started on interrupts and debouncing
BartMassey Nov 19, 2024
3062d99
polling chapter initial draft
guptaarnav Mar 26, 2025
fa44239
small change to volatile reads in polling chapter
guptaarnav Mar 26, 2025
06a44bc
small polling ch changes
guptaarnav Mar 26, 2025
42daee0
editing ch15 summary
guptaarnav Mar 26, 2025
fa8dac3
starting ch15 interrupts
guptaarnav Mar 26, 2025
56fbdef
trying again
guptaarnav Mar 26, 2025
0ddf745
finished draft polling chapter
guptaarnav Mar 29, 2025
1d9385e
wrapping up interrupt draft
guptaarnav Mar 29, 2025
056a56b
cleaned up 08 polling-led-toggle.rs
BartMassey Mar 30, 2025
747fed6
added 08 blink-held example
BartMassey Mar 30, 2025
8fb5b33
cleaned up issues in 08 code
BartMassey Mar 30, 2025
ae07805
made slight change to 15 waiting section
BartMassey Mar 30, 2025
3ad6505
removed last trace of 15 concurrency
BartMassey Mar 30, 2025
750db85
fixed chapter numbering and linking issues
BartMassey Mar 31, 2025
4638f49
fixed chapter number in reference, again
BartMassey Jul 19, 2025
09da4f1
added speaker section to interrupt chapter
BartMassey Jul 19, 2025
b7737a3
added interrupt challenge problem
BartMassey Jul 20, 2025
0306bc9
added PWM addendum to interrupts
BartMassey Jul 20, 2025
c5dd30f
Update mdbook/src/08-inputs-and-outputs/polling.md
BartMassey Jul 22, 2025
5134ee3
Update mdbook/src/15-interrupts/README.md
BartMassey Jul 22, 2025
90a7fcf
rolled up changes suggested by review of PR #56
BartMassey Jul 29, 2025
2208e10
made a final editing pass; believe ready to publish
BartMassey Jul 29, 2025
f5d30f5
fixed a few more reported mistakes in interrupts chapter
BartMassey Jul 31, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ members = [
"mdbook/src/03-setup",
"mdbook/src/05-meet-your-software",
"mdbook/src/06-hello-world",
"mdbook/src/07-registers",
"mdbook/src/08-led-roulette",
"mdbook/src/10-uart",
"mdbook/src/11-i2c",
"mdbook/src/12-led-compass",
"mdbook/src/13-punch-o-meter",
"mdbook/src/14-snake-game",
"mdbook/src/07-led-roulette",
"mdbook/src/08-inputs-and-outputs",
"mdbook/src/09-registers",
"mdbook/src/11-uart",
"mdbook/src/12-i2c",
"mdbook/src/13-led-compass",
"mdbook/src/14-punch-o-meter",
"mdbook/src/15-interrupts",
"mdbook/src/16-snake-game",
"mdbook/src/appendix/3-mag-calibration",
"mdbook/src/serial-setup",
]
Expand Down
1 change: 0 additions & 1 deletion mdbook/src/05-meet-your-software/examples/delay-print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#![no_std]

use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use microbit::board::Board;
use microbit::hal::timer::Timer;
use panic_rtt_target as _;
Expand Down
15 changes: 15 additions & 0 deletions mdbook/src/08-inputs-and-outputs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "inputs-and-polling"
version = "0.1.0"
edition = "2021"

[dependencies]
microbit-v2 = "0.15.0"
cortex-m-rt = "0.7.3"
rtt-target = "0.5.0"
panic-rtt-target = "0.1.3"
embedded-hal = "1.0.0"

[dependencies.cortex-m]
version = "0.7"
features = ["critical-section-single-core"]
17 changes: 17 additions & 0 deletions mdbook/src/08-inputs-and-outputs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Inputs and Polling

In earlier chapters, we’ve explored GPIO pins primarily as outputs—driving LEDs on and off. However, GPIO pins can also be configured as inputs, allowing your program to read signals from the physical world, like button presses or switch toggles. In this chapter, we'll learn how to read these input signals and do something useful with them.

## Reading Button State

The micro:bit v2 has two physical buttons, Button A and Button B, connected to GPIO pins configured as inputs. Specifically, Button A is connected to pin P0.14, and Button B to pin P0.23. (You can verify this from the official [pinmap table].)

[pinmap table]: https://tech.microbit.org/hardware/schematic/#v2-pinmap

Reading the state of a GPIO input involves checking whether the voltage level at the pin is high (3.3V, logic level 1) or low (0V, logic level 0). Each button on the micro:bit is connected to a pin. When the button is *not* pressed, that pin is held high; when the button is pressed, the pin is held low.

Let's now apply this knowledge to reading the state of Button A by checking if the button is "low" (pressed).

```rust
{{#include examples/button-a-bsp.rs}}
```
94 changes: 94 additions & 0 deletions mdbook/src/08-inputs-and-outputs/examples/blink-held.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#![no_main]
#![no_std]

use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use embedded_hal::digital::{InputPin, OutputPin};
use microbit::hal::timer::Timer;
use microbit::{hal::gpio, Board};
use panic_rtt_target as _;
use rtt_target::rtt_init_print;

const ON_TICKS: u16 = 25;
const OFF_TICKS: u16 = 75;

#[derive(Clone, Copy)]
enum Light {
Lit(u16),
Unlit(u16),
}

impl Light {
fn flip(self) -> Self {
match self {
Light::Lit(_) => Light::Unlit(OFF_TICKS),
Light::Unlit(_) => Light::Lit(ON_TICKS),
}
}

fn tick_down(self) -> Self {
match self {
Light::Lit(ticks) => Light::Lit(ticks.max(1) - 1),
Light::Unlit(ticks) => Light::Unlit(ticks.max(1) - 1),
}
}
}

#[derive(Clone, Copy)]
enum Indicator {
Off,
Blinking(Light),
}

#[entry]
fn main() -> ! {
rtt_init_print!();
let board = Board::take().unwrap();
let mut timer = Timer::new(board.TIMER0);

// Configure buttons
let mut button_a = board.buttons.button_a;

// Configure LED (top-left LED at row1, col1)
let mut row1 = board
.display_pins
.row1
.into_push_pull_output(gpio::Level::Low);
let _col1 = board
.display_pins
.col1
.into_push_pull_output(gpio::Level::Low);

let mut state = Indicator::Off;
loop {
let button_pressed = button_a.is_low().unwrap();
match (button_pressed, state) {
// Turn indicator off when no button.
(false, _) => {
row1.set_low().unwrap();
state = Indicator::Off;
}
//
(true, Indicator::Off) => {
row1.set_high().unwrap();
state = Indicator::Blinking(Light::Lit(ON_TICKS));
}
(true, Indicator::Blinking(light)) => {
match light {
Light::Lit(0) | Light::Unlit(0) => {
let light = light.flip();
match light {
Light::Lit(_) => row1.set_high().unwrap(),
Light::Unlit(_) => row1.set_low().unwrap(),
}
state = Indicator::Blinking(light);
}
Light::Lit(_) | Light::Unlit(_) => {
state = Indicator::Blinking(light.tick_down());
}
}
}
}
timer.delay_ms(10_u32);
}
}
24 changes: 24 additions & 0 deletions mdbook/src/08-inputs-and-outputs/examples/button-a-bsp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#![no_main]
#![no_std]

use cortex_m_rt::entry;
use embedded_hal::digital::InputPin;
use microbit::Board;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};

#[entry]
fn main() -> ! {
rtt_init_print!();
let board = Board::take().unwrap();

let mut button_a = board.buttons.button_a;

loop {
if button_a.is_low().unwrap() {
rprintln!("Button A pressed");
} else {
rprintln!("Button A not pressed");
}
}
}
47 changes: 47 additions & 0 deletions mdbook/src/08-inputs-and-outputs/examples/polling-led-toggle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#![no_main]
#![no_std]

use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use embedded_hal::digital::{InputPin, OutputPin};
use microbit::hal::timer::Timer;
use microbit::{hal::gpio, Board};
use panic_rtt_target as _;
use rtt_target::rtt_init_print;

#[entry]
fn main() -> ! {
rtt_init_print!();
let board = Board::take().unwrap();
let mut timer = Timer::new(board.TIMER0);

// Configure buttons
let mut button_a = board.buttons.button_a;
let mut button_b = board.buttons.button_b;

// Configure LED (top-left LED at row1, col1)
let mut row1 = board
.display_pins
.row1
.into_push_pull_output(gpio::Level::Low);
let _col1 = board
.display_pins
.col1
.into_push_pull_output(gpio::Level::Low);

loop {
let on_pressed = button_a.is_low().unwrap();
let off_pressed = button_b.is_low().unwrap();
match (on_pressed, off_pressed) {
// Stay in current state until something is pressed.
(false, false) => (),
// Change to on state.
(false, true) => row1.set_high().unwrap(),
// Change to off state.
(true, false) => row1.set_low().unwrap(),
// Stay in current state until something is released.
(true, true) => (),
}
timer.delay_ms(10_u32);
}
}
7 changes: 7 additions & 0 deletions mdbook/src/08-inputs-and-outputs/my-solution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# My solution

Here's my solution (in `src/main.rs`). Hopefully that was pretty easy. You'll soon see that simple polling like this is not very practical.

```rust
{{#include src/main.rs}}
```
84 changes: 84 additions & 0 deletions mdbook/src/08-inputs-and-outputs/polling-sucks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Polling sucks, actually

Oh yeah, turn signals usually blink, right? How could we extend our program to blink the turn signal LED when a button is pressed. We know how to blink an LED from our Hello World program; we turn on the LED, wait for some time, and then turn it off. But how can we do this in our main loop while also checking for button presses? We could try something like this:

```rust
loop {
if button_a.is_low().unwrap() {
// Blink left arrow
display.show(&LEFT_ARROW);
timer.delay_ms(500_u32);
display.show(&BLANK);
timer.delay_ms(500_u32);
} else if button_b.is_low().unwrap() {
// Blink right arrow
display.show(&RIGHT_ARROW);
timer.delay_ms(500_u32);
display.show(&BLANK);
timer.delay_ms(500_u32);
} else {
display.show(&BLANK);
}
timer.delay_ms(10_u32);
}
```

Can you see the problem? We're trying to do two things at once here:

1. Check for button presses
2. Blink the LED

But the processor can only do one thing at a time. If we press a button during the blink delay, the processor won't be able to respond until the delay is over and the loop starts again. As a result, we get a barely-responsive program (try for yourself and see how slow the button is).

A "smarter" program would know that the processor isn't actually doing anything while the blink delay is running. The program could very well do other things while waiting for the delay to finish — namely, checking for button presses.

## Superloops

The term *superloop* in embedded systems is used to refer to a main control loop that does a bunch of things in sequence. It's the natural extension of the simple control flow we've been using so far. To handle logic that could be perceived as mutliple things happening at once, we need to be a bit more clever in how we structure the program so that we can be reasonably responsive to events.

In the case of our turn signal program, where we want to blink the LEDs when a button is pressed, and be quick to stop blinking when the button is released, we can create a "state machine" to represent the various states of the program. We have three states for the buttons:

1. No button is pressed
2. Button A is pressed
3. Button B is pressed

We also have three states for the display:

1. No LEDs are on
2. We are in the active blink state for the display (the LEDs are on)
3. We are in the inactive blink state for the display (the LEDs are off and waiting to be turned on once the blinking period is over)

Since we need to ensure responsiveness, we have to combine these different states. To fully represent all states of our program, we would have the following:

1. No button is pressed
2. Button A is pressed, and we are in the active blink state (the left arrow is showing on the display)
3. Button A is pressed, and we are in the inactive blink state (nothing is showing on the display)
4. Button B is pressed, and we are in the active blink state (the right arrow is showing on the display)
5. Button B is pressed, and we are in the inactive blink state (nothing is showing on the display)

When either button is first pressed, and we transition from state (1) to either state (2) or (4), we will initialize a timer counter that counts up starting from the moment a button is pressed. When the timer reaches some threshold amount (like half a second) and the buttons are still pressed, we will then transition to state (3) or (5), respectively, and reinitialize the timer counter. When the timer again reaches some threshold amount, we will transition back to state (2) or (4), respectively. If at any time during states (2), (3), (4), or (5) we see that the button is no longer pressed, we transition back to state (1).

Our main superloop control flow will repeatedly poll the buttons, compare our current timer counter (if we have one) to a threshold, and change states if any of the above conditions are met.

We have implemented this superloop as a demonstration (`examples/blink-held.rs`), but with the state machine simplified only to blink an LED when button A is held.

```rust
{{#include examples/blink-held.rs}}
```

This is still a bit complex. The 10ms loop delay is more
than adequate to catch button changes.

Superloops work and are often used in embedded systems, but the programmer has to be careful to maintain a high degree of responsiveness to events. Note how our superloop program is different from the previous simple polling example. Any state transition step in the superloop as written above should take a fairly small amount of time (e.g. we no longer have delays that could block the processor for long periods of time and cause us to miss any events). It's not always easy to transform a simple polling program into a superloop where all state transitions are quick and relatively non-blocking, and in these cases, we will have the rely on alternative techniques for handling the different events being executed at the same time.

## Concurrency

Doing multiple things at once is called *concurrent* programming. Concurrency shows up in many places in programming, but especially in embedded systems. There's a whole host of techniques for implementing systems that interact with peripherals while maintaining a high degree of responsiveness (e.g. interrupt handling, cooperative multitasking, event queues, etc.). We'll explore some of these in later chapters.

There is a good introduction to concurrency in an embedded context [here] that
you might read through before proceeding.

[here]: https://docs.rust-embedded.org/book/concurrency/index.html


For now, let's take a deeper look into what's happening when we call `button_a.is_low()` or `display_pins.row1.set_high()`.
14 changes: 14 additions & 0 deletions mdbook/src/08-inputs-and-outputs/polling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Polling

Now that we've learned how to read GPIO inputs, let's consider how we might use these reads practically. Suppose we want our program to turn on an LED when Button A is pressed and turn it off when Button B is pressed. We can do this by polling the state of both buttons in a loop, and responding accordingly when a button is read to be pressed. Here's how we might write this program:

```rust
{{#include examples/polling-led-toggle.rs}}
```

This method of repeatedly checking inputs in a loop is called polling. When we check the state of some input, we say we are *polling* that input. In this case, we are polling both Button A and Button B.

Polling is simple but allows us to do interesting things based on the external world. For all of our device's inputs, we can "poll" them in a loop, and respond to the results in some way, one by one. This kind of method is very conceptually simple and is a good starting point for many projects. We'll soon find out why polling might not be the best method for all (or even most) cases, but let's try it out first.

**Note** "Polling" is often used on two levels of granularity. At one level, "polling" is used to refer to asking (once) what the state of an input is. At a higher level, "polling", or perhaps "polling in a loop", is used to refer to asking (repeatedly) what the state of an input is in a simple control flow like the one we used above. This kind of use of the word to refer to a control flow is used only in the simplest of programs, and seldom used in production (it's not practical as we'll soon see), so generally when embedded engineers talk about polling, they mean the former, i.e. to ask (once) what the state of an input is.

54 changes: 54 additions & 0 deletions mdbook/src/08-inputs-and-outputs/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#![no_main]
#![no_std]

use cortex_m_rt::entry;
use embedded_hal::digital::InputPin;
use microbit::{board::Board, display::blocking::Display, hal::Timer};
use panic_rtt_target as _;
use rtt_target::rtt_init_print;

// Define LED patterns
const LEFT_ARROW: [[u8; 5]; 5] = [
[0, 0, 1, 0, 0],
[0, 1, 0, 0, 0],
[1, 1, 1, 1, 1],
[0, 1, 0, 0, 0],
[0, 0, 1, 0, 0],
];

const RIGHT_ARROW: [[u8; 5]; 5] = [
[0, 0, 1, 0, 0],
[0, 0, 0, 1, 0],
[1, 1, 1, 1, 1],
[0, 0, 0, 1, 0],
[0, 0, 1, 0, 0],
];

const CENTER_LED: [[u8; 5]; 5] = [
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
];

#[entry]
fn main() -> ! {
rtt_init_print!();
let board = Board::take().unwrap();
let mut timer = Timer::new(board.TIMER0);

let mut display = Display::new(board.display_pins);
let mut button_a = board.buttons.button_a;
let mut button_b = board.buttons.button_b;

loop {
if button_a.is_low().unwrap() {
display.show(&mut timer, LEFT_ARROW, 10);
} else if button_b.is_low().unwrap() {
display.show(&mut timer, RIGHT_ARROW, 10);
} else {
display.show(&mut timer, CENTER_LED, 10);
}
}
}
Loading