From 8d776b9ebbf51f7b0b761e883394a5cf3553fc74 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 14 Apr 2021 19:33:10 +0200 Subject: [PATCH] tone: add package for producing tones using the PWM interface --- Makefile | 4 +- examples/tone/tone.go | 33 ++++++++++ tone/notes.go | 147 ++++++++++++++++++++++++++++++++++++++++++ tone/tone.go | 73 +++++++++++++++++++++ 4 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 examples/tone/tone.go create mode 100644 tone/notes.go create mode 100644 tone/tone.go diff --git a/Makefile b/Makefile index 0417ab538..aef00980b 100644 --- a/Makefile +++ b/Makefile @@ -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: diff --git a/examples/tone/tone.go b/examples/tone/tone.go new file mode 100644 index 000000000..1e519bb80 --- /dev/null +++ b/examples/tone/tone.go @@ -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) + } +} diff --git a/tone/notes.go b/tone/notes.go new file mode 100644 index 000000000..d7b55f44e --- /dev/null +++ b/tone/notes.go @@ -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, +} diff --git a/tone/tone.go b/tone/tone.go new file mode 100644 index 000000000..8061df8a2 --- /dev/null +++ b/tone/tone.go @@ -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) +}