I am trying to program my Arduino Mega 2560 to effectively create a PWM signal on any digital pin using timer interrupts and timer1. The pins will pulse a finite amount of times (not indefinitely). I am first trying this with a 256-prescaler value, which I believe means that each increment of my TCNT1 is 16.0 microseconds, and since it is a 16-bit register, it can reach a max. value of 1048567 microseconds.
I am designing the program so that the period and duty cycle are changed by Serial commands that the user can send (I already have that part figured out). I am using clear timer on compare mode and OCR1A and OCR1B to toggle the digital pin between HIGH and LOW.
If the desired period is less than 1048567 microseconds, I can divide the period by the modified clock (modified by the prescaler) to figure out the OCR1A value, and then based on the desired duty cycle, calculate OCR1B. Then I set the ISR vectors for A and B to toggle the pin and count down until the desired number of cycles is completed.
What I would like to also allow the user to do is set periods/duty cycles that would be longer than 1048567 microseconds, but I'm not sure how to accomplish that. For example, if I wanted to have the pin be HIGH for 2 seconds with a total period of 10 seconds, I would choose values for OCR1B and OCR1A that, through the ISR(TIMER1_COMPA_vect) and ISR(TIMER1_COMPB_vect), I could keep track (through a counter variable) of how many interrupts had occurred. If enough interrupts had occurred to equal the time of that the pin had to be HIGH and/or LOW, the ISRs would actually trigger the toggling of the pin (instead of just counting the number of interrupts).
How could I choose the OCR1B and OCR1A values that would accomplish this optimally? Is it reasonable to keep track of time this way as a workaround to the limitation of the timer counter max value? I am open to any suggestion and I appreciate any input. Thank you.
-
What sort of precision do you want? For something like 2 seconds on and 5 seconds off, I would have thought that simply toggling a pin by checking if time is up in the main loop would do it.Nick Gammon– Nick Gammon ♦06/27/2016 05:44:10Commented Jun 27, 2016 at 5:44
1 Answer 1
By maintaining your own second-level prescaler you can quite easily keep track of longer periods.
The most efficient method is to use a "divide and multiply by two" scheme.
- Start with a custom pre-scaler of 1.
- While the count period is> 65535:
- Multiply the pre-scaler by 2.
- Divide the count period by 2
uint16_t ps = 1;
uint32_t period = 429864;
while (period > 65535) {
ps <<= 1;
period >>= 1;
}
Then you can load the count period register with the now divided count period and use a variable to count the number of times the interrupt has been triggered. When it equals your custom pre-scaler value you reset the count to 0 and run your actual interrupt routine.
The left and right shifts multiply by two and divide by two respectively in a very efficient manner. Far more than multiplying by 10, or dividing the period by 65536 and using that as a prescaler.