diff options
Diffstat (limited to 'docs/source/timers.rst')
-rw-r--r-- | docs/source/timers.rst | 310 |
1 files changed, 309 insertions, 1 deletions
diff --git a/docs/source/timers.rst b/docs/source/timers.rst index c156d4f..e0e57cb 100644 --- a/docs/source/timers.rst +++ b/docs/source/timers.rst @@ -1,7 +1,315 @@ +.. highlight:: cpp + .. _timers: ======== Timers ======== -Stub. +There are four general purpose timers in the Maple microcontroller +that can be configured to generate periodic or delayed events with +minimal work done by the microcontroller. For example, the :ref:`PWM +<pwm>` channels, once enabled, generate regular square-wave signals on +specific output pins that will continue even if the microcontroller is +busy crunching numbers or handling communications interrupts. By +attaching interrupt handlers to these channels (instead of just +changing the voltage on an external pin), more complex events like +printing to a serial port, updating a variable, or emitting a whale +mating call can be scheduled. You can even modify the configuration of +the timer itself at a regular interval; the possibilities are endless! + +The four timers each have four separate compare channels. Each timer +is a single 16-bit counter that can be configured with both a +prescaler and an overflow value. The prescaler acts as a divider of +the 72MHz system clock; without prescaling the counter would get up to +65536 (2 to the 16th power) and roll over more than a thousand times a +second; even with the full prescaler it rolls over about once a +minute. The overflow value is the maximum value the counter will go up +to. It defaults to the full 65535; smaller values will cause the +counter to reset to zero more frequently. + + +Caveats +------- + +**PWM Conflicts:** Because PWM functionality on a given pin depends on +the configuration of the timer and channel, you must chose your +channels carefully if you want to use both timer interrupts and PWM in +the same program. Refer to the full :ref:`pin mapping table +<pin-mapping-mega-table>` to match up timer channels and Maple header +pin numbers. + +**Overhead:** there is some overhead associated with function and +interrupt calls (loading and unloading the stack, preparing state, +etc.) and this overhead can fudge your timing. Imperfect code +branching also means that, e.g., channel 1 interrupts may get called a +couple clock cycles sooner than a channel 4 interrupt, all other +configuration being the same. + +.. compound:: + + **Jitter:** other interrupts (USB, Serial, SysTick, or other + timers) can and will get called before or during the timer + interrupt routines, causing pseudo-random delays and other + frustrations. + + Disabling the USB port (by calling ``SerialUSB.end()``, or just + running off a battery) helps a lot, but then you lose the + auto-reset and communications functionality. This will require + that you put your Maple into :ref:`perpetual bootloader mode + <troubleshooting-perpetual-bootloader>` before uploading a new + program to it. + + Disabling SysTick with ``systick_disable()`` helps as well. + However, calling this function will break the ``micros()`` and + ``millis()`` functions. + +**General:** working with timers and interrupts can be tricky and hard +to debug; they are a somewhat "advanced" topic. Start simple, test +with :ref:`ASSERT() <language-assert>`, and don't try to do too much +in your interrupt handlers! Make sure that what you're trying to do in +a handler isn't going to block other interrupts from firing (e.g. USB, +Serial, SysTick) if those other interrupts are important for your +program. + +.. _timers-modes: + +General Timer Modes +------------------- + +``TIMER_DISABLED`` + + Exactly what it sounds like: the timer stops counting, interrupts + are not called, and no state changes are output. + +``TIMER_PWM`` + + This is the default mode for pins after initialization. See the + :ref:`PWM docs <pwm>` for more information on this mode. + + .. note:: + + ``Timer1.setChannel1Mode(TIMER_PWM)`` may not work as expected; + if you want PWM functionality on a channel, make sure you don't + set it to something else. + +``TIMER_OUTPUTCOMPARE`` + + In this mode, the timer counts from 0 to the overflow value + repeatedly; every time the counter value reaches one of the + channel compare values, the corresponding interrupt is fired. + +SysTick Peripheral +------------------ + +The SysTick peripheral allows another, simple way to perform periodic +or delayed events. This separate timer does not conflict with any +other peripherals, but the associated 1kHz interrupt can jitter the +general purpose timer interrupts; this is clearly seen when running +VGA code, where the timing jitters are transformed into visual jags in +the image. The SysTick peripheral can be disabled by calling +``systick_disable()``, and re-enabled using ``systick_resume()``. + +Function Reference +------------------ + +For all of these functions, ``Timer1`` can be replaced with +``Timer2``, ``Timer3``, or ``Timer4``; the channel numbers also range +from 1 to 4. + +``Timer1.pause()``/\ ``Timer1.resume()`` + + These functions start and stop the counter without affecting the + rest of the configuration. These functions can be used during the + setup period to prevent interrupts from firing before they are + completely configured. Note that there is some function call + overhead with these functions, so they are not a perfect way to + align multiple timers to the same count value. + +``Timer1.setOverflow(val)`` + + Sets the overflow (or "reload") value for the whole timer; when + the counter reaches this value it resets to zero. Defaults to + 65535 (the largest unsigned 16bit integer); setting it to anything + lower will cause interrupts to be called more frequently (see the + setPeriod function below for a shortcut). This number sets the + maximum value for the channel compare values. + +``Timer1.setPrescaleFactor(val)`` + + The prescaler acts as a clock divider to slow down the rate at + which the counter increments. For example, the system clock rate + is 72MHz, so the counter will reach 65535 in (13.89 nanoseconds) * + (65535 counts) = (910.22 microseconds), or about a thousand times + a second. If you set the prescaler to 1098, then the clock rate is + effectively 65.56KHz, and the counter will reach 65536 in (15.25 + microseconds) * (65536 counts) = (0.999 seconds), or just about + once a second. Use the :ref:`setPeriod <timers-set-period>` + function below if you are allergic to math! + +.. _timers-set-period: + +``Timer1.setPeriod(val)`` + + This tricky trick will configure the prescaler and overflow values + to generate a timer reload with a period as close to val + microseconds as possible. It returns the chosen overflow value, + which you can then use to set the channel compare values + appropriately: if you just want the interrupts to fire when the + clock rolls over and you don't care about the relative "phase", + you can always set the channel compare values to 1. + + Remember: a microsecond is 1/1,000,000th of a second, or 1/1,000 + of a millisecond. The prescaler itself is 16bit, so the longest + period that can be configured is 1/(72MHz) * (2^32) = (59.65 + seconds) or about a minute. You can get around this by creating an + interrupt that increments a 32-bit variable, by using the + ``millis()`` function, or by interfacing with an external + real-time-clock chip. + +``Timer1.setCount(val)``/\ ``Timer1.getCount()`` + + These functions let you mess with the counter's brains + directly. You can probably make it not work if you try! The timer + is 16bit, so ``val`` and the return value of ``getCount`` are + ``uint16``. + +``Timer1.setChannel1Mode(MODE)`` + + This sets the given channel (here 1) of the given timer (here 1) + to the given mode. See the :ref:`list above <timers-modes>` for + possible values; for interrupts you want ``TIMER_OUTPUTCOMPARE``. + +``Timer1.setCompare1(val)`` + + Sets the compare value for the given channel; when the counter + reaches this value the interrupt for this channel will fire if the + channel is in output compare mode and an interrupt is attached. + + By default this only changes the relative offsets between events + on a single timer ("phase"); they don't control the frequency with + which they occur. However, a common trick is to increment the + compare value manually in the interrupt handler so that the event + will fire again after the increment period. There can be a + different increment value for each channel, so this trick allows + events to be programmed at 4 different rates on a single timer! + Note that function call overhead means that the smallest increment + rate is a couple microseconds. + +``Timer1.attachCompare1Interrupt(function)``/\ ``Timer1.detachCompare1Interrupt()`` + + This is how to attach or disable an interrupt handlers to timer + channels; this what will get called when the counter reaches the + compare value set with ``setCompareN(val)``. ``function`` + (sometimes referred to as an ISR: "interrupt service routine") + should be of a type that does not accept or return any values + (C/C++ programmers: ``void (function*)(void)``). They are just + like any other function in your sketch/program and must be + initialized at the top of the file and defined below. + + ``function`` should try to do what it has to do as fast as + possible. Blinking the LED, some logic, PWM updates, and Serial + writes are fine; writing to SerialUSB or waiting for user input + can take a long time and other compare interrupts won't fire. Tip: + if you have a ``delay()`` in your ISR, you're probably doing it + wrong. + + Stay vigilant here... function pointers are serious business, and + once you go down that path you'll find yourself in a `forest of + parentheses <http://mitpress.mit.edu/sicp/>`_ before you know it. + +Code Examples +------------- + +LED blink +^^^^^^^^^ + +\ :: + + #define LED_PIN 13 + #define LED_RATE 500000 // in microseconds; should give 0.5Hz toggles + + void handler_led(void); + + int toggle = 0; + + void setup() + { + // Set up the LED to blink + pinMode(LED_PIN, OUTPUT); + + // Setup Timer + Timer2.setChannel1Mode(TIMER_OUTPUTCOMPARE); + Timer2.setPeriod(LED_RATE); // in microseconds + Timer2.setCompare1(1); // overflow might be small + Timer2.attachCompare1Interrupt(handler_led); + } + + void loop() { + // Nothing! It's all in the interrupts + } + + void handler_led(void) { + toggle ^= 1; + digitalWrite(LED_PIN, toggle); + } + +Racing Counters +^^^^^^^^^^^^^^^ + +\ :: + + #define BUTTON_PIN 38 + + void handler_count1(void); + void handler_count2(void); + + int count1 = 0; + int count2 = 0; + + void setup() + { + // Set up BUT for input + pinMode(BUTTON_PIN, INPUT_PULLUP); + + // Setup Counting Timers + Timer3.setChannel1Mode(TIMER_OUTPUTCOMPARE); + Timer4.setChannel1Mode(TIMER_OUTPUTCOMPARE); + Timer3.pause(); + Timer4.pause(); + Timer3.setCount(0); + Timer4.setCount(0); + Timer3.setOverflow(30000); + Timer4.setOverflow(30000); + Timer3.setCompare1(1000); // somewhere in the middle + Timer4.setCompare1(1000); + Timer3.attachCompare1Interrupt(handler1); + Timer4.attachCompare1Interrupt(handler2); + Timer3.resume(); + Timer4.resume(); + } + + void loop() { + // Display the running counts + SerialUSB.print("Count 1: "); + SerialUSB.print(count1); + SerialUSB.print("\t\tCount 2: "); + SerialUSB.println(count2); + + // Run... while BUT is held, pause Count2 + for(int i = 0; i<1000; i++) { + if(digitalRead(BUTTON_PIN)) { + Timer4.pause(); + } else { + Timer4.resume(); + } + delay(1); + } + } + + void handler1(void) { + count1++; + } + void handler2(void) { + count2++; + } |