Skip to content

Commit 1d47f54

Browse files
aykevldeadprogram
authored andcommitted
Add PWM tour
This tour includes blinking LEDs, fading LEDs, and controlling servos.
1 parent 669b4ab commit 1d47f54

File tree

7 files changed

+910
-0
lines changed

7 files changed

+910
-0
lines changed

content/tour/pwm/_index.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
title: "PWM"
3+
description: "Using the PWM peripheral of common microcontrollers."
4+
type: "docs"
5+
weight: 4
6+
---
7+
8+
PWM, or Pulse Width Modulation, is a deceptively simple but very powerful technique for controlling various things in the real world. Perhaps the most well known application is dimming LEDs, but PWM can also be used to control servo motors and producing crude (square wave) tones on speakers.

content/tour/pwm/blink.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
---
2+
title: "LED: blink"
3+
description: "Get a LED to blink using the PWM peripheral."
4+
weight: 1
5+
---
6+
7+
PWM, or pulse-width modulation, is a method of turning a GPIO pin high and low at a fixed frequency but changing the percent of time it is turned on. This has numerous applications, such as dimming LEDs. We'll see various applications of PWM throughout this tour.
8+
9+
To start off with, we're going to do something slightly unusual with PWM: we're going to blink an LED. Remember that PWM is a way of quickly turning an output high and low, if we do that slow enough we can see the LED blink. This should make it easier to see how PWM works.
10+
11+
At first we'll just define some variables. These may vary by board, so it's useful to define them in a single place.
12+
13+
```go
14+
// Board specific configuration.
15+
led := machine.LED
16+
pwm := machine.TCC0
17+
```
18+
19+
Next we're going to configure the PWM peripheral itself:
20+
21+
```go
22+
// Configure the timer/PWM.
23+
err := pwm.Configure(machine.PWMConfig{
24+
Period: 2e9, // two seconds for a single cycle
25+
})
26+
if err != nil {
27+
println("could not configure:", err.Error())
28+
return
29+
}
30+
```
31+
32+
This configures the PWM peripheral for a cycle time of two seconds. That's much longer than PWM is commonly used for, but it allows to see us what happens.
33+
34+
Next up, we connect the PWM peripheral to the given output pin:
35+
36+
```go
37+
// Get the channel for this PWM peripheral.
38+
ch, err := pwm.Channel(led)
39+
if err != nil {
40+
println("could not obtain channel:", err.Error())
41+
return
42+
}
43+
```
44+
45+
We get a channel number back. One PWM peripheral typically has 2-4 channels, which can be connected to certain pins. Which pins can be used depends a lot on the hardware, which we'll [get to later](../multiple/#finding-the-correct-pwm-peripheral-and-pins). For now, all you need to know is that the PWM channel is now connected to a single GPIO pin.
46+
47+
To actually make the LED blink, we will set the duty cycle to half the "top" value of the LED:
48+
49+
```go
50+
// Blink the LED, setting it to "on" half the time.
51+
pwm.Set(ch, pwm.Top()/2)
52+
```
53+
54+
The top value is equivalent to a duty cycle of 100% - or 100% of the time on. So using the top divided by 2 results in a duty cycle of 50%, or "high" for half the time. Dividing by 3 results in a duty cycle of 33%, etc. By varying the formula you can either use a particular percentage (like we do here) or if you know the period size you can calculate a specific "on" time within the cycle (such as "500ms on, 2000ms off").
55+
56+
To get a feel for how it works, you can try a few things:
57+
58+
* You can change the period to something other than `2e9` (two seconds) and see what happens to the "top" value.
59+
* You can change the duty cycle by changing the `pwm.Set` call.
60+
* You can connect a different LED to the virtual board, and blink it.
61+
* You can try using a different PWM peripheral. These boards also have `TCC1` and `TCC2`.
62+
63+
## More information
64+
65+
SparkFun has written an [excellent tutorial on PWM](https://learn.sparkfun.com/tutorials/pulse-width-modulation) that is well worth a read!
66+
67+
68+
69+
<script type="module">
70+
import { setupTour } from '/tour.js';
71+
let code = `
72+
package main
73+
74+
import "machine"
75+
76+
func main() {
77+
// Board specific configuration.
78+
led := machine.LED
79+
pwm := machine.TCC0
80+
81+
// Configure the timer/PWM.
82+
err := pwm.Configure(machine.PWMConfig{
83+
Period: 2e9, // two seconds for a single cycle
84+
})
85+
if err != nil {
86+
println("could not configure:", err.Error())
87+
return
88+
}
89+
90+
// Get the channel for this PWM peripheral.
91+
ch, err := pwm.Channel(led)
92+
if err != nil {
93+
println("could not obtain channel:", err.Error())
94+
return
95+
}
96+
97+
// Blink the LED, setting it to "on" half the time.
98+
pwm.Set(ch, pwm.Top()/2)
99+
100+
println("top value: ", pwm.Top())
101+
println("LED channel:", ch)
102+
103+
// Main returns, so the program exits. Yet the LED continues blinking.
104+
}
105+
`;
106+
setupTour({
107+
code: code,
108+
boards: {
109+
'arduino-nano33': {},
110+
'circuitplay-express': {},
111+
}});
112+
</script>

content/tour/pwm/fade.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
---
2+
title: "LED: fade"
3+
description: "Fade a LED using the PWM peripheral."
4+
weight: 2
5+
---
6+
7+
Next up, we're going to do what PWM was actually designed for! Namely, quickly controlling an output so that the average output can be controlled precisely.
8+
9+
Most of it is similar to blinking an LED using the PWM peripheral, but fading needs a little bit more work:
10+
11+
```go
12+
// Fade the LED in.
13+
for percentOn := 0; percentOn <= 100; percentOn++ {
14+
pwm.Set(ch, pwm.Top()*uint32(percentOn)/100)
15+
time.Sleep(time.Second / 100)
16+
}
17+
```
18+
19+
Here we fade the LED in. This loop loops 101 times (from 0 to 100 inclusive), setting the output to a percentage of the input. The code `pwm.Top()*uint32(percentOn)/100` is essentially the integer variant of the following formula:
20+
21+
```math
22+
\frac{T}{100} * P
23+
```
24+
25+
Where \\(T\\) is the top value (`pwm.Top()`) and \\(P\\) is the percentage (`percentOn`).
26+
27+
Fading out is very similar. It's almost identical, except it loops from 100 to 0 (inclusive):
28+
29+
```go
30+
// Fade the LED out.
31+
for percentOn := 100; percentOn >= 0; percentOn-- {
32+
pwm.Set(ch, pwm.Top()*uint32(percentOn)/100)
33+
time.Sleep(time.Second / 100)
34+
}
35+
```
36+
37+
38+
<script type="module">
39+
import { setupTour } from '/tour.js';
40+
let code = `
41+
package main
42+
43+
import "machine"
44+
import "time"
45+
46+
func main() {
47+
// Board specific configuration.
48+
led := machine.LED
49+
pwm := machine.TCC0
50+
51+
// Configure the timer/PWM.
52+
// Use the default period, which works well enough for LEDs.
53+
err := pwm.Configure(machine.PWMConfig{})
54+
if err != nil {
55+
println("could not configure:", err.Error())
56+
return
57+
}
58+
59+
// Get the channel for this PWM peripheral.
60+
ch, err := pwm.Channel(led)
61+
if err != nil {
62+
println("could not obtain channel:", err.Error())
63+
return
64+
}
65+
66+
for {
67+
// Fade the LED in.
68+
for percentOn := 0; percentOn <= 100; percentOn++ {
69+
pwm.Set(ch, pwm.Top()*uint32(percentOn)/100)
70+
time.Sleep(time.Second / 100)
71+
}
72+
73+
// Fade the LED out.
74+
for percentOn := 100; percentOn >= 0; percentOn-- {
75+
pwm.Set(ch, pwm.Top()*uint32(percentOn)/100)
76+
time.Sleep(time.Second / 100)
77+
}
78+
}
79+
}
80+
`;
81+
setupTour({
82+
code: code,
83+
boards: {
84+
'arduino-nano33': {},
85+
'circuitplay-bluefruit': {
86+
code: code.replace('machine.TCC0', 'machine.PWM0'),
87+
},
88+
'circuitplay-express': {},
89+
'pico': {
90+
code: code.replace('machine.TCC0', 'machine.PWM4'),
91+
},
92+
}});
93+
</script>

0 commit comments

Comments
 (0)