Bouncer is a button input handler package for use with TinyGo on arm microcontrollers. It supports systick-based debouncing of button input and recognizes button-press-lengths of different durations
Bouncer assumes you are using long-lived goroutines which listen for updates on a long-lived channel. At the end of a buttonDown -> buttonUp sequence, a Bouncer recognizes the duration of the press and sends this information to interested subscribers. When setting up your Bouncer(s), you can add an output channel for each of your interested subscribers.
- Pass an unconfigured pin here (Configure will reconfigure it to InputPullup anyway)
- With
...outs
you'll add one or more channels on which the bouncer will publishPressLength
events to your interested goroutines.
A custom duration for short, long, & extra long presses can be set in a BouncerConfig
struct. To override default values, pass this to Configure, or pass an empty BouncerConfig
to keep default values. The bouncer's pin is set to InputPullup
In Configure
, a function becomes the button's pin interrupt handler, firing on PinRising
& PinFalling
, sending the button's pin state to the Bouncer's isrChan
channel, which is consumed by RecognizeAndPublish
This is the button-press-length recognizer & publisher goroutine.
- The function blocks on communication from one of two channels
tickerCh
– a systick from theSysTick_Handler
was received from the relayisrChan
– a button interrupt event was received
- Initially,
RecognizeAndPublish
is looking for a buttonDown event, and will ignore both systicks & buttonUp interrupts. - After the first buttonDown event arrives, the time is noted for later evaluation, and the function begins to increment
ticks
whenever a SysTick is received ontickerCh
. - At this point, the function begins to expect buttonUp events; buttonDown events are ignored.
- Upon the first debounced buttonUp event, the time is subtracted from the buttonDown time, resulting in a buttonDown duration. This duration is compared to the set of
PressLength
durations, thereby becoming recognized. - The resulting
PressLength
is published to all output channels
A systick is a machine-level event to which we can attach our own handler. Since this is global in nature, it doesn't belong in this package; instead, you must set up a "SysTick_Handler" yourself and allow your Bouncer to consume its channel, indirectly through a relay (Debounce
) in order to fan-out the ticks to multiple bouncers. You'll set up the system timer, define your Systick handler, set up your bouncers, and then call Debounce to begin debouncing.
In your init
or main
, set up a timer set to an interval of your desired debounce duration. The following will fire 10 times per second, obtaining a ~100ms debounce threshold; a higher denominator (shorter duration) may be more appropriate, perhaps 40 -> 25ms. It's up to you.
func launchSystick() {
err := arm.SetupSystemTimer(machine.CPUFrequency() / 10)
if err != nil {
println("error launching systick timer!")
}
println("launched systick timer...")
}
SysTick_Handler is attached to a function using the go macro //go:export SysTick_Handler
. The function should simply select
and send to a struct channel with a buffer of 1.
One interesting thing about this is that you never need to call it; you only define it and the macro will call it under the hood for you – as many times per second as the call to arm.SetupSystemTimer
above.
//go:export SysTick_Handler
func handleSystick() {
select {
case tickCh <- struct{}{}:
default:
}
}
Call Debounce in order to begin relaying the systicks to all bouncers you've set up. Pass it the channel to which your SysTick_Handler is sending.
go bouncer.Debounce(tickCh)
Subscribing bouncers to the relay is done internally by the package – simply call the package-level function Relay
as a goroutine and pass it the same channel tickCh
produced by our systick handler. Do not consume tickCh
in more than 1 place.