Project

General

Profile

Statistics
| Branch: | Revision:

robobuggy / arduino / InterruptRCRecieverReference / RCArduinoFastLib.cpp @ c5d6b0e8

History | View | Annotate | Download (13.3 KB)

1
/*****************************************************************************************************************************
2
// RCArduinoFastLib by DuaneB is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
3
//
4
// http://rcarduino.blogspot.com
5
//
6
*****************************************************************************************************************************/
7

    
8
#include "Arduino.h"
9
#include "RCArduinoFastLib.h"
10

    
11
/*----------------------------------------------------------------------------------------
12

13
This is essentially a derivative of the Arduino Servo Library created by Michael Margolis
14

15
As the technique is very similar to the Servo class, it can be useful to study in order
16
to understand the servo class.
17

18
What does the library do ? It uses a very inexpensive and common 4017 Counter IC
19
To generate pulses to independently drive up to 10 servos from two Arduino Pins
20

21
As previously mentioned, the library is based on the techniques used in the Arduino Servo
22
library created by Michael Margolis. This means that the library uses Timer1 and Timer1 output
23
compare register A.
24

25
OCR1A is linked to digital pin 9 and so we use digital pin 9 to generate the clock signal
26
for the 4017 counter.
27

28
Pin 12 is used as the reset pin.
29

30
*/
31

    
32
void CRCArduinoFastServos::setup()
33
{
34
 m_sCurrentOutputChannelA = 0;
35
 while(m_sCurrentOutputChannelA < RC_CHANNEL_OUT_COUNT)
36
 {
37
  m_ChannelOutA[m_sCurrentOutputChannelA].m_unPulseWidth = microsecondsToTicks(RCARDUINO_SERIAL_SERVO_MAX);
38

    
39
#if defined (MORE_SERVOS_PLEASE)
40
  m_ChannelOutB[m_sCurrentOutputChannelA].m_unPulseWidth = microsecondsToTicks(RCARDUINO_SERIAL_SERVO_MAX);
41
#endif
42

    
43
   m_sCurrentOutputChannelA++;
44
  }
45
}
46

    
47

    
48
// Timer1 Output Compare A interrupt service routine
49
// call out class member function OCR1A_ISR so that we can
50
// access out member variables
51
ISR(TIMER1_COMPA_vect)
52
{
53
    CRCArduinoFastServos::OCR1A_ISR();
54
}
55

    
56
void CRCArduinoFastServos::OCR1A_ISR()
57
{
58
    // If the channel number is >= 10, we need to reset the counter
59
    // and start again from zero.
60
    // to do this we pulse the reset pin of the counter
61
    // this sets output 0 of the counter high, effectivley
62
    // starting the first pulse of our first channel
63
  if(m_sCurrentOutputChannelA >= RC_CHANNEL_OUT_COUNT)
64
  {
65
    // reset our current servo/output channel to 0
66
    m_sCurrentOutputChannelA = 0;
67
   
68
    CRCArduinoFastServos::setChannelPinLowA(RC_CHANNEL_OUT_COUNT-1);
69
  }
70
  else
71
  {  
72
    CRCArduinoFastServos::setChannelPinLowA(m_sCurrentOutputChannelA-1);
73
  }
74
  
75
  CRCArduinoFastServos::setCurrentChannelPinHighA();
76
   
77
    // set the duration of the output pulse
78
    CRCArduinoFastServos::setOutputTimerForPulseDurationA();
79

    
80
    // done with this channel so move on.
81
    m_sCurrentOutputChannelA++;
82
}
83

    
84
void CRCArduinoFastServos::setChannelPinLowA(uint8_t sChannel)
85
{
86
    volatile CPortPin *pPortPin = m_ChannelOutA + sChannel;
87

    
88
    if(pPortPin->m_sPinMask)
89
     *pPortPin->m_pPort ^= pPortPin->m_sPinMask;
90
}
91

    
92
void CRCArduinoFastServos::setCurrentChannelPinHighA()
93
{
94
    volatile CPortPin *pPortPin = m_ChannelOutA + m_sCurrentOutputChannelA;
95

    
96
    if(pPortPin->m_sPinMask)
97
     *pPortPin->m_pPort |= pPortPin->m_sPinMask;
98
}
99

    
100
// After we set an output pin high, we need to set the timer to comeback for the end of the pulse
101
void CRCArduinoFastServos::setOutputTimerForPulseDurationA()
102
{
103
  OCR1A = TCNT1 + m_ChannelOutA[m_sCurrentOutputChannelA].m_unPulseWidth;
104
}
105

    
106
#if defined(MORE_SERVOS_PLEASE)
107
// Timer1 Output Compare B interrupt service routine
108
// call out class member function OCR1B_ISR so that we can
109
// access out member variables
110
ISR(TIMER1_COMPB_vect)
111
{
112
    CRCArduinoFastServos::OCR1B_ISR();
113
}
114

    
115
void CRCArduinoFastServos::OCR1B_ISR()
116
{
117
  if(m_sCurrentOutputChannelB >= RC_CHANNEL_OUT_COUNT)
118
  {
119
    // reset our current servo/output channel to 0
120
    m_sCurrentOutputChannelB = 0;
121
    CRCArduinoFastServos::setChannelPinLowB(RC_CHANNEL_OUT_COUNT-1);
122
  }
123
  else
124
  {  
125
    CRCArduinoFastServos::setChannelPinLowB(m_sCurrentOutputChannelB-1);
126
  }
127
  
128
  CRCArduinoFastServos::setCurrentChannelPinHighB();
129
   
130
    // set the duration of the output pulse
131
    CRCArduinoFastServos::setOutputTimerForPulseDurationB();
132

    
133
    // done with this channel so move on.
134
    m_sCurrentOutputChannelB++;
135
}
136

    
137
void CRCArduinoFastServos::setChannelPinLowB(uint8_t sChannel)
138
{
139
    volatile CPortPin *pPortPin = m_ChannelOutB + sChannel;
140

    
141
    if(pPortPin->m_sPinMask)
142
     *pPortPin->m_pPort ^= pPortPin->m_sPinMask;
143
}
144

    
145
void CRCArduinoFastServos::setCurrentChannelPinHighB()
146
{
147
    volatile CPortPin *pPortPin = m_ChannelOutB + m_sCurrentOutputChannelB;
148
   
149
    if(pPortPin->m_sPinMask)
150
     *pPortPin->m_pPort |= pPortPin->m_sPinMask;
151
}
152

    
153

    
154

    
155
// After we set an output pin high, we need to set the timer to comeback for the end of the pulse
156
void CRCArduinoFastServos::setOutputTimerForPulseDurationB()
157
{
158
  OCR1B = TCNT1 + m_ChannelOutB[m_sCurrentOutputChannelB].m_unPulseWidth;
159
}
160
#endif
161

    
162
// updates a channel to a new value, the class will continue to pulse the channel
163
// with this value for the lifetime of the sketch or until writeChannel is called
164
// again to update the value
165
void CRCArduinoFastServos::writeMicroseconds(uint8_t nChannel,uint16_t unMicroseconds)
166
{
167
    // dont allow a write to a non existent channel
168
    if(nChannel > RCARDUINO_MAX_SERVOS)
169
        return;
170

    
171
  // constraint the value just in case
172
  unMicroseconds = constrain(unMicroseconds,RCARDUINO_SERIAL_SERVO_MIN,RCARDUINO_SERIAL_SERVO_MAX);
173

    
174
#if defined(MORE_SERVOS_PLEASE)
175
  if(nChannel >= RC_CHANNEL_OUT_COUNT)
176
  {
177
    unMicroseconds = microsecondsToTicks(unMicroseconds);
178
    unsigned char sChannel = nChannel-RC_CHANNEL_OUT_COUNT;
179
    // disable interrupts while we update the multi byte value output value
180
    uint8_t sreg = SREG;
181
    cli();
182
     
183
    m_ChannelOutB[sChannel].m_unPulseWidth = unMicroseconds;
184

    
185
    // enable interrupts
186
    SREG = sreg;
187
    return;
188
  }
189
#endif
190
 
191
  unMicroseconds = microsecondsToTicks(unMicroseconds);
192
 
193
  // disable interrupts while we update the multi byte value output value
194
  uint8_t sreg = SREG;
195
  cli();
196
 
197
  m_ChannelOutA[nChannel].m_unPulseWidth = unMicroseconds;
198

    
199
  // enable interrupts
200
  SREG = sreg;
201
}
202

    
203
uint16_t CRCArduinoFastServos::ticksToMicroseconds(uint16_t unTicks)
204
{
205
    return unTicks / 2;
206
}
207

    
208
uint16_t CRCArduinoFastServos::microsecondsToTicks(uint16_t unMicroseconds)
209
{
210
 return unMicroseconds * 2;
211
}
212

    
213
void CRCArduinoFastServos::attach(uint8_t sChannel,uint8_t sPin)
214
{
215
    if(sChannel >= RCARDUINO_MAX_SERVOS)
216
        return;
217

    
218
  #if defined(MORE_SERVOS_PLEASE)
219
  if(sChannel >= RC_CHANNEL_OUT_COUNT)
220
  {
221
    // disable interrupts while we update the multi byte value output value
222
    uint8_t sreg = SREG;
223
    cli();
224
     
225
    m_ChannelOutB[sChannel-RC_CHANNEL_OUT_COUNT].m_unPulseWidth = microsecondsToTicks(RCARDUINO_SERIAL_SERVO_DEFAULT);
226
    m_ChannelOutB[sChannel-RC_CHANNEL_OUT_COUNT].m_pPort = getPortFromPin(sPin);
227
    m_ChannelOutB[sChannel-RC_CHANNEL_OUT_COUNT].m_sPinMask = getPortPinMaskFromPin(sPin);
228
    // enable interrupts
229
    SREG = sreg;
230
    pinMode(sPin,OUTPUT);
231
    return;
232
  }
233
  #endif
234
 
235
  // disable interrupts while we update the multi byte value output value
236
  uint8_t sreg = SREG;
237
  cli();
238
 
239
  m_ChannelOutA[sChannel].m_unPulseWidth = microsecondsToTicks(RCARDUINO_SERIAL_SERVO_DEFAULT);
240
  m_ChannelOutA[sChannel].m_pPort = getPortFromPin(sPin);
241
  m_ChannelOutA[sChannel].m_sPinMask = getPortPinMaskFromPin(sPin);
242

    
243
  // enable interrupts
244
  SREG = sreg;
245
 
246
  pinMode(sPin,OUTPUT);
247
}
248

    
249
// this allows us to run different refresh frequencies on channel A and B
250
// for example servos at a 70Hz rate on A and ESCs at 250Hz on B
251
void CRCArduinoFastServos::setFrameSpaceA(uint8_t sChannel,uint16_t unMicroseconds)
252
{
253
  // disable interrupts while we update the multi byte value output value
254
  uint8_t sreg = SREG;
255
  cli();
256
 
257
  m_ChannelOutA[sChannel].m_unPulseWidth = microsecondsToTicks(unMicroseconds);
258
  m_ChannelOutA[sChannel].m_pPort = 0;
259
  m_ChannelOutA[sChannel].m_sPinMask = 0;
260

    
261
  // enable interrupts
262
  SREG = sreg;
263
}
264

    
265
#if defined (MORE_SERVOS_PLEASE)
266
void CRCArduinoFastServos::setFrameSpaceB(uint8_t sChannel,uint16_t unMicroseconds)
267
{
268
  // disable interrupts while we update the multi byte value output value
269
  uint8_t sreg = SREG;
270
  cli();
271
 
272
  m_ChannelOutB[sChannel].m_unPulseWidth = microsecondsToTicks(unMicroseconds);
273
  m_ChannelOutB[sChannel].m_pPort = 0;
274
  m_ChannelOutB[sChannel].m_sPinMask = 0;
275

    
276
  // enable interrupts
277
  SREG = sreg;
278
}
279
#endif
280
// Easy to optimise this, but lets keep it readable instead, its short enough.
281
volatile uint8_t* CRCArduinoFastServos::getPortFromPin(uint8_t sPin)
282
{
283
    volatile uint8_t* pPort = RC_CHANNELS_NOPORT;
284
   
285
    if(sPin <= 7)
286
    {
287
        pPort = &PORTD;
288
    }
289
    else if(sPin <= 13)
290
    {
291
        pPort = &PORTB;
292
    }
293
    else if(sPin <= A5) // analog input pin 5
294
    {
295
        pPort = &PORTC;
296
    }
297
   
298
    return pPort;
299
}
300

    
301
// Easy to optimise this, but lets keep it readable instead, its short enough.
302
uint8_t CRCArduinoFastServos::getPortPinMaskFromPin(uint8_t sPin)
303
{
304
    uint8_t sPortPinMask = RC_CHANNELS_NOPIN;
305
   
306
    if(sPin <= A5)
307
    {
308
        if(sPin <= 7)
309
        {
310
            sPortPinMask = (1 << sPin);
311
        }
312
        else if(sPin <= 13)
313
        {
314
            sPin -= 8;
315
            sPortPinMask = (1 << sPin);
316
        }
317
        else if(sPin <= A5)
318
        {
319
            sPin -= A0;
320
            sPortPinMask = (1 << sPin);
321
        }
322
    }
323
   
324
    return sPortPinMask;
325
}  
326

    
327
void CRCArduinoFastServos::begin()
328
{
329
    TCNT1 = 0;              // clear the timer count 
330

    
331
    // Initilialise Timer1
332
    TCCR1A = 0;             // normal counting mode
333
    TCCR1B = 2;     // set prescaler of 64 = 1 tick = 4us
334

    
335
    // ENABLE TIMER1 OCR1A INTERRUPT to enabled the first bank (A) of ten servos
336
    TIFR1 |= _BV(OCF1A);     // clear any pending interrupts;
337
    TIMSK1 |=  _BV(OCIE1A) ; // enable the output compare interrupt
338

    
339
#if defined(MORE_SERVOS_PLEASE)
340

    
341
    // ENABLE TIMER1 OCR1B INTERRUPT to enable the second bank (B) of 10 servos
342
    TIFR1 |= _BV(OCF1B);     // clear any pending interrupts;
343
    TIMSK1 |=  _BV(OCIE1B) ; // enable the output compare interrupt
344

    
345
#endif
346

    
347
    OCR1A = TCNT1 + 4000; // Start in two milli seconds
348
   
349
    for(uint8_t sServo = 0;sServo<RC_CHANNEL_OUT_COUNT;sServo++)
350
    {
351
        Serial.println(m_ChannelOutA[sServo].m_unPulseWidth);
352

    
353
#if defined(MORE_SERVOS_PLEASE)
354
        Serial.println(m_ChannelOutB[sServo].m_unPulseWidth);
355
#endif
356
        }
357
}
358

    
359
volatile CRCArduinoFastServos::CPortPin CRCArduinoFastServos::m_ChannelOutA[RC_CHANNEL_OUT_COUNT];
360
uint8_t CRCArduinoFastServos::m_sCurrentOutputChannelA;
361

    
362
#if defined(MORE_SERVOS_PLEASE)  
363
volatile CRCArduinoFastServos::CPortPin CRCArduinoFastServos::m_ChannelOutB[RC_CHANNEL_OUT_COUNT];
364
uint8_t CRCArduinoFastServos::m_sCurrentOutputChannelB;
365
#endif
366

    
367
volatile uint16_t CRCArduinoPPMChannels::m_unChannelSignalIn[RC_CHANNEL_IN_COUNT];
368
uint8_t CRCArduinoPPMChannels::m_sCurrentInputChannel = 0;
369

    
370
uint16_t CRCArduinoPPMChannels::m_unChannelRiseTime = 0;
371
uint8_t volatile CRCArduinoPPMChannels::m_sOutOfSynchErrorCounter = 0;
372

    
373
void CRCArduinoPPMChannels::begin()
374
{
375
 m_sOutOfSynchErrorCounter = 0;
376
 attachInterrupt(0,CRCArduinoPPMChannels::INT0ISR,RISING);
377
}
378

    
379
// we could save a few micros by writting this directly in the signal handler rather than using attach interrupt
380
void CRCArduinoPPMChannels::INT0ISR()
381
{
382
  // only ever called for rising edges, so no need to check the pin state
383
 
384
  // calculate the interval between this pulse and the last one we received which is recorded in m_unChannelRiseTime
385
  uint16_t ulInterval = TCNT1 - m_unChannelRiseTime;
386
 
387
  // if all of the channels have been received we should be expecting the frame space next, lets check it
388
  if(m_sCurrentInputChannel == RC_CHANNEL_IN_COUNT)
389
  {
390
    // we have received all the channels we wanted, this should be the frame space
391
    if(ulInterval < MINIMUM_FRAME_SPACE)
392
    {
393
     // it was not so we need to resynch
394
     forceResynch();
395
    }
396
    else
397
    {
398
      // it was the frame space, next interval will be channel 0
399
      m_sCurrentInputChannel = 0;
400
    }
401
  }
402
  else
403
  {
404
    // if we were expecting a channel, but found a space instead, we need to resynch
405
    if(ulInterval > MAXIMUM_PULSE_SPACE)
406
    {
407
      forceResynch();
408
    }
409
    else
410
    {
411
     // its a good signal, lets record it and move onto the next channel
412
     m_unChannelSignalIn[m_sCurrentInputChannel++] = ulInterval;
413
    }
414
  }
415
  // record the current time
416
  m_unChannelRiseTime = TCNT1; 
417
}
418

    
419
// if we force a resynch we set the channel
420
void CRCArduinoPPMChannels::forceResynch()
421
{
422
    m_sCurrentInputChannel = RC_CHANNEL_IN_COUNT;
423
   
424
    if(m_sOutOfSynchErrorCounter<255)
425
     m_sOutOfSynchErrorCounter++;
426
}
427

    
428
uint8_t CRCArduinoPPMChannels::getSynchErrorCounter()
429
{
430
  uint8_t sErrors = m_sOutOfSynchErrorCounter;
431
 
432
  m_sOutOfSynchErrorCounter = 0;
433
 
434
  return sErrors;
435
}
436

    
437
uint16_t CRCArduinoPPMChannels::getChannel(uint8_t sChannel)
438
{
439
 uint16_t ulPulse;
440
 unsigned char sreg = SREG;
441

    
442
 cli();
443

    
444
 ulPulse = m_unChannelSignalIn[sChannel];
445
 m_unChannelSignalIn[sChannel] = 0;
446
 
447
 SREG = sreg;
448

    
449
 return ulPulse>>1;
450
}