Skip to content
Merged
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
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
33 changes: 33 additions & 0 deletions examples/tone/tone.go
Original file line number Diff line number Diff line change
@@ -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 changes: 147 additions & 0 deletions tone/notes.go
Original file line number Diff line number Diff line change
@@ -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 changes: 73 additions & 0 deletions tone/tone.go
Original file line number Diff line number Diff line change
@@ -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)
}