diff --git a/Makefile b/Makefile index 1be5a68..104734a 100644 --- a/Makefile +++ b/Makefile @@ -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 @@ -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: diff --git a/README.md b/README.md index a348b20..6a7d5da 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/examples/servo/servo.go b/examples/servo/servo.go new file mode 100644 index 0000000..c986d84 --- /dev/null +++ b/examples/servo/servo.go @@ -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) + } +} diff --git a/servo/servo.go b/servo/servo.go new file mode 100644 index 0000000..5be92ac --- /dev/null +++ b/servo/servo.go @@ -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)) +}