1
\$\begingroup\$

Imagine you were trying to write a C routine for an 8bit microcontroller that converted a desired timespan (be it seconds, milliseconds, hertz, etc). to the corresponding hardware timer preload value (taking into account the clock source and frequency of the timer). The goal is to make the function as easy as possible to use, with as few arguments as possible. What would this function look like?

Would you pass in a uint32_t representing the desired hertz of the timer (0 = 0Hz, 1000 = 1kHz, etc.):

void set_timer(uint32_t hz);

Would you prefer using a uint16_t representing kHz instead (0 = 0Hz, 1 = 1KHz, 10 = 10Khz)?

void set_timer(uint16_t khz);

Or would a 32bit fixed point argument be more desirable (to be able to represent fraction of hertz)?

void set_timer(q15_16_t hz);

Or would this fixed point value represent seconds instead of Hz?

void set_timer(q15_16_t sec);

Or would you use a 16bit fixed point instead of a 32bit fixed point.

Or would you even dare want to use a floating point? Or what about a half-precision float?

What do you think?

\$\endgroup\$
5
  • 1
    \$\begingroup\$ '8-bit mirocontroller' is still a very wide landscape. Is your code intended for 256-instruction chips, or an 128k-instruction chip? I would not dare to try floating point on the first, but it is a very real possibility on the last. My gut answer would be: why not provide them all, with names that clearly show the type of parameter, and make sure unneeded code is not linked into the application. \$\endgroup\$ Commented Nov 20, 2012 at 22:14
  • 1
    \$\begingroup\$ In my experience it is quite rare to want to set a timer to a run-time computed period or frequency. You usually know this up front, so I do what you say but in my preprocessor. I already have a global constant called FREQ_INST for the instruction clock frequency, so only need to specify the desired timer period. Since it's done at build time, you can use all the floating point with plenty of extra bits you want. For example see the TIMER2_xxx macros in SOURCE>PIC>STD.INS.PAS after installing my PIC development tools from embedinc.com/pic/dload.htm. \$\endgroup\$ Commented Nov 20, 2012 at 23:05
  • \$\begingroup\$ I agree with you, Wouter: providing a range of options is attractive for ease of development, but I was more interested in hearing what others preferred. Has anyone ever desired setting a timer to, say 1.3Hz, or is 1Hz more of less always good enough? \$\endgroup\$
    – TRISAbits
    Commented Nov 20, 2012 at 23:06
  • \$\begingroup\$ @Olin. For a known fixed frequency, I believe that your suggestion is the right approach, but in some application the FREQ_INST is scaled dynamically, which obviously changes the timer elapsed time. In such situation, a function is required. \$\endgroup\$
    – TRISAbits
    Commented Nov 20, 2012 at 23:12
  • \$\begingroup\$ uint32_t is terribly, terribly inefficient on every known 8-bitter on the market, so you should avoid using it. \$\endgroup\$
    – Lundin
    Commented Nov 23, 2012 at 13:49

3 Answers 3

1
\$\begingroup\$

First I would suggest using time instead of frequency, since the latter implies that the timer is of a continuous kind. The user is often interested in a certain "one-shot" delay, when waiting for some sort of hardware to become available.

Second, uint32_t is a big NO on 8-bit microcontrollers. As soon as you use 32-bit numbers, every 8-bitter on the market will create a flood of inefficient machine code, likely with internal subroutine calls. 16 bit numbers are bad enough. Though of course, there are cases where you need high resolution accuracy and then you might have to reluctantly use large integer types.

In this case, I very much doubt that your 8-bit MCU has a 32-bit timer peripheral. De facto standard for 8-bitters is to have 16-bit hardware timers. There is no point of specifying a resolution that doesn't correspond with the hardware.

Using floating point is even worse: if you ever find yourself needing floating point accuracy on a 8-bit MCU, you most likely picked the wrong MCU for your project to begin with. (The cost argument of 8-bit vs 32-bit has been obsolete forever.)

Based on the above argument, I would suggest something like:

void set_timer (uint16_t ms);

where ms is the time in miliseconds.

The ideal case is however to calculate the needed timing in the pre-processor, then show that into a constant, which is then directly read into the timer hardware registers.

\$\endgroup\$
3
  • \$\begingroup\$ Thanks for the great feedback! I agree that pre-processed constants are ideal from a computational point of view, but on systems where the system clock frequency is dynamically scaled (to frequencies that are not know at compile time), dynamic calculations cannot be avoided. It's interesting that you mention using milliseconds: I would have thought Hz to be a preferred parameter since it makes the timer calculation easy, but I do like the compromise you present. \$\endgroup\$
    – TRISAbits
    Commented Nov 24, 2012 at 20:10
  • 1
    \$\begingroup\$ @TRISAbits Regarding milliseconds: the pre-scaler registers of the timer peripheral will be corresponding to time rather than frequency, so by specifying the parameter as time, you might actually save one runtime division 1/freq. But I suppose that depends on the formula used to calculate the register value. \$\endgroup\$
    – Lundin
    Commented Nov 26, 2012 at 7:26
  • \$\begingroup\$ I was (perhaps prematurely) assuming that to perform the calculation, one would use the processor's cycle/second (frequency) instead of second/cycle. If using the latter, I agree that using a ms argument avoids a 1/freq. I'm glad you brought this idea up: I've always used timer = uc_frequency/desired_timer_freq, but the reverse for ms could easily be done. \$\endgroup\$
    – TRISAbits
    Commented Nov 26, 2012 at 17:10
0
\$\begingroup\$

Generally, I use hardware timers one of two ways:

  1. I set the timer to trigger an event (interrupt) at a particular frequency, and then just leave it running at that frequency; if I want to do things at various rates, I'll perform some tasks every third interrupt, some every eigth, etc. This is my most common approach for handling timing when the system is "awake".
  2. For low-speed timers which need to run while the CPU is asleep, I prefer to use a free-running counter which wraps at some interval, and then set a compare register with the absolute time when the next wake-up event should occur.

On some occasions, I may use a timer to generate a "beep" frequency; in most such cases, I'll attach the timer to a hardware PWM. When generating a "beep", it's possible that I might want to specify a frequency, though more commonly I would specify a period or else an index into a table of useful frequencies held by the routine which sets things up for the beep.

For the first style of usage, the API isn't particularly relevant, since it's "set up and forget". For the beeper application, the timer API isn't relevant, since a "beep" API will often combine the timer setup with the code necessary to enable or disable the PWM. Only with the second usage is an API particularly relevant. For that, the most useful style of API would probably be "set next wakeup to occur no later than the indicated absolute time, if it isn't already scheduled sooner", with a 32-bit parameter that represents a continuous (wrapping) time, and combined with a routine which reports the current time on the same 32-bit scale. If the hardware is e.g. only a 16-bit counter, the routines should act suitably (e.g. the "read time" routine, if called at least once every 65535 ticks, should return a continuously-increasing 32-bit value; if the "set alarm" routine is used to set an alarm more than 32768 ticks in the future, it should set the alarm 32768 ticks in the future (if the requested time was e.g. 65,538 ticks in the future, the routine should notice the time is more than 65,536 ticks in the future, rather than just using the low 16 bits of the time and regarding it as two ticks in the future).

\$\endgroup\$
2
  • \$\begingroup\$ The API is relevant if you need various delays in your program but only got one single timer, such as a RTC. You'd then have to wrap in the hardware into some sort of general-purpose library. Typically, you build up an array of structs, each struct containing a "callback" function pointer and a timeout value. At each interrupt, a software counter is increased, and if it matches one of the individual timeouts, you call the corresponding function. Such functions are typically minimal, just setting some flag or register value. \$\endgroup\$
    – Lundin
    Commented Nov 23, 2012 at 13:55
  • \$\begingroup\$ @Lundin: The API for the code which determines when the next interesting event will occur is very relevant, but that doesn't imply that the API for the routine to configure the hardware timer is especially important/ Even if the application makes heavy use of the former API, the code to implement that API will likely have only one or two places that actually set the hardware timer. \$\endgroup\$
    – supercat
    Commented Nov 23, 2012 at 22:37
0
\$\begingroup\$

I personally use:

// Returns actual timer rate in Hz
uint32_t set_timer(uint32_t hz);

But in the world of constrained systems, you'll never give everybody exactly what they want. So make a routine that's simple for simple cases, and don't bother trying to make a routine that covers every potential use case -- one can just directly access the hardware for that.

\$\endgroup\$
1
  • \$\begingroup\$ Using a function like this is a great idea, since the calculations routine can be shared amongst all timers, as long as said timers have the same prescale/postscale/width etc (which unfortunately isn't always the case on certain flavors of MCU). One of the downside of using integer Hz is that you cannot specify time lapses of, say, 0.3sec. Perhaps a hybrid implementation is preferred: provide multiple implementations, as Wouter suggested, using the function signature you presented. This would allow odd Hz values to be used while minimizing code space. Thank you for the suggestion! \$\endgroup\$
    – TRISAbits
    Commented Nov 24, 2012 at 20:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.