Skip to content

Commit

Permalink
tic: Add I²C support for Tic Stepper Motor Controllers
Browse files Browse the repository at this point in the history
  • Loading branch information
z-riley committed Oct 25, 2024
2 parents 853aec6 + 55b66c6 commit 2571f6a
Show file tree
Hide file tree
Showing 6 changed files with 2,340 additions and 0 deletions.
23 changes: 23 additions & 0 deletions tic/doc.go
Original file line number Diff line number Diff line change
@@ -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
71 changes: 71 additions & 0 deletions tic/example_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
83 changes: 83 additions & 0 deletions tic/registers.go
Original file line number Diff line number Diff line change
@@ -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 := []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 := []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 := make([]byte, 5)
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 := []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
}
Loading

0 comments on commit 2571f6a

Please sign in to comment.