tinygo-org / drivers

TinyGo drivers for sensors, displays, wireless adaptors, and other devices that use I2C, SPI, GPIO, ADC, and UART interfaces.
https://tinygo.org
BSD 3-Clause "New" or "Revised" License
626 stars 195 forks source link

Add a servo library (like Arduino Servo.h) #131

Closed Jacalz closed 3 years ago

Jacalz commented 4 years ago

For Arduino programming using their language, a specific driver library exists for easy and simple control over Servo parts. This seems to be non supported at the moment when using tinygo.

This functionality would be very useful for especially Arduino users that want to use their servos, but I guess that it might be useful for other boards too, depending on implementation. I will mostly be using it on an Arduino Uno though...

See the Servo Reference and Servo Library Repository for more information.

Related to https://github.com/tinygo-org/tinygo/issues/855 about PWM support needed to implement this.

wlwatkins commented 4 years ago

IS there any ETA for servo support on arduino uno/nano?

alankrantas commented 4 years ago

I wrote a test code that can control servos, however this does not work on 8-bit AVR boards (like Arduino Unos and Nanos), probably not until they can get more accurate timing support.

Modified: making ServoRoutine timing more accurate.

package main

import (
    "machine"
    "time"
)

type Device struct {
    pin      machine.Pin
    on       bool
    angle    uint8
    pulseMin uint16
    pulseMax uint16
}

func Init(pin machine.Pin) Device {
    pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    return Device{pin: pin, on: false, pulseMin: 600, pulseMax: 2400}
}

func (d *Device) ServoRoutine() {
    for {
        if d.attached {
            pulse := valueMapping(int32(d.angle), 0, 180, d.pulseMin, d.pulseMax)
            d.pin.High()
            time.Sleep(time.Microsecond * time.Duration(pulse))
            d.pin.Low()
            time.Sleep(time.Microsecond * time.Duration(20000.0-pulse))
        }
    }
}

func (d *Device) Angle(angle uint8) {
    if angle < 0 {
        angle = 0
    } else if angle > 180 {
        angle = 180
    }
    d.on = true
    d.angle = angle
}

func (d *Device) PulseRange(min, max uint16) {
    d.pulseMin = min
    d.pulseMax = max
}

func (d *Device) Deinit() {
    d.on = false
}

func valueMapping(value, min, max, newMin, newMax uint16) float32 {
    scale := float32(value-min) / float32(max-min)
    return float32(newMin) + scale*float32(newMax-newMin)
}

func main() {

    servo1 := Init(machine.P8)
    servo2 := Init(machine.P12)

    go servo1.ServoRoutine()
    go servo2.ServoRoutine()

    for i := 0; i <= 2; i++ {
        servo1.Angle(0)
        servo2.Angle(0)
        time.Sleep(time.Millisecond * 1000)

        servo1.Angle(90)
        servo2.Angle(90)
        time.Sleep(time.Millisecond * 1000)

        servo1.Angle(180)
        servo2.Angle(180)
        time.Sleep(time.Millisecond * 1000)
    }

    // stop "PWM" signal (otherwise the servos may turn again when main() ended)
    servo1.Deinit()
    servo2.Deinit()

}
ewwaller commented 4 years ago

alankrantas: I wrote code very similar to that this weekend for a Cortex M4 (sam19g). I found it was not nearly low enough in jitter to allow for stable servo control. Did you get it to work in practice? Also, a minor comment. The period of the signal should be 20 mSec to provide a 50Hz update, In your code, the period is 20mS + the pulse width.

alankrantas commented 4 years ago

@ewwaller I tested it on micro:bit and Arduino Nano 33 IoT and it actually worked well enough on several types of servos. I guess I was being lazy, since 0.5-2.5 ms is small compared to 20ms.

How about this?

func (d *Device) ServoRoutine() {
    for {
        if d.attached {
            pulse := valueMapping(int32(d.angle), 0, 180, d.pulseMin, d.pulseMax)
            d.pin.High()
            time.Sleep(time.Microsecond * time.Duration(pulse))
            d.pin.Low()
            time.Sleep(time.Microsecond * time.Duration(20000.0-pulse))
        }
    }
}
ewwaller commented 4 years ago

Looks right.   I am interested in why it may have been more stable on the Nano 33 vs the Cortex M4.  Which scheduler were you using?

alankrantas commented 4 years ago

The default one I think, never tried to change it.

Just did a test on my Metro M4 (powered via the DC jack). Works fine except some very small jittering.

Nerzal commented 4 years ago

Won't work on Arduino UNO on any scheduler (none, tasks, coroutines)

aykevl commented 3 years ago

I have a prototype servo driver that works on both the nrf52832 and Arduino Uno (AVR) and probably works on the SAM D21 too (so far untested). It is totally jitter free with the one servo I have, a problem that I've heard is common with Servo.h and should allow driving multiple servos from a single PWM instance.

Nerzal commented 3 years ago

@aykevl can you give us a link? :)

aykevl commented 3 years ago

@Nerzal here is the WIP code: https://github.com/tinygo-org/drivers/compare/dev...servo It currently does not compile with https://github.com/tinygo-org/tinygo/pull/1121 as I changed the interface a little bit.

Nerzal commented 3 years ago

Thank U ❤️

deadprogram commented 3 years ago

See #264 so now closing.