Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 12 additions & 11 deletions mdbook/src/15-interrupts/README.md
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should explain further about proc macros here; it's a complicated concept in its own right. Maybe link to the Book of Macros?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, I think maybe link to the Macros section in the Rust Book (which then itself links to the Book of Macros)?

From skimming these just now, while the Book of Macros mentions ways to expand macros (like the Playground option), they don't mention the rust-analyzer command.

I think maybe it is worth mentioning briefly how to expand macros, so that someone reading this can get a quick understanding of roughly what the interrupts macro does without having to learn Macro syntax.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I edited that bit now, what do you think?

Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The model of computation used by our NRF52833 is the one used by almost every mo

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.

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.
The next step is actually jumping to the code that should be run in response to an interrupt. An Interrupt Service Routine (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.

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.

Expand All @@ -31,21 +31,20 @@ The ISR handler function is "special". The name `GPIOTE` is required here, indic
that this ISR should be stored at the entry for the `GPIOTE` interrupt in the interrupt table.

The `#[interrupt]` decoration is used at compile time to mark a function to be treated specially as
an ISR. (This is a "proc macro", in case you feel like exploring that concept.)
an ISR. (This is a "proc macro": you can read more about it in the [Rust book] if you wish.)

Marking a function with `#[interrupt]` implies several special things about the function:
Essentially, a "proc macro" translates source code into other source code. If you are curious as to what any particular macro use translates into,
you could expand that macro invocation. You can do this by using either the Tools in the [Rust Playground] or the "rust-analyzer: Expand macro" command in your IDE.

* The compiler will check that the function takes no arguments and returns no value. The CPU has no
arguments to provide to an ISR, and no place to put a return value from the ISR.
Marking a function with `#[interrupt]` implies several special things about the function:

* The compiler will place a vector to this function at the location in the interrupt table
implied by the function's name.
* The compiler will check that the function takes no arguments and returns no value (or never returns). The CPU has no
arguments to provide to an ISR, and no place to put a return value from the ISR. This is because interrupt handlers have their own call stack (at least *conceptually* if not always in practice).

* The function will be compiled to finishing by using a return-from-interrupt instruction rather
than the normal function return instruction.
* A vector to this function (that is a function pointer) will be placed at the location in the interrupt table
which corresponds to the function's name.

* Since the function finishes in a non-standard way, the compiler will understand not to allow
directly calling the ISR from normal code.
* The compiler will prevent directly calling the ISR from normal code.

There are two steps to configure the interrupt. First, the GPIOTE must be set up to generate an
interrupt when the pin connected to Button A goes from high to low voltage. Second, the NVIC must be
Expand All @@ -72,3 +71,5 @@ Normally, once an ISR is complete the main program continues running just as it

[NVIC and Interrupt Priority]: nvic-and-interrupt-priority.html
[Registers]: ../09-registers/index.html
[Rust Playground]: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024
[Rust book]: https://doc.rust-lang.org/book/ch20-05-macros.html
2 changes: 2 additions & 0 deletions mdbook/src/15-interrupts/nvic-and-interrupt-priority.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Preemption allows processors to respond very quickly to critical events. For ex

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.

Note that if interrupts are disabled entirely, all incoming interrupts will be pended. Pending interrupts will be handled once interrupts are enabled again.

In embedded Rust, we can program the NVIC using the [`cortex-m`] crate, which provides methods to
enable and disable (called `unmask` and `mask`) interrupts, set interrupt priorities, and trigger
interrupts from software. Frameworks such as [RTIC] can handle NVIC configuration for you, taking
Expand Down
4 changes: 2 additions & 2 deletions mdbook/src/15-interrupts/sharing-data-with-globals.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
> *[Interrupts Is Threads]* by James Munns, which contains more discussion about this
> topic.
As I mentioned in the last section, when an interrupt occurs we aren't passed any arguments and
cannot return any result. This makes it hard for our program interact with peripherals and other
As I mentioned previously, when an interrupt occurs we aren't passed any arguments and
cannot return any result. This makes it hard for our program to interact with peripherals and other
main program state. Before worrying about this bare-metal embedded problem, it is likely worth
thinking about threads in "std" Rust.

Expand Down
2 changes: 1 addition & 1 deletion mdbook/src/15-interrupts/the-challenge.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Let's make the MB2 into a siren! But not just any siren — an
interrupt-driven siren. That way we can turn the siren on
and the rest of our program can run on, ignoring it.

Make your siren sweep the pitch from 220Hz to 440Hz and back
Make your siren sweep the pitch from 440Hz to 660Hz and back
over a one-second period. The main program should start the
siren, then print a ten-second countdown from 10 to 1, then
stop the siren and print "launch!". The main program should
Expand Down