Skip to content

Commit 2208e10

Browse files
committed
made a final editing pass; believe ready to publish
1 parent 90a7fcf commit 2208e10

File tree

8 files changed

+37
-48
lines changed

8 files changed

+37
-48
lines changed

mdbook/src/08-inputs-and-outputs/polling-sucks.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ Can you see the problem? We're trying to do two things at once here:
2828
1. Check for button presses
2929
2. Blink the LED
3030

31-
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 much less responsive program (try for yourself and see how much worse the interrupt latency is).
31+
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).
3232

33-
A "smarter" program would know that the processor isn't actually doing anything while the blink delay is running, so it could very well do other things while waiting for the delay to finish, namely checking for button presses.
33+
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 finishnamely, checking for button presses.
3434

3535
## Superloops
3636

@@ -58,11 +58,9 @@ Since we need to ensure responsiveness, we have to combine these different state
5858

5959
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).
6060

61-
Our main superloop control flow will repeatedly poll the buttons, and compare our current timer counter (if we have one) to a threshold, and transition states if any of the above conditions are met.
61+
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.
6262

63-
We have implemented this superloop as a demonstration
64-
(`examples/blink-held.rs`), but with the state machine
65-
simplified only to blink an LED when button A is held.
63+
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.
6664

6765
```rust
6866
{{#include examples/blink-held.rs}}
@@ -75,7 +73,7 @@ Superloops work and are often used in embedded systems, but the programmer has t
7573

7674
## Concurrency
7775

78-
Doing multiple things at once is called *concurrent* programming, and shows up in many places in programming, but especially in embedded systems. There's a whole host of techniques for implementing systems that concurrently 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.
76+
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.
7977

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

mdbook/src/08-inputs-and-outputs/the-challenge.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ You'll need to:
1212
- Continuously poll Button A and Button B.
1313
- Update the LED display according to the button state with a clear indication of each state (left, right, or neutral).
1414

15-
I hope you don't mess up, it's so hard to share the road with people who don't use their turn signals properly.
15+
I hope you don't mess up! It's *so* hard to share the road with people who don't use their turn signals properly.

mdbook/src/15-interrupts/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
## Interrupts
22

3-
So far, we've gone though a fair bunch of topics about embedded software. We've read out buttons, waited for timers, done serial communication, and talked to other things on the Microbit board using I2C. Each of these things involved waiting for one or more peripherals to become ready. So far, our waiting was by "polling": repeatedly asking the peripheral if it's done yet, until it is.
3+
So far, we've touched a bunch of hardware on the MB2. We've read out buttons, waited for timers, done serial communication, and talked to devices using I2C. Each of these things involved waiting for one or more peripherals to become ready. So far, our waiting was by "polling": repeatedly asking the peripheral if it's done yet, until it is.
44

55
Seeing as our microcontroller only has a single CPU core, it cannot do anything else while it waits. On top of that, a CPU core continuously polling a peripheral wastes power, and in a lot of applications, we can't have that. Can we do better?
66

7-
Luckily, we can. While our little microcontroller can't compute things in parallel, it can easily switch between different tasks during execution, responding to events from the outside world. This switching is done using a feature called "interrupts"!
7+
Luckily, we can! While our little microcontroller can't compute things in parallel, it can easily switch between different tasks during execution, responding to events from the outside world. This switching is done using a feature called "interrupts".
88

99
Interrupts are aptly named: they allow peripherals to actually interrupt the core program execution at any point in time. On our MB2's nRF52833, peripherals are connected to the core's Nested Vectored Interrupt Controller (NVIC). The NVIC can stop the CPU in its tracks, instruct it to go do something else, and once that's done, get the CPU back to what it was doing before it was interrupted. We'll cover the Nested and Vectored parts of the interrupt controller later: let's first focus on how the core switches tasks.
1010

1111
### Handling Interrupts
1212

1313
The model of computation used by our NRF52833 is the one used by almost every modern CPU. Inside the CPU are "scratch-pad" storage locations known as "CPU registers". (Confusingly, these CPU registers are different from the "device registers" we discussed earlier in the [Registers] chapter.) To carry out a computation, the CPU typically loads values from memory to CPU registers, performs the computation using the register values, then stores the result back to memory. (This is known as a "load-store architecture".)
1414

15-
All context about the computation the CPU is currently running is stored in the CPU registers. If the core is going to switch tasks, it must store the contents of the CPU registers somewhere so that the new task can use the registers as its own scratch-pad. When the new task is complete the CPU can then restore the register values and restart the old computation. Sure enough, that is exactly the first thing the core does in response to an interrupt request: it stops what it's doing immediately and stores the contents of the CPU registers on the stack.
15+
Everything about the computation the CPU is currently running is stored in the CPU registers. If the core is going to switch tasks, it must store the contents of the CPU registers somewhere so that the new task can use the registers as its own scratch-pad. When the new task is complete the CPU can then restore the register values and restart the old computation. Sure enough, that is exactly the first thing the core does in response to an interrupt request: it stops what it's doing immediately and stores the contents of the CPU registers on the stack.
1616

17-
The next step is actually jumping to the code that should be run in response to an interrupt. An Interrupt Service Routines (ISR), often referred to as an interrupt "handler", is a special functions in your application code that gets called by the core in response to interrupts. An "interrupt table" in memory contains an "interrupt vector" for every possible interrupt: the interrupt vector indicates what ISR to call when a specific interrupt is received. We describe the details of ISR vectoring in the [NVIC and Interrupt Priority] section.
17+
The next step is actually jumping to the code that should be run in response to an interrupt. An Interrupt Service Routines (ISR), often referred to as an interrupt "handler", is a special function in your application code that gets called by the core in response to interrupts. An "interrupt table" in memory contains an "interrupt vector" for every possible interrupt: the interrupt vector indicates what ISR to call when a specific interrupt is received. We describe the details of ISR vectoring in the [NVIC and Interrupt Priority] section.
1818

1919
An ISR function "returns" using a special return-from-interrupt machine instruction that causes the CPU to restore the CPU registers and jump back to where it was before the ISR was called.
2020

@@ -63,4 +63,4 @@ keep the interrupt handlers short and quick.
6363
Normally, once an ISR is complete the main program continues running just as it would have if the interrupt had not happened. This is a bit of a problem, though: how does your application notice that the ISR has run and done things? Seeing as an ISR doesn't have any input parameters or result, how can ISR code interact with application code?
6464

6565
[NVIC and Interrupt Priority]: nvic-and-interrupt-priority.html
66-
[Registers]: ../09-registers/
66+
[Registers]: ../09-registers/index.html

mdbook/src/15-interrupts/debouncing.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ the main program.
1515

1616
The solution comes through another form of hardware concurrency: the `TIMER` peripheral we have used
1717
a bunch already. You can set the timer when a "good" button interrupt is received, and not respond
18-
to further interrupts until the timer peripheral has counted enough time off. The timers in
19-
`nrf-hal` come configured with a 32-bit count value and a "tick rate" of 1 MHz: a million ticks per
20-
second. For a 100ms debounce, just let the timer count off 100,000 ticks. Anytime the button
21-
interrupt handler sees that the timer is running, it can just do nothing.
18+
to further interrupts for that button until the timer peripheral has counted enough time off. The
19+
timers in `nrf-hal` come configured with a 32-bit count value and a "tick rate" of 1 MHz: a million
20+
ticks per second. For a 100ms debounce, just let the timer count off 100,000 ticks. Anytime the
21+
button interrupt handler sees that the timer is running, it can just do nothing.
2222

2323
The implementation of all this can be seen in the next example (`examples/count-debounce.rs`). When
2424
you run the example you should see one count per button press.
@@ -27,6 +27,6 @@ you run the example you should see one count per button press.
2727
{{#include examples/count-debounce.rs}}
2828
```
2929

30-
> **NOTE** The buttons on the MB2 are a little fiddly: it's pretty easy to push one down enough to
30+
**NOTE** The buttons on the MB2 are a little fiddly: it's pretty easy to push one down enough to
3131
feel a "click" but not enough to actually make contact with the switch. I recommend using a
3232
fingernail to press the button when testing.

mdbook/src/15-interrupts/nvic-and-interrupt-priority.md

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ each pin, or a UART having both a "data received" and "data finished transmissio
2222
job of the NVIC is to prioritize these interrupts, remember which ones still need to be processed,
2323
and then cause the processor to run the relevant interrupt handler code.
2424

25-
Depending on its configuration, the NVIC can ensure the current interrupt is fully processed before
26-
a new one is executed, or it can stop the processor in the middle of one interrupt in order to
27-
handle another that's higher priority. This is called "preemption" and allows processors to respond
28-
very quickly to critical events. For example, a robot controller might use low-priority interrupts
29-
to manage sending status information to the operator, but also have a high-priority interrupt when a
30-
sensor detects an imminent collision so that it can immediately stop moving the motors. You wouldn't
31-
want it to wait until it had finished sending a data packet to get around to stopping!
25+
### Interrupt Priorities
26+
27+
The NVIC has a settable "priority" for each interrupt. Depending on its configuration, the NVIC can ensure the current interrupt is fully processed before a new one is executed, or it can "preempt" the processor in the middle of one interrupt in order to handle another that's higher priority.
28+
29+
Preemption allows processors to respond very quickly to critical events. For example, a robot controller might use low-priority interrupts to manage sending status information to the operator, but also take a high-priority interrupt when a sensor detects an imminent collision so that it can immediately stop moving the motors. You wouldn't want the robot to wait until it had finished sending a data packet to get around to stopping!
30+
31+
If an equal-priority or lower-priority interrupt occurs during an ISR, it will be "pended": the NVIC will remember the new interrupt and run its ISR sometime after the current ISR completes. When an ISR function returns the NVIC looks to see if, while the ISR was running, other interrupts have happened that need to be handled. If so, the NVIC checks the interrupt table and calls the highest-priority ISR vectored there. Otherwise, the CPU returns to the running program.
3232

3333
In embedded Rust, we can program the NVIC using the [`cortex-m`] crate, which provides methods to
3434
enable and disable (called `unmask` and `mask`) interrupts, set interrupt priorities, and trigger
@@ -68,13 +68,6 @@ script to arrange for the address of that function to be placed in the right par
6868
For more details on how these interrupt handlers are managed in Rust, see the Exceptions and
6969
Interrupts chapters in the [Embedded Rust Book].
7070

71-
### Intertupt Priorities
72-
73-
The NVIC has a settable "priority" for each interrupt. If a higher-priority interrupt happens during the execution of an ISR, that ISR will be paused just as the main program was, and the higher-priority ISR will be run.
74-
75-
If an equal-priority or lower-priority interrupt occurs during an ISR, it will be "pended": the NVIC will remember the new interrupt and run its ISR sometime after the current ISR completes. Thus, when an ISR function returns, the NVIC looks to see if, while the ISR was running, other interrupts have happened that need to be handled. If so, the NVIC checks the interrupt table and calls one of the highest-priority ISRs vectored there. Otherwise, the CPU returns to the running program.
76-
77-
7871
[Architecture Reference Manual]: https://developer.arm.com/documentation/ddi0403/latest
7972
[`cortex-m-rt`]: https://docs.rs/cortex-m-rt
8073
[Embedded Rust Book]: https://docs.rust-embedded.org/book/

mdbook/src/15-interrupts/sharing-data-with-globals.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
## Sharing Data With Globals
22

3-
> **NOTE** This content is partially taken with permission from the blog post
3+
> **NOTE** This content is partially taken (with permission) from the blog post
44
> *[Interrupts Is Threads]* by James Munns, which contains more discussion about this
55
> topic.
66
77
As I mentioned in the last section, when an interrupt occurs we aren't passed any arguments and
88
cannot return any result. This makes it hard for our program interact with peripherals and other
9-
main program state. Before worrying about this
10-
bare-metal embedded problem, it is likely worth thinking about threads in "std" Rust.
9+
main program state. Before worrying about this bare-metal embedded problem, it is likely worth
10+
thinking about threads in "std" Rust.
1111

1212
### "std" Rust: Sharing Data With A Thread
1313

@@ -174,15 +174,15 @@ data could end up corrupted.
174174
In embedded Rust we care about the same things when it comes to sharing data with interrupt
175175
handlers! Similar to threads, interrupts can occur at any time, sort of like a thread waking up and
176176
accessing some shared data. This means that the data we share with an interrupt must live long
177-
enough, and we must be careful to ensure that our main code isn't in the middle of accessing some
178-
data shared with the interrupt, just to have the interrupt run and ALSO access that data!
177+
enough, and we must be careful to ensure that our main code isn't in the middle of working with some
178+
data shared with an ISR when that ISR gets run and *also* tries to work with the data!
179179

180180
In fact, in embedded Rust, we model interrupts in a similar way that we model threads in Rust: the
181181
same rules apply, for the same reasons. However, in embedded Rust, we have some crucial differences:
182182

183183
* Interrupts don't work exactly like threads: we set them up ahead of time, and they wait until some
184184
event happens (like a button being pressed, or a timer expiring). At that point they run, but
185-
without access to any context.
185+
without access to any passed-in context.
186186

187187
* Interrupts can be triggered multiple times, once for each time that the event occurs.
188188

@@ -275,8 +275,8 @@ main loop, just after the `wfi()` "wait for interrupt". The count will then be r
275275
an interrupt handler finishes (`examples/count-bounce.rs`). Again, the count is bumped up 1 on every
276276
push of the MB2 A button.
277277

278-
Maybe. Especially if your MB2 is old, you may see a single press bump the counter by several. *This
279-
is not a software bug.* Mostly. In the next section, I'll talk about what might be going on and how
280-
we should deal with it.
278+
Maybe. Especially if your MB2 is old (!), you may see a single press bump the counter by
279+
several. *This is not a software bug.* Mostly. In the next section, I'll talk about what might be
280+
going on and how we should deal with it.
281281

282282
[Interrupts Is Threads]: https://onevariable.com/blog/interrupts-is-threads

mdbook/src/15-interrupts/the-mb2-speaker.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ Let's push the speaker cone out and then in 220 times per second. This will prod
2020
220-cycles-per-second pressure wave. The unit "cycles-per-second" is Hertz; we will be producing a
2121
220Hz tone (a musical "A3"), which is not unpleasant on this shrill speaker.
2222

23-
We'll make our tone for five seconds and then stop. It is important to remember that our program
24-
lives in flash on the MB2 — if we let the tone run forever then it will start up again each time we
25-
reset or even power on the MB2. This can rapidly become quite annoying.
23+
We'll make our tone play for five seconds and then stop. It is important to remember that our
24+
program lives in flash on the MB2 — the tone will start up again each time we reset or even power on
25+
the MB2. If we let the tone run forever, this behavior can rapidly become quite annoying.
2626

2727
Here's the code (`examples/square-wave.rs`).
2828

0 commit comments

Comments
 (0)