You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
My contact info is available by clicking the "Contact Me" tab at the top of my website.
6
+
Written: 26 March 2016
7
+
Updated: 26 March 2016
8
+
9
+
LICENSE: GNU GPLV3 or later (refer to .h file and attached license for details)
10
+
11
+
-outputs PPM signal on Arduino pin 9
12
+
*/
13
+
14
+
#include<eRCaGuy_PPM_Writer.h>
15
+
16
+
const byte CH1 = 0,
17
+
CH2 = 1,
18
+
CH3 = 2,
19
+
CH4 = 3,
20
+
CH5 = 4,
21
+
CH6 = 5,
22
+
CH7 = 6,
23
+
CH8 = 7;
24
+
25
+
26
+
voidsetup()
27
+
{
28
+
Serial.begin(115200);
29
+
Serial.println(F("\n\nbegin"));
30
+
31
+
//manually set a few channels
32
+
PPMWriter.setChannelVal(CH1,900*2); //set channel 1 (index 0) in the PPM train to 900us
33
+
PPMWriter.setChannelVal(CH2,1000*2); //set channel 2 (index 2) in the PPM train to 1000us
34
+
PPMWriter.setChannelVal(CH3,1100*2);
35
+
PPMWriter.setChannelVal(CH4,1200*2);
36
+
37
+
PPMWriter.setPPMPeriod(20000*2UL); //currently you cannot set above 65535 since Timer1 is a 16-bit timer/counter, and I'm not taking into account rollovers past this value, *yet*; NB: the UL is *mandatory* to force the constant/literal multiplication here to follow unsigned long rules *instead of* signed int rules, which I believe are default otherwise here in this compiler's C++ handling; take out the UL and you'll see weird results/errors for any values where the result of the multiplication is > 32767, which is the max value storable in a signed int.
//NOTE/TODO: ALLOW ONE TO CHOOSE WHICH TIMER YOU'D LIKE TO USE FOR THIS LIBRARY IN THE "eRCaGuy_TimerCounterTimers.h" file
62
+
#include"eRCaGuy_TimerCounterTimers.h"
63
+
61
64
//macros
62
65
#definereadPinA() (PINB & _BV(1)) //Arduino pin 9
63
66
#definetogglePinA() (TCCR1C = _BV(FOC1A)) //see datasheet pg. 135 & 132; force OC1A pin (Arduino D9) to toggle; datasheet pg. 132: setting one or both of the COM1A1:0 bits to 1 overrides the normal port functionality of the I/O pin it is connected to, so this is how you must toggle the pin; digitalWrite(9,HIGH/LOW) on pin 9 will NOT work anymore on this pin; note, however, that *reading* the pin port directly, or calling digitalRead(9) DOES still work!
@@ -77,19 +80,33 @@ eRCaGuy_PPM_Writer PPMWriter; //preinstantiation of object
longincrementVal; //units of 0.5us; make long too allow for (and later be able to handle) the rare case of negative values, which would occur if someone tries to set the PPM period too short
//-get the total 32-bit (unsigned long) count on the timer, ***in units of COUNTS, not microseconds***
227
+
//--ex: with prescaler = 8, you have 0.5us/count for a 16Mhz Arduino, so you divide counts by 2 to get us
228
+
//-Note that the time returned WILL update even in Interrupt Service Routines (ISRs), so if you call this function in an ISR, and you want the time to be as close as possible to a certain event that occurred which called the ISR you are in, make sure to call getCount() first thing when you enter the ISR.
229
+
//-Also, note that calling getCount() is faster than calling getMicros() and is therefore the preferable way to measure a time interval.
230
+
//--For example: call getCount() at the beginning of some event, then at the end. Take the difference and divide it by 2 (assuming 16Mhz Arduino, prescaler is 8) to get the time interval in microseconds.
uint8_t SREG_old = SREG; //back up the AVR Status Register; see example in datasheet on pg. 14, as well as Nick Gammon's "Interrupts" article - http://www.gammon.com.au/forum/?id=11488
236
+
noInterrupts(); //prepare for critical section of code
237
+
{
238
+
unsignedint TCNTn_save = TCNTn; //grab the counter value from timer
239
+
bool overflowFlag = bitRead(TIFRn,0); //grab the timer overflow flag value; see datasheet pg. 160, for ex.
240
+
if (overflowFlag) //if the overflow flag is set
241
+
{
242
+
TCNTn_save = TCNTn; //update variable just saved since the overflow flag could have just tripped between previously saving the TCNTn value and reading bit 0 of TIFRn. If this is the case, TCNTn might have just changed from 255 to 0 (for an 8-bit timer), and so we need to grab the new value of TCNTn to prevent an error of up to 127.5us in any time obtained using this counter. (Note: 255 counts / 2 counts/us = 127.5us).
243
+
//Note: this line of code DID in fact fix the error just described, in which I periodically saw an error of ~127.5us in some values read in by some PWM read code I wrote.
244
+
_overflowCount++; //force the overflow count to increment
245
+
TIFRn |= _BV(0); //reset the Timer overflow flag since we just manually incremented above; ex: see datasheet pg. 160; this prevents execution of the timer's overflow ISR
//-returns the 32-bit timer time, with full resolution, as a float.
256
+
//--Ex: for a 16Mhz Arduino, with prescaler of 8, the resolution is 0.5us per count; with prescaler of 1, the resolution is 0.0625us per count. Both of these are better than the default Arduino micros() resolution of 4us
257
+
//-this function is slower than calling getCount() and therefore is not the preferred way of getting time. It is better to get the time by calling getCount() then dividing the value by the appropriate divisor to convert to us.
//-turns off the timer's overflow interrupt so that you no longer interrupt your code (every 128us for an 8-bit timer with prescaler of 8) in order to increment your overflow counter.
267
+
//-This may be desirable when you are no longer needing timestamps and want your main code or other interrupts to run more jitter-free, but you don't want to call end() in order to change all of the timer's settings back to default.
268
+
//-Assuming 16Mhz Arduino, 8-bit timer, and prescaler of 8 (ie: 0.5us/count), turning off the overflow interrupt will give you savings of approximately 4~5us every 128us. This is a CPU processing time savings of ~4%.
269
+
//--Source: Nick Gammon; "Interrupts" article; "How long does it take to execute an ISR?" section, found here: http://www.gammon.com.au/forum/?id=11488
270
+
//-Note: If you disable the timer overflow interrupt but still call getCount() or getMicros() at least every 128us (or whatever your interrupt period is given your clock speed, timer size [8 or 16-bit], and prescaler), you will notice no difference in the counter, since calling getCount() or getMicros() also checks the interrupt flag and increments the overflow counter automatically. You have to wait > 128us between calls before you see any missed overflow counts.
//-this allows you to attach your own custom function that you want to be called every overflow interrupt. Very useful for recurring events, for example, but you'll have to write some more smarts into your function if you want specific timing.
290
+
//-this will also be very useful for my upcoming SoftwarePWM library, for example, which will allow software PWMing (analogWrite) on EVERY Arduino pin!
@@ -278,6 +397,7 @@ void eRCaGuy_PPM_Writer::setChannelVal(byte channel_i, unsigned int val)
278
397
//setPPMPeriod
279
398
//-control the PPM output frequency by setting the desired PPM train frame period directly
280
399
//-units are 0.5us
400
+
//-currently, the longest value you can set is 2^16 - 1, or 65535, since the longest timer/counter is only 16-bits, and I'm not taking into account roll-overs beyond the length of the timer/counter, *yet*. ***********TODO: do take them into account so I can start using 8-bit timer/counters for this library too.***************
281
401
//-ex: setPPMPeriod(20000*2) sets a pd of 20000us (40000 0.5us counts), which results in a PPM freq. of 1/20ms=50Hz
282
402
//-WARNING: if your channel values are all at their max values, the local variable "_minFrameSpace" acts as a safety feature to ensure the frame space after the last channel is longer than the longest channel. This is necessary because that is how a device *reading* a PPM signal finds the first frame: it knows that the *longest* frame is the frame space, and that the first channel comes after that. In the event that you set the PPM period too short, or have too many channels, with all of them maxed out, the PPM writer will force the frame space after the last channel to be at least as long as what is set in the local variable "_minFrameSpace." In this event, the PPM period will be lengthened for that frame, and the desired PPM frequency will not be reached.
283
403
//--ex: assume 9 channels, max channel value of 2100us, PPM period set to 20ms; if all channels are simultaneously maxed out it takes 2100x9 = 18.9ms just to write the channels. To keep the 20ms PPM period, the frameSpace would have to be 20-18.9 = 1.1ms. However, THIS WILL BREAK THE SIGNAL AND PREVENT THE DEVICE READING THE PPM SIGNAL FROM DETERMINING WHERE THE START OF THE PPM FRAME IS. Therefore, the _minFrameSpace (default 3ms last I set it) will be used instead of 1.1ms. This makes the PPM frame 18.9ms + 3ms = 21.9ms (45.7Hz) instead of the desired 20ms (50Hz). Depending on your settings, since this PPM writer is totally user-customizable, you may get even more drastic results. Ex: if you were trying to fit 12 full-length channels in a 20ms PPM frame....you do the math.
0 commit comments