eBoard ๐Ÿ‰  โ‘ โ‘งโ‘จ
Written for SIA 2017/2018
eagle_Servo.h
Go to the documentation of this file.
1 #ifndef EAGLE_EBOARD_HELPLIB_SERVO
2 #define EAGLE_EBOARD_HELPLIB_SERVO
3  //import servo-class
4  //shameless copy paste :D
6  #define _useTimer1
7  typedef enum { _timer1, _Nbr_16timers } timer16_Sequence_t;
8  #define MIN_PULSE_WIDTH 544 // the shortest pulse sent to a servo
9  #define MAX_PULSE_WIDTH 2400 // the longest pulse sent to a servo
10  #define DEFAULT_PULSE_WIDTH 1500 // default pulse width when servo is attached
11  #define REFRESH_INTERVAL 20000 // minumim time to refresh servos in microseconds
12 
13  #define SERVOS_PER_TIMER 12 // the maximum number of servos controlled by one timer
14  #define MAX_SERVOS (_Nbr_16timers * SERVOS_PER_TIMER)
15 
16  #define INVALID_SERVO 255 // flag indicating an invalid servo index
17 
23  typedef struct {
25  uint8_t nbr :6 ; // a pin number from 0 to 63
27  uint8_t isActive :1 ; // true if this channel is enabled, pin not pulsed if false
28  } ServoPin_t;
33  typedef struct {
37  volatile unsigned int ticks;
38  } servo_t;
50  class Servo {
51  public:
53  Servo();
59  uint8_t attach(int pin); // attach the given pin to the next free channel, sets pinMode, returns channel number or 0 if failure
67  uint8_t attach(int pin, int min, int max); // as above but also sets min and max values for writes.
69  void detach();
74  void write(int value); // if value is < 200 its treated as an angle, otherwise as pulse width in microseconds
79  void writeMicroseconds(int value); // Write pulse width in microseconds
84  inline int read(); // returns current pulse width as an angle between 0 and 180 degrees
89  int readMicroseconds(); // returns current pulse width in microseconds for this servo (was read_us() in first release)
94  inline bool attached(); // return true if this servo is attached, otherwise false
95  private:
97  uint8_t servoIndex; // index into the channel data for this servo
99  int8_t min; // minimum is this value times 4 added to MIN_PULSE_WIDTH
101  int8_t max; // maximum is this value times 4 added to MAX_PULSE_WIDTH
102  };
104  #define usToTicks(_us) (( clockCyclesPerMicrosecond()* _us) / 8) // converts microseconds to tick (assumes prescale of 8) // 12 Aug 2009
105  #define ticksToUs(_ticks) (( (unsigned)_ticks * 8)/ clockCyclesPerMicrosecond() ) // converts from ticks back to microseconds
106 
107 
108  #define TRIM_DURATION 2 // compensation ticks to trim adjust for digitalWrite delays // 12 August 2009
109 
110  //#define NBR_TIMERS (MAX_SERVOS / SERVOS_PER_TIMER)
111 
112  static servo_t servos[MAX_SERVOS]; // static array of servo structures
113  static volatile int8_t Channel[_Nbr_16timers ]; // counter for the servo being pulsed for each timer (or -1 if refresh interval)
114 
115  uint8_t ServoCount = 0; // the total number of attached servos
116 
117  // convenience macros
118  #define SERVO_INDEX_TO_TIMER(_servo_nbr) ((timer16_Sequence_t)(_servo_nbr / SERVOS_PER_TIMER)) // returns the timer controlling this servo
119  #define SERVO_INDEX_TO_CHANNEL(_servo_nbr) (_servo_nbr % SERVOS_PER_TIMER) // returns the index of the servo on this timer
120  #define SERVO_INDEX(_timer,_channel) ((_timer*SERVOS_PER_TIMER) + _channel) // macro to access servo index by timer and channel
121  #define SERVO(_timer,_channel) (servos[SERVO_INDEX(_timer,_channel)]) // macro to access servo class by timer and channel
122 
123  #define SERVO_MIN() (MIN_PULSE_WIDTH - this->min * 4) // minimum value in uS for this servo
124  #define SERVO_MAX() (MAX_PULSE_WIDTH - this->max * 4) // maximum value in uS for this servo
125 
126  static inline void handle_interrupts(timer16_Sequence_t timer, volatile uint16_t *TCNTn, volatile uint16_t* OCRnA)
127  {
128  if( Channel[timer] < 0 )
129  *TCNTn = 0; // channel set to -1 indicated that refresh interval completed so reset the timer
130  else{
131  if( SERVO_INDEX(timer,Channel[timer]) < ServoCount && SERVO(timer,Channel[timer]).Pin.isActive == true )
132  digitalWrite( SERVO(timer,Channel[timer]).Pin.nbr,LOW); // pulse this channel low if activated
133  }
134 
135  Channel[timer]++; // increment to the next channel
136  if( SERVO_INDEX(timer,Channel[timer]) < ServoCount && Channel[timer] < SERVOS_PER_TIMER) {
137  *OCRnA = *TCNTn + SERVO(timer,Channel[timer]).ticks;
138  if(SERVO(timer,Channel[timer]).Pin.isActive == true) // check if activated
139  digitalWrite( SERVO(timer,Channel[timer]).Pin.nbr,HIGH); // its an active channel so pulse it high
140  }
141  else {
142  // finished all channels so wait for the refresh period to expire before starting over
143  if( ((unsigned)*TCNTn) + 4 < usToTicks(REFRESH_INTERVAL) ) // allow a few ticks to ensure the next OCR1A not missed
144  *OCRnA = (unsigned int)usToTicks(REFRESH_INTERVAL);
145  else
146  *OCRnA = *TCNTn + 4; // at least REFRESH_INTERVAL has elapsed
147  Channel[timer] = -1; // this will get incremented at the end of the refresh period to start again at the first channel
148  }
149  }
150 
151  #ifndef WIRING // Wiring pre-defines signal handlers so don't define any if compiling for the Wiring platform
152  // Interrupt handlers for Arduino
153  #if defined(_useTimer1)
154  SIGNAL (TIMER1_COMPA_vect)
155  {
156  handle_interrupts(_timer1, &TCNT1, &OCR1A);
157  }
158  #endif
159  #elif defined WIRING
160  // Interrupt handlers for Wiring
161  #if defined(_useTimer1)
162  void Timer1Service()
163  {
164  handle_interrupts(_timer1, &TCNT1, &OCR1A);
165  }
166  #endif
167  #endif
168 
169  inline int Servo::read() // return the value as degrees
170  {
171  return map( this->readMicroseconds()+1, SERVO_MIN(), SERVO_MAX(), 0, 180);
172  }
173 
174  static void initISR(timer16_Sequence_t timer) {
175  if(timer == _timer1) {
176  TCCR1A = 0; // normal counting mode
177  TCCR1B = _BV(CS11); // set prescaler of 8
178  TCNT1 = 0; // clear the timer count
179  TIFR1 |= _BV(OCF1A); // clear any pending interrupts;
180  TIMSK1 |= _BV(OCIE1A) ; // enable the output compare interrupt
181  #if defined(WIRING)
182  timerAttach(TIMER1OUTCOMPAREA_INT, Timer1Service);
183  #endif
184  }
185  }
186 
187  static void finISR(timer16_Sequence_t timer) {
188  #if defined WIRING // Wiring
189  if(timer == _timer1) {
190  TIMSK &= ~_BV(OCIE1A) ; // disable timer 1 output compare interrupt
191  timerDetach(TIMER1OUTCOMPAREA_INT);
192  }
193  else if(timer == _timer3) {
194  ETIMSK &= ~_BV(OCIE3A); // disable the timer3 output compare A interrupt
195  timerDetach(TIMER3OUTCOMPAREA_INT);
196  }
197  #else
198  //For arduino - in future: call here to a currently undefined function to reset the timer
199  (void) timer; // squash "unused parameter 'timer' [-Wunused-parameter]" warning
200  #endif
201  }
202 
203  static boolean isTimerActive(timer16_Sequence_t timer) {
204  // returns true if any servo is active on this timer
205  for(uint8_t channel=0; channel < SERVOS_PER_TIMER; channel++) {
206  if(SERVO(timer,channel).Pin.isActive == true)
207  return true;
208  }
209  return false;
210  }
211 
212  Servo::Servo() {
213  if( ServoCount < MAX_SERVOS) {
214  this->servoIndex = ServoCount++; // assign a servo index to this instance
215  servos[this->servoIndex].ticks = usToTicks(DEFAULT_PULSE_WIDTH); // store default values - 12 Aug 2009
216  }
217  else
218  this->servoIndex = INVALID_SERVO ; // too many servos
219  }
220 
221  uint8_t Servo::attach(int pin) {
222  return this->attach(pin, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH);
223  }
224 
225  uint8_t Servo::attach(int pin, int min, int max) {
226  if(this->servoIndex < MAX_SERVOS ) {
227  pinMode( pin, OUTPUT) ; // set servo pin to output
228  servos[this->servoIndex].Pin.nbr = pin;
229  // todo min/max check: abs(min - MIN_PULSE_WIDTH) /4 < 128
230  this->min = (MIN_PULSE_WIDTH - min)/4; //resolution of min/max is 4 uS
231  this->max = (MAX_PULSE_WIDTH - max)/4;
232  // initialize the timer if it has not already been initialized
233  timer16_Sequence_t timer = SERVO_INDEX_TO_TIMER(servoIndex);
234  if(isTimerActive(timer) == false)
235  initISR(timer);
236  servos[this->servoIndex].Pin.isActive = true; // this must be set after the check for isTimerActive
237  }
238  return this->servoIndex ;
239  }
240 
241  void Servo::detach() {
242  servos[this->servoIndex].Pin.isActive = false;
243  timer16_Sequence_t timer = SERVO_INDEX_TO_TIMER(servoIndex);
244  if(isTimerActive(timer) == false) {
245  finISR(timer);
246  }
247  }
248 
249  void Servo::write(int value) {
250  if(value < MIN_PULSE_WIDTH)
251  { // treat values less than 544 as angles in degrees (valid values in microseconds are handled as microseconds)
252  if(value < 0) value = 0;
253  if(value > 180) value = 180;
254  value = map(value, 0, 180, SERVO_MIN(), SERVO_MAX());
255  }
256  this->writeMicroseconds(value);
257  }
258 
259  void Servo::writeMicroseconds(int value) {
260  // calculate and store the values for the given channel
261  byte channel = this->servoIndex;
262  if( (channel < MAX_SERVOS) ) // ensure channel is valid
263  {
264  if( value < SERVO_MIN() ) // ensure pulse width is valid
265  value = SERVO_MIN();
266  else if( value > SERVO_MAX() )
267  value = SERVO_MAX();
268 
269  value = value - TRIM_DURATION;
270  value = usToTicks(value); // convert to ticks after compensating for interrupt overhead - 12 Aug 2009
271 
272  uint8_t oldSREG = SREG;
273  cli();
274  servos[channel].ticks = value;
275  SREG = oldSREG;
276  }
277  }
278 
279 
281  unsigned int pulsewidth;
282  if( this->servoIndex != INVALID_SERVO )
283  pulsewidth = ticksToUs(servos[this->servoIndex].ticks) + TRIM_DURATION ; // 12 aug 2009
284  else
285  pulsewidth = 0;
286 
287  return pulsewidth;
288  }
289 
290  inline bool Servo::attached() {
291  return servos[this->servoIndex].Pin.isActive ;
292  }
293 
295 #endif
void write(int value)
sets angle or pulse width of the Servo
ServoPin_t stores the data Set for any Servo usable by the Servo-Library.
Definition: eagle_Servo.h:33
ServoPin_t stores the data Set for any Pin usable by the Servo-Library.
Definition: eagle_Servo.h:23
bool attached()
check attached status
int read()
reads the current pulse width as angle
int8_t max
maximum is this value times 4 added to MAX_PULSE_WIDTH
Definition: eagle_Servo.h:101
volatile unsigned int ticks
the tick-speed for the Servo-Unit
Definition: eagle_Servo.h:37
void detach()
detaches the connected pin
Servo()
the constructor
ServoPin_t Pin
the used ServoPin
Definition: eagle_Servo.h:35
uint8_t attach(int pin)
this will tell the Servo-instance which pin the data cable is connected to
uint8_t isActive
checks if the Servo connected to this pin is currently active
Definition: eagle_Servo.h:27
void writeMicroseconds(int value)
sets pulse width of the Servo
uint8_t servoIndex
the channel index this servo is using
Definition: eagle_Servo.h:97
uint8_t nbr
stores the pinID [0;63]
Definition: eagle_Servo.h:25
int readMicroseconds()
reads the current pulse width
int8_t min
minimum is this value times 4 added to MIN_PULSE_WIDTH
Definition: eagle_Servo.h:99
The standard Arduino Servo Class.
Definition: eagle_Servo.h:50