Maple Docs: 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 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 pin mapping 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.
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 (SerialUSB.end() or
just run off a battery) helps a lot, but then you loose the auto-reset
and communications functionality.
General: working with timers and interrupts can be tricky and hard to
debug; they are a somewhat "advanced" topic. Start simple, test with
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 (eg USB, Serial, SysTick) if those other interrupts are
important for your program.
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 PWM docs 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 is and the associatedmillis() and
delay() functions are 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. A future version of
libmaple will allow the SysTick interrupt to be disabled when it is not needed.
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
setPeriodfunction 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
setPeriodfunction below if you are allergic to math! uint16 Timer1.setPeriod(val)- This tricky trick will configure the prescaler and overflow values to
generate a timer reload with a period as close to
valmicroseconds 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 to1.
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 themillis()function, or by interfacing with an external real-time-clock chip. Timer1.setCount(val)
uint16 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
valand the return value ofgetCountareuint16. Timer1.setChannel1Mode(MODE)
- This sets the given channel (here 1) of the given timer (here 1) to the
given mode. See the table above 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 overheads mean 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. 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 adelay()in your interrupt routine 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 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++; }
Recommended Reading
- STMicro documentation for STM32F103RB microcontroller:
About this Document
A more recent version of this document may be available from the LeafLabs website. Our documentation is versioned on github; you can track changes to the master branch at this link.
This documentation is released under a
Creative Commons Attribution-Share Alike 3.0 license.
Translations are welcomed; give us a ping to make sure we aren't in the process of revising or editing first.
About
Contact
Community
Distributors
Licensing
Feeds
Search
Contact webmaster@leaflabs.com with website issues
Powered by WordPress, nginx, Linux, vim, and coffee.
This site intended to be valid HTML 4.01 Strict. Best viewed with any standards-compliant browser.
Copyright LeafLabs LLC, 2009-2010, a member of the
Green Street Space.
Unless otherwise noted all content on this website is released under the
Creative Commons Attribution Licence 3.0
Hello Anonymous! Login?
