Skip to content

Commit

Permalink
servo: add driver using PWM
Browse files Browse the repository at this point in the history
This PR adds support for controlling servos. For example, on the Arduino
Uno it should be able to control up to 6 servos jitter-free when using
all available PWM pins.

I haven't added support for setting a position in degrees, mainly
because this varies by servo and it's probably necessary to configure
the bounds in some way. Therefore, I added just SetMicroseconds. This
makes the API possible to use and leaves the possibility of adding a
SetPosition in the future.
  • Loading branch information
aykevl authored and deadprogram committed May 12, 2021
1 parent 428db3c commit c765ef3
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 1 deletion.
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ smoke-test:
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setpixel/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/servo
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/shifter/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/sht3x/main.go
Expand Down Expand Up @@ -196,7 +198,7 @@ DRIVERS = $(wildcard */)
NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \
hcsr04 ssd1331 ws2812 thermistor apa102 easystepper ssd1351 ili9341 wifinina shifter hub75 \
hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x dht keypad4x4 max72xx p1am tone tm1637 \
pcf8563 mcp2515
pcf8563 mcp2515 servo
TESTS = $(filter-out $(addsuffix /%,$(NOTESTS)),$(DRIVERS))

unit-test:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ The following 64 devices are supported.
| [PCF8563 real time clock](https://www.nxp.com/docs/en/data-sheet/PCF8563.pdf) | I2C |
| [Resistive Touchscreen (4-wire)](http://ww1.microchip.com/downloads/en/Appnotes/doc8091.pdf) | GPIO |
| [Semihosting](https://wiki.segger.com/Semihosting) | Debug |
| [Servo](https://learn.sparkfun.com/tutorials/hobby-servo-tutorial/all) | PWM |
| [Shift register (PISO)](https://en.wikipedia.org/wiki/Shift_register#Parallel-in_serial-out_\(PISO\)) | GPIO |
| [Shift registers (SIPO)](https://en.wikipedia.org/wiki/Shift_register#Serial-in_parallel-out_(SIPO)) | GPIO |
| [SHT3x Digital Humidity Sensor](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/0_Datasheets/Humidity/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf) | I2C |
Expand Down
43 changes: 43 additions & 0 deletions examples/servo/servo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package main

import (
"machine"
"time"

"tinygo.org/x/drivers/servo"
)

// Configuration for the Arduino Uno.
// Please change the PWM and pin if you want to try this example on a different
// board.
var (
pwm = machine.Timer1
pin = machine.D9
)

func main() {
s, err := servo.New(pwm, pin)
if err != nil {
for {
println("could not configure servo")
time.Sleep(time.Second)
}
return
}

println("setting to 0°")
s.SetMicroseconds(1000)
time.Sleep(3 * time.Second)

println("setting to 45°")
s.SetMicroseconds(1500)
time.Sleep(3 * time.Second)

println("setting to 90°")
s.SetMicroseconds(2000)
time.Sleep(3 * time.Second)

for {
time.Sleep(time.Second)
}
}
82 changes: 82 additions & 0 deletions servo/servo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package servo

import "machine"

// PWM is the interface necessary for controlling typical servo motors.
type PWM interface {
Configure(config machine.PWMConfig) error
Channel(pin machine.Pin) (channel uint8, err error)
Top() uint32
Set(channel uint8, value uint32)
}

// Array is an array of servos controlled by a single PWM peripheral. On most
// chips, one PWM peripheral can control multiple servos (usually two or four).
type Array struct {
pwm PWM
}

// Servo is a single servo (connected to one PWM output) that's part of a servo
// array.
type Servo struct {
pwm PWM
channel uint8
}

const pwmPeriod = 20e6 // 20ms

// NewArray returns a new servo array based on the given PWM, for if you want to
// control multiple servos from a single PWM peripheral. Using a single PWM for
// multiple servos saves PWM peripherals for other uses and might use less power
// depending on the chip.
//
// If you only want to control a single servo, you could use the New shorthand
// instead.
func NewArray(pwm PWM) (Array, error) {
err := pwm.Configure(machine.PWMConfig{
Period: pwmPeriod,
})
if err != nil {
return Array{}, err
}
return Array{pwm}, nil
}

// Add adds a new servo to the servo array. Please check the chip documentation
// which pins can be controlled by the given PWM: depending on the chip this
// might be rigid (only a single pin) or very flexible (you can pick any pin).
func (array Array) Add(pin machine.Pin) (Servo, error) {
channel, err := array.pwm.Channel(pin)
if err != nil {
return Servo{}, err
}
return Servo{
pwm: array.pwm,
channel: channel,
}, nil
}

// New is a shorthand for NewArray and array.Add. This is useful if you only
// want to control just a single servo.
func New(pwm PWM, pin machine.Pin) (Servo, error) {
array, err := NewArray(pwm)
if err != nil {
return Servo{}, err
}
return array.Add(pin)
}

// SetMicroseconds sets the output signal to be high for the given number of
// microseconds. For many servos the range is normally between 1000µs and 2000µs
// for 90° of rotation (with 1500µs being the 'neutral' middle position).
//
// In many cases they can actually go a bit further, with a wider range of
// supported pulse ranges. For example, they might allow pulse widths from 500µs
// to 2500µs, but be warned that going outside of the 1000µs-2000µs range might
// break the servo as it might destroy the gears if it doesn't support this
// range. Therefore, to be sure check the datasheet before you try values
// outside of the 1000µs-2000µs range.
func (s Servo) SetMicroseconds(microseconds int16) {
value := uint64(s.pwm.Top()) * uint64(microseconds) / (pwmPeriod / 1000)
s.pwm.Set(s.channel, uint32(value))
}

0 comments on commit c765ef3

Please sign in to comment.