Skip to content

Commit df93f04

Browse files
aykevldeadprogram
authored andcommitted
compiler-internals: add page on interrupts
See: tinygo-org/tinygo#782 Also reorder other pages a bit, in order from "relevant if you're doing low-level stuff as a user" to "if you want to dig deeper into the compiler".
1 parent 4fb8d03 commit df93f04

File tree

8 files changed

+127
-7
lines changed

8 files changed

+127
-7
lines changed

content/compiler-internals/calling-convention.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: "Calling convention"
3-
weight: 4
3+
weight: 7
44
---
55

66
Go uses a stack-based calling convention and passes a pointer to the argument list as the first argument in the function. There were/are [plans to switch to a register-based calling convention](https://github.com/golang/go/issues/18597) but they're now on hold.

content/compiler-internals/datatypes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: "Datatypes"
3-
weight: 3
3+
weight: 6
44
---
55

66
TinyGo uses a different representation for some data types than standard Go.

content/compiler-internals/differences-from-go.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: "Differences from Go"
3-
weight: 2
3+
weight: 8
44
---
55

66
* A whole program is compiled in a single step, without intermediate linking. This makes incremental development much slower for large programs but enables far more optimization opportunities. We are actively working on build caching so that at least part of the work does not need to be redone on every compilation.

content/compiler-internals/harvard-arch.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: "Harvard architectures (AVR)"
3-
weight: 5
3+
weight: 10
44
---
55

66
The AVR architecture is a modified Harvard architecture, which means that flash and RAM live in different address spaces. In practice, this means that any given pointer may either point to RAM or flash, but this is not visible from the pointer itself.

content/compiler-internals/heap-allocation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: "Heap allocation"
3-
weight: 2
3+
weight: 5
44
---
55

66
Many operations in Go rely on heap allocation. TinyGo will try to optimize them away using escape analysis, but that is not always possible in practice.
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
---
2+
title: "Interrupts"
3+
weight: 2
4+
---
5+
6+
Interrupts are crucial if you want to do anything high-performance on microcontrollers. Unfortunately, the only close substitute in the Go language ([`os/signal.Notify`](https://godoc.org/os/signal#Notify)) is rather heavyweight. Therefore TinyGo uses a simpler way to work with interrupts.
7+
8+
Note: in most cases, you shouldn't need to work directly with interrupts. The machine package tries to abstract it away to provide a simple interface to work with. However, this page is here if you want to work on the machine package or need to override an interrupt.
9+
10+
## Basics
11+
12+
Interrupts are a bit like threads, but without any real concurrency. You could see them as a kind of callbacks at the hardware level.
13+
14+
Perhaps the most simple example of an interrupt is the UART (serial) interrupt on receiving a byte. Almost every microcontroller supports this. To use it, you generally will need to take the following steps:
15+
16+
1. You need to define an interrupt handler. This is a function like any other, but is somehow marked specially to function as an interrupt handler. It depends on the compiler and programming language how this is done exactly.
17+
2. You need to configure the UART to enable receive interrupts. This is a setting in the UART peripheral itself, not in the interrupt handler.
18+
3. Depending on the chip, you need to set a priority for the interrupt. Not all microcontrollers support interrupt priorities, but many (especially more advanced) do. You set this not in the peripheral but in a centralized interrupt controller.
19+
4. Finally, you need to enable the interrupt. Some very simple chips (like the AVR) do not support this: you are expected to control the interrupt by enabling/disabling interrupt sources as in step 2.
20+
21+
TinyGo abstracts most of the complications away, but it's good to be aware of what is going on behind the scenes.
22+
23+
## Example
24+
25+
The following is an example of how interrupts work:
26+
27+
```go
28+
func (uart UART) Configure(config UARTConfig) {
29+
// [...] other configuration
30+
31+
// Enable the receive interrupt (step 2).
32+
// What the below line does is that it enables exactly one interrupt source
33+
// in the UART: the RXDRDY (RX ready) source. Enabling this makes sure the
34+
// interrupt is triggered whenever a byte is received in the UART.
35+
nrf.UART0.INTENSET.Set(nrf.UART_INTENSET_RXDRDY_Msk)
36+
37+
// Register a new interrupt handler (step 1). This is the TinyGo way of
38+
// saying to the compiler that the UART0.handleInterrupt function is
39+
// special and should be called whenever the UART0 interrupt is triggered.
40+
intr := interrupt.New(nrf.IRQ_UART0, UART0.handleInterrupt)
41+
42+
// Now we have a handle to the interrupt. The default on this chip is the
43+
// highest possible priority. We'd like to set the UART to a lower
44+
// priority, which we do here. The magic constant here will in a future
45+
// version be replaced with a regular constant for a low-priority
46+
// interrupt.
47+
intr.SetPriority(0xc0)
48+
49+
// Finally, the interrupt must be enabled. Without this, the interrupt will
50+
// still be triggered but the handler will never be called.
51+
intr.Enable()
52+
}
53+
54+
// This is the function that will be called. As a convenience, the interrupt
55+
// handle is also provided as a parameter but you can usually ignore it.
56+
func (uart *UART) handleInterrupt(intr interrupt.Interrupt) {
57+
// Multiple interrupt sources are often mapped to a single interrupt
58+
// handler. Therefore, we need to differentiate between various events.
59+
// In this case, only one interrupt source has been configured so
60+
// technically we could avoid this check, but it's good practice (for
61+
// future changes) to check for the event anyway.
62+
if nrf.UART0.EVENTS_RXDRDY.Get() != 0 {
63+
// This particular chip won't retrigger the interrupt when this event
64+
// is not cleared, but it won't clear the event either. So if we want
65+
// to differentiate between events in the next interrupt (when this
66+
// interrupt was triggered by a hypothetical other interrupt source) we
67+
// need to manually clear it first.
68+
nrf.UART0.EVENTS_RXDRDY.Set(0x0)
69+
70+
// And finally, we can receive the byte from the UART. This is done by
71+
// reading the RXD register, which also has the side effect of
72+
// informing the hardware that this byte has been read.
73+
b := byte(nrf.UART0.RXD.Get())
74+
75+
// Now do whatever you'd like with the just received byte.
76+
uart.Receive(b)
77+
}
78+
}
79+
```
80+
81+
82+
## Troubleshooting
83+
84+
### The interrupt won't fire
85+
86+
* Check that you have enabled the interrupt source in the peripheral (step 2).
87+
* Check that you have enabled the interrupt with the `intr.Enable()` call (step 4).
88+
* Check that you are listening for the correct interrupt and not for an interrupt for a different peripheral, for example (step 1).
89+
90+
### The interrupt keeps firing
91+
92+
Depending on the chip family, you may need to clear an interrupt source. This is for example the case in the Microchip SAM family of microcontrollers. If you don't do this, the interrupt will fire continuously thinking it still needs to be handled even though you have long since handled the interrupt.
93+
94+
### I get a compile error
95+
96+
You may get a compile error like the following:
97+
98+
```text
99+
src/machine/machine_nrf.go:91:23: closures are not supported in interrupt.New
100+
```
101+
102+
This could mean several things:
103+
104+
* If you're passing an inline function closure to `interrupt.New`, you are not allowed to use variables from the outer scope. However, global variables are fine.
105+
* If you're passing a bound method, make sure the method has a function receiver. That is, define the interrupt handler like so:
106+
107+
```go
108+
func (uart *UART) handleInterrupt(intr interrupt.Interrupt) { ... }
109+
```
110+
111+
instead of like so:
112+
113+
```go
114+
// this won't compile
115+
func (uart UART) handleInterrupt(intr interrupt.Interrupt) { ... }
116+
```
117+
118+
The reason is that the latter will actually make a copy of the UART variable to be stored with the interrupt, which is currently not supported in the compiler and will lead to reduced performance even when it becomes supported.
119+
120+
Additionally, make sure the variable you're binding to is a global variable instead of a local variable.

content/compiler-internals/microcontrollers.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: "Microcontroller Targets"
3-
weight: 5
3+
weight: 1
44
---
55

66
TinyGo was designed to run on microcontrollers, but the Go language wasn't. This means there are a few challenges to writing Go code for microcontrollers.

content/compiler-internals/pipeline.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: "Pipeline"
3-
weight: 5
3+
weight: 11
44
---
55

66
Like most compilers, TinyGo is a compiler built as a pipeline of transformations

0 commit comments

Comments
 (0)