You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
devices/ads1x15/ads1x15.go

502 lines
12 KiB
Go

// Copyright 2018 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 ads1x15
import (
"encoding/binary"
"errors"
"fmt"
"math"
"sync"
"time"
"periph.io/x/conn/analog"
"periph.io/x/conn/i2c"
"periph.io/x/conn/physic"
"periph.io/x/conn/pin"
)
// I2CAddr is the default I2C address for the ADS1x15 components.
const I2CAddr uint16 = 0x48
// Channel is the analog reading to do. It can be either an absolute reading or
// a differential reading between two pins.
type Channel int
// Value channels.
const (
// Absolute reading.
Channel0 Channel = 4
Channel1 Channel = 5
Channel2 Channel = 6
Channel3 Channel = 7
// Differential reading.
Channel0Minus1 Channel = 0
Channel0Minus3 Channel = 1
Channel1Minus3 Channel = 2
Channel2Minus3 Channel = 3
)
func (c Channel) String() string {
switch c {
case Channel0:
return "0"
case Channel1:
return "1"
case Channel2:
return "2"
case Channel3:
return "3"
case Channel0Minus1:
return "0-1"
case Channel0Minus3:
return "0-3"
case Channel1Minus3:
return "1-3"
case Channel2Minus3:
return "2-3"
default:
return "Invalid"
}
}
func (c Channel) number() int {
switch c {
case Channel0:
return 0
case Channel1:
return 1
case Channel2:
return 2
case Channel3:
return 3
case Channel0Minus1:
return 4
case Channel0Minus3:
return 5
case Channel1Minus3:
return 6
case Channel2Minus3:
return 7
default:
return -1
}
}
// ConversionQuality represents a request for a compromise between energy
// saving versus conversion quality.
type ConversionQuality int
const (
// SaveEnergy optimizes the power consumption of the ADC, at the expense of
// the quality by converting at the highest possible rate.
SaveEnergy ConversionQuality = 0
// BestQuality will use the lowest suitable data rate to reduce the impact of
// the noise on the reading.
BestQuality ConversionQuality = 1
)
// Opts holds the configuration options.
type Opts struct {
I2cAddress uint16
}
// DefaultOpts are the recommended default options.
var DefaultOpts = Opts{
I2cAddress: I2CAddr,
}
// PinADC represents a pin which is able to read an electric potential.
type PinADC interface {
analog.PinADC
// ReadContinuous opens a channel and reads continuously at the frequency the
// pin was configured for.
ReadContinuous() <-chan analog.Sample
}
// Dev is an handle to an ADS1015/ADS1115 ADC.
type Dev struct {
c i2c.Dev
name string
dataRates map[int]uint16
mu sync.Mutex // For executePreparedQuery()
}
// NewADS1015 creates a new driver for the ADS1015 (12-bit ADC).
func NewADS1015(i i2c.Bus, opts *Opts) (*Dev, error) {
return &Dev{
c: i2c.Dev{Bus: i, Addr: opts.I2cAddress},
name: "ADS1015",
dataRates: map[int]uint16{
128: 0x0000,
250: 0x0020,
490: 0x0040,
920: 0x0060,
1600: 0x0080,
2400: 0x00A0,
3300: 0x00C0,
},
}, nil
}
// NewADS1115 creates a new driver for the ADS1115 (16-bit ADC).
func NewADS1115(i i2c.Bus, opts *Opts) (*Dev, error) {
return &Dev{
c: i2c.Dev{Bus: i, Addr: opts.I2cAddress},
name: "ADS1115",
dataRates: map[int]uint16{
8: 0x0000,
16: 0x0020,
32: 0x0040,
64: 0x0060,
128: 0x0080,
250: 0x00A0,
475: 0x00C0,
860: 0x00E0,
},
}, nil
}
// String implements conn.Resource.
func (d *Dev) String() string {
return d.name
}
// Halt implements conn.Resource.
func (d *Dev) Halt() error {
return nil
}
// PinForChannel returns an AnalogPin for the requested channel at the
// requested frequency.
//
// The channel can either be an absolute reading or a differential one.
func (d *Dev) PinForChannel(c Channel, maxVoltage physic.ElectricPotential, f physic.Frequency, q ConversionQuality) (PinADC, error) {
// Determine the most appropriate gain
gain, err := d.bestGainForElectricPotential(maxVoltage)
if err != nil {
return nil, err
}
// Validate the gain.
gainConf, ok := gainConfig[gain]
if !ok {
return nil, errors.New("gain must be one of: 2/3, 1, 2, 4, 8, 16")
}
// Determine the voltage multiplier for this gain.
voltageMultiplier, ok := gainVoltage[gain]
if !ok {
return nil, errors.New("gain must be one of: 2/3, 1, 2, 4, 8, 16")
}
// Determine the most appropriate data rate.
dataRate, err := d.bestDataRateForFrequency(f, q)
if err != nil {
return nil, err
}
dataRateConf, ok := d.dataRates[dataRate]
if !ok {
// Write a nice error message in case the data rate is not found.
keys := []int{}
for k := range d.dataRates {
keys = append(keys, k)
}
return nil, fmt.Errorf("invalid data rate. Accepted values: %d", keys)
}
// Build the configuration value
var config uint16
config = ads1x15ConfigOsSingle // Go out of power-down mode for conversion.
// Specify mux value.
config |= uint16(c) << ads1x15ConfigMuxOffset
// Validate the passed in gain and then set it in the config.
config |= gainConf
// Set the mode (continuous or single shot).
config |= ads1x15ConfigModeSingle
// Set the data rate (this is controlled by the subclass as it differs
// between ADS1015 and ADS1115).
config |= dataRateConf
config |= ads1x15ConfigCompQueDisable // Disable comparator mode.
// Build the query to the ADC.
configBytes := [2]byte{}
binary.BigEndian.PutUint16(configBytes[:], config)
// The wait for the ADC sample to finish is based on the sample rate.
waitTime := time.Second / time.Duration(dataRate)
return &analogPin{
adc: d,
c: c,
query: [...]byte{ads1x15PointerConfig, configBytes[0], configBytes[1]},
voltageMultiplier: voltageMultiplier,
waitTime: waitTime,
requestedFrequency: f,
}, nil
}
func (d *Dev) executePreparedQuery(query []byte, waitTime time.Duration, voltageMultiplier physic.ElectricPotential) (analog.Sample, error) {
// Lock the ADC converter to avoid multiple simultaneous readings.
d.mu.Lock()
defer d.mu.Unlock()
// Send the config value to start the ADC conversion.
// Explicitly break the 16-bit value down to a big endian pair of bytes.
if err := d.c.Tx(query, nil); err != nil {
return analog.Sample{}, err
}
// Wait for the ADC sample to finish.
time.Sleep(waitTime)
// Retrieve the result.
data := []byte{0, 0}
if err := d.c.Tx([]byte{ads1x15PointerConversion}, data); err != nil {
return analog.Sample{}, err
}
// Convert the raw data into physical value.
raw := int16(binary.BigEndian.Uint16(data))
return analog.Sample{
Raw: int32(raw),
V: physic.ElectricPotential(raw) * voltageMultiplier / physic.ElectricPotential(1<<15),
}, nil
}
// bestGainForElectricPotential returns the gain the most adapted to read up to
// the specified difference of potential.
func (d *Dev) bestGainForElectricPotential(voltage physic.ElectricPotential) (int, error) {
var max physic.ElectricPotential
difference := physic.ElectricPotential(math.MaxInt64)
currentBestGain := -1
for key, value := range gainVoltage {
// We compute the maximum in case we need to display an error
if value > max {
max = value
}
newDiff := value - voltage
if newDiff >= 0 && newDiff < difference {
difference = newDiff
currentBestGain = key
}
}
if currentBestGain < 0 {
return 0, errors.New("maximum voltage which can be read is " + max.String())
}
return currentBestGain, nil
}
// bestDataRateForFrequency returns the gain the most data rate to read samples
// at least at the requested frequency.
func (d *Dev) bestDataRateForFrequency(f physic.Frequency, q ConversionQuality) (int, error) {
var max physic.Frequency
currentBestDataRate := -1
// In order to save energy, we are going to select the fastest conversion
// rate, as explained in the ADS1115 specifications: 9.4.3 Duty Cycling For
// Low Power.
// When searching for the best quality, we will select the slowest conversion
// rate which is still faster than the requested frequency.
var comparator func(physic.Frequency, physic.Frequency) bool
var difference physic.Frequency
switch q {
case SaveEnergy:
// Saving energy requires the maximum data rate
difference = physic.Frequency(-1)
comparator = func(newDiff physic.Frequency, difference physic.Frequency) bool { return newDiff > difference }
case BestQuality:
// Best quality requires the minimum difference between the target and the capability
difference = physic.Frequency(math.MaxInt64)
comparator = func(newDiff physic.Frequency, difference physic.Frequency) bool { return newDiff < difference }
default:
return 0, errors.New("unknown value for ConversionQuality")
}
for key := range d.dataRates {
freq := physic.Frequency(key) * physic.Hertz
// We compute the minimum in case we need to display an error
if freq > max {
max = freq
}
newDiff := freq - f
// Conversion rate slower than the requested frequency is not suitable
if newDiff < 0 {
continue
}
if comparator(newDiff, difference) {
difference = newDiff
currentBestDataRate = key
}
}
if currentBestDataRate < 0 {
return 0, errors.New("maximum frequency which can be read is " + max.String())
}
return currentBestDataRate, nil
}
//
const (
ads1x15PointerConversion = 0x00
ads1x15PointerConfig = 0x01
ads1x15PointerLowThreshold = 0x02
ads1x15PointerHighThreshold = 0x03
// Write: Set to start a single-conversion.
ads1x15ConfigOsSingle = 0x8000
ads1x15ConfigMuxOffset = 12
ads1x15ConfigModeContinuous = 0x0000
// Single shoot mode.
ads1x15ConfigModeSingle = 0x0100
ads1x15ConfigCompWindow = 0x0010
ads1x15ConfigCompAactiveHigh = 0x0008
ads1x15ConfigCompLatching = 0x0004
ads1x15ConfigCompQueDisable = 0x0003
)
var (
// Mapping of gain values to config register values.
gainConfig = map[int]uint16{
2 / 3: 0x0000,
1: 0x0200,
2: 0x0400,
4: 0x0600,
8: 0x0800,
16: 0x0A00,
}
gainVoltage = map[int]physic.ElectricPotential{
2 / 3: 6144 * physic.MilliVolt,
1: 4096 * physic.MilliVolt,
2: 2048 * physic.MilliVolt,
4: 1024 * physic.MilliVolt,
8: 512 * physic.MilliVolt,
16: 256 * physic.MilliVolt,
}
)
type analogPin struct {
// Immutable.
adc *Dev
c Channel
query [3]byte
voltageMultiplier physic.ElectricPotential
waitTime time.Duration
requestedFrequency physic.Frequency
// Mutable.
mu sync.Mutex
stop chan struct{}
}
// Range returns the maximum supported range [min, max] of the values.
func (p *analogPin) Range() (analog.Sample, analog.Sample) {
max := analog.Sample{Raw: math.MaxInt16, V: p.voltageMultiplier}
min := analog.Sample{Raw: -math.MaxInt16, V: -p.voltageMultiplier}
return min, max
}
// Read returns the current pin level.
func (p *analogPin) Read() (analog.Sample, error) {
return p.adc.executePreparedQuery(p.query[:], p.waitTime, p.voltageMultiplier)
}
func (p *analogPin) ReadContinuous() <-chan analog.Sample {
// We need to lock if there are multiple Halt or ReadContinuous
// calls simultaneously.
p.mu.Lock()
defer p.mu.Unlock()
// First release the current continuous reading if there is one
if p.stop != nil {
p.stop <- struct{}{}
p.stop = nil
}
reading := make(chan analog.Sample, 16)
p.stop = make(chan struct{})
t := time.NewTicker(p.requestedFrequency.Period())
go func(s <-chan struct{}) {
defer t.Stop()
defer close(reading)
for {
select {
case <-s:
return
case <-t.C:
value, err := p.Read()
if err != nil {
// In continuous mode, we'll ignore errors silently.
continue
}
reading <- value
}
}
}(p.stop)
return reading
}
func (p *analogPin) Name() string {
return p.adc.name + "(" + p.c.String() + ")"
}
func (p *analogPin) Number() int {
return p.c.number()
}
func (p *analogPin) Function() string {
return string(p.Func())
}
// Func implements pin.PinFunc.
func (p *analogPin) Func() pin.Func {
return analog.ADC
}
// SupportedFuncs implements pin.PinFunc.
func (p *analogPin) SupportedFuncs() []pin.Func {
return []pin.Func{analog.ADC}
}
// SetFunc implements pin.PinFunc.
func (p *analogPin) SetFunc(f pin.Func) error {
if f == analog.ADC {
return nil
}
return errors.New("pin function cannot be changed")
}
func (p *analogPin) Halt() error {
// We need to lock if there are multiple Halt or ReadContinuous
// calls simultaneously.
p.mu.Lock()
defer p.mu.Unlock()
if p.stop != nil {
p.stop <- struct{}{}
p.stop = nil
}
return nil
}
func (p *analogPin) String() string {
return p.Name()
}
var _ analog.PinADC = &analogPin{}
var _ pin.Pin = &analogPin{}
var _ pin.PinFunc = &analogPin{}