diff options
Diffstat (limited to 'docs/source/timers.rst')
-rw-r--r-- | docs/source/timers.rst | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/docs/source/timers.rst b/docs/source/timers.rst new file mode 100644 index 0000000..56dd686 --- /dev/null +++ b/docs/source/timers.rst @@ -0,0 +1,206 @@ +.. highlight:: cpp + +.. _timers: + +Timers +====== + +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 can generate regular square-wave signals on specific +output pins without consuming extra clock cycles. By attaching +interrupt handlers to these channels (instead of just changing the +voltage on an external pin), more complex events can be scheduled. + +.. contents:: Contents + :local: + +Introduction +------------ + +.. _timers-prescale: + +The four timers each have four separate compare channels. Each channel +has an associated 16-bit counter that can be configured with a 16-bit +prescaler and a 16-bit overflow value. The prescaler determines how +fast the counter changes, while the overflow value determines when it +gets reset. + +The prescaler acts as a divider of the 72MHz system clock. That is, +with a prescaler of 1, the channel's counter increments with a +frequency of 72MHz, rolling over (passing the maximum 16-bit unsigned +integer value of 65,535) more than a thousand times a second. With a +prescaler of 7200, it has a frequency of (72/7200) MHz = 10 KHz, +rolling over approximately every 6.55 seconds. + +The overflow value is the maximum value the counter will go up to. It +defaults to the full 65,535; smaller values will cause the counter to +reset to zero more frequently. + +Whenever a channel's counter reaches its overflow value, an "update +event" interrupt is generated. You can configure the Maple to notify +you when this takes place, by registering an interrupt handler, which +is a function that will be called when the update event occurs. + +libmaple Reference +------------------ + +The libmaple API for interacting with timers is documented at the +:ref:`HardwareTimer reference <lang-hardwaretimer>`. + +Caveats +------- + +.. _timers-pwm-conflicts: + +**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 following table to match up timer +channels and Maple header pin numbers: + +.. _timers-pin-channel-map: + +.. csv-table:: + :header: Timer, Ch. 1 pin, Ch. 2 pin, Ch. 3 pin, Ch. 4 pin + + ``Timer1``, 6, 7, 8, -- + ``Timer2``, 2, 3, 1, 0 + ``Timer3``, 12, 11, 27, 28 + ``Timer4``, 5, 9, 14, 24 + +**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 pseudorandom 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 (or somehow causing your program to re-enable serial + over USB using :ref:`SerialUSB.begin() <lang-serialusb-begin>`). + + 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. + +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()``. + +Code Examples +------------- + +LED blink +^^^^^^^^^ + +:: + + #define LED_RATE 500000 // in microseconds; should give 0.5Hz toggles + + void handler_led(void); + + void setup() + { + // Set up the LED to blink + pinMode(BOARD_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) { + toggleLED(); + } + +Racing Counters +^^^^^^^^^^^^^^^ + +:: + + void handler_count1(void); + void handler_count2(void); + + int count1 = 0; + int count2 = 0; + + void setup() + { + // Set up BUT for input + pinMode(BOARD_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(BOARD_BUTTON_PIN)) { + Timer4.pause(); + } else { + Timer4.resume(); + } + delay(1); + } + } + + void handler1(void) { + count1++; + } + void handler2(void) { + count2++; + } |