diff --git a/config.toml b/config.toml
index 047ef97c..41f58b71 100644
--- a/config.toml
+++ b/config.toml
@@ -30,3 +30,7 @@ weight = 30
name = " Slack"
url = "https://gophers.slack.com/messages/CDJD3SUP6/"
weight = 40
+
+[markup.goldmark.renderer]
+# Allow raw HTML and JavaScript.
+unsafe = true
diff --git a/content/microcontrollers/arduino-nano.md b/content/microcontrollers/arduino-nano.md
index a4a34f90..90f2da7d 100644
--- a/content/microcontrollers/arduino-nano.md
+++ b/content/microcontrollers/arduino-nano.md
@@ -18,6 +18,31 @@ Note: the AVR backend of LLVM is still experimental so you may encounter bugs.
| ADC | YES | YES |
| PWM | YES | YES |
+## Pins
+
+| Pin | Hardware pin | PWM
+| ------------ | ------------ | ----------------
+| `D0` | `PD0` |
+| `D1` | `PD1` |
+| `D2` | `PD2` |
+| `D3` | `PD3` | `Timer2`
+| `D4` | `PD4` |
+| `D5` | `PD5` | `Timer0`
+| `D6` | `PD6` | `Timer0`
+| `D7` | `PD7` |
+| `D8` | `PB0` |
+| `D9` | `PB1` | `Timer1`
+| `D10` | `PB2` | `Timer1`
+| `D11` | `PB3` | `Timer2`
+| `D12` | `PB4` |
+| `D13`, `LED` | `PB5` |
+| `ADC0` | `PC0` |
+| `ADC1` | `PC1` |
+| `ADC2` | `PC2` |
+| `ADC3` | `PC3` |
+| `ADC4` | `PC4` |
+| `ADC5` | `PC5` |
+
## Machine Package Docs
[Documentation for the machine package for the Arduino Nano](../machine/arduino-nano)
diff --git a/content/microcontrollers/arduino-nano33-iot.md b/content/microcontrollers/arduino-nano33-iot.md
index 70052510..7ac84c92 100644
--- a/content/microcontrollers/arduino-nano33-iot.md
+++ b/content/microcontrollers/arduino-nano33-iot.md
@@ -16,6 +16,33 @@ The [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot) is a very small A
| ADC | YES | YES |
| PWM | YES | YES |
+## Pins
+
+| Pin | Hardware pin | PWM
+| ------------ | ------------ | ----------------
+| `RX0` | `PB23` |
+| `TX1` | `PB22` |
+| `D2` | `PB10` | `TCC0`
+| `D3` | `PB11` | `TCC0`
+| `D4` | `PA07` | `TCC1`
+| `D5` | `PA05` | `TCC0`
+| `D6` | `PA04` | `TCC0`
+| `D7` | `PA06` | `TCC1`
+| `D8` | `PA18` | `TCC0`
+| `D9` | `PA20` | `TCC0`
+| `D10` | `PA21` | `TCC0`
+| `D11` | `PA16` | `TCC2`, `TCC0`
+| `D12` | `PA19` | `TCC0`
+| `D13`, `LED` | `PA17` | `TCC2`, `TCC0`
+| `A0` | `PA02` |
+| `A1` | `PB02` |
+| `A2` | `PA11` | `TCC1`, `TCC0`
+| `A3` | `PA10` | `TCC1`, `TCC0`
+| `A4` | `PB08` |
+| `A5` | `PB09` |
+| `A6` | `PA09` | `TCC0`, `TCC1`
+| `A7` | `PB03` |
+
## Machine Package Docs
[Documentation for the machine package for the Arduino Nano33 IoT](../machine/arduino-nano33)
diff --git a/content/microcontrollers/arduino-uno.md b/content/microcontrollers/arduino-uno.md
index 3ae9fcbf..af811ca3 100644
--- a/content/microcontrollers/arduino-uno.md
+++ b/content/microcontrollers/arduino-uno.md
@@ -18,6 +18,31 @@ Note: the AVR backend of LLVM is still experimental so you may encounter bugs.
| ADC | YES | YES |
| PWM | YES | YES |
+## Pins
+
+| Pin | Hardware pin | PWM
+| ------------ | ------------ | ----------------
+| `D0` | `PD0` |
+| `D1` | `PD1` |
+| `D2` | `PD2` |
+| `D3` | `PD3` | `Timer2`
+| `D4` | `PD4` |
+| `D5` | `PD5` | `Timer0`
+| `D6` | `PD6` | `Timer0`
+| `D7` | `PD7` |
+| `D8` | `PB0` |
+| `D9` | `PB1` | `Timer1`
+| `D10` | `PB2` | `Timer1`
+| `D11` | `PB3` | `Timer2`
+| `D12` | `PB4` |
+| `D13`, `LED` | `PB5` |
+| `ADC0` | `PC0` |
+| `ADC1` | `PC1` |
+| `ADC2` | `PC2` |
+| `ADC3` | `PC3` |
+| `ADC4` | `PC4` |
+| `ADC5` | `PC5` |
+
## Machine Package Docs
[Documentation for the machine package for the Arduino Uno](../machine/arduino)
diff --git a/content/microcontrollers/arduino-zero.md b/content/microcontrollers/arduino-zero.md
index 0772fe08..99486cce 100644
--- a/content/microcontrollers/arduino-zero.md
+++ b/content/microcontrollers/arduino-zero.md
@@ -16,6 +16,34 @@ The [Arduino Zero](https://store.arduino.cc/arduino-zero) is a very small ARM de
| ADC | YES | YES |
| PWM | YES | YES |
+## Pins
+
+| Pin | Hardware pin | PWM
+| ------------- | ------------ | ----------------
+| `D0` | `PA11` | `TCC1`, `TCC0`
+| `D1` | `PA10` | `TCC1`, `TCC0`
+| `D2` | `PA14` | `TCC0`
+| `D3` | `PA09` | `TCC0`, `TCC1`
+| `D4` | `PA08` | `TCC0`, `TCC1`
+| `D5` | `PA15` | `TCC0`
+| `D6` | `PA20` | `TCC0`
+| `D7` | `PA21` | `TCC0`
+| `D8` | `PA06` | `TCC1`
+| `D9` | `PA07` | `TCC1`
+| `D10` | `PA18` | `TCC0`
+| `D11` | `PA16` | `TCC2`, `TCC0`
+| `D12` | `PA19` | `TCC0`
+| `D13`, `LED` | `PA17` | `TCC2`, `TCC0`
+| `LED2` | `PA27` |
+| `LED3` | `PB03` |
+| `AREF` | `PA03` |
+| `A0` | `PA02` |
+| `A1` | `PB08` |
+| `A2` | `PB09` |
+| `A3` | `PA04` | `TCC0`
+| `A4` | `PA05` | `TCC0`
+| `A5` | `PA02` |
+
## Machine Package Docs
[Documentation for the machine package for the Arduino Zero](../machine/arduino-zero)
diff --git a/content/microcontrollers/circuit-playground-express.md b/content/microcontrollers/circuit-playground-express.md
index 002a2864..9b569e0b 100644
--- a/content/microcontrollers/circuit-playground-express.md
+++ b/content/microcontrollers/circuit-playground-express.md
@@ -16,6 +16,20 @@ The [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
| ADC | YES | YES |
| PWM | YES | YES |
+## Pins
+
+| Pin | Hardware pin | PWM
+| ------------ | ------------ | ----------------
+| `A0` | `PA02` |
+| `A1` | `PA05` | `TCC0`
+| `A2` | `PA06` | `TCC1`
+| `A3` | `PA07` | `TCC1`
+| `A4` | `PB03` |
+| `A5` | `PB02` |
+| `A6` | `PB09` |
+| `A7` | `PB08` |
+| `D13`, `LED` | `PA17` | `TCC2`, `TCC0`
+
## Machine Package Docs
[Documentation for the machine package for the Circuit Playground Express](../machine/circuitplay-express)
diff --git a/content/microcontrollers/machine/pwm.md b/content/microcontrollers/machine/pwm.md
new file mode 100644
index 00000000..be613dd4
--- /dev/null
+++ b/content/microcontrollers/machine/pwm.md
@@ -0,0 +1,424 @@
+---
+title: PWM
+weight: 1
+---
+
+PWM, short for Pulse Width Modulation, is a trick to dim a digital output by switching the output on and off quickly enough so that the average remains at a value somewhere between on and off. Using this trick, a digital output starts to behave like an analog output. Some applications of PWM:
+
+ * Dimming LEDs. By turning a LED on and off a few hundred times a second (or more), it appears as if the LED has a constant brightness. The duty cycle (how much percent of the time the output is high) determines how bright the LED appears to be.
+ * Creating square waves, for example in simple speakers. By controlling the PWM frequency and leaving the duty cycle at 50%, a speaker can produce a square wave of a particular frequency.
+ * Controlling servo motors. Most servos are controlled by sending a pulse between 1ms and 2ms to control the position, with a full PWM period of 20ms. That means that for the middle position, the signal should be 1.5ms high and then 18.5ms low (together 20ms).
+
+The PWM API in TinyGo is a bit different from some other PWM APIs, such as the ones in [Arduino](https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/), [Mbed](https://os.mbed.com/handbook/PwmOut), [CircuitPython](https://learn.adafruit.com/circuitpython-essentials/circuitpython-pwm), and some others. This might make the API a bit harder to use, but it also makes some things explicit that would otherwise be hidden and cause problems.
+
+If you just want to fade an LED, feel free to skip the background section. But a bit of background might help to understand PWMs a bit more.
+
+## Background
+
+In the example below you can control a simulated LED using PWM. You can change the duty cycle and the speed and see how that affects the LED. Notice how, if the frequency is high enough, the output appears continuous.
+
+
+
+
+
+
+
+
+
+
+
+In most cases, a PWM is a combination of a counter that counts up or down and a few channels with a value that this counter is continuously compared against.
+
+The below example is a very simple 3-bit PWM peripheral. It can only count up to 7 before it starts again at zero. Most PWM peripherals are 8, 16 or 24 bits and thus have a much larger range. You can control the 'top' value as in most real-world PWM peripherals. It also has just two channels: many real-world PWMs have four or sometimes more channels.
+
+
+
+
+
+
+
+
+
+
+
+While playing around with this, you may notice a few things:
+
+ * The counter top determines the period of the output signal. It is easy to calculate the frequency from that, which is the inverse of the period.
+ * The channel compare value determines the duty cycle of the signal. Every time the counter matches zero, all channel outputs are turned on and every time a channel matches the compare value, the channel is turned off again.
+ * While you can change the period (and thus the frequency) of the output signal by changing the top value, the channel compare values will not automatically change with it and thus the duty cycle will change if you change the frequency: even though the 'on' period remains the same, the 'off' period changes.
+ * While you can control the duty cycle of every channel independently, you cannot control the period (and thus the frequency) for individual channels. You can only change it for the entire PWM peripheral.
+
+There are also a few things that are supported by many real-world PWMs but not by this simplified example:
+
+ * (Nearly?) all PWM peripherls support a prescaler to reduce the tick frequency, which allows for much longer periods and thus much lower frequencies than would otherwise be possible.
+ * Most PWM peripherals are actually called timer/counter peripherals, because they can also count events (by incrementing the counter with some external trigger instead of from an internal clock source) and they allow timestamping events by loading the current counter into one of the channels. These channels are also called capture/compare channels because they cannot just compare the value to the current counter but can also be loaded with the current counter.
+ * PWM peripherals support many more configurations than just counting up. Some count down by default (reloading with the top value when they hit zero) and some count both up and down in a kind of triangle waveform. You don't need all of that for just controlling LEDs, but some hardware needs very precise control over the generated waveform.
+ * These peripherals are also called timers, because sometimes they can be used as just that: a timer, with no external connections. You can keep track of the current time by reading the counter value and registering an interrupt on overflow so the timer value keeps incrementing.
+ * Some chips (such as many AVRs) may not have a dedicated top value. Instead, you can assign one of the capture/compare channels as the top value. Of course, that means you cannot use that channel for generating a PWM output.
+
+Most of these features are either abstracted away by the TinyGo API or left to be implemented in the future.
+
+## Fading an LED
+
+Fading an LED is relatively simple. For example, this code sets the on-board LED of an [Arduino Nano 33 IoT](../../arduino-nano33-iot/) to 25% output:
+
+```go
+func main() {
+ // Configure the PWM peripheral. The default PWMConfig is fine for LEDs.
+ pwm := machine.TCC0
+ err := pwm.Configure(machine.PWMConfig{})
+ handleError(err, "failed to configure TCC0")
+
+ // Obtain a PWM channel.
+ ch, err := pwm.Channel(machine.LED)
+ handleError(err, "failed to obtain PWM channel for LED")
+
+ // Set the channel to a 25% duty cycle.
+ pwm.Set(ch, pwm.Top() / 4)
+
+ // Wait before exiting.
+ time.Sleep(time.Hour)
+}
+```
+
+There are several steps involved here:
+
+ 1. The PWM must be configured first. An empty (zero) `PWMConfig` is designed to work fine for LEDs. Which PWM to pick depends on the chip and the board. If you check the [board description for this board](../../arduino-nano33-iot/), you can see that the Arduino Nano 33 IoT has TCC2 and TCC0 connected to the LED pin - we are using TCC0 here.
+ 2. Next a channel is obtained for this PWM. In general, one channel is connected to a single pin although there are exceptions.
+ 3. Finally you can set the output to 25% of the "top" value of the PWM, which is the value for a duty cycle of 100%. The top value varies a lot by hardware and by configuration, so you can't rely on any particular values. `pwm.Set(ch, pwm.Top())` sets the duty cycle to 100% and `pwm.Set(ch, 0)` sets the duty cycle to 0%.
+
+You can also control multiple LEDs from a single PWM peripheral, as long as they are supported by that PWM peripheral. For example, a [Particle Argon](../../particle-argon) has an RGB LED that you can control like this:
+
+```go
+func main() {
+ // Configure the PWM peripheral. The default PWMConfig is fine for LEDs.
+ pwm := machine.PWM0
+ err := pwm.Configure(machine.PWMConfig{})
+ handleError(err, "failed to configure PWM0")
+
+ // Obtain the various channels.
+ red, err := pwm.Channel(machine.LED_RED)
+ handleError(err, "failed to obtain PWM channel for LED_RED")
+ green, err := pwm.Channel(machine.LED_GREEN)
+ handleError(err, "failed to obtain PWM channel for LED_GREEN")
+ blue, err := pwm.Channel(machine.LED_BLUE)
+ handleError(err, "failed to obtain PWM channel for LED_BLUE")
+
+ // Invert the output, because this is a common anode RGB LED.
+ // The same effect could be obtained by using the inverse value in the Set
+ // method (pwm.Set(red, 0) for full output, for example).
+ pwm.SetInverting(red, true)
+ pwm.SetInverting(green, true)
+ pwm.SetInverting(blue, true)
+
+ // Set the RGB LED to orange.
+ pwm.Set(red, pwm.Top()) // 100% duty cycle
+ pwm.Set(green, pwm.Top()/4) // 25% duty cycle
+ pwm.Set(blue, 0) // 0% duty cycle
+
+ // Wait before exiting.
+ time.Sleep(time.Hour)
+}
+```
+
+Finally, this mechanism doesn't just let you fade an LED, but also blink it. This is done by using a PWM frequency of 1Hz or period of 1 second, which can be set using the `Period` member of `machine.PWMConfig`. This code sample is again for the [Arduino Nano 33 IoT](../../arduino-nano33-iot/):
+
+```go
+func main() {
+ // Configure the PWM peripheral. A period of 1e9 nanoseconds (or 1 second)
+ // results in a frequency of 1Hz.
+ pwm := machine.TCC0
+ err := pwm.Configure(machine.PWMConfig{Period: 1e9})
+ handleError(err, "failed to configure TCC0")
+
+ // Obtain a PWM channel.
+ ch, err := pwm.Channel(machine.LED)
+ handleError(err, "failed to obtain PWM channel for LED")
+
+ // Set the channel to a 50% duty cycle, for equal on and off time.
+ pwm.Set(ch, pwm.Top()/2)
+
+ // Wait before exiting, while the LED continues blinking.
+ time.Sleep(time.Hour)
+}
+```
+
+This can have some advantages over manually blinking using the `High()` and `Low()` methods, such as that the blinking will be very precisely timed (no interference from interrupts) and you don't need to dedicate a goroutine to blinking this LED. Not all hardware supports these long PWM periods, however.
+
+
+## Creating sound
+
+For a speaker it's important to get the frequency exactly right, otherwise it'll have the wrong tone. Apart from the frequency it's important that the duty cycle is always 50% to get a square wave.
+
+The following example is written for an [Adafruit Circuit Playground Bluefruit](../../circuit-playground-bluefruit). The frequencies in this case are obtained [from Wikipedia](https://en.wikipedia.org/wiki/Piano_key_frequencies) and match emergency vehicle sounds in some places.
+
+```go
+const lowNote = uint64(1e9) / 880 // A5 = 880Hz
+const highNote = uint64(1e9) / 988 // B5 = 987.7666Hz
+
+func main() {
+ // Configure the PWM peripheral with a specific frequency.
+ pwm := machine.PWM0
+ err := pwm.Configure(machine.PWMConfig{
+ // Use the lowest frequency note (or longest period) in the Period
+ // member below. This ensures the PWM can later easily be reconfigured
+ // with a higher frequency or shorter period. The other way around is
+ // unsupported and may or may not work.
+ Period: lowNote,
+ })
+ handleError(err, "failed to configure PWM")
+
+ // Obtain a PWM channel from a pin connected to the speaker.
+ ch, err := pwm.Channel(machine.D12)
+ handleError(err, "failed to obtain PWM channel for the audio output")
+
+ // Sound a siren. Be aware that this can be quite loud!
+ for {
+ // Create the high note (B5).
+ pwm.SetPeriod(highNote)
+ pwm.Set(ch, pwm.Top()/2)
+ time.Sleep(500 * time.Millisecond)
+
+ // Create the low note (A5).
+ pwm.SetPeriod(lowNote)
+ pwm.Set(ch, pwm.Top()/2)
+ time.Sleep(500 * time.Millisecond)
+ }
+}
+```
+
+In this example the PWM is initially configured with the lowest frequency (the longest period). Then it is possible to later reconfigure the frequency using `SetPeriod` to the same or a higher frequency.
+
+`SetPeriod` has the side effect that the duty cycle changes when the frequency changes. This happens because `SetPeriod` does not change the "on" time of any of the channels, it only changes the period. Therefore, to remain at 50% duty cycle the duty cycle needs to be set to 50% again after the period is changed.
diff --git a/content/microcontrollers/pybadge.md b/content/microcontrollers/pybadge.md
index 19a6d774..3b61fc66 100644
--- a/content/microcontrollers/pybadge.md
+++ b/content/microcontrollers/pybadge.md
@@ -18,6 +18,33 @@ It has many built-in devices, such as a 1.8" 160x128 Color TFT Display, 8 x butt
| ADC | YES | YES |
| PWM | YES | YES |
+## Pins
+
+| Pin | Hardware pin | PWM
+| ------------ | ------------ | ----------------
+| `D0` | `PB17` | TCC3, TCC0
+| `D1` | `PB16` | TCC3, TCC0
+| `D2`, `A8` | `PB03` |
+| `D3`, `A9` | `PB02` | TCC2
+| `D4` | `PA14` | TCC2, TCC1
+| `D5` | `PA16` | TCC1, TCC0
+| `D6` | `PA18` | TCC1, TCC0
+| `D7` | `PB14` | TCC4, TCC0
+| `D8` | `PA15` | TCC2, TCC1
+| `D9` | `PA19` | TCC1, TCC0
+| `D10` | `PA20` | TCC1, TCC0
+| `D11` | `PA21` | TCC1, TCC0
+| `D12` | `PA22` | TCC1, TCC0
+| `D13`, `LED` | `PA23` | TCC1, TCC0
+| `A0` | `PA02` |
+| `A1` | `PA05` |
+| `A2` | `PB08` |
+| `A3` | `PB09` |
+| `A4` | `PA04` |
+| `A5` | `PA06` |
+| `A6` | `PB01` |
+| `A7` | `PB04` |
+
## Machine Package Docs
[Documentation for the machine package for the Adafruit PyBadge](../machine/pybadge)