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 |
} |