Skip to content

Commit 36f25f4

Browse files
committed
Simplify min, max logic and increase range
Different servo models can accept a wide range of pulse widths. Even different servos of the same model might vary a bit. Currently, the Arduino Servo library has a severely restricted hard limit on the pulse widths that can be sent to servos. Specifically: - Minimum pulse width must be between [32, 1052]. - Maximum pulse width must be between [1888, 2908]. Many popular servos have min/max pulse widths that fall in that unavailable range between (1052, 1888). For instance, the [Parallax Feedback 360° High-Speed Servo](https://parallax.com/sites/default/files/downloads/900-00360-Feedback-360-HS-Servo-v1.2.pdf) operates between [1280, 1720]. Before this commit, each instance of `Servo` stored their `min` and `max` values as `int8_t`. Since that only leaves room for values in the range [-128, 127], it can't store meaningful servo pulse widths, which are typically in the ~[1000, 2000]µs range. To compensate, `min` and `max` store the distance from the default values, divided by 4… There are two problems with this: - The first, mentioned above, is that you can never stray more than 512µs from `MIN_PULSE_WIDTH` and `MAX_PULSE_WIDTH`. - The second is that unexpected and unnecessary rounding occurs. Simply storing `min` and `max` as `uint16_t` and using the values directly solves this problem, and reduces the complexity involved in working around it. This commit makes the library faster, and allows it to work with a wider range of servos. It also fixes some subtle bugs where the minimum value was hardcoded to `MIN_PULSE_WIDTH`. Tested on an Arduino Uno with a [Tower Pro Micro Servo SG90](http://www.ee.ic.ac.uk/pcheung/teaching/DE1_EE/stores/sg90_datasheet.pdf), and a [Parallax Feedback 360° High-Speed Servo](https://parallax.com/sites/default/files/downloads/900-00360-Feedback-360-HS-Servo-v1.2.pdf).
1 parent 931445b commit 36f25f4

File tree

5 files changed

+39
-56
lines changed

5 files changed

+39
-56
lines changed

src/Servo.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ class Servo
113113
bool attached(); // return true if this servo is attached, otherwise false
114114
private:
115115
uint8_t servoIndex; // index into the channel data for this servo
116-
int8_t min; // minimum is this value times 4 added to MIN_PULSE_WIDTH
117-
int8_t max; // maximum is this value times 4 added to MAX_PULSE_WIDTH
116+
int16_t min; // minimum pulse width in microseconds
117+
int16_t max; // maximum pulse width in microseconds
118118
};
119119

120120
#endif

src/avr/Servo.cpp

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,6 @@ uint8_t ServoCount = 0; // the total number
4444
#define SERVO_INDEX(_timer,_channel) ((_timer*SERVOS_PER_TIMER) + _channel) // macro to access servo index by timer and channel
4545
#define SERVO(_timer,_channel) (servos[SERVO_INDEX(_timer,_channel)]) // macro to access servo class by timer and channel
4646

47-
#define SERVO_MIN() (MIN_PULSE_WIDTH - this->min * 4) // minimum value in uS for this servo
48-
#define SERVO_MAX() (MAX_PULSE_WIDTH - this->max * 4) // maximum value in uS for this servo
49-
5047
/************ static functions common to all instances ***********************/
5148

5249
static inline void handle_interrupts(timer16_Sequence_t timer, volatile uint16_t *TCNTn, volatile uint16_t* OCRnA)
@@ -240,9 +237,8 @@ uint8_t Servo::attach(int pin, int min, int max)
240237
if(this->servoIndex < MAX_SERVOS ) {
241238
pinMode( pin, OUTPUT) ; // set servo pin to output
242239
servos[this->servoIndex].Pin.nbr = pin;
243-
// todo min/max check: abs(min - MIN_PULSE_WIDTH) /4 < 128
244-
this->min = (MIN_PULSE_WIDTH - min)/4; //resolution of min/max is 4 uS
245-
this->max = (MAX_PULSE_WIDTH - max)/4;
240+
this->min = min;
241+
this->max = max;
246242
// initialize the timer if it has not already been initialized
247243
timer16_Sequence_t timer = SERVO_INDEX_TO_TIMER(servoIndex);
248244
if(isTimerActive(timer) == false)
@@ -263,11 +259,11 @@ void Servo::detach()
263259

264260
void Servo::write(int value)
265261
{
266-
if(value < MIN_PULSE_WIDTH)
262+
if(value < this->min)
267263
{ // treat values less than the minimum pulse width as angles in degrees (valid values in microseconds are handled as microseconds)
268264
if(value < 0) value = 0;
269265
if(value > 180) value = 180;
270-
value = map(value, 0, 180, SERVO_MIN(), SERVO_MAX());
266+
value = map(value, 0, 180, this->min, this->max);
271267
}
272268
this->writeMicroseconds(value);
273269
}
@@ -278,10 +274,10 @@ void Servo::writeMicroseconds(int value)
278274
byte channel = this->servoIndex;
279275
if( (channel < MAX_SERVOS) ) // ensure channel is valid
280276
{
281-
if( value < SERVO_MIN() ) // ensure pulse width is valid
282-
value = SERVO_MIN();
283-
else if( value > SERVO_MAX() )
284-
value = SERVO_MAX();
277+
if( value < this->min ) // ensure pulse width is valid
278+
value = this->min;
279+
else if( value > this->max )
280+
value = this->max;
285281

286282
value = value - TRIM_DURATION;
287283
value = usToTicks(value); // convert to ticks after compensating for interrupt overhead - 12 Aug 2009
@@ -295,7 +291,7 @@ void Servo::writeMicroseconds(int value)
295291

296292
int Servo::read() // return the value as degrees
297293
{
298-
return map( this->readMicroseconds()+1, SERVO_MIN(), SERVO_MAX(), 0, 180);
294+
return map(this->readMicroseconds()+1, this->min, this->max, 0, 180);
299295
}
300296

301297
int Servo::readMicroseconds()
@@ -315,4 +311,3 @@ bool Servo::attached()
315311
}
316312

317313
#endif // ARDUINO_ARCH_AVR
318-

src/megaavr/Servo.cpp

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ static volatile int8_t currentServoIndex[_Nbr_16timers]; // index for the serv
2020
#define SERVO_INDEX(_timer,_channel) ((_timer*SERVOS_PER_TIMER) + _channel) // macro to access servo index by timer and channel
2121
#define SERVO(_timer,_channel) (servos[SERVO_INDEX(_timer,_channel)]) // macro to access servo class by timer and channel
2222

23-
#define SERVO_MIN() (MIN_PULSE_WIDTH - this->min * 4) // minimum value in uS for this servo
24-
#define SERVO_MAX() (MAX_PULSE_WIDTH - this->max * 4) // maximum value in uS for this servo
25-
2623
void ServoHandler(int timer)
2724
{
2825
if (currentServoIndex[timer] < 0) {
@@ -131,9 +128,8 @@ uint8_t Servo::attach(int pin, int min, int max)
131128
if (this->servoIndex < MAX_SERVOS) {
132129
pinMode(pin, OUTPUT); // set servo pin to output
133130
servos[this->servoIndex].Pin.nbr = pin;
134-
// todo min/max check: abs(min - MIN_PULSE_WIDTH) /4 < 128
135-
this->min = (MIN_PULSE_WIDTH - min)/4; //resolution of min/max is 4 uS
136-
this->max = (MAX_PULSE_WIDTH - max)/4;
131+
this->min = min;
132+
this->max = max;
137133
// initialize the timer if it has not already been initialized
138134
timer = SERVO_INDEX_TO_TIMER(servoIndex);
139135
if (isTimerActive(timer) == false) {
@@ -158,14 +154,14 @@ void Servo::detach()
158154
void Servo::write(int value)
159155
{
160156
// treat values less than the minimum pulse width as angles in degrees (valid values in microseconds are handled as microseconds)
161-
if (value < MIN_PULSE_WIDTH)
157+
if (value < this->min)
162158
{
163159
if (value < 0)
164160
value = 0;
165161
else if (value > 180)
166162
value = 180;
167163

168-
value = map(value, 0, 180, SERVO_MIN(), SERVO_MAX());
164+
value = map(value, 0, 180, this->min, this->max);
169165
}
170166
writeMicroseconds(value);
171167
}
@@ -176,10 +172,10 @@ void Servo::writeMicroseconds(int value)
176172
byte channel = this->servoIndex;
177173
if( (channel < MAX_SERVOS) ) // ensure channel is valid
178174
{
179-
if (value < SERVO_MIN()) // ensure pulse width is valid
180-
value = SERVO_MIN();
181-
else if (value > SERVO_MAX())
182-
value = SERVO_MAX();
175+
if (value < this->min) // ensure pulse width is valid
176+
value = this->min;
177+
else if (value > this->max)
178+
value = this->max;
183179

184180
value = value - TRIM_DURATION;
185181
value = usToTicks(value); // convert to ticks after compensating for interrupt overhead
@@ -189,7 +185,7 @@ void Servo::writeMicroseconds(int value)
189185

190186
int Servo::read() // return the value as degrees
191187
{
192-
return map(readMicroseconds()+1, SERVO_MIN(), SERVO_MAX(), 0, 180);
188+
return map(readMicroseconds()+1, this->min, this->max, 0, 180);
193189
}
194190

195191
int Servo::readMicroseconds()
@@ -208,4 +204,4 @@ bool Servo::attached()
208204
return servos[this->servoIndex].Pin.isActive;
209205
}
210206

211-
#endif
207+
#endif

src/sam/Servo.cpp

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,6 @@ static volatile int8_t Channel[_Nbr_16timers ]; // counter for the s
3838
#define SERVO_INDEX(_timer,_channel) ((_timer*SERVOS_PER_TIMER) + _channel) // macro to access servo index by timer and channel
3939
#define SERVO(_timer,_channel) (servos[SERVO_INDEX(_timer,_channel)]) // macro to access servo class by timer and channel
4040

41-
#define SERVO_MIN() (MIN_PULSE_WIDTH - this->min * 4) // minimum value in uS for this servo
42-
#define SERVO_MAX() (MAX_PULSE_WIDTH - this->max * 4) // maximum value in uS for this servo
43-
4441
/************ static functions common to all instances ***********************/
4542

4643
//------------------------------------------------------------------------------
@@ -202,9 +199,8 @@ uint8_t Servo::attach(int pin, int min, int max)
202199
if (this->servoIndex < MAX_SERVOS) {
203200
pinMode(pin, OUTPUT); // set servo pin to output
204201
servos[this->servoIndex].Pin.nbr = pin;
205-
// todo min/max check: abs(min - MIN_PULSE_WIDTH) /4 < 128
206-
this->min = (MIN_PULSE_WIDTH - min)/4; //resolution of min/max is 4 uS
207-
this->max = (MAX_PULSE_WIDTH - max)/4;
202+
this->min = min;
203+
this->max = max;
208204
// initialize the timer if it has not already been initialized
209205
timer = SERVO_INDEX_TO_TIMER(servoIndex);
210206
if (isTimerActive(timer) == false) {
@@ -229,14 +225,14 @@ void Servo::detach()
229225
void Servo::write(int value)
230226
{
231227
// treat values less than the minimum pulse width as angles in degrees (valid values in microseconds are handled as microseconds)
232-
if (value < MIN_PULSE_WIDTH)
228+
if (value < this->min)
233229
{
234230
if (value < 0)
235231
value = 0;
236232
else if (value > 180)
237233
value = 180;
238234

239-
value = map(value, 0, 180, SERVO_MIN(), SERVO_MAX());
235+
value = map(value, 0, 180, this->min, this->max);
240236
}
241237
writeMicroseconds(value);
242238
}
@@ -247,10 +243,10 @@ void Servo::writeMicroseconds(int value)
247243
byte channel = this->servoIndex;
248244
if( (channel < MAX_SERVOS) ) // ensure channel is valid
249245
{
250-
if (value < SERVO_MIN()) // ensure pulse width is valid
251-
value = SERVO_MIN();
252-
else if (value > SERVO_MAX())
253-
value = SERVO_MAX();
246+
if (value < this->min) // ensure pulse width is valid
247+
value = this->min;
248+
else if (value > this->max)
249+
value = this->max;
254250

255251
value = value - TRIM_DURATION;
256252
value = usToTicks(value); // convert to ticks after compensating for interrupt overhead
@@ -260,7 +256,7 @@ void Servo::writeMicroseconds(int value)
260256

261257
int Servo::read() // return the value as degrees
262258
{
263-
return map(readMicroseconds()+1, SERVO_MIN(), SERVO_MAX(), 0, 180);
259+
return map(readMicroseconds()+1, this->min, this->max, 0, 180);
264260
}
265261

266262
int Servo::readMicroseconds()

src/samd/Servo.cpp

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,6 @@ static volatile int8_t currentServoIndex[_Nbr_16timers]; // index for the serv
3838
#define SERVO_INDEX(_timer,_channel) ((_timer*SERVOS_PER_TIMER) + _channel) // macro to access servo index by timer and channel
3939
#define SERVO(_timer,_channel) (servos[SERVO_INDEX(_timer,_channel)]) // macro to access servo class by timer and channel
4040

41-
#define SERVO_MIN() (MIN_PULSE_WIDTH - this->min * 4) // minimum value in uS for this servo
42-
#define SERVO_MAX() (MAX_PULSE_WIDTH - this->max * 4) // maximum value in uS for this servo
43-
4441
#define WAIT_TC16_REGS_SYNC(x) while(x->COUNT16.STATUS.bit.SYNCBUSY);
4542

4643
/************ static functions common to all instances ***********************/
@@ -217,9 +214,8 @@ uint8_t Servo::attach(int pin, int min, int max)
217214
if (this->servoIndex < MAX_SERVOS) {
218215
pinMode(pin, OUTPUT); // set servo pin to output
219216
servos[this->servoIndex].Pin.nbr = pin;
220-
// todo min/max check: abs(min - MIN_PULSE_WIDTH) /4 < 128
221-
this->min = (MIN_PULSE_WIDTH - min)/4; //resolution of min/max is 4 uS
222-
this->max = (MAX_PULSE_WIDTH - max)/4;
217+
this->min = min;
218+
this->max = max;
223219
// initialize the timer if it has not already been initialized
224220
timer = SERVO_INDEX_TO_TIMER(servoIndex);
225221
if (isTimerActive(timer) == false) {
@@ -244,14 +240,14 @@ void Servo::detach()
244240
void Servo::write(int value)
245241
{
246242
// treat values less than the minimum pulse width as angles in degrees (valid values in microseconds are handled as microseconds)
247-
if (value < MIN_PULSE_WIDTH)
243+
if (value < this->min)
248244
{
249245
if (value < 0)
250246
value = 0;
251247
else if (value > 180)
252248
value = 180;
253249

254-
value = map(value, 0, 180, SERVO_MIN(), SERVO_MAX());
250+
value = map(value, 0, 180, this->min, this->max);
255251
}
256252
writeMicroseconds(value);
257253
}
@@ -262,10 +258,10 @@ void Servo::writeMicroseconds(int value)
262258
byte channel = this->servoIndex;
263259
if( (channel < MAX_SERVOS) ) // ensure channel is valid
264260
{
265-
if (value < SERVO_MIN()) // ensure pulse width is valid
266-
value = SERVO_MIN();
267-
else if (value > SERVO_MAX())
268-
value = SERVO_MAX();
261+
if (value < this->min) // ensure pulse width is valid
262+
value = this->min;
263+
else if (value > this->max)
264+
value = this->max;
269265

270266
value = value - TRIM_DURATION;
271267
value = usToTicks(value); // convert to ticks after compensating for interrupt overhead
@@ -275,7 +271,7 @@ void Servo::writeMicroseconds(int value)
275271

276272
int Servo::read() // return the value as degrees
277273
{
278-
return map(readMicroseconds()+1, SERVO_MIN(), SERVO_MAX(), 0, 180);
274+
return map(readMicroseconds()+1, this->min, this->max, 0, 180);
279275
}
280276

281277
int Servo::readMicroseconds()

0 commit comments

Comments
 (0)