Skip to content

Commit 90a7fcf

Browse files
committed
rolled up changes suggested by review of PR #56
1 parent 5134ee3 commit 90a7fcf

File tree

12 files changed

+282
-324
lines changed

12 files changed

+282
-324
lines changed

mdbook/src/08-inputs-and-outputs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ The micro:bit v2 has two physical buttons, Button A and Button B, connected to G
88

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

11-
Reading the state of a GPIO input involves checking whether the voltage level at the pin is high (1) or low (0). The buttons on the micro:bit are connected to pins; when the buttons are pressed, they pull the voltage at the pin low (to 0V ground).
11+
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.
1212

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

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

Lines changed: 16 additions & 5 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 worse it 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 much less responsive program (try for yourself and see how much worse the interrupt latency 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, so it could very well do other things while waiting for the delay to finish, namely checking for button presses.
3434

3535
## Superloops
3636

@@ -56,11 +56,22 @@ Since we need to ensure responsiveness, we have to combine these different state
5656
4. Button B is pressed, and we are in the active blink state (the right arrow is showing on the display)
5757
5. Button B is pressed, and we are in the inactive blink state (nothing is showing on the display)
5858

59-
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).
59+
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, and compare our current timer counter (if we have one) to a threshold, and transition states if any of the above conditions are met.
6262

63-
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.
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.
66+
67+
```rust
68+
{{#include examples/blink-held.rs}}
69+
```
70+
71+
This is still a bit complex. The 10ms loop delay is more
72+
than adequate to catch button changes.
73+
74+
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.
6475

6576
## Concurrency
6677

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ This method of repeatedly checking inputs in a loop is called polling. When we
1010

1111
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.
1212

13-
>> **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.
13+
**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.
14+

mdbook/src/15-interrupts/README.md

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

3-
So far, we've gone though a fair bunch of topics about embedded software. We've read out buttons,
4-
waited for timers, done serial communication, and talked to other things on the Microbit board using
5-
I2C. Each of these things involved waiting for one or more peripherals to become ready. So far, our
6-
waiting was by "polling": repeatedly asking the peripheral if it's done yet, until it is.
7-
8-
Seeing as our microcontroller only has a single CPU core, it cannot do anything else while it
9-
waits. On top of that, a CPU core continuously polling a peripheral wastes power, and in a lot of
10-
applications, we can't have that. Can we do better?
11-
12-
Luckily, we can. While our little microcontroller can't compute things in parallel, it can easily
13-
switch between different tasks during execution, responding to events from the outside world. This
14-
switching is done using a feature called "interrupts"!
15-
16-
Interrupts are aptly named: they allow peripherals to actually interrupt the core program execution
17-
at any point in time. On our MB2's nRF52833, peripherals are connected to the core's Nested Vectored
18-
Interrupt Controller (NVIC). The NVIC can stop the CPU in its tracks, instruct it to go do something
19-
else, and once that's done, get the CPU back to what it was doing before it was interrupted. We'll
20-
cover the Nested and Vectored parts of the interrupt controller later: let's first focus on how the
21-
core switches tasks.
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.
4+
5+
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?
6+
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"!
8+
9+
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.
2210

2311
### Handling Interrupts
2412

25-
Computation is always contextual: the core always needs memory to load inputs and store outputs to.
26-
Our microcontroller is of what's known as a load-store-architecture, and as such the core does not
27-
store and load it's computation parameters and results in RAM directly. Instead, our core has
28-
access to a small amount scratch pad memory: the CPU registers. Confusingly, these CPU registers
29-
are different from the device registers we discussed earlier in the [Registers] chapter.
13+
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".)
3014

31-
As far as the core is concerned, all context about the computation that it is doing is stored in the
32-
CPU registers. If the core is going to switch tasks, it must store the contents of the CPU registers
33-
somewhere, so that the new task can use them as their own scratchpad memory. Sure enough, that is
34-
exactly the first thing the core does in response to an interrupt request: it stops what it's doing
35-
immediately and stores the contents of the CPU registers on the stack.
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.
3616

37-
The next step is actually jumping to the code that should be run in response to an interrupt.
38-
Interrupt Service Routines (ISRs), often referred to as interrupt handlers, are special functions in
39-
your application code that get called by the core in response to specific interrupts. An ISR
40-
function "returns" using a special return-from-interrupt machine instruction that causes the CPU to
41-
restore the CPU registers and jump back to where it was before the ISR was called.
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.
18+
19+
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.
4220

4321
## Poke The MB2
4422

@@ -49,35 +27,40 @@ Let's define an ISR and configure an interrupt to "poke" the MB2 when Button A i
4927
{{#include examples/poke.rs}}
5028
```
5129

52-
The ISR handler function is "special". The name `GPIOTE` is required here, and the function must be
53-
decorated with `#[interrupt]` so that it returns using a return-from-interrupt instruction rather
54-
than the normal way. The function may not take arguments and must return `()`.
30+
The ISR handler function is "special". The name `GPIOTE` is required here, indicating
31+
that this ISR should be stored at the entry for the `GPIOTE` interrupt in the interrupt table.
32+
33+
The `#[interrupt]` decoration is used at compile time to mark a function to be treated specially as
34+
an ISR. (This is a "proc macro", in case you feel like exploring that concept.)
35+
36+
Marking a function with `#[interrupt]` implies several special things about the function:
37+
38+
* The compiler will check that the function takes no arguments and returns no value. The CPU has no
39+
arguments to provide to an ISR, and no place to put a return value from the ISR.
40+
41+
* The compiler will place a vector to this function at the location in the interrupt table
42+
implied by the function's name.
43+
44+
* The function will be compiled to finishing by using a return-from-interrupt instruction rather
45+
than the normal function return instruction.
46+
47+
* Since the function finishes in a non-standard way, the compiler will understand not to allow
48+
directly calling the ISR from normal code.
5549

5650
There are two steps to configure the interrupt. First, the GPIOTE must be set up to generate an
5751
interrupt when the pin connected to Button A goes from high to low voltage. Second, the NVIC must be
5852
configured to allow the interrupt. Order matters a bit: doing things in the "wrong" order may
59-
generate a bogus interrupt before you are ready to handle it.
53+
generate an interrupt before you are ready to handle it.
54+
55+
**Note** As with most microcontrollers, there is a lot of flexibility in when the GPIOTE can generate an interrupt. Interrupts can be generated on low-to-high pin transition, high-to-low (as here), any change ("edge"), when low, or when high. On the nRF52833, interrupts generate an event that must be manually cleared in the ISR to ensure that the ISR is not called a second time for the same interrupt. Other microcontrollers may work a little differently — you should read Rust crate and microcontroller documentation to understand the details on a different board.
6056

61-
When you push the A Button, you will see an "ouch" message and then a panic. Why does the interrupt
62-
handler call `panic!()`? Try commenting the `panic!()` call out and see what happens when you push
63-
the button. You will see "ouch" messages scroll off the screen. The GPIOTE records when an interrupt
64-
has been issued, and that record is kept until it is explicitly cleared by the running
65-
program. Without the `panic!()`, when the interrupt handler returns the GPIOTE will re-enable the
66-
interrupt, notice that an interrupt has been issued and not cleared, and run the handler again. This
67-
will continue forever: each time the interrupt handler returns it will be called again. In the next
68-
section we will see how to clear the interrupt indication from within the interrupt handler.
57+
When you push the A Button, you will see an "ouch" message and then a panic. Why does the interrupt handler call `panic!()`? Try commenting the `panic!()` call out and see what happens when you push the button. You will see "ouch" messages scroll off the screen. The NVIC records when an interrupt has been issued: that "event" is kept until it is explicitly cleared by the running program. Without the `panic!()`, when the interrupt handler returns the NVIC will (in this case) re-enable the interrupt, notice that there is still an interrupt event pending, and run the handler again. This will continue forever: each time the interrupt handler returns it will be called again. In the next section we will see how to clear the interrupt indication from within the interrupt handler.
6958

7059
You may define ISRs for many different interrupt sources: when I2C is ready, when a timer expires,
7160
and on and on. Inside an ISR you can do pretty much anything you want, but it's good practice to
7261
keep the interrupt handlers short and quick.
7362

74-
When the ISR function returns (using a magic instruction), the CPU looks to see if interrupts have
75-
happened that need to be handled, and if so calls one of the handlers (according to a priority order
76-
set by the NVIC). Otherwise, the CPU restores the CPU registers and returns to the running program
77-
as if nothing has happened.
78-
79-
But if the core just goes on with its life after handling an interrupt, how does your device know
80-
that it happened? Seeing as an ISR doesn't have any input parameters or result, how can ISR code
81-
interact with application code?
63+
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?
8264

83-
[Registers]: https://docs.rust-embedded.org/discovery-mb2/07-registers
65+
[NVIC and Interrupt Priority]: nvic-and-interrupt-priority.html
66+
[Registers]: ../09-registers/

0 commit comments

Comments
 (0)