From b67d281d85bd59a9738a9a43c4db1027f81d9208 Mon Sep 17 00:00:00 2001 From: Marti Bolivar Date: Wed, 8 Dec 2010 23:39:37 -0500 Subject: Servo library tested and debugged. Some additional HardwareTimer methods introduced to make this convenient; ancillary libmaple/timers.h changes resulted. --- libraries/Servo/Servo.cpp | 138 ++++++++++++++++++++++++++++++++++++++++++++++ libraries/Servo/Servo.h | 116 ++++++++++++++++++++++++++++++++++++++ libraries/Servo/rules.mk | 35 ++++++++++++ 3 files changed, 289 insertions(+) create mode 100644 libraries/Servo/Servo.cpp create mode 100644 libraries/Servo/Servo.h create mode 100644 libraries/Servo/rules.mk (limited to 'libraries/Servo') diff --git a/libraries/Servo/Servo.cpp b/libraries/Servo/Servo.cpp new file mode 100644 index 0000000..4f60fbe --- /dev/null +++ b/libraries/Servo/Servo.cpp @@ -0,0 +1,138 @@ +/****************************************************************************** + * The MIT License + * + * Copyright (c) 2010, LeafLabs, LLC. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + *****************************************************************************/ + +#include "boards.h" +#include "io.h" +#include "pwm.h" +#include "wirish_math.h" + +#include "Servo.h" + +// Configure prescaler and overflow for a 20msec period (could just +// use HardwareTimer::setPeriod(), but this lets conversions below +// happen more statically, in combination with an inlined map() -- a +// premature optimization? TODO profile speed/size tradeoff) +#define CYC_20MSEC (20000 * CYCLES_PER_MICROSECOND) +#define SERVO_PRE ((uint16)((CYC_20MSEC >> 16) + 1)) +#define SERVO_OVF ((uint16)((CYC_20MSEC / SERVO_PRE) - 1)) +#define SERVO_TAU_USEC \ + ((uint32)(((double)SERVO_OVF) * SERVO_PRE / CYCLES_PER_MICROSECOND + 0.5)) + +#define US_TO_COMPARE(us) ((uint16)map(us, 0, SERVO_TAU_USEC, 0, SERVO_OVF)) +#define COMPARE_TO_US(c) ((uint32)map(c, 0, SERVO_OVF, 0, SERVO_TAU_USEC)) + +#define ANGLE_TO_US(a) ((uint16)(map(a, 0, 180, this->min, this->max))) +#define US_TO_ANGLE(us) ((uint8)(map(us, this->min, this->max, 0, 180))) + +Servo::Servo() { + this->pin = NOT_ATTACHED; + this->timer = 0; + this->channel = TIMER_INVALID; + this->min = SERVO_DEFAULT_MIN_PW; + this->max = SERVO_DEFAULT_MAX_PW; +} + +bool Servo::attach(uint8_t pin) { + return this->attach(pin, SERVO_DEFAULT_MIN_PW, SERVO_DEFAULT_MAX_PW); +} + +bool Servo::attach(uint8_t pin, uint16_t min, uint16_t max) { + timer_dev_num timer_num = PIN_MAP[pin].timer_num; + uint32_t channel = PIN_MAP[pin].timer_chan; + if (timer_num == TIMER_INVALID) { + // don't reset any members or ASSERT(0), to keep driving any + // previously attach()ed servo. + return false; + } + this->pin = pin; + this->timer = getTimer(timer_num); + this->channel = channel; + this->min = min; + this->max = max; + + pinMode(pin, PWM); + + this->timer->pause(); + this->timer->setPrescaleFactor(SERVO_PRE); + this->timer->setOverflow(SERVO_OVF); + this->timer->generateUpdate(); + this->timer->resume(); + return true; +} + +bool Servo::detach() { + if (this->pin == NOT_ATTACHED) return false; + + this->timer->setChannelMode(this->channel, TIMER_DISABLED); + + this->pin = NOT_ATTACHED; + this->timer = 0; + this->channel = TIMER_INVALID; + this->min = SERVO_DEFAULT_MIN_PW; + this->max = SERVO_DEFAULT_MAX_PW; + + return true; +} + +void Servo::write(unsigned int value) { + if (value < SERVO_MAX_WRITE_ANGLE) { + this->writeMicroseconds(ANGLE_TO_US(value)); + } else { + this->writeMicroseconds(value); + } +} + +void Servo::writeMicroseconds(uint16_t pulseWidth) { + if (this->pin == NOT_ATTACHED) { + ASSERT(0); + return; + } + + pulseWidth = constrain(pulseWidth, this->min, this->max); + pwmWrite(this->pin, US_TO_COMPARE(pulseWidth)); +} + +int Servo::read() const { + if (this->pin == NOT_ATTACHED) { + ASSERT(0); + return 0; + } + + unsigned int pw = this->readMicroseconds(); + int a = US_TO_ANGLE(pw); + // map() round-trips in a weird way we correct for here + return a == 0 || a == 180 ? a : a + 1; +} + +uint16_t Servo::readMicroseconds() const { + if (this->pin == NOT_ATTACHED) { + ASSERT(0); + return 0; + } + + unsigned int compare = this->timer->getCompare(this->channel); + uint16_t c = COMPARE_TO_US(compare); + // map() round-trips in a weird way we correct for here + return c == 0 || c == 180 ? c : c + 1; +} diff --git a/libraries/Servo/Servo.h b/libraries/Servo/Servo.h new file mode 100644 index 0000000..d35572a --- /dev/null +++ b/libraries/Servo/Servo.h @@ -0,0 +1,116 @@ +/****************************************************************************** + * The MIT License + * + * Copyright (c) 2010, LeafLabs, LLC. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + *****************************************************************************/ + +#ifndef _SERVO_H_ +#define _SERVO_H_ + +#include + +#include "HardwareTimer.h" + +/* Note on Arduino compatibility: + + In the Arduino implementation, PWM is done "by hand" in the sense + that timer channels are hijacked in groups and an ISR is set which + toggles Servo::attach()ed pins using digitalWrite(). + + While this scheme allows any pin to drive a servo, it chews up + cycles and complicates the programmer's notion of when a particular + timer channel will be in use. + + This implementation only allows Servo instances to Servo::attach() + to pins that already have a timer channel associated with them, and + just uses pwmWrite() to drive the wave. + + This introduces an incompatibility: while the Arduino + implementation of attach() returns the affected channel on success + and 0 on failure, this one returns true on success and false on + failure. + + RC Servos expect a pulse every 20ms. Since periods are set for + entire timers, rather than individual channels, attach()ing a Servo + to a pin can interfere with other pins associated with the same + timer. As always, the pin mapping mega table is your friend. + */ + +// Pin number of unattached pins +#define NOT_ATTACHED (-1) + +// Maximum angle in degrees you can write(), exclusive. Value chosen +// for Arduino compatibility. +#define SERVO_MAX_WRITE_ANGLE (200) + +// Default min (0 deg)/max(180 deg) pulse widths, in microseconds. +// Value chosen for Arduino compatibility. +#define SERVO_DEFAULT_MIN_PW (544) +#define SERVO_DEFAULT_MAX_PW (2400) + +class Servo { +public: + Servo(); + + /* Pin has to have a timer channel associated with it already; + * sets pinMode to PWM and returns true iff successful (failure + * when pin doesn't support PWM). doesn't detach any ISRs + * associated with timer channel. */ + bool attach(uint8_t pin); + + /* Like attach(int), but with (inclusive) min (0 degree) and max + * (180 degree) pulse widths, in microseconds. + */ + bool attach(uint8_t pin, uint16_t min, uint16_t max); + + /* Return pin number if currently attach()ed to a pin, + NOT_ATTACHED otherwise. */ + int attached() const { return pin; } + + /* Stop driving the wave by disabling the output compare + interrupt. Returns true if this call did anything. */ + bool detach(); + + /* If value < MAX_WRITE_ANGLE, treated as an angle in degrees. + Otherwise, it's treated as a pulse width. */ + void write(unsigned int value); + + /* If outside of [min, max] determined by attach(), it is clamped + to lie in that range. */ + void writeMicroseconds(uint16_t pulseWidth); + + /* Return servo target angle, in degrees. This will lie between 0 + and 180. */ + int read() const; + + /* Returns the current pulse width, in microseconds. This will + lie within the [min, max] range. */ + uint16_t readMicroseconds() const; + +private: + int8_t pin; + HardwareTimer *timer; + int channel; + uint16_t min; + uint16_t max; +}; + +#endif /* _SERVO_H_ */ diff --git a/libraries/Servo/rules.mk b/libraries/Servo/rules.mk new file mode 100644 index 0000000..13cd364 --- /dev/null +++ b/libraries/Servo/rules.mk @@ -0,0 +1,35 @@ +# Standard things +sp := $(sp).x +dirstack_$(sp) := $(d) +d := $(dir) +BUILDDIRS += $(BUILD_PATH)/$(d) + +# Local flags +CFLAGS_$(d) := $(WIRISH_INCLUDES) $(LIBMAPLE_INCLUDES) + +# Local rules and targets +cSRCS_$(d) := + +# examples/UDPApp/udpapp.c \ +# examples/SocketApp/socketapp.c \ +# examples/WebClient/webclient.c \ +# examples/WebServer/webserver.c \ +# examples/Flash/webserver.c \ + +cppSRCS_$(d) := Servo.cpp + +cFILES_$(d) := $(cSRCS_$(d):%=$(d)/%) +cppFILES_$(d) := $(cppSRCS_$(d):%=$(d)/%) + +OBJS_$(d) := $(cFILES_$(d):%.c=$(BUILD_PATH)/%.o) \ + $(cppFILES_$(d):%.cpp=$(BUILD_PATH)/%.o) +DEPS_$(d) := $(OBJS_$(d):%.o=%.d) + +$(OBJS_$(d)): TGT_CFLAGS := $(CFLAGS_$(d)) + +TGT_BIN += $(OBJS_$(d)) + +# Standard things +-include $(DEPS_$(d)) +d := $(dirstack_$(sp)) +sp := $(basename $(sp)) \ No newline at end of file -- cgit v1.2.3