tone: add package for producing tones using the PWM interface

This commit is contained in:
Ayke van Laethem
2021-04-14 19:33:10 +02:00
committed by Ron Evans
parent 6bc466f79b
commit df343190c2
4 changed files with 256 additions and 1 deletions
+3 -1
View File
@@ -117,6 +117,8 @@ smoke-test:
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/thermistor/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=circuitplay-bluefruit ./examples/tone
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/touch/resistive/fourwire/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/touch/resistive/pyportal_touchpaint/main.go
@@ -177,7 +179,7 @@ endif
DRIVERS = $(wildcard */)
NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \
hcsr04 ssd1331 ws2812 thermistor apa102 easystepper ssd1351 ili9341 wifinina shifter hub75 \
hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x dht keypad4x4 max72xx p1am
hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x dht keypad4x4 max72xx p1am tone
TESTS = $(filter-out $(addsuffix /%,$(NOTESTS)),$(DRIVERS))
unit-test:
+33
View File
@@ -0,0 +1,33 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/tone"
)
var (
// Configuration for the Adafruit Circuit Playground Bluefruit.
pwm = machine.PWM0
pin = machine.D12
)
func main() {
speaker, err := tone.New(pwm, pin)
if err != nil {
println("failed to configure PWM")
return
}
// Two tone siren.
for {
println("nee")
speaker.SetNote(tone.B5)
time.Sleep(time.Second / 2)
println("naw")
speaker.SetNote(tone.A5)
time.Sleep(time.Second / 2)
}
}
+147
View File
@@ -0,0 +1,147 @@
package tone
// Note represents a MIDI note number. For example, Note(69) is A4 or 440Hz.
type Note uint8
// Define all the notes in a format similar to the Tone library in the Arduino
// IDE.
const (
A0 Note = iota + 21 // 27.5Hz
AS0
B0
C1
CS1
D1
DS1
E1
F1
FS1
G1
GS1
A1 // 55Hz
AS1
B1
C2
CS2
D2
DS2
E2
F2
FS2
G2
GS2
A2 // 110Hz
AS2
B2
C3
CS3
D3
DS3
E3
F3
FS3
G3
GS3
A3 // 220Hz
AS3
B3
C4
CS4
D4
DS4
E4
F4
FS4
G4
GS4
A4 // 440Hz
AS4
B4
C5
CS5
D5
DS5
E5
F5
FS5
G5
GS5
A5 // 880Hz
AS5
B5
C6
CS6
D6
DS6
E6
F6
FS6
G6
GS6
A6 // 1760Hz
AS6
B6
C7
CS7
D7
DS7
E7
F7
FS7
G7
GS7
A7 // 3520Hz
AS7
B7
C8
CS8
D8
DS8
E8
F8
FS8
G8
GS8
A8 // 7040Hz
AS8
B8
)
// Period returns the period in nanoseconds of a single wave.
func (n Note) Period() uint64 {
if n == 0 {
// Assume that a zero note means no sound.
return 0
}
octave := (n - 9) / 12
note := (n - 9) - octave*12
// Start with a base period (in nanoseconds) of 6.875Hz (quarter the
// frequency of A0) and shift it right with the octave to get the base
// period of this note.
// 145454545 = 1e9 / 6.875
basePeriod := uint32(145454545) >> octave
// Make the pitch higher based on the note within the octave.
period := uint64(basePeriod) * uint64(tones[note]) / 32768
return period
}
// Constants to calculate the pitch within an octave. Python oneliner:
// [round(1e9/(440*(2**(n/12))) / (1e9/440) * 0x8000) for n in range(12)]
var tones = [12]uint16{
32768,
30929,
29193,
27554,
26008,
24548,
23170,
21870,
20643,
19484,
18390,
17358,
}
+73
View File
@@ -0,0 +1,73 @@
package tone
import (
"machine"
)
// PWM is the interface necessary for controlling a speaker.
type PWM interface {
Configure(config machine.PWMConfig) error
Channel(pin machine.Pin) (channel uint8, err error)
Top() uint32
Set(channel uint8, value uint32)
SetPeriod(period uint64) error
}
// Speaker is a configured audio output channel based on a PWM.
type Speaker struct {
pwm PWM
ch uint8
}
// New returns a new Speaker instance readily configured for the given PWM and
// pin combination. The lowest frequency possible is 27.5Hz, or A0. The audio
// output uses a PWM so the audio will form a square wave, a sound that
// generally sounds rather harsh.
func New(pwm PWM, pin machine.Pin) (Speaker, error) {
err := pwm.Configure(machine.PWMConfig{
Period: uint64(1e9) / 55 / 2,
})
if err != nil {
return Speaker{}, err
}
ch, err := pwm.Channel(pin)
if err != nil {
return Speaker{}, err
}
return Speaker{pwm, ch}, nil
}
// Stop disables the speaker, setting the output to low continuously.
func (s Speaker) Stop() {
s.pwm.Set(s.ch, 0)
}
// SetPeriod sets the period for the signal in nanoseconds. Use the following
// formula to convert frequency to period:
//
// period = 1e9 / frequency
//
// You can also use s.SetNote() instead for MIDI note numbers.
func (s Speaker) SetPeriod(period uint64) {
// Disable output.
s.Stop()
if period == 0 {
// Assume a period of 0 is intended as "no output".
return
}
// Reconfigure period.
s.pwm.SetPeriod(period)
// Make this a square wave by setting the channel position to half the
// period.
s.pwm.Set(s.ch, s.pwm.Top()/2)
}
// SetNote starts playing the given note. For example, s.SetNote(C4) will
// produce a 440Hz square wave tone.
func (s Speaker) SetNote(note Note) {
period := note.Period()
s.SetPeriod(period)
}