218 lines
5.7 KiB
Go
218 lines
5.7 KiB
Go
// Copyright 2017 The Periph Authors. All rights reserved.
|
|
// Use of this source code is governed under the Apache License, Version 2.0
|
|
// that can be found in the LICENSE file.
|
|
|
|
// Package gpiostream defines digital streams.
|
|
//
|
|
// Warning
|
|
//
|
|
// This package is still in flux as development is on-going.
|
|
package gpiostream
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"periph.io/x/periph/conn/gpio"
|
|
"periph.io/x/periph/conn/physic"
|
|
)
|
|
|
|
// Stream is the interface to define a generic stream.
|
|
type Stream interface {
|
|
// Frequency is the minimum data rate at which the binary stream is usable.
|
|
//
|
|
// For example, a bit stream may have a 10kHz data rate.
|
|
Frequency() physic.Frequency
|
|
// Duration of the binary stream. For infinitely looping streams, it is the
|
|
// duration of the non-looping part.
|
|
Duration() time.Duration
|
|
}
|
|
|
|
// BitStream is a stream of bits to be written or read.
|
|
type BitStream struct {
|
|
// Bits is a densely packed bitstream.
|
|
//
|
|
// The stream is required to be a multiple of 8 samples.
|
|
Bits []byte
|
|
// Freq is the rate at each the bit (not byte) stream should be processed.
|
|
Freq physic.Frequency
|
|
// LSBF when true means than Bits is in LSB-first. When false, the data is
|
|
// MSB-first.
|
|
//
|
|
// With MSBF, the first bit processed is the most significant one (0x80). For
|
|
// example, I²C, I2S PCM and SPI use MSB-first at the word level. This
|
|
// requires to pack words correctly.
|
|
//
|
|
// With LSBF, the first bit processed is the least significant one (0x01).
|
|
// For example, Ethernet uses LSB-first at the byte level and MSB-first at
|
|
// the word level.
|
|
LSBF bool
|
|
}
|
|
|
|
// Frequency implements Stream.
|
|
func (b *BitStream) Frequency() physic.Frequency {
|
|
return b.Freq
|
|
}
|
|
|
|
// Duration implements Stream.
|
|
func (b *BitStream) Duration() time.Duration {
|
|
if b.Freq == 0 {
|
|
return 0
|
|
}
|
|
return b.Freq.Duration() * time.Duration(len(b.Bits)*8)
|
|
}
|
|
|
|
// GoString implements fmt.GoStringer.
|
|
func (b *BitStream) GoString() string {
|
|
return fmt.Sprintf("&gpiostream.BitStream{Bits: %x, Freq:%s, LSBF:%t}", b.Bits, b.Freq, b.LSBF)
|
|
}
|
|
|
|
// EdgeStream is a stream of edges to be written.
|
|
//
|
|
// This struct is more efficient than BitStream for short repetitive pulses,
|
|
// like controlling a servo. A PWM can be created by specifying a slice of
|
|
// twice the same resolution and make it looping via a Program.
|
|
type EdgeStream struct {
|
|
// Edges is the list of Level change. It is assumed that the signal starts
|
|
// with gpio.High. Use a duration of 0 for Edges[0] to start with a Low
|
|
// instead of the default High.
|
|
//
|
|
// The value is a multiple of Res. Use a 0 value to 'extend' a continuous
|
|
// signal that lasts more than "2^16-1*Res" duration by skipping a pulse.
|
|
Edges []uint16
|
|
// Res is the minimum resolution at which the edges should be
|
|
// rasterized.
|
|
//
|
|
// The lower the value, the more memory shall be used when rasterized.
|
|
Freq physic.Frequency
|
|
}
|
|
|
|
// Frequency implements Stream.
|
|
func (e *EdgeStream) Frequency() physic.Frequency {
|
|
return e.Freq
|
|
}
|
|
|
|
// Duration implements Stream.
|
|
func (e *EdgeStream) Duration() time.Duration {
|
|
if e.Freq == 0 {
|
|
return 0
|
|
}
|
|
t := 0
|
|
for _, edge := range e.Edges {
|
|
t += int(edge)
|
|
}
|
|
return e.Freq.Duration() * time.Duration(t)
|
|
}
|
|
|
|
// Program is a loop of streams.
|
|
//
|
|
// This is itself a stream, it can be used to reduce memory usage when repeated
|
|
// patterns are used.
|
|
type Program struct {
|
|
Parts []Stream // Each part must be a BitStream, EdgeStream or Program
|
|
Loops int // Set to -1 to create an infinite loop
|
|
}
|
|
|
|
// Frequency implements Stream.
|
|
func (p *Program) Frequency() physic.Frequency {
|
|
if p.Loops == 0 {
|
|
return 0
|
|
}
|
|
var buf [16]physic.Frequency
|
|
freqs := buf[:0]
|
|
for _, part := range p.Parts {
|
|
if f := part.Frequency(); f != 0 {
|
|
freqs = insertFreq(freqs, f)
|
|
}
|
|
}
|
|
if len(freqs) == 0 {
|
|
return 0
|
|
}
|
|
f := freqs[0]
|
|
for i := 1; i < len(freqs); i++ {
|
|
if r := freqs[i]; r*2 < f {
|
|
break
|
|
}
|
|
// Take in account Nyquist rate. https://wikipedia.org/wiki/Nyquist_rate
|
|
f *= 2
|
|
}
|
|
return f
|
|
}
|
|
|
|
// Duration implements Stream.
|
|
func (p *Program) Duration() time.Duration {
|
|
if p.Loops == 0 {
|
|
return 0
|
|
}
|
|
var d time.Duration
|
|
for _, s := range p.Parts {
|
|
d += s.Duration()
|
|
}
|
|
if p.Loops > 1 {
|
|
d *= time.Duration(p.Loops)
|
|
}
|
|
return d
|
|
}
|
|
|
|
//
|
|
|
|
// PinIn allows to read a bit stream from a pin.
|
|
//
|
|
// Caveat
|
|
//
|
|
// This interface doesn't enable sampling multiple pins in a
|
|
// synchronized way or reading in a continuous uninterrupted way. As such, it
|
|
// should be considered experimental.
|
|
type PinIn interface {
|
|
// StreamIn reads for the pin at the specified resolution to fill the
|
|
// provided buffer.
|
|
//
|
|
// May only support a subset of the structs implementing Stream.
|
|
StreamIn(p gpio.Pull, b Stream) error
|
|
}
|
|
|
|
// PinOut allows to stream to a pin.
|
|
//
|
|
// The Stream may be a Program, a BitStream or an EdgeStream. If it is a
|
|
// Program that is an infinite loop, a separate goroutine can be used to cancel
|
|
// the program. In this case StreamOut() returns without an error.
|
|
//
|
|
// Caveat
|
|
//
|
|
// This interface doesn't enable streaming to multiple pins in a
|
|
// synchronized way or reading in a continuous uninterrupted way. As such, it
|
|
// should be considered experimental.
|
|
type PinOut interface {
|
|
StreamOut(s Stream) error
|
|
}
|
|
|
|
//
|
|
|
|
// insertFreq inserts in reverse order, highest frequency first.
|
|
func insertFreq(l []physic.Frequency, f physic.Frequency) []physic.Frequency {
|
|
i := search(len(l), func(i int) bool { return l[i] < f })
|
|
l = append(l, 0)
|
|
copy(l[i+1:], l[i:])
|
|
l[i] = f
|
|
return l
|
|
}
|
|
|
|
// search implements the same algorithm as sort.Search().
|
|
//
|
|
// It was extracted to to not depend on sort, which depends on reflect.
|
|
func search(n int, f func(int) bool) int {
|
|
lo := 0
|
|
for hi := n; lo < hi; {
|
|
if i := int(uint(lo+hi) >> 1); !f(i) {
|
|
lo = i + 1
|
|
} else {
|
|
hi = i
|
|
}
|
|
}
|
|
return lo
|
|
}
|
|
|
|
var _ Stream = &BitStream{}
|
|
var _ Stream = &EdgeStream{}
|
|
var _ Stream = &Program{}
|