mirror of https://github.com/periph/devices
tic: Add I²C support for Tic Stepper Motor Controllers (#78)
* tic: Add I²C support for Tic Stepper Motor Controllers (#1) * Initialise buffers as arrays instead of slices * Remove redundant step modes and improve docs * Make Offset type private * Remove shadowed variablepull/81/head v3.7.2
parent
536214b3ab
commit
5bc0352f3a
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2024 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 tic interfaces with Tic Stepper Motor Controllers via I²C.
|
||||||
|
//
|
||||||
|
// # More Details
|
||||||
|
//
|
||||||
|
// See https://www.pololu.com/category/212/tic-stepper-motor-controllers for
|
||||||
|
// more details about the device range.
|
||||||
|
//
|
||||||
|
// # Product Pages
|
||||||
|
//
|
||||||
|
// Tic T500: https://www.pololu.com/product/3134
|
||||||
|
//
|
||||||
|
// Tic T834: https://www.pololu.com/product/3132
|
||||||
|
//
|
||||||
|
// Tic T825: https://www.pololu.com/product/3130
|
||||||
|
//
|
||||||
|
// Tic T249: https://www.pololu.com/product/3138
|
||||||
|
//
|
||||||
|
// Tic 36v4: https://www.pololu.com/product/3140
|
||||||
|
package tic
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
// Copyright 2024 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 tic_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"periph.io/x/conn/v3/i2c/i2creg"
|
||||||
|
"periph.io/x/conn/v3/physic"
|
||||||
|
"periph.io/x/devices/v3/tic"
|
||||||
|
"periph.io/x/host/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example() {
|
||||||
|
// Make sure periph is initialized.
|
||||||
|
if _, err := host.Init(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open default I²C bus.
|
||||||
|
bus, err := i2creg.Open("")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to open I²C: %v", err)
|
||||||
|
}
|
||||||
|
defer bus.Close()
|
||||||
|
|
||||||
|
// Create a new motor controller.
|
||||||
|
dev, err := tic.NewI2C(bus, tic.Tic36v4, tic.I2CAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the current limit with respect to the motor.
|
||||||
|
if err := dev.SetCurrentLimit(1000 * physic.MilliAmpere); err != nil {
|
||||||
|
log.Fatalf("failed to set current limit: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The "Exit safe start" command is required before the motor can move.
|
||||||
|
if err := dev.ExitSafeStart(); err != nil {
|
||||||
|
log.Fatalf("failed to exit safe start: err %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the target velocity to 200 microsteps per second.
|
||||||
|
if err := dev.SetTargetVelocity(2000000); err != nil {
|
||||||
|
log.Fatalf("failed to set target velocity: err %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a ticker to frequently send commands before the timeout period
|
||||||
|
// elapses (1000ms default).
|
||||||
|
ticker := time.NewTicker(900 * time.Millisecond)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
// Stop after 3 seconds.
|
||||||
|
stop := time.After(3 * time.Second)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
// Any command sent to the Tic will reset the timeout. However,
|
||||||
|
// this can be done explicitly using ResetCommandTimeout().
|
||||||
|
if err := dev.ResetCommandTimeout(); err != nil {
|
||||||
|
log.Fatalf("failed to reset command timeout: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
// Copyright 2024 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 tic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getVar8 reads an 8 bit value from the Tic at a given register offset.
|
||||||
|
func (d *Dev) getVar8(offset offset) (uint8, error) {
|
||||||
|
const length = 1
|
||||||
|
buffer, err := d.getSegment(cmdGetVariable, offset, length)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getVar16 reads a 16 bit value from the Tic at a given register offset.
|
||||||
|
func (d *Dev) getVar16(offset offset) (uint16, error) {
|
||||||
|
const length = 2
|
||||||
|
buffer, err := d.getSegment(cmdGetVariable, offset, length)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return binary.LittleEndian.Uint16(buffer), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getVar32 reads a 32 bit value from the Tic at a given register offset.
|
||||||
|
func (d *Dev) getVar32(offset offset) (uint32, error) {
|
||||||
|
const length = 4
|
||||||
|
buffer, err := d.getSegment(cmdGetVariable, offset, length)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return binary.LittleEndian.Uint32(buffer), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandQuick sends a command without additional data.
|
||||||
|
func (d *Dev) commandQuick(cmd command) error {
|
||||||
|
writeBuf := [1]byte{uint8(cmd)}
|
||||||
|
err := d.c.Tx(writeBuf[:], nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandW7 sends a command with a 7 bit value. The MSB of val is ignored.
|
||||||
|
func (d *Dev) commandW7(cmd command, val uint8) error {
|
||||||
|
writeBuf := [2]byte{byte(cmd), val & 0x7F}
|
||||||
|
err := d.c.Tx(writeBuf[:], nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandW32 sends a command with a 32 bit value.
|
||||||
|
func (d *Dev) commandW32(cmd command, val uint32) error {
|
||||||
|
writeBuf := [5]byte{byte(cmd)}
|
||||||
|
writeBuf[0] = byte(cmd)
|
||||||
|
binary.LittleEndian.PutUint32(writeBuf[1:], val) // write the uint32 value
|
||||||
|
|
||||||
|
err := d.c.Tx(writeBuf[:], nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSegment sends a command and receives "length" bytes back.
|
||||||
|
func (d *Dev) getSegment(
|
||||||
|
cmd command, offset offset, length uint,
|
||||||
|
) ([]byte, error) {
|
||||||
|
// Transmit command and offset value
|
||||||
|
writeBuf := [2]byte{byte(cmd), byte(offset)}
|
||||||
|
err := d.c.Tx(writeBuf[:], nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the requested number of bytes
|
||||||
|
readBuf := make([]byte, length)
|
||||||
|
err = d.c.Tx(nil, readBuf)
|
||||||
|
return readBuf, err
|
||||||
|
}
|
||||||
@ -0,0 +1,347 @@
|
|||||||
|
// Copyright 2024 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 tic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"periph.io/x/conn/v3/i2c"
|
||||||
|
"periph.io/x/conn/v3/i2c/i2ctest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetVar8(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
offset offset
|
||||||
|
ops []i2ctest.IO
|
||||||
|
want uint8
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "success",
|
||||||
|
offset: 0xAA,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA1, 0xAA}},
|
||||||
|
{Addr: I2CAddr, R: []byte{0xAB}},
|
||||||
|
},
|
||||||
|
want: 0xAB,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no bytes received",
|
||||||
|
offset: 0xAA,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA1, 0xAA}},
|
||||||
|
{Addr: I2CAddr, R: []byte{}},
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
b := i2ctest.Playback{
|
||||||
|
Ops: test.ops,
|
||||||
|
DontPanic: true,
|
||||||
|
}
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
dev := Dev{
|
||||||
|
c: &i2c.Dev{Bus: &b, Addr: I2CAddr},
|
||||||
|
variant: Tic36v4,
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := dev.getVar8(test.offset)
|
||||||
|
if test.expectErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error, got: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got != test.want {
|
||||||
|
t.Fatalf("wanted: %d, got: %d", test.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetVar16(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
offset offset
|
||||||
|
ops []i2ctest.IO
|
||||||
|
want uint16
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "success",
|
||||||
|
offset: 0xAA,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA1, 0xAA}},
|
||||||
|
{Addr: I2CAddr, R: []byte{0xCD, 0xAB}},
|
||||||
|
},
|
||||||
|
want: 0xABCD,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no bytes received",
|
||||||
|
offset: 0xAA,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA1, 0xAA}},
|
||||||
|
{Addr: I2CAddr, R: []byte{}},
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
b := i2ctest.Playback{
|
||||||
|
Ops: test.ops,
|
||||||
|
DontPanic: true,
|
||||||
|
}
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
dev := Dev{
|
||||||
|
c: &i2c.Dev{Bus: &b, Addr: I2CAddr},
|
||||||
|
variant: Tic36v4,
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := dev.getVar16(test.offset)
|
||||||
|
if test.expectErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error, got: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got != test.want {
|
||||||
|
t.Fatalf("wanted: %d, got: %d", test.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetVar32(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
offset offset
|
||||||
|
ops []i2ctest.IO
|
||||||
|
want uint32
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "success",
|
||||||
|
offset: 0xAA,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA1, 0xAA}},
|
||||||
|
{Addr: I2CAddr, R: []byte{0xEF, 0xBE, 0xAD, 0xDE}},
|
||||||
|
},
|
||||||
|
want: 0xDEADBEEF,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no bytes received",
|
||||||
|
offset: 0xAA,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA1, 0xAA}},
|
||||||
|
{Addr: I2CAddr, R: []byte{}},
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
b := i2ctest.Playback{
|
||||||
|
Ops: test.ops,
|
||||||
|
DontPanic: true,
|
||||||
|
}
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
dev := Dev{
|
||||||
|
c: &i2c.Dev{Bus: &b, Addr: I2CAddr},
|
||||||
|
variant: Tic36v4,
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := dev.getVar32(test.offset)
|
||||||
|
if test.expectErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error, got: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got != test.want {
|
||||||
|
t.Fatalf("wanted: %d, got: %d", test.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandQuick(t *testing.T) {
|
||||||
|
const cmd = 0xAA
|
||||||
|
|
||||||
|
b := i2ctest.Playback{
|
||||||
|
Ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{cmd}},
|
||||||
|
},
|
||||||
|
DontPanic: true,
|
||||||
|
}
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
dev := Dev{
|
||||||
|
c: &i2c.Dev{Bus: &b, Addr: I2CAddr},
|
||||||
|
variant: Tic36v4,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := dev.commandQuick(cmd)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandW7(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
cmd command
|
||||||
|
val uint8
|
||||||
|
ops []i2ctest.IO
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "success",
|
||||||
|
cmd: 0xAA,
|
||||||
|
val: 0x0B,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xAA, 0x0B}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "val MSB truncated",
|
||||||
|
cmd: 0xAA,
|
||||||
|
val: 0b1111_1111,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xAA, 0b0111_1111}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
b := i2ctest.Playback{
|
||||||
|
Ops: test.ops,
|
||||||
|
DontPanic: true,
|
||||||
|
}
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
dev := Dev{
|
||||||
|
c: &i2c.Dev{Bus: &b, Addr: I2CAddr},
|
||||||
|
variant: Tic36v4,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := dev.commandW7(test.cmd, test.val)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandW32(t *testing.T) {
|
||||||
|
const (
|
||||||
|
cmd = 0xAA
|
||||||
|
val uint32 = 0xBBCCDDEE
|
||||||
|
)
|
||||||
|
|
||||||
|
b := i2ctest.Playback{
|
||||||
|
Ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xAA, 0xEE, 0xDD, 0xCC, 0xBB}},
|
||||||
|
},
|
||||||
|
DontPanic: true,
|
||||||
|
}
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
dev := Dev{
|
||||||
|
c: &i2c.Dev{Bus: &b, Addr: I2CAddr},
|
||||||
|
variant: Tic36v4,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := dev.commandW32(cmd, val)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSegment(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
cmd command
|
||||||
|
offset offset
|
||||||
|
length uint
|
||||||
|
want []uint8
|
||||||
|
ops []i2ctest.IO
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "read 1 byte",
|
||||||
|
cmd: 0xAA,
|
||||||
|
offset: 0xBB,
|
||||||
|
length: 1,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xAA, 0xBB}},
|
||||||
|
{Addr: I2CAddr, R: []byte{0xCC}},
|
||||||
|
},
|
||||||
|
want: []byte{0xCC},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "read 4 bytes",
|
||||||
|
cmd: 0xAA,
|
||||||
|
offset: 0xBB,
|
||||||
|
length: 4,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xAA, 0xBB}},
|
||||||
|
{Addr: I2CAddr, R: []byte{0xCC, 0xDD, 0xEE, 0xFF}},
|
||||||
|
},
|
||||||
|
want: []byte{0xCC, 0xDD, 0xEE, 0xFF},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid length",
|
||||||
|
cmd: 0xAA,
|
||||||
|
offset: 0xBB,
|
||||||
|
length: 0,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xAA, 0xBB}},
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
b := i2ctest.Playback{
|
||||||
|
Ops: test.ops,
|
||||||
|
DontPanic: true,
|
||||||
|
}
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
dev := Dev{
|
||||||
|
c: &i2c.Dev{Bus: &b, Addr: I2CAddr},
|
||||||
|
variant: Tic36v4,
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := dev.getSegment(test.cmd, test.offset, test.length)
|
||||||
|
if test.expectErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error, got: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(got, test.want) {
|
||||||
|
t.Fatalf("wanted: %d, got: %d", test.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,486 @@
|
|||||||
|
// Copyright 2024 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 tic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"periph.io/x/conn/v3/i2c"
|
||||||
|
"periph.io/x/conn/v3/i2c/i2ctest"
|
||||||
|
"periph.io/x/conn/v3/physic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewI2C(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
variant Variant
|
||||||
|
ops []i2ctest.IO
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "success",
|
||||||
|
variant: TicT500,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA1, 0x49}},
|
||||||
|
{Addr: I2CAddr, R: []byte{0x00}},
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid variant",
|
||||||
|
variant: Variant("periph"),
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "connection failure",
|
||||||
|
variant: TicT500,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA1, 0x49}},
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
b := i2ctest.Playback{
|
||||||
|
Ops: test.ops,
|
||||||
|
DontPanic: true,
|
||||||
|
}
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
_, err := NewI2C(&b, test.variant, I2CAddr)
|
||||||
|
if test.expectErr && err == nil {
|
||||||
|
t.Fatalf("expected error, got: %v", err)
|
||||||
|
} else if !test.expectErr && err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTargetPosition(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
ops []i2ctest.IO
|
||||||
|
want int32
|
||||||
|
expectErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "success",
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA1, 0x09}},
|
||||||
|
{Addr: I2CAddr, R: []byte{byte(PlanningModeTargetPosition)}},
|
||||||
|
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA1, 0x0A}},
|
||||||
|
{Addr: I2CAddr, R: []byte{0xEE, 0xDB, 0xEA, 0x0D}},
|
||||||
|
},
|
||||||
|
want: 0xDEADBEE,
|
||||||
|
expectErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "incorrect planning mode",
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA1, 0x09}},
|
||||||
|
{Addr: I2CAddr, R: []byte{byte(PlanningModeTargetVelocity)}},
|
||||||
|
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA1, 0x0A}},
|
||||||
|
{Addr: I2CAddr, R: []byte{0xEE, 0xDB, 0xEA, 0x0D}},
|
||||||
|
},
|
||||||
|
expectErr: ErrIncorrectPlanningMode,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
b := i2ctest.Playback{
|
||||||
|
Ops: test.ops,
|
||||||
|
DontPanic: true,
|
||||||
|
}
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
dev := Dev{
|
||||||
|
c: &i2c.Dev{Bus: &b, Addr: I2CAddr},
|
||||||
|
variant: TicT825,
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := dev.GetTargetPosition()
|
||||||
|
if !errors.Is(err, test.expectErr) {
|
||||||
|
t.Fatalf("expected error: %v, got: %v", test.expectErr, err)
|
||||||
|
}
|
||||||
|
if got != test.want {
|
||||||
|
t.Fatalf("wanted: %d, got: %d", test.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetStepMode(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
variant Variant
|
||||||
|
mode StepMode
|
||||||
|
ops []i2ctest.IO
|
||||||
|
expectErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "success",
|
||||||
|
variant: TicT825,
|
||||||
|
mode: StepModeFull,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0x94, 0x00}},
|
||||||
|
},
|
||||||
|
expectErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid step mode",
|
||||||
|
variant: TicT825,
|
||||||
|
mode: StepMode(0xFF),
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0x94, 0xFF}},
|
||||||
|
},
|
||||||
|
expectErr: ErrInvalidSetting,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsupported variant",
|
||||||
|
variant: TicT500,
|
||||||
|
mode: StepModeMicrostep256,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0x94, 0x09}},
|
||||||
|
},
|
||||||
|
expectErr: ErrUnsupportedVariant,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
b := i2ctest.Playback{
|
||||||
|
Ops: test.ops,
|
||||||
|
DontPanic: true,
|
||||||
|
}
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
dev := Dev{
|
||||||
|
c: &i2c.Dev{Bus: &b, Addr: I2CAddr},
|
||||||
|
variant: test.variant,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := dev.SetStepMode(test.mode)
|
||||||
|
if !errors.Is(err, test.expectErr) {
|
||||||
|
t.Fatalf("expected error: %v, got: %v", test.expectErr, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetDecayMode(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
variant Variant
|
||||||
|
mode DecayMode
|
||||||
|
ops []i2ctest.IO
|
||||||
|
expectErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "success",
|
||||||
|
variant: TicT825,
|
||||||
|
mode: DecayModeMixed,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0x92, 0x00}},
|
||||||
|
},
|
||||||
|
expectErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid decay mode",
|
||||||
|
variant: TicT825,
|
||||||
|
mode: DecayMode(0xFF),
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0x94, 0xFF}},
|
||||||
|
},
|
||||||
|
expectErr: ErrInvalidSetting,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsupported decay mode",
|
||||||
|
variant: TicT825,
|
||||||
|
mode: DecayModeMixed75,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0x94, 0x04}},
|
||||||
|
},
|
||||||
|
expectErr: ErrUnsupportedVariant,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsupported variant",
|
||||||
|
variant: TicT500,
|
||||||
|
mode: DecayModeMixed,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0x94, 0x00}},
|
||||||
|
},
|
||||||
|
expectErr: ErrUnsupportedVariant,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
b := i2ctest.Playback{
|
||||||
|
Ops: test.ops,
|
||||||
|
DontPanic: true,
|
||||||
|
}
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
dev := Dev{
|
||||||
|
c: &i2c.Dev{Bus: &b, Addr: I2CAddr},
|
||||||
|
variant: test.variant,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := dev.SetDecayMode(test.mode)
|
||||||
|
if !errors.Is(err, test.expectErr) {
|
||||||
|
t.Fatalf("expected error: %v, got: %v", test.expectErr, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetCurrentLimit(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
variant Variant
|
||||||
|
ops []i2ctest.IO
|
||||||
|
want physic.ElectricCurrent
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "T500 success",
|
||||||
|
variant: TicT500,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA1, 0x4A}},
|
||||||
|
{Addr: I2CAddr, R: []byte{0x09}},
|
||||||
|
},
|
||||||
|
want: 1092 * physic.MilliAmpere,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "T249 success",
|
||||||
|
variant: TicT249,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA1, 0x4A}},
|
||||||
|
{Addr: I2CAddr, R: []byte{0x0A}},
|
||||||
|
},
|
||||||
|
want: 400 * physic.MilliAmpere,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "36v4 success",
|
||||||
|
variant: Tic36v4,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA1, 0x4A}},
|
||||||
|
{Addr: I2CAddr, R: []byte{0x0A}},
|
||||||
|
},
|
||||||
|
want: 716 * physic.MilliAmpere,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "T825 success",
|
||||||
|
variant: TicT825,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA1, 0x4A}},
|
||||||
|
{Addr: I2CAddr, R: []byte{0x0A}},
|
||||||
|
},
|
||||||
|
want: 320 * physic.MilliAmpere,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
b := i2ctest.Playback{
|
||||||
|
Ops: test.ops,
|
||||||
|
DontPanic: true,
|
||||||
|
}
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
dev := Dev{
|
||||||
|
c: &i2c.Dev{Bus: &b, Addr: I2CAddr},
|
||||||
|
variant: test.variant,
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := dev.GetCurrentLimit()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got != test.want {
|
||||||
|
t.Fatalf("wanted: %v, got: %v", test.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetCurrentLimit(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
variant Variant
|
||||||
|
limit physic.ElectricCurrent
|
||||||
|
ops []i2ctest.IO
|
||||||
|
want int32
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "T500 success",
|
||||||
|
variant: TicT500,
|
||||||
|
limit: 500 * physic.MilliAmpere,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0x91, 0x04}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "36v4 success",
|
||||||
|
variant: Tic36v4,
|
||||||
|
limit: 500 * physic.MilliAmpere,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0x91, 0x06}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "36v4 lower limit",
|
||||||
|
variant: Tic36v4,
|
||||||
|
limit: 10 * physic.NanoAmpere,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0x91, 0x00}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "36v4 upper limit",
|
||||||
|
variant: Tic36v4,
|
||||||
|
limit: 10 * physic.Ampere,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0x91, 0x7F}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
b := i2ctest.Playback{
|
||||||
|
Ops: test.ops,
|
||||||
|
DontPanic: true,
|
||||||
|
}
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
dev := Dev{
|
||||||
|
c: &i2c.Dev{Bus: &b, Addr: I2CAddr},
|
||||||
|
variant: test.variant,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := dev.SetCurrentLimit(test.limit)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetEnergized(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
ops []i2ctest.IO
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "device energized",
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA1, 0x01}},
|
||||||
|
{Addr: I2CAddr, R: []byte{0x01}},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "device not energized",
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA1, 0x01}},
|
||||||
|
{Addr: I2CAddr, R: []byte{0x00}},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
b := i2ctest.Playback{
|
||||||
|
Ops: test.ops,
|
||||||
|
DontPanic: true,
|
||||||
|
}
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
dev := Dev{
|
||||||
|
c: &i2c.Dev{Bus: &b, Addr: I2CAddr},
|
||||||
|
variant: Tic36v4,
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := dev.IsEnergized()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got != test.want {
|
||||||
|
t.Fatalf("wanted: %t, got: %t", test.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetErrorsOccurred(t *testing.T) {
|
||||||
|
const want uint32 = 0xAABBCCDD
|
||||||
|
|
||||||
|
b := i2ctest.Playback{
|
||||||
|
Ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA2, 0x04}},
|
||||||
|
{Addr: I2CAddr, R: []byte{0xDD, 0xCC, 0xBB, 0xAA}},
|
||||||
|
},
|
||||||
|
DontPanic: true,
|
||||||
|
}
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
dev := Dev{
|
||||||
|
c: &i2c.Dev{Bus: &b, Addr: I2CAddr},
|
||||||
|
variant: Tic36v4,
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := dev.GetErrorsOccurred()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("wanted: %d, got: %d", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetPinState(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
pin Pin
|
||||||
|
ops []i2ctest.IO
|
||||||
|
want PinState
|
||||||
|
expectErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "success",
|
||||||
|
pin: PinRX,
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA1, 0x48}},
|
||||||
|
{Addr: I2CAddr, R: []byte{0x80}},
|
||||||
|
},
|
||||||
|
want: PinStateOutputLow,
|
||||||
|
expectErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid pin",
|
||||||
|
pin: Pin(0xFF),
|
||||||
|
ops: []i2ctest.IO{
|
||||||
|
{Addr: I2CAddr, W: []byte{0xA1, 0x48}},
|
||||||
|
},
|
||||||
|
expectErr: ErrInvalidSetting,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
b := i2ctest.Playback{
|
||||||
|
Ops: test.ops,
|
||||||
|
DontPanic: true,
|
||||||
|
}
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
dev := Dev{
|
||||||
|
c: &i2c.Dev{Bus: &b, Addr: I2CAddr},
|
||||||
|
variant: Tic36v4,
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := dev.GetPinState(test.pin)
|
||||||
|
if !errors.Is(err, test.expectErr) {
|
||||||
|
t.Fatalf("expected error: %v, got: %v", test.expectErr, err)
|
||||||
|
}
|
||||||
|
if got != test.want {
|
||||||
|
t.Fatalf("wanted: %d, got: %d", test.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue