Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tic: Add I²C support for Tic Stepper Motor Controllers #78

Merged
merged 6 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 := [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
}

Check warning on line 77 in tic/registers.go

View check run for this annotation

Codecov / codecov/patch

tic/registers.go#L76-L77

Added lines #L76 - L77 were not covered by tests

// Read the requested number of bytes
readBuf := make([]byte, length)
err = d.c.Tx(nil, readBuf)
return readBuf, err
}
Loading