mirror of https://github.com/periph/devices
parent
4c7310e315
commit
adae653577
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright 2025 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 mcp472x_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"periph.io/x/conn/v3/i2c/i2creg"
|
||||||
|
"periph.io/x/conn/v3/physic"
|
||||||
|
"periph.io/x/devices/v3/mcp472x"
|
||||||
|
"periph.io/x/host/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Example demonstrating how to initialize the MCP4728 and set an output
|
||||||
|
// voltage.
|
||||||
|
func Example() {
|
||||||
|
if _, err := host.Init(); err != nil {
|
||||||
|
log.Fatal("Error calling host.init()")
|
||||||
|
}
|
||||||
|
bus, err := i2creg.Open("")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer bus.Close()
|
||||||
|
// For a MCP4725, or to use VCC for the reference voltage, specify it as:
|
||||||
|
// 3_300 * physic.MilliVolt, etc.
|
||||||
|
dev, err := mcp472x.New(bus, mcp472x.DefaultAddress, mcp472x.MCP4728, mcp472x.MCP4728InternalRef)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Program channel 3 to output 512mV
|
||||||
|
op := mcp472x.SetOutputParam{DAC: 3, V: 512 * physic.MilliVolt, UseInternalRef: true}
|
||||||
|
err = dev.SetOutput(op)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
|
||||||
|
// Power down the channel
|
||||||
|
op.V = 0
|
||||||
|
op.PDMode = mcp472x.PDMode1K
|
||||||
|
_ = dev.SetOutput(op)
|
||||||
|
}
|
||||||
@ -0,0 +1,382 @@
|
|||||||
|
// Copyright 2025 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.
|
||||||
|
|
||||||
|
// This package provides a driver for the Microchip MCP472x Series of Digital
|
||||||
|
// to Analog converters. It works with the MCP4725 and MCP4728 chips. The
|
||||||
|
// MCP4728 shares a common command interface, but offers 4 outputs and has
|
||||||
|
// a precision internal voltage reference. The MCP4725 uses VCC for vRef.
|
||||||
|
//
|
||||||
|
// # Datasheets
|
||||||
|
//
|
||||||
|
// # MCP4725
|
||||||
|
//
|
||||||
|
// https://ww1.microchip.com/downloads/en/devicedoc/22039d.pdf
|
||||||
|
//
|
||||||
|
// # MCP4728
|
||||||
|
//
|
||||||
|
// https://www.digikey.com/htmldatasheets/production/623709/0/0/1/mcp4728.html
|
||||||
|
package mcp472x
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"periph.io/x/conn/v3/i2c"
|
||||||
|
"periph.io/x/conn/v3/physic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Variant represents the model of the device.
|
||||||
|
type Variant string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// The internal precision reference for the MCP4728
|
||||||
|
MCP4728InternalRef physic.ElectricPotential = 2048 * physic.MilliVolt
|
||||||
|
MCP4725 Variant = "MCP4725"
|
||||||
|
MCP4728 Variant = "MCP4728"
|
||||||
|
stepCount = 1 << 12 // 12-bit D/A
|
||||||
|
maxCount = stepCount - 1
|
||||||
|
|
||||||
|
// DefaultAddress is the default I²C address (0x60) for MCP472x devices.
|
||||||
|
DefaultAddress i2c.Addr = 0x60
|
||||||
|
// Number of output channels for each model.
|
||||||
|
Channels4728 = 4
|
||||||
|
Channels4725 = 1
|
||||||
|
|
||||||
|
boostBit byte = 0x10
|
||||||
|
busyFlag byte = 0x80
|
||||||
|
cmdInternalRef byte = 0x80
|
||||||
|
cmdMultiWrite byte = 0x40
|
||||||
|
cmdSingleWrite byte = 0x08
|
||||||
|
cmdWriteWithSave4725 byte = 0x60
|
||||||
|
cmdWriteWithSave4728 byte = 0x50
|
||||||
|
dacMask byte = 0x03
|
||||||
|
pdMask byte = 0x03
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errInvalidVoltage = errors.New("mcp472x: voltage out of range")
|
||||||
|
errBusy = errors.New("mcp472x: device busy")
|
||||||
|
errInvalidInputCount = errors.New("mcp472x: invalid number of inputs provided")
|
||||||
|
errInvalidVariant = errors.New("mcp472x: invalid variant")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Channel PowerDown mode.
|
||||||
|
type PDMode byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
PDModeNormal PDMode = iota
|
||||||
|
// The remaining values specify resistance value used to tie the output pin
|
||||||
|
// to ground.
|
||||||
|
PDMode1K
|
||||||
|
PDMode100K
|
||||||
|
PDMode500K
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dev represents an MCP472X D/A converter.
|
||||||
|
type Dev struct {
|
||||||
|
d i2c.Dev
|
||||||
|
variant Variant
|
||||||
|
maxChannels int
|
||||||
|
vRef physic.ElectricPotential
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOutputParam represents a parameter for programming a DAC output
|
||||||
|
// channel.
|
||||||
|
type SetOutputParam struct {
|
||||||
|
// DAC is the D/A channel number. Always 0 for MCP4725.
|
||||||
|
DAC byte
|
||||||
|
// V is the voltage to set the output to. For external reference, the
|
||||||
|
// value can be 0-VCC. For the MCP4728 using the internal precision
|
||||||
|
// reference, the value can be 0 - (2 * MCP4728_INTERNAL_REF)
|
||||||
|
V physic.ElectricPotential
|
||||||
|
// For the MCP4728 using the internal reference, you can boost the
|
||||||
|
// gain of the output to 2 * MCP4728_INTERNAL_REF, at the cost of
|
||||||
|
// resolution. vOut cannot exceed VCC. If you need vOut > 3.3V,
|
||||||
|
// ensure VCC=5v.
|
||||||
|
BoostGain bool
|
||||||
|
// True to use the internal reference. Used only for MCP4728.
|
||||||
|
UseInternalRef bool
|
||||||
|
// Powerdown mode for this channel
|
||||||
|
PDMode PDMode
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates and returns a representation of a MCP472X Digital to Analog
|
||||||
|
// converter. vRef sets the reference voltage used by the device. The value of
|
||||||
|
// vRef is used to convert a voltage parameter to a count value that the
|
||||||
|
// device internally uses for settings.
|
||||||
|
//
|
||||||
|
// For the MCP4728, the internal 2.048v reference can be used, or you can
|
||||||
|
// specify that VCC be used. For the internal reference, you can use the default
|
||||||
|
// gain which will have full scale voltage be 0-2.048v, or set gain to 2, which
|
||||||
|
// will make output be from 0-4.096v. Note that VCC must be 5V in this case.
|
||||||
|
//
|
||||||
|
// For the MCP4725, VCC is used as vRef.
|
||||||
|
func New(bus i2c.Bus, addr i2c.Addr, variant Variant, vRef physic.ElectricPotential) (*Dev, error) {
|
||||||
|
if variant != MCP4725 && variant != MCP4728 {
|
||||||
|
return nil, errInvalidVariant
|
||||||
|
}
|
||||||
|
d := &Dev{
|
||||||
|
d: i2c.Dev{Bus: bus, Addr: uint16(addr)},
|
||||||
|
variant: variant,
|
||||||
|
vRef: vRef,
|
||||||
|
maxChannels: Channels4728,
|
||||||
|
}
|
||||||
|
if variant == MCP4725 {
|
||||||
|
d.maxChannels = Channels4725
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PotentialToCount converts the specified voltage to the count for the
|
||||||
|
// A/D converter. It returns the required count, whether boostGain should
|
||||||
|
// be enabled. If the voltage is negative, or otherwise out of range, an
|
||||||
|
// error is returned. The count will roughly be v/(vRef/4095).
|
||||||
|
func (d *Dev) PotentialToCount(v physic.ElectricPotential) (uint16, bool, error) {
|
||||||
|
if (v < 0) || (v > d.vRef && d.variant == MCP4725) || (v > (2 * d.vRef)) {
|
||||||
|
return 0, false, errInvalidVoltage
|
||||||
|
}
|
||||||
|
boost := false
|
||||||
|
stepValue := d.vRef / maxCount
|
||||||
|
count := uint16(float64(v)/float64(stepValue) + 0.5)
|
||||||
|
if count > maxCount && d.variant == MCP4728 {
|
||||||
|
boost = true
|
||||||
|
count = count >> 1
|
||||||
|
}
|
||||||
|
return count, boost, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the current and EEPROM registers for a 4725. The bit structure is
|
||||||
|
// different between the two models...
|
||||||
|
func (d *Dev) convert4725(bytes []byte) ([]SetOutputParam, []SetOutputParam, error) {
|
||||||
|
busy := bytes[0]&busyFlag == 0x0
|
||||||
|
step := float64(d.vRef) / float64(maxCount)
|
||||||
|
count := float64((uint16(bytes[1]&0xf0) << 4) | (uint16(bytes[1]&0x0f) << 4) | (uint16(bytes[2]) >> 4))
|
||||||
|
op := SetOutputParam{V: physic.ElectricPotential(step * count)}
|
||||||
|
op.PDMode = PDMode((bytes[0] >> 1) & pdMask)
|
||||||
|
current := []SetOutputParam{op}
|
||||||
|
|
||||||
|
count = float64(uint16(bytes[4]) | (uint16(bytes[3]&0x0f) << 8))
|
||||||
|
op = SetOutputParam{V: physic.ElectricPotential(step * count)}
|
||||||
|
op.PDMode = PDMode((bytes[3] >> 5) & pdMask)
|
||||||
|
eeprom := []SetOutputParam{op}
|
||||||
|
var err error
|
||||||
|
if busy {
|
||||||
|
err = errBusy
|
||||||
|
}
|
||||||
|
return current, eeprom, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert4728 converts the bytes read for current and EEPROM registers for
|
||||||
|
// an MCP4728 to SetOutputParams Refer to the datasheet for more information.
|
||||||
|
func (d *Dev) convert4728(bytes []byte) ([]SetOutputParam, []SetOutputParam, error) {
|
||||||
|
step := d.vRef / maxCount
|
||||||
|
current, eeprom := make([]SetOutputParam, 0), make([]SetOutputParam, 0)
|
||||||
|
pos := 0
|
||||||
|
vals := make([]SetOutputParam, 2)
|
||||||
|
busy := false
|
||||||
|
// A-D
|
||||||
|
for channelID := range d.maxChannels {
|
||||||
|
// Current Output Parameters, and EEPROM Parameters
|
||||||
|
for i := range 2 {
|
||||||
|
busy = busy || bytes[pos]&busyFlag == 0x0
|
||||||
|
count := physic.ElectricPotential(uint16(bytes[pos+2]) | uint16(bytes[pos+1]&0x0f)<<8)
|
||||||
|
pdMode := PDMode(bytes[pos+1] >> 5 & pdMask)
|
||||||
|
boost := bytes[pos+1]&boostBit == boostBit
|
||||||
|
vref := bytes[pos+1]&cmdInternalRef == cmdInternalRef
|
||||||
|
if boost && vref {
|
||||||
|
// Boost Gain is turned on, and it's using the internal reference, so
|
||||||
|
// double the count...
|
||||||
|
count *= 2
|
||||||
|
}
|
||||||
|
vals[i] = SetOutputParam{
|
||||||
|
DAC: byte(channelID),
|
||||||
|
V: physic.ElectricPotential(step * count),
|
||||||
|
PDMode: pdMode,
|
||||||
|
BoostGain: boost,
|
||||||
|
UseInternalRef: vref,
|
||||||
|
}
|
||||||
|
pos += 3
|
||||||
|
}
|
||||||
|
current = append(current, vals[0])
|
||||||
|
eeprom = append(eeprom, vals[1])
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if busy {
|
||||||
|
err = errBusy
|
||||||
|
}
|
||||||
|
return current, eeprom, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FastWrite sends raw A/D count values to the converter. Bits 0-11 represent,
|
||||||
|
// the count, and bits 12 and 13 the PowerDown mode, if applicable. Use
|
||||||
|
// PotentialToCount to convert a specific voltage value to the count value.
|
||||||
|
//
|
||||||
|
// The number of values supplied must exactly match the number of channels
|
||||||
|
// supported by the device.
|
||||||
|
func (d *Dev) FastWrite(values ...uint16) (err error) {
|
||||||
|
if len(values) != d.maxChannels {
|
||||||
|
err = errInvalidInputCount
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w := make([]byte, len(values)*2)
|
||||||
|
for ix, val := range values {
|
||||||
|
w[ix*2] = byte(val>>8) & 0x3f // mask off the two high bits.
|
||||||
|
w[ix*2+1] = byte(val & 0xff)
|
||||||
|
}
|
||||||
|
err = d.d.Tx(w, nil)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("mcp472x: %w", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOutput reads the configured output values and programmed EEPROM values,
|
||||||
|
// and returns the results. If the device signals it is busy with an EEPROM
|
||||||
|
// write, the function will retry up to 9 times to read values.
|
||||||
|
func (d *Dev) GetOutput() (current []SetOutputParam, eeprom []SetOutputParam, err error) {
|
||||||
|
var r []byte
|
||||||
|
bytesChannel := 5
|
||||||
|
if d.variant == MCP4728 {
|
||||||
|
bytesChannel = 6
|
||||||
|
}
|
||||||
|
r = make([]byte, bytesChannel*d.maxChannels)
|
||||||
|
for range 10 {
|
||||||
|
err = d.d.Tx(nil, r)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("mcp472x: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if d.variant == MCP4725 {
|
||||||
|
current, eeprom, err = d.convert4725(r)
|
||||||
|
} else {
|
||||||
|
current, eeprom, err = d.convert4728(r)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// The device is busy with an eeprom write. Wait and try again.
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOutput sets the output of the specified output channels. For an MCP4725, you may
|
||||||
|
// pass only one parameter.
|
||||||
|
func (d *Dev) SetOutput(params ...SetOutputParam) (err error) {
|
||||||
|
if len(params) == 0 || len(params) > d.maxChannels {
|
||||||
|
err = errInvalidInputCount
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.variant == MCP4725 {
|
||||||
|
w := d.paramToBytes(¶ms[0])
|
||||||
|
err = d.d.Tx(w, nil)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("mcp472x: %w", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var w []byte
|
||||||
|
w = make([]byte, 0)
|
||||||
|
for _, param := range params {
|
||||||
|
bytes := d.paramToBytes(¶m)
|
||||||
|
w = append(w, bytes...)
|
||||||
|
}
|
||||||
|
w[0] |= cmdMultiWrite
|
||||||
|
err = d.d.Tx(w, nil)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("mcp472x: error writing to device: %w", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOutputWithSave sets the channel output values AND saves the values to
|
||||||
|
// EEPROM. On power-up, the output value will be set to the saved settings.
|
||||||
|
func (d *Dev) SetOutputWithSave(params ...SetOutputParam) (err error) {
|
||||||
|
if len(params) == 0 || len(params) > d.maxChannels {
|
||||||
|
err = errInvalidInputCount
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var w []byte
|
||||||
|
for _, param := range params {
|
||||||
|
bytes := d.paramToBytes(¶m)
|
||||||
|
w = append(w, bytes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.variant == MCP4725 {
|
||||||
|
w[0] |= cmdWriteWithSave4725
|
||||||
|
err = d.d.Tx(w, nil)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("mcp472x: error writing to device: %w", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w[0] |= cmdWriteWithSave4728
|
||||||
|
if len(params) == 1 {
|
||||||
|
w[0] |= cmdSingleWrite
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.d.Tx(w, nil)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("mcp472x: error writing to device: %w", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements conn.Resource
|
||||||
|
func (d *Dev) Halt() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the variant name
|
||||||
|
func (d *Dev) String() string {
|
||||||
|
return string(d.variant)
|
||||||
|
}
|
||||||
|
|
||||||
|
// paramToBytes converts a SetOutputParam to the appropriate bit structure for
|
||||||
|
// write. The bit structure for writes is different depending on the variant.
|
||||||
|
func (d *Dev) paramToBytes(op *SetOutputParam) (bytes []byte) {
|
||||||
|
count, boost, _ := d.PotentialToCount(op.V)
|
||||||
|
if d.variant == MCP4725 {
|
||||||
|
b := cmdMultiWrite
|
||||||
|
b |= (byte(op.PDMode&PDMode(pdMask)) << 1)
|
||||||
|
bytes = append(bytes, b)
|
||||||
|
bytes = append(bytes, byte(count>>4)&0xff)
|
||||||
|
bytes = append(bytes, byte(count<<4)&0xf0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 4728
|
||||||
|
// set the DAC Channel bits, and UDAC
|
||||||
|
bytes = append(bytes, byte(((op.DAC&dacMask)<<1)&0xff))
|
||||||
|
b := byte(count>>8) & 0xff
|
||||||
|
if op.UseInternalRef {
|
||||||
|
b |= cmdInternalRef
|
||||||
|
}
|
||||||
|
if boost {
|
||||||
|
b |= boostBit
|
||||||
|
}
|
||||||
|
|
||||||
|
b |= byte(op.PDMode&PDMode(pdMask)) << 5
|
||||||
|
bytes = append(bytes, b)
|
||||||
|
bytes = append(bytes, byte(count&0xff))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal compares two SetOutputParam values for equality.
|
||||||
|
func (op SetOutputParam) Equal(op2 SetOutputParam) bool {
|
||||||
|
return op.V == op2.V &&
|
||||||
|
op.BoostGain == op2.BoostGain &&
|
||||||
|
op.UseInternalRef == op2.UseInternalRef &&
|
||||||
|
op.PDMode == op2.PDMode
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a JSON representation of the Output Parameter
|
||||||
|
func (op SetOutputParam) String() string {
|
||||||
|
bytes, _ := json.Marshal(&op)
|
||||||
|
return string(bytes)
|
||||||
|
}
|
||||||
@ -0,0 +1,402 @@
|
|||||||
|
// Copyright 2025 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 mcp472x
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"periph.io/x/conn/v3/i2c/i2ctest"
|
||||||
|
"periph.io/x/conn/v3/physic"
|
||||||
|
)
|
||||||
|
|
||||||
|
var liveDevice bool
|
||||||
|
|
||||||
|
func getDev(testName string, variant Variant, vRef physic.ElectricPotential) (*Dev, error) {
|
||||||
|
addr := DefaultAddress
|
||||||
|
if variant == MCP4725 {
|
||||||
|
addr = 0x62
|
||||||
|
}
|
||||||
|
d, err := New(&i2ctest.Playback{Ops: recordingData[testName], DontPanic: true}, addr, variant, vRef)
|
||||||
|
return d, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasic(t *testing.T) {
|
||||||
|
d, err := New(nil, 0, MCP4728, MCP4728InternalRef)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := d.String()
|
||||||
|
if len(s) == 0 {
|
||||||
|
t.Error("expected string received \"\"")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPotentialToCount(t *testing.T) {
|
||||||
|
|
||||||
|
d, err := New(nil, 0, MCP4728, MCP4728InternalRef)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
count, boost, err := d.PotentialToCount(0)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if count != 0 {
|
||||||
|
t.Errorf("v=0, count=%d", count)
|
||||||
|
}
|
||||||
|
if boost {
|
||||||
|
t.Errorf("v=0 boost=%t", boost)
|
||||||
|
}
|
||||||
|
|
||||||
|
count, boost, err = d.PotentialToCount(MCP4728InternalRef)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if count != maxCount {
|
||||||
|
t.Errorf("v=%s, count=%d", MCP4728InternalRef, count)
|
||||||
|
}
|
||||||
|
if boost {
|
||||||
|
t.Errorf("count=%d boost=%t", count, boost)
|
||||||
|
}
|
||||||
|
count, boost, _ = d.PotentialToCount(MCP4728InternalRef >> 1)
|
||||||
|
if count != (stepCount >> 1) {
|
||||||
|
t.Errorf("invalid count expected %d received %d", (stepCount>>1)-1, count)
|
||||||
|
}
|
||||||
|
if boost {
|
||||||
|
t.Errorf("count=%d boost=%t", count, boost)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = d.PotentialToCount(physic.ElectricPotential(-1))
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error on negative voltage")
|
||||||
|
}
|
||||||
|
_, _, err = d.PotentialToCount(3 * MCP4728InternalRef)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error on out of range voltage")
|
||||||
|
}
|
||||||
|
_, boost, _ = d.PotentialToCount(4 * physic.Volt)
|
||||||
|
if !boost {
|
||||||
|
t.Error("expected boost for 4v output")
|
||||||
|
}
|
||||||
|
d, _ = New(nil, 0, MCP4725, 3300*physic.MilliVolt)
|
||||||
|
_, _, err = d.PotentialToCount(5 * physic.Volt)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error on out of range voltage")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOutputParams4725(t *testing.T) {
|
||||||
|
d, err := New(nil, 0, MCP4725, 3_300*physic.MilliVolt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
outputParam SetOutputParam
|
||||||
|
expectedBytes []byte
|
||||||
|
}{
|
||||||
|
{SetOutputParam{DAC: 0,
|
||||||
|
V: 0,
|
||||||
|
PDMode: PDMode1K,
|
||||||
|
},
|
||||||
|
[]byte{0x42, 0x0, 0x0},
|
||||||
|
},
|
||||||
|
{SetOutputParam{DAC: 1,
|
||||||
|
V: MCP4728InternalRef >> 1,
|
||||||
|
PDMode: PDMode500K,
|
||||||
|
},
|
||||||
|
[]byte{0x46, 0x4f, 0x70},
|
||||||
|
},
|
||||||
|
{SetOutputParam{DAC: 2,
|
||||||
|
V: MCP4728InternalRef >> 2,
|
||||||
|
PDMode: PDModeNormal,
|
||||||
|
},
|
||||||
|
[]byte{0x40, 0x27, 0xb0},
|
||||||
|
},
|
||||||
|
{SetOutputParam{DAC: 3,
|
||||||
|
V: MCP4728InternalRef >> 9,
|
||||||
|
PDMode: PDMode100K,
|
||||||
|
},
|
||||||
|
[]byte{0x44, 0x0, 0x50},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
bytes := d.paramToBytes(&tc.outputParam)
|
||||||
|
if len(bytes) == len(tc.expectedBytes) {
|
||||||
|
for ix := range bytes {
|
||||||
|
if bytes[ix] != tc.expectedBytes[ix] {
|
||||||
|
t.Errorf("for OutputParam %s, byte %d got 0x%x %.8b expected 0x%x %.8b", tc.outputParam.String(), ix, bytes[ix], bytes[ix], tc.expectedBytes[ix], tc.expectedBytes[ix])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("testcase %s expected %d bytes, received %d", tc.outputParam.String(), len(tc.expectedBytes), len(bytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Equality
|
||||||
|
d1 := SetOutputParam{DAC: 1, V: physic.Volt, UseInternalRef: true, BoostGain: false}
|
||||||
|
d2 := SetOutputParam{DAC: 1, V: physic.Volt, UseInternalRef: true, BoostGain: false}
|
||||||
|
if !d1.Equal(d2) {
|
||||||
|
t.Errorf("expected d1==d2")
|
||||||
|
}
|
||||||
|
// Test Inequality
|
||||||
|
d1.V = physic.MilliVolt
|
||||||
|
if d1.Equal(d2) {
|
||||||
|
t.Errorf("expected d1!=d2")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the String method works and we can unmarshal the JSON into
|
||||||
|
// a new SetOutputParam
|
||||||
|
s2 := d2.String()
|
||||||
|
d3 := SetOutputParam{}
|
||||||
|
err = json.Unmarshal([]byte(s2), &d3)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if !d3.Equal(d2) {
|
||||||
|
t.Errorf("Expected unmarshal from string to equal original\ns2=%s, d3=%s", s2, d3.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOutputParams4728(t *testing.T) {
|
||||||
|
d, err := New(nil, 0, MCP4728, MCP4728InternalRef)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
outputParam SetOutputParam
|
||||||
|
expectedBytes []byte
|
||||||
|
}{
|
||||||
|
{SetOutputParam{DAC: 0,
|
||||||
|
V: 0,
|
||||||
|
BoostGain: false,
|
||||||
|
UseInternalRef: true,
|
||||||
|
PDMode: PDMode1K,
|
||||||
|
},
|
||||||
|
[]byte{0x0, 0xa0, 0x0},
|
||||||
|
},
|
||||||
|
{SetOutputParam{DAC: 1,
|
||||||
|
V: MCP4728InternalRef >> 1,
|
||||||
|
BoostGain: false,
|
||||||
|
UseInternalRef: true,
|
||||||
|
PDMode: PDMode500K,
|
||||||
|
},
|
||||||
|
[]byte{0x02, 0xe8, 0x00},
|
||||||
|
},
|
||||||
|
{SetOutputParam{DAC: 2,
|
||||||
|
V: MCP4728InternalRef >> 2,
|
||||||
|
PDMode: PDModeNormal,
|
||||||
|
},
|
||||||
|
[]byte{0x04, 0x04, 0x00},
|
||||||
|
},
|
||||||
|
{SetOutputParam{DAC: 3,
|
||||||
|
V: MCP4728InternalRef >> 6,
|
||||||
|
PDMode: PDMode100K,
|
||||||
|
},
|
||||||
|
[]byte{0x06, 0x40, 0x40},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
bytes := d.paramToBytes(&tc.outputParam)
|
||||||
|
if len(bytes) == len(tc.expectedBytes) {
|
||||||
|
for ix := range bytes {
|
||||||
|
if bytes[ix] != tc.expectedBytes[ix] {
|
||||||
|
t.Errorf("for OutputParam %s, byte %d got 0x%x expected 0x%x", tc.outputParam.String(), ix, bytes[ix], tc.expectedBytes[ix])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("testcase %s expected %d bytes, received %d", tc.outputParam.String(), len(tc.expectedBytes), len(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSetGetOutput(d *Dev, t *testing.T) {
|
||||||
|
outputs := make([]SetOutputParam, d.maxChannels)
|
||||||
|
for i := range physic.ElectricPotential(d.maxChannels) {
|
||||||
|
outputs[i] = SetOutputParam{DAC: byte(i), V: (i + 1) * 256 * physic.MilliVolt, UseInternalRef: true, PDMode: PDModeNormal}
|
||||||
|
t.Logf("outputs[%d]=%s", i, outputs[i].String())
|
||||||
|
}
|
||||||
|
err := d.SetOutput(outputs...)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cur, eeprom, err := d.GetOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Logf("cur=%#v cur[0].V=%s", cur, cur[0].V.String())
|
||||||
|
if len(cur) != d.maxChannels {
|
||||||
|
t.Errorf("expected %d channels of current output values", d.maxChannels)
|
||||||
|
}
|
||||||
|
|
||||||
|
for ix, op := range outputs {
|
||||||
|
curChannel := cur[ix]
|
||||||
|
diffMilliVolt := math.Abs(float64((curChannel.V - op.V) / physic.MilliVolt))
|
||||||
|
if diffMilliVolt > 2.0 {
|
||||||
|
t.Errorf("Channel %d Read after program. Expected < 2mV, got %f", op.DAC, diffMilliVolt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("eeprom=%#v", eeprom)
|
||||||
|
if len(eeprom) != d.maxChannels {
|
||||||
|
t.Errorf("expected %d channels of eeprom output values", d.maxChannels)
|
||||||
|
}
|
||||||
|
if liveDevice {
|
||||||
|
time.Sleep(30 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSetEEPROM(d *Dev, dac byte, t *testing.T) {
|
||||||
|
op := SetOutputParam{DAC: dac, V: 512 * physic.MilliVolt, UseInternalRef: true, PDMode: PDModeNormal}
|
||||||
|
for range 2 {
|
||||||
|
err := d.SetOutputWithSave(op)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cur, eeprom, err := d.GetOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
curChannel := cur[op.DAC]
|
||||||
|
diffMilliVolt := math.Abs(float64((curChannel.V - op.V) / physic.MilliVolt))
|
||||||
|
if diffMilliVolt > 2.0 {
|
||||||
|
t.Errorf("Read after program. Expected difference <2mV, got %f", diffMilliVolt)
|
||||||
|
}
|
||||||
|
|
||||||
|
curEeprom := eeprom[op.DAC]
|
||||||
|
diffMilliVolt = math.Abs(float64((curEeprom.V - op.V) / physic.MilliVolt))
|
||||||
|
if diffMilliVolt > 2.0 {
|
||||||
|
t.Errorf("Read EEPROM after program. Expected <2mV, got %f", diffMilliVolt)
|
||||||
|
}
|
||||||
|
if curEeprom.PDMode != op.PDMode {
|
||||||
|
t.Errorf("Read EEPROM after program. Expected PDMode %d, got %d", op.PDMode, curEeprom.PDMode)
|
||||||
|
}
|
||||||
|
op.V *= 2
|
||||||
|
op.PDMode += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFastWrite(d *Dev, t *testing.T) {
|
||||||
|
t.Logf("testFastWrite(variant=%s)", d.variant)
|
||||||
|
|
||||||
|
vals := make([]uint16, d.maxChannels)
|
||||||
|
t.Log("Writing 0V to all channels")
|
||||||
|
err := d.FastWrite(vals...)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cur, _, err := d.GetOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
for ix, val := range cur {
|
||||||
|
if val.V != 0 {
|
||||||
|
t.Errorf("Channel %d expected 0V, got %s", ix, val.V)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if liveDevice {
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, write 768mV to all channels.
|
||||||
|
vTest := 768 * physic.MilliVolt
|
||||||
|
count, _, _ := d.PotentialToCount(vTest)
|
||||||
|
for ix := range len(vals) {
|
||||||
|
vals[ix] = count
|
||||||
|
}
|
||||||
|
t.Logf("Writing %s to all channels", vTest)
|
||||||
|
err = d.FastWrite(vals...)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cur, _, err = d.GetOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
for ix := range len(cur) {
|
||||||
|
diffMillivolts := math.Abs(float64((cur[ix].V - vTest) / physic.MilliVolt))
|
||||||
|
if diffMillivolts > 2 {
|
||||||
|
t.Errorf("Channel %d expected %s, received %s", ix, vTest, cur[ix].V)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if liveDevice {
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetGetOutput4728(t *testing.T) {
|
||||||
|
d, err := getDev("TestSetGetOutput4728", MCP4728, MCP4728InternalRef)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testSetGetOutput(d, t)
|
||||||
|
|
||||||
|
err = d.SetOutput()
|
||||||
|
if err == nil || err != errInvalidInputCount {
|
||||||
|
t.Error("SetOutput() failed to error on zero outputs")
|
||||||
|
}
|
||||||
|
err = d.SetOutput(SetOutputParam{}, SetOutputParam{}, SetOutputParam{}, SetOutputParam{}, SetOutputParam{})
|
||||||
|
if err == nil || err != errInvalidInputCount {
|
||||||
|
t.Error("SetOutput() failed to error on too many outputs")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetEEPROM4728(t *testing.T) {
|
||||||
|
d, err := getDev("TestSetEEPROM4728", MCP4728, MCP4728InternalRef)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testSetEEPROM(d, 2, t)
|
||||||
|
|
||||||
|
err = d.SetOutputWithSave()
|
||||||
|
if err == nil || err != errInvalidInputCount {
|
||||||
|
t.Error("SetOutputWithSave() failed to error on zero outputs")
|
||||||
|
}
|
||||||
|
err = d.SetOutputWithSave(SetOutputParam{}, SetOutputParam{}, SetOutputParam{}, SetOutputParam{}, SetOutputParam{})
|
||||||
|
if err == nil || err != errInvalidInputCount {
|
||||||
|
t.Error("SetOutputWithSave() failed to error on too many outputs")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFastWrite4728(t *testing.T) {
|
||||||
|
d, err := getDev("TestFastWrite4728", MCP4728, MCP4728InternalRef)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testFastWrite(d, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetGetOutput4725(t *testing.T) {
|
||||||
|
d, err := getDev("TestSetGetOutput4725", MCP4725, 3_300*physic.MilliVolt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testSetGetOutput(d, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetEEPROM4725(t *testing.T) {
|
||||||
|
d, err := getDev("TestSetEEPROM4725", MCP4725, 3_300*physic.MilliVolt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testSetEEPROM(d, 0, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFastWrite4725(t *testing.T) {
|
||||||
|
d, err := getDev("TestFastWrite4725", MCP4725, 3_300*physic.MilliVolt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testFastWrite(d, t)
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
// Copyright 2025 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 mcp472x
|
||||||
|
|
||||||
|
import (
|
||||||
|
"periph.io/x/conn/v3/i2c/i2ctest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Auto-Generated by i2ctest.BusTest
|
||||||
|
|
||||||
|
var recordingData = map[string][]i2ctest.IO{
|
||||||
|
"TestSetGetOutput4728": {
|
||||||
|
{Addr: 0x60, W: []uint8{0x40, 0x82, 0x0, 0x2, 0x84, 0x0, 0x4, 0x86, 0x0, 0x6, 0x88, 0x0}},
|
||||||
|
{Addr: 0x60, R: []uint8{0xc0, 0x82, 0x0, 0xc8, 0x82, 0x0, 0xd0, 0x84, 0x0, 0xd8, 0x84, 0x0, 0xe0, 0x86, 0x0, 0xe8, 0xa8, 0x0, 0xf0, 0x88, 0x0, 0xf8, 0x88, 0x0}}},
|
||||||
|
"TestSetEEPROM4728": {
|
||||||
|
{Addr: 0x60, W: []uint8{0x5c, 0x84, 0x0}},
|
||||||
|
{Addr: 0x60, R: []uint8{0x40, 0x82, 0x0, 0x4f, 0x7d, 0xff, 0x50, 0x84, 0x0, 0x5f, 0x7b, 0xff, 0x60, 0x84, 0x0, 0x6f, 0x7b, 0xff, 0x70, 0x88, 0x0, 0x7f, 0x77, 0xff}},
|
||||||
|
{Addr: 0x60, R: []uint8{0xc0, 0x82, 0x0, 0xc8, 0x82, 0x0, 0xd0, 0x84, 0x0, 0xd8, 0x84, 0x0, 0xe0, 0x84, 0x0, 0xe8, 0x84, 0x0, 0xf0, 0x88, 0x0, 0xf8, 0x88, 0x0}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x5c, 0xa8, 0x0}},
|
||||||
|
{Addr: 0x60, R: []uint8{0x40, 0x82, 0x0, 0x4f, 0x7d, 0xff, 0x50, 0x84, 0x0, 0x5f, 0x7b, 0xff, 0x60, 0xa8, 0x0, 0x6f, 0x57, 0xff, 0x70, 0x88, 0x0, 0x7f, 0x77, 0xff}},
|
||||||
|
{Addr: 0x60, R: []uint8{0xc0, 0x82, 0x0, 0xc8, 0x82, 0x0, 0xd0, 0x84, 0x0, 0xd8, 0x84, 0x0, 0xe0, 0xa8, 0x0, 0xe8, 0xa8, 0x0, 0xf0, 0x88, 0x0, 0xf8, 0x88, 0x0}}},
|
||||||
|
"TestFastWrite4728": {
|
||||||
|
{Addr: 0x60, W: []uint8{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
|
||||||
|
{Addr: 0x60, R: []uint8{0xc0, 0x80, 0x0, 0xc8, 0x82, 0x0, 0xd0, 0x80, 0x0, 0xd8, 0x84, 0x0, 0xe0, 0x80, 0x0, 0xe8, 0xa8, 0x0, 0xf0, 0x80, 0x0, 0xf8, 0x88, 0x0}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x6, 0x0, 0x6, 0x0, 0x6, 0x0, 0x6, 0x0}},
|
||||||
|
{Addr: 0x60, R: []uint8{0xc0, 0x86, 0x0, 0xc8, 0x82, 0x0, 0xd0, 0x86, 0x0, 0xd8, 0x84, 0x0, 0xe0, 0x86, 0x0, 0xe8, 0xa8, 0x0, 0xf0, 0x86, 0x0, 0xf8, 0x88, 0x0}}},
|
||||||
|
"TestSetGetOutput4725": {
|
||||||
|
{Addr: 0x62, W: []uint8{0x40, 0x13, 0xe0}},
|
||||||
|
{Addr: 0x62, R: []uint8{0xc0, 0x13, 0xe0, 0x24, 0xf7}}},
|
||||||
|
"TestSetEEPROM4725": {
|
||||||
|
{Addr: 0x62, W: []uint8{0x60, 0x27, 0xb0}},
|
||||||
|
{Addr: 0x62, R: []uint8{0x40, 0x27, 0xb0, 0x6d, 0x84}},
|
||||||
|
{Addr: 0x62, R: []uint8{0xc0, 0x27, 0xb0, 0x2, 0x7b}},
|
||||||
|
{Addr: 0x62, W: []uint8{0x62, 0x4f, 0x70}},
|
||||||
|
{Addr: 0x62, R: []uint8{0x42, 0x4f, 0x70, 0x4b, 0x8}},
|
||||||
|
{Addr: 0x62, R: []uint8{0xc2, 0x4f, 0x70, 0x24, 0xf7}}},
|
||||||
|
"TestFastWrite4725": {
|
||||||
|
{Addr: 0x62, W: []uint8{0x0, 0x0}},
|
||||||
|
{Addr: 0x62, R: []uint8{0xc0, 0x0, 0x0, 0x24, 0xf7}},
|
||||||
|
{Addr: 0x62, W: []uint8{0x3, 0xb9}},
|
||||||
|
{Addr: 0x62, R: []uint8{0xc0, 0x3b, 0x90, 0x24, 0xf7}}},
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue