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 
4 
13 //=====================================================================================================================================================
14 // Macro Definitions
15 //=====================================================================================================================================================
16 
17  //import servo-class
18  //shameless copy paste :D
20 
21  #define _useTimer1
22  typedef enum { _timer1, _Nbr_16timers } timer16_Sequence_t;
23  #define MIN_PULSE_WIDTH 544 // the shortest pulse sent to a servo
24  #define MAX_PULSE_WIDTH 2400 // the longest pulse sent to a servo
25  #define DEFAULT_PULSE_WIDTH 1500 // default pulse width when servo is attached
26  #define REFRESH_INTERVAL 20000 // minumim time to refresh servos in microseconds
27 
28  #define SERVOS_PER_TIMER 12 // the maximum number of servos controlled by one timer
29  #define MAX_SERVOS (_Nbr_16timers * SERVOS_PER_TIMER)
30 
31  #define INVALID_SERVO 255 // flag indicating an invalid servo index
32 
33 //=====================================================================================================================================================
34 // ServoPin_t
35 //=====================================================================================================================================================
36 
41  typedef struct {
43  uint8_t nbr :6 ; // a pin number from 0 to 63
45  uint8_t isActive :1 ; // true if this channel is enabled, pin not pulsed if false
46  } ServoPin_t;
47 
48 //=====================================================================================================================================================
49 // Servo_t
50 //=====================================================================================================================================================
51 
56  typedef struct {
58  ServoPin_t Pin;
60  volatile unsigned int ticks;
61  } servo_t;
62 
63 //=====================================================================================================================================================
64 // Servo
65 //=====================================================================================================================================================
66 
67  //-------------------------------------------------------------------------------------------------------------------------------------------------
68  // class
69  //-------------------------------------------------------------------------------------------------------------------------------------------------
70 
72 
84  class Servo {
85  public:
87  Servo();
93  uint8_t attach(int pin); // attach the given pin to the next free channel, sets pinMode, returns channel number or 0 if failure
101  uint8_t attach(int pin, int min, int max); // as above but also sets min and max values for writes.
103  void detach();
108  void write(int value); // if value is < 200 its treated as an angle, otherwise as pulse width in microseconds
113  void writeMicroseconds(int value); // Write pulse width in microseconds
118  inline int read(); // returns current pulse width as an angle between 0 and 180 degrees
123  int readMicroseconds(); // returns current pulse width in microseconds for this servo (was read_us() in first release)
128  inline bool attached(); // return true if this servo is attached, otherwise false
129  private:
131  uint8_t servoIndex; // index into the channel data for this servo
133  int8_t min; // minimum is this value times 4 added to MIN_PULSE_WIDTH
135  int8_t max; // maximum is this value times 4 added to MAX_PULSE_WIDTH
136  };
138 
139  //-------------------------------------------------------------------------------------------------------------------------------------------------
140  // definitions
141  //-------------------------------------------------------------------------------------------------------------------------------------------------
142 
143  #define usToTicks(_us) (( clockCyclesPerMicrosecond()* _us) / 8) // converts microseconds to tick (assumes prescale of 8) // 12 Aug 2009
144  #define ticksToUs(_ticks) (( (unsigned)_ticks * 8)/ clockCyclesPerMicrosecond() ) // converts from ticks back to microseconds
145 
146 
147  #define TRIM_DURATION 2 // compensation ticks to trim adjust for digitalWrite delays // 12 August 2009
148 
149  //#define NBR_TIMERS (MAX_SERVOS / SERVOS_PER_TIMER)
150 
151  static servo_t servos[MAX_SERVOS]; // static array of servo structures
152  static volatile int8_t Channel[_Nbr_16timers ]; // counter for the servo being pulsed for each timer (or -1 if refresh interval)
153 
154  uint8_t ServoCount = 0; // the total number of attached servos
155 
156  // convenience macros
157  #define SERVO_INDEX_TO_TIMER(_servo_nbr) ((timer16_Sequence_t)(_servo_nbr / SERVOS_PER_TIMER)) // returns the timer controlling this servo
158  #define SERVO_INDEX_TO_CHANNEL(_servo_nbr) (_servo_nbr % SERVOS_PER_TIMER) // returns the index of the servo on this timer
159  #define SERVO_INDEX(_timer,_channel) ((_timer*SERVOS_PER_TIMER) + _channel) // macro to access servo index by timer and channel
160  #define SERVO(_timer,_channel) (servos[SERVO_INDEX(_timer,_channel)]) // macro to access servo class by timer and channel
161 
162  #define SERVO_MIN() (MIN_PULSE_WIDTH - this->min * 4) // minimum value in uS for this servo
163  #define SERVO_MAX() (MAX_PULSE_WIDTH - this->max * 4) // maximum value in uS for this servo
164 
165 
166  //========= TIMER
167 
168  static void initISR(timer16_Sequence_t timer) {
169  if(timer == _timer1) {
170  TCCR1A = 0; // normal counting mode
171  TCCR1B = _BV(CS11); // set prescaler of 8
172  TCNT1 = 0; // clear the timer count
173  TIFR1 |= _BV(OCF1A); // clear any pending interrupts;
174  TIMSK1 |= _BV(OCIE1A) ; // enable the output compare interrupt
175  #if defined(WIRING)
176  timerAttach(TIMER1OUTCOMPAREA_INT, Timer1Service);
177  #endif
178  }
179  }
180 
181  static void finISR(timer16_Sequence_t timer) {
182  #if defined WIRING // Wiring
183  if(timer == _timer1) {
184  TIMSK &= ~_BV(OCIE1A) ; // disable timer 1 output compare interrupt
185  timerDetach(TIMER1OUTCOMPAREA_INT);
186  }
187  else if(timer == _timer3) {
188  ETIMSK &= ~_BV(OCIE3A); // disable the timer3 output compare A interrupt
189  timerDetach(TIMER3OUTCOMPAREA_INT);
190  }
191  #else
192  //For arduino - in future: call here to a currently undefined function to reset the timer
193  (void) timer; // squash "unused parameter 'timer' [-Wunused-parameter]" warning
194  #endif
195  }
196 
197  static bool isTimerActive(timer16_Sequence_t timer) {
198  // returns true if any servo is active on this timer
199  for(uint8_t channel=0; channel < SERVOS_PER_TIMER; channel++) {
200  if(SERVO(timer,channel).Pin.isActive == true)
201  return true;
202  }
203  return false;
204  }
205 
206  static inline void handle_interrupts(timer16_Sequence_t timer, volatile uint16_t *TCNTn, volatile uint16_t* OCRnA)
207  {
208  if( Channel[timer] < 0 )
209  *TCNTn = 0; // channel set to -1 indicated that refresh interval completed so reset the timer
210  else{
211  if( SERVO_INDEX(timer,Channel[timer]) < ServoCount && SERVO(timer,Channel[timer]).Pin.isActive == true )
212  digitalWrite( SERVO(timer,Channel[timer]).Pin.nbr,LOW); // pulse this channel low if activated
213  }
214 
215  Channel[timer]++; // increment to the next channel
216  if( SERVO_INDEX(timer,Channel[timer]) < ServoCount && Channel[timer] < SERVOS_PER_TIMER) {
217  *OCRnA = *TCNTn + SERVO(timer,Channel[timer]).ticks;
218  if(SERVO(timer,Channel[timer]).Pin.isActive == true) // check if activated
219  digitalWrite( SERVO(timer,Channel[timer]).Pin.nbr,HIGH); // its an active channel so pulse it high
220  }
221  else {
222  // finished all channels so wait for the refresh period to expire before starting over
223  if( ((unsigned)*TCNTn) + 4 < usToTicks(REFRESH_INTERVAL) ) // allow a few ticks to ensure the next OCR1A not missed
224  *OCRnA = (unsigned int)usToTicks(REFRESH_INTERVAL);
225  else
226  *OCRnA = *TCNTn + 4; // at least REFRESH_INTERVAL has elapsed
227  Channel[timer] = -1; // this will get incremented at the end of the refresh period to start again at the first channel
228  }
229  }
230 
231  #ifndef WIRING // Wiring pre-defines signal handlers so don't define any if compiling for the Wiring platform
232  // Interrupt handlers for Arduino
233  #if defined(_useTimer1)
234  SIGNAL (TIMER1_COMPA_vect)
235  {
236  handle_interrupts(_timer1, &TCNT1, &OCR1A);
237  }
238  #endif
239  #elif defined WIRING
240  // Interrupt handlers for Wiring
241  #if defined(_useTimer1)
242  void Timer1Service()
243  {
244  handle_interrupts(_timer1, &TCNT1, &OCR1A);
245  }
246  #endif
247  #endif
248 
249  inline int Servo::read() // return the value as degrees
250  {
251  return map( this->readMicroseconds()+1, SERVO_MIN(), SERVO_MAX(), 0, 180);
252  }
253 
254 
255 Servo::Servo() {
256  if( ServoCount < MAX_SERVOS) {
257  this->servoIndex = ServoCount++; // assign a servo index to this instance
258  servos[this->servoIndex].ticks = usToTicks(DEFAULT_PULSE_WIDTH); // store default values - 12 Aug 2009
259  }
260  else
261  this->servoIndex = INVALID_SERVO ; // too many servos
262  }
263 
264  uint8_t Servo::attach(int pin) {
265  return this->attach(pin, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH);
266  }
267 
268  uint8_t Servo::attach(int pin, int min, int max) {
269  if(this->servoIndex < MAX_SERVOS ) {
270  pinMode( pin, OUTPUT) ; // set servo pin to output
271  servos[this->servoIndex].Pin.nbr = pin;
272  // todo min/max check: abs(min - MIN_PULSE_WIDTH) /4 < 128
273  this->min = (MIN_PULSE_WIDTH - min)/4; //resolution of min/max is 4 uS
274  this->max = (MAX_PULSE_WIDTH - max)/4;
275  // initialize the timer if it has not already been initialized
276  timer16_Sequence_t timer = SERVO_INDEX_TO_TIMER(servoIndex);
277  if(isTimerActive(timer) == false)
278  initISR(timer);
279  servos[this->servoIndex].Pin.isActive = true; // this must be set after the check for isTimerActive
280  }
281  return this->servoIndex ;
282  }
283 
284  void Servo::detach() {
285  servos[this->servoIndex].Pin.isActive = false;
286  timer16_Sequence_t timer = SERVO_INDEX_TO_TIMER(servoIndex);
287  if(isTimerActive(timer) == false) {
288  finISR(timer);
289  }
290  }
291 
292  void Servo::write(int value) {
293  if(value < MIN_PULSE_WIDTH)
294  { // treat values less than 544 as angles in degrees (valid values in microseconds are handled as microseconds)
295  if(value < 0) value = 0;
296  if(value > 180) value = 180;
297  value = map(value, 0, 180, SERVO_MIN(), SERVO_MAX());
298  }
299  this->writeMicroseconds(value);
300  }
301 
302  void Servo::writeMicroseconds(int value) {
303  // calculate and store the values for the given channel
304  byte channel = this->servoIndex;
305  if( (channel < MAX_SERVOS) ) // ensure channel is valid
306  {
307  if( value < SERVO_MIN() ) // ensure pulse width is valid
308  value = SERVO_MIN();
309  else if( value > SERVO_MAX() )
310  value = SERVO_MAX();
311 
312  value = value - TRIM_DURATION;
313  value = usToTicks(value); // convert to ticks after compensating for interrupt overhead - 12 Aug 2009
314 
315  uint8_t oldSREG = SREG;
316  cli();
317  servos[channel].ticks = value;
318  SREG = oldSREG;
319  }
320  }
321 
322 
324  unsigned int pulsewidth;
325  if( this->servoIndex != INVALID_SERVO )
326  pulsewidth = ticksToUs(servos[this->servoIndex].ticks) + TRIM_DURATION ; // 12 aug 2009
327  else
328  pulsewidth = 0;
329 
330  return pulsewidth;
331  }
332 
333  inline bool Servo::attached() {
334  return servos[this->servoIndex].Pin.isActive ;
335  }
336 
337 
338 
339 
340 
342 #endif
void write(int value)
sets angle or pulse width of the Servo
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:135
void detach()
detaches the connected pin
Servo()
the constructor
uint8_t attach(int pin)
this will tell the Servo-instance which pin the data cable is connected to
void writeMicroseconds(int value)
sets pulse width of the Servo
uint8_t servoIndex
the channel index this servo is using
Definition: eagle_Servo.h:131
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:133
[NANO] The standard Arduino Servo Class
Definition: eagle_Servo.h:84