Skip to content
Open
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
56 changes: 26 additions & 30 deletions analog_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import (
)

const (
// Default number of analog reads to average over.
defaultSamples = 32

// Calibrated[Min|Max]AI was calculated using the EuroPi calibration program:
// https://github.com/Allen-Synthesis/EuroPi/blob/main/software/programming_instructions.md#calibrate-the-module
CalibratedMinAI = 300
CalibratedMaxAI = 44009

DefaultSamples = 1000
calibratedMinAI = 300
calibratedMaxAI = 44009
)

func init() {
Expand All @@ -26,85 +27,80 @@ type AnalogReader interface {
Range(steps uint16) uint16
}

// A struct for handling the reading of analogue control voltage.
// The analogue input allows you to 'read' CV from anywhere between 0 and 12V.
type AnalogInput struct {
machine.ADC
type analogInput struct {
adc machine.ADC
samples uint16
}

// NewAI creates a new AnalogInput.
func NewAI(pin machine.Pin) *AnalogInput {
func newAnalogInput(pin machine.Pin) *analogInput {
adc := machine.ADC{Pin: pin}
adc.Configure(machine.ADCConfig{})
return &AnalogInput{ADC: adc, samples: DefaultSamples}
return &analogInput{adc: adc, samples: defaultSamples}
}

// Samples sets the number of reads for an more accurate average read.
func (a *AnalogInput) Samples(samples uint16) {
func (a *analogInput) Samples(samples uint16) {
a.samples = samples
}

// Percent return the percentage of the input's current relative range as a float between 0.0 and 1.0.
func (a *AnalogInput) Percent() float32 {
return float32(a.read()) / CalibratedMaxAI
func (a *analogInput) Percent() float32 {
return float32(a.read()) / calibratedMaxAI
}

// ReadVoltage return the current read voltage between 0.0 and 10.0 volts.
func (a *AnalogInput) ReadVoltage() float32 {
func (a *analogInput) ReadVoltage() float32 {
return a.Percent() * MaxVoltage
}

// Range return a value between 0 and the given steps (not inclusive) based on the range of the analog input.
func (a *AnalogInput) Range(steps uint16) uint16 {
func (a *analogInput) Range(steps uint16) uint16 {
return uint16(a.Percent() * float32(steps))
}

func (a *AnalogInput) read() uint16 {
func (a *analogInput) read() uint16 {
var sum int
for i := 0; i < int(a.samples); i++ {
sum += Clamp(int(a.Get())-CalibratedMinAI, 0, CalibratedMaxAI)
sum += Clamp(int(a.adc.Get())-calibratedMinAI, 0, calibratedMaxAI)
}
return uint16(sum / int(a.samples))
}

// A struct for handling the reading of knob voltage and position.
type Knob struct {
machine.ADC
type knob struct {
adc machine.ADC
samples uint16
}

// NewKnob creates a new Knob struct.
func NewKnob(pin machine.Pin) *Knob {
func newKnob(pin machine.Pin) *knob {
adc := machine.ADC{Pin: pin}
adc.Configure(machine.ADCConfig{})
return &Knob{ADC: adc, samples: DefaultSamples}
return &knob{adc: adc, samples: defaultSamples}
}

// Samples sets the number of reads for an more accurate average read.
func (k *Knob) Samples(samples uint16) {
func (k *knob) Samples(samples uint16) {
k.samples = samples
}

// Percent return the percentage of the knob's current relative range as a float between 0.0 and 1.0.
func (k *Knob) Percent() float32 {
func (k *knob) Percent() float32 {
return 1 - float32(k.read())/math.MaxUint16
}

// ReadVoltage return the current read voltage between 0.0 and 10.0 volts.
func (k *Knob) ReadVoltage() float32 {
func (k *knob) ReadVoltage() float32 {
return k.Percent() * MaxVoltage
}

// Range return a value between 0 and the given steps (not inclusive) based on the range of the knob's position.
func (k *Knob) Range(steps uint16) uint16 {
func (k *knob) Range(steps uint16) uint16 {
return uint16(k.Percent() * float32(steps))
}

func (k *Knob) read() uint16 {
func (k *knob) read() uint16 {
var sum int
for i := 0; i < int(k.samples); i++ {
sum += int(k.Get())
sum += int(k.adc.Get())
}
return uint16(sum / int(k.samples))
}
87 changes: 28 additions & 59 deletions digital_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"time"
)

const DefaultDebounceDelay = time.Duration(50 * time.Millisecond)
const defaultDebounceDelay = time.Duration(50 * time.Millisecond)

// DigitalReader is an interface for common digital inputs methods.
type DigitalReader interface {
Expand All @@ -15,48 +15,46 @@ type DigitalReader interface {
Value() bool
}

// DigitalInput is a struct for handling reading of the digital input.
type DigitalInput struct {
Pin machine.Pin
type digitalReader struct {
pin machine.Pin
debounceDelay time.Duration
lastInput time.Time
callback func(p machine.Pin)
callback func(machine.Pin)
}

// NewDI creates a new DigitalInput struct.
func NewDI(pin machine.Pin) *DigitalInput {
func newDigitalReader(pin machine.Pin) *digitalReader {
pin.Configure(machine.PinConfig{Mode: machine.PinInputPulldown})
return &DigitalInput{
Pin: pin,
return &digitalReader{
pin: pin,
lastInput: time.Now(),
debounceDelay: DefaultDebounceDelay,
debounceDelay: defaultDebounceDelay,
}
}

// LastInput return the time of the last high input (triggered at 0.8v).
func (d *DigitalInput) LastInput() time.Time {
func (d *digitalReader) LastInput() time.Time {
return d.lastInput
}

// Value returns true if the input is high (above 0.8v), else false.
func (d *DigitalInput) Value() bool {
func (d *digitalReader) Value() bool {
// Invert signal to match expected behavior.
return !d.Pin.Get()
return !d.pin.Get()
}

// Handler sets the callback function to be call when a rising edge is detected.
func (d *DigitalInput) Handler(handler func(p machine.Pin)) {
d.HandlerWithDebounce(handler, 0)
func (d *digitalReader) Handler(callback func(machine.Pin)) {
d.HandlerWithDebounce(callback, 0)
}

// Handler sets the callback function to be call when a rising edge is detected and debounce delay time has elapsed.
func (d *DigitalInput) HandlerWithDebounce(handler func(p machine.Pin), delay time.Duration) {
d.callback = handler
func (d *digitalReader) HandlerWithDebounce(callback func(machine.Pin), delay time.Duration) {
d.callback = callback
d.debounceDelay = delay
d.Pin.SetInterrupt(machine.PinFalling, d.debounceWrapper)
d.pin.SetInterrupt(machine.PinFalling, d.debounceWrapper)
}

func (d *DigitalInput) debounceWrapper(p machine.Pin) {
func (d *digitalReader) debounceWrapper(p machine.Pin) {
t := time.Now()
if t.Before(d.lastInput.Add(d.debounceDelay)) {
return
Expand All @@ -65,52 +63,23 @@ func (d *DigitalInput) debounceWrapper(p machine.Pin) {
d.lastInput = t
}

// Button is a struct for handling push button behavior.
type Button struct {
Pin machine.Pin
debounceDelay time.Duration
lastInput time.Time
callback func(p machine.Pin)
type digitalInput struct {
DigitalReader
}

// NewButton creates a new Button struct.
func NewButton(pin machine.Pin) *Button {
pin.Configure(machine.PinConfig{Mode: machine.PinInputPulldown})
return &Button{
Pin: pin,
lastInput: time.Now(),
debounceDelay: DefaultDebounceDelay,
func newDigitalInput(pin machine.Pin) *digitalInput {
return &digitalInput{
newDigitalReader(pin),
}
}

// Handler sets the callback function to be call when the button is pressed.
func (b *Button) Handler(handler func(p machine.Pin)) {
b.HandlerWithDebounce(handler, 0)
type button struct {
DigitalReader
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unexport this field maybe so that users can't access the field through EuroPi. I'd prefer something like this be set on EuroPi initialization, maybe more on that in another comment.

}

// Handler sets the callback function to be call when the button is pressed and debounce delay time has elapsed.
func (b *Button) HandlerWithDebounce(handler func(p machine.Pin), delay time.Duration) {
b.callback = handler
b.debounceDelay = delay
b.Pin.SetInterrupt(machine.PinFalling, b.debounceWrapper)
}

func (b *Button) debounceWrapper(p machine.Pin) {
t := time.Now()
if t.Before(b.lastInput.Add(b.debounceDelay)) {
return
func newButton(pin machine.Pin) *button {
pin.Configure(machine.PinConfig{Mode: machine.PinInputPulldown})
return &button{
newDigitalReader(pin),
}
b.callback(p)
b.lastInput = t
}

// LastInput return the time of the last button press.
func (b *Button) LastInput() time.Time {
return b.lastInput
}

// Value returns true if button is currently pressed, else false.
func (b *Button) Value() bool {
// Invert signal to match expected behavior.
return !b.Pin.Get()
}
22 changes: 10 additions & 12 deletions display.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,40 +17,38 @@ const (
)

var (
DefaultChannel = machine.I2C0
DefaultFont = &proggy.TinySZ8pt7b
White = color.RGBA{255, 255, 255, 255}
DefaultFont = &proggy.TinySZ8pt7b
White = color.RGBA{255, 255, 255, 255}
)

// Display is a wrapper around `ssd1306.Device` for drawing graphics and text to the OLED.
type Display struct {
type display struct {
ssd1306.Device
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another exported field on an unexported type which is readily accesible to users.


font *tinyfont.Font
}

// NewDisplay returns a new Display struct.
func NewDisplay(channel *machine.I2C, sdaPin, sclPin machine.Pin) *Display {
func newDisplay(channel *machine.I2C, sdaPin, sclPin machine.Pin) *display {
channel.Configure(machine.I2CConfig{
Frequency: OLEDFreq,
SDA: sdaPin,
SCL: sclPin,
})

display := ssd1306.NewI2C(DefaultChannel)
display.Configure(ssd1306.Config{
d := ssd1306.NewI2C(channel)
d.Configure(ssd1306.Config{
Address: OLEDAddr,
Width: OLEDWidth,
Height: OLEDHeight,
})
return &Display{Device: display, font: DefaultFont}
return &display{Device: d, font: DefaultFont}
}

// Font overrides the default font used by `WriteLine`.
func (d *Display) Font(font *tinyfont.Font) {
func (d *display) Font(font *tinyfont.Font) {
d.font = font
}

// WriteLine writes the given text to the display where x, y is the bottom leftmost pixel of the text.
func (d *Display) WriteLine(text string, x, y int16) {
func (d *display) WriteLine(text string, x, y int16) {
tinyfont.WriteLine(d, d.font, x, y, text, White)
}
Loading