ServoEasing
LightweightServo.hpp
Go to the documentation of this file.
1 /*
2  * LightweightServo.hpp
3  *
4  * Lightweight Servo implementation only for pin 9 and 10 using only timer1 hardware and no interrupts or other overhead.
5  * Provides auto initialization.
6  * 300 bytes code size / 4 bytes RAM including auto initialization compared to 700 / 48 bytes for Arduino Servo library.
7  * 8 bytes for each call to setLightweightServoPulse...
8  *
9  * Copyright (C) 2019 Armin Joachimsmeyer
10  * armin.joachimsmeyer@gmail.com
11  *
12  * This file is part of ServoEasing https://github.com/ArminJo/ServoEasing.
13  *
14  * ServoEasing is free software: you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation, either version 3 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
22  * See the GNU General Public License for more details.
23 
24  * You should have received a copy of the GNU General Public License
25  * along with this program. If not, see <http://www.gnu.org/licenses/gpl.html>.
26  *
27  */
28 
29 #ifndef _LIGHTWEIGHT_SERVO_HPP
30 #define _LIGHTWEIGHT_SERVO_HPP
31 
32 #if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__)
33 #include "LightweightServo.h"
34 
35 /*
36  * Variables to enable adjustment for different servo types
37  * 544 and 2400 are values compatible with standard arduino values
38  * 4 bytes RAM compared to 48 bytes for standard Arduino library
39  */
40 int sMicrosecondsForServo0Degree = 544;
41 int sMicrosecondsForServo180Degree = 2400;
42 
43 /*
44  * Use 16 bit timer1 for generating 2 servo signals entirely by hardware without any interrupts.
45  * Use FastPWM mode and generate pulse at start of the 20 ms period
46  * The 2 servo signals are tied to pin 9 and 10 of an 328.
47  * Attention - both pins are set to OUTPUT here!
48  * 32 bytes code size
49  */
50 void initLightweightServoPin9And10() {
51  /*
52  * Periods below 20 ms gives problems with long signals i.e. the positioning is not possible
53  */
54  DDRB |= _BV(DDB1) | _BV(DDB2); // set pins OC1A = PortB1 -> PIN 9 and OC1B = PortB2 -> PIN 10 to output direction
55  TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM11); // FastPWM Mode mode TOP (20 ms) determined by ICR1 - non-inverting Compare Output mode OC1A+OC1B
56  TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS11); // set prescaler to 8, FastPWM mode mode bits WGM13 + WGM12
57  ICR1 = ISR1_COUNT_FOR_20_MILLIS; // set period to 20 ms
58  // do not set counter here, since with counter = 0 (default) no output signal is generated.
59 }
60 
61 /*
62  * Use 16 bit timer1 for generating 2 servo signals entirely by hardware without any interrupts.
63  * Use FastPWM mode and generate pulse at start of the 20 ms period
64  * The 2 servo signals are tied to pin 9 and 10 of an ATMega328.
65  * Attention - the selected pin is set to OUTPUT here!
66  * 54 bytes code size
67  */
68 void initLightweightServoPin9_10(bool aUsePin9, bool aUsePin10) {
69 
70  uint8_t tNewTCCR1A = TCCR1A & (_BV(COM1A1) | _BV(COM1B1)); // keep existing COM1A1 and COM1B1 settings
71  tNewTCCR1A |= _BV(WGM11); // FastPWM Mode mode TOP (20 ms) determined by ICR1
72 
73  if (aUsePin9) {
74  DDRB |= _BV(DDB1); // set OC1A = PortB1 -> PIN 9 to output direction
75  tNewTCCR1A |= _BV(COM1A1); // non-inverting Compare Output mode OC1A
76  OCR1A = UINT16_MAX; // Set counter > ICR1 here, to avoid output signal generation.
77  }
78  if (aUsePin10) {
79  DDRB |= _BV(DDB2); // set OC1B = PortB2 -> PIN 10 to output direction
80  tNewTCCR1A |= _BV(COM1B1); // non-inverting Compare Output mode OC1B
81  OCR1B = UINT16_MAX; // Set counter > ICR1 here, to avoid output signal generation.
82  }
83  TCCR1A = tNewTCCR1A;
84  TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS11); // set prescaler to 8, FastPWM Mode mode bits WGM13 + WGM12
85  ICR1 = ISR1_COUNT_FOR_20_MILLIS; // set period to 20 ms
86 }
87 
88 /*
89  * Disables Pin 10!
90  */
91 void initLightweightServoPin9() {
92  DDRB |= _BV(DDB1); // set OC1A = PortB1 -> PIN 9 to output direction
93  TCCR1A = _BV(WGM11) | _BV(COM1A1); // FastPWM Mode mode TOP (20 ms) determined by ICR1, non-inverting Compare Output mode OC1A
94  TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS11); // set prescaler to 8, FastPWM Mode mode bits WGM13 + WGM12
95  ICR1 = ISR1_COUNT_FOR_20_MILLIS; // set period to 20 ms
96  OCR1A = UINT16_MAX; // Set counter > ICR1 here, to avoid output signal generation.
97 }
98 /*
99  * Disables Pin 9!
100  */
101 void initLightweightServoPin10() {
102  DDRB |= _BV(DDB2); // set OC1B = PortB2 -> PIN 10 to output direction
103  TCCR1A = _BV(WGM11) | _BV(COM1B1); // FastPWM Mode mode TOP (20 ms) determined by ICR1, non-inverting Compare Output mode OC1B
104  TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS11); // set prescaler to 8, FastPWM Mode mode bits WGM13 + WGM12
105  ICR1 = ISR1_COUNT_FOR_20_MILLIS; // set period to 20 ms
106  OCR1B = UINT16_MAX; // Set counter > ICR1 here, to avoid output signal generation.
107 }
108 
109 void deinitLightweightServoPin9_10(bool aUsePin9, bool aUsePin10) {
110  if (aUsePin9) {
111  DDRB &= ~(_BV(DDB1)); // set OC1A = PortB1 -> PIN 9 to input direction
112  TCCR1A &= ~(_BV(COM1A1)); // disable non-inverting Compare Output mode OC1A
113  }
114  if (aUsePin10) {
115  DDRB &= ~(_BV(DDB2)); // set OC1B = PortB2 -> PIN 10 to input direction
116  TCCR1A &= ~(_BV(COM1B1)); // disable non-inverting Compare Output mode OC1B
117  }
118 }
119 
120 /*
121  * If value is below 180 then assume degree, otherwise assume microseconds
122  * If aUpdateFast then enable starting a new output pulse if more than 5 ms since last one, some servo might react faster in this mode.
123  * If aUsePin9 is false, then Pin10 is used
124  * 236 / 186(without auto init) bytes code size
125  */
126 int writeLightweightServo(int aDegree, bool aUsePin9, bool aUpdateFast) {
127  if (aDegree <= 180) {
128  aDegree = DegreeToMicrosecondsLightweightServo(aDegree);
129  }
130  writeMicrosecondsLightweightServo(aDegree, aUsePin9, aUpdateFast);
131  return aDegree;
132 }
133 
134 void writeMicrosecondsLightweightServo(int aMicroseconds, bool aUsePin9, bool aUpdateFast) {
135 #if !defined(DISABLE_SERVO_TIMER_AUTO_INITIALIZE)
136  // auto initialize
137  if ((TCCR1B != (_BV(WGM13) | _BV(WGM12) | _BV(CS11))) || (aUsePin9 && ((TCCR1A & ~_BV(COM1B1)) != (_BV(COM1A1) | _BV(WGM11))))
138  || (!aUsePin9 && ((TCCR1A & ~_BV(COM1A1)) != (_BV(COM1B1) | _BV(WGM11))))) {
139  initLightweightServoPin9_10(aUsePin9, !aUsePin9);
140  }
141 #endif
142  // since the resolution is 1/2 of microsecond
143  aMicroseconds *= 2;
144  if (aUpdateFast) {
145  uint16_t tTimerCount = TCNT1;
146  if (tTimerCount > 5000) {
147  // more than 2.5 ms since last pulse -> start a new one
148  TCNT1 = ICR1 - 1;
149  }
150  }
151  if (aUsePin9) {
152  OCR1A = aMicroseconds;
153  } else {
154  OCR1B = aMicroseconds;
155  }
156 }
157 
158 /*
159  * Sets the period of the servo pulses. Reasonable values are 2500 to 20000 microseconds.
160  * No parameter checking is done here!
161  */
162 void setLightweightServoRefreshRate(unsigned int aRefreshPeriodMicroseconds) {
163  ICR1 = aRefreshPeriodMicroseconds * 2;
164 }
165 /*
166  * Set the mapping pulse width values for 0 and 180 degree
167  */
168 void setLightweightServoPulseMicrosFor0And180Degree(int aMicrosecondsForServo0Degree, int aMicrosecondsForServo180Degree) {
169  sMicrosecondsForServo0Degree = aMicrosecondsForServo0Degree;
170  sMicrosecondsForServo180Degree = aMicrosecondsForServo180Degree;
171 }
172 
173 /*
174  * Pin 9 / Channel A. If value is below 180 then assume degree, otherwise assume microseconds
175  */
176 void write9(int aDegree, bool aUpdateFast) {
177  writeLightweightServo(aDegree, true, aUpdateFast);
178 }
179 
180 void writeMicroseconds9(int aMicroseconds, bool aUpdateFast) {
181  writeMicrosecondsLightweightServo(aMicroseconds, true, aUpdateFast);
182 }
183 
184 /*
185  * Without auto initialize!
186  */
187 void writeMicroseconds9Direct(int aMicroseconds) {
188  OCR1A = aMicroseconds * 2;
189 }
190 
191 /*
192  * Pin 10 / Channel B
193  */
194 void write10(int aDegree, bool aUpdateFast) {
195  writeLightweightServo(aDegree, false, aUpdateFast);
196 }
197 
198 void writeMicroseconds10(int aMicroseconds, bool aUpdateFast) {
199  writeMicrosecondsLightweightServo(aMicroseconds, false, aUpdateFast);
200 }
201 
202 /*
203  * Without auto initialize!
204  */
205 void writeMicroseconds10Direct(int aMicroseconds) {
206  OCR1B = aMicroseconds * 2;
207 }
208 
209 /*
210  * Conversion functions
211  */
212 int DegreeToMicrosecondsLightweightServo(int aDegree) {
213  return (map(aDegree, 0, 180, sMicrosecondsForServo0Degree, sMicrosecondsForServo180Degree));
214 }
215 
216 int MicrosecondsToDegreeLightweightServo(int aMicroseconds) {
217  return map(aMicroseconds, sMicrosecondsForServo0Degree, sMicrosecondsForServo180Degree, 0, 180);
218 }
219 
220 #endif // defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__)
221 #endif // _LIGHTWEIGHT_SERVO_HPP
LightweightServo.h