RobotC Header File: Better Timers

This header file, by default, has ten timers. It can easily be expanded to up to 127 independent timers. Each timer can be set to any value, not just 0. The value can be retrieved in hours, minutes, seconds, or milliseconds. Each function returns a float variable, so you can get up to 6 decimal digits of accuracy. Each function also has the option of subtracting higher units, or “rolling over”. For instance, the Timer_Msecs(…) function can subtract any whole seconds before returning the value. This is useful for display situations, such as the included timers_demo.c, which displays the current timer value in the standard hours:minutes:seconds.milliseconds format. Extensive documentation is also included.

This header file does not use any of the built-in timers. Please consult the included documentation for further details.

The statistics below are with Release optimization enabled.

File "timers_demo.c" compiled on Jul 21 2012 21:21:38

Compile Statistics:  (timers_demo.c)
    0.010 Total Compile Time (seconds)
      757 Total code bytes, (after 93 bytes removed during optimization)
       24 Constant Pool Size (in bytes)
      134 Used memory locations (of 12000), 1 Tasks, 7 Procedures
      146 User Source file lines,       650 tokens
    6,169 System Include file lines, 26,792 tokens
        0 Errors, 0 Warnings, 0 Info Messages
                                 CPU     ......Lines/sec.....
                             Seconds         Total     Source
     Scanner/parsing: Setup    0.003     2,232,950     51,624
    Scanner/parsing: System    0.000    39,312,329    908,883
Scanner/parsing: User Files    0.002     3,838,470     88,743
            Code Generation    0.003     2,342,731     54,162
              Code Optimize    0.001    11,562,450    267,318
                      Total    0.010       640,903     14,817
       64 Total symbols added to symbol table
    1.083 Avg hash bucket depth (1.0 is best possible)

I’d like to acknowledge this thread. It was very useful in the creation of this header and I recommend it to programmers of all skill levels.

You may also be interested in my other header file: LCD.h.
timers_v1.0.zip (4.19 KB)

Great post! Thanks for sharing your work. Moved to Technical Discussion section as requested.

Thanks for posting some useful code. I’m going to make some comments but its always hard to do that without sounding critical of what you have written. Please take this as being constructive with the goal of helping you become a better programmer by hearing someone else’s thoughts , some of this will be subjective and you may choose to act on it or not.

First there are a couple of bugs/omissions that you should include.

In the demo file, the code will not give a display on the LCD unless the following is included by using the motors and sensors setup.


#pragma config(UART_Usage, UART1, VEX_2x16_LCD, baudRate19200, IOPins, None, None)

By default the LCD is disabled.

In timers.h the bounds check is off by one, you have an array with 10 elements so the array can be indexed as 0 through 9. The bounds check will also allow array index 10 to be used.

the check should be.

if (Timer < 0 || Timer >= Timers_Count)
        {
        return false;
        }
    else
        {
        return true;
        }
}

Some style comments.

There seem to be many unneeded semi-colons. They don’t cause errors but I’m not used to seeing this type of thing.

void Timer_Set(const char Timer, const float Value = 0)
{
  if (!Timer_BoundsCheck(Timer)) {return;};
  Timers[Timer] = nSysTime - Value;
};

Why the extra ; after the function, same with the ; after the braces around the return statement.

Most of the time I make every attempt to avoid using floating point numbers in embedded code, this becomes especially important on a processor without a hardware floating point unit. As the system variable nSysTime is a long, a 32 bit integer, I would have made the Timers array also 32 bit. There will also be issues with the precision of the values that a single precision float may hold, although floats can have a large range of values that comes at the expense of decreasing precision. For example, if nSysTime has reached 23 hours, 59 mins, 59.999 secs (ie. 246060*1000 - 1 or 86399999) when converted to a float this becomes 86400000 as the fractional part of a float is only 23 bits long. Now in practice on this processor used in this environment where the cortex is only on for a few minutes it will not make much difference, however, its worth mentioning.

Formatting, well that’s up to you, I prefer more whitespace but it’s subjective.

comments, I have to disagree here, comments are important. You will forget how code works and why you did certain things when you revisit it perhaps several months later. For example, this function

float Timer_Secs(const char Timer, const bool SubtractHigherUnit = false)
{
  if (!Timer_BoundsCheck(Timer)) {return -1;};
  const float Time = (nSysTime - Timers[Timer]) / 1000;
  if (SubtractHigherUnit)
  {
    return Time - (floor(Timer_Mins(Timer)) * 60);
    } else {
    return Time;
  }
}

I would have liked to see an explanation of what was happening so I did not have to work it out from the logic. Perhaps as follows.

//
// Return the number of elapsed seconds as a floating point number
// Setting SubtractHigherUnit to true will limit to the range 0 - 59.999
//
float 
Timer_Secs(const char Timer, const bool SubtractHigherUnit = false)
{
  // bounds check timer index
  if (!Timer_BoundsCheck(Timer)) 
    return -1;
  
  // Convert timer to floating point time in seconds
  const float Time = (nSysTime - Timers[Timer]) / 1000;

  // Are we limiting to the range 0-59 ?
  if (SubtractHigherUnit) {
      // Subtract number of minutes * 60
      return Time - (floor(Timer_Mins(Timer)) * 60);
      }  
  else {
      return Time;
      }
}

I should also mention that there is a possible error condition here. Assume the following

Timers[Timer] is set to 0
nSysTime has reached the value 59999

Time will be set to 59.999, so far so good.

Now we will limit to the 0-59.999 range as SubtractHigherUnit is set true so you call the function Timer_Mins. However, nSysTime may have rolled over to 60000 so floor(Timer_Mins(Timer)) * 60 will become 60 and Time will be set to -0.001. A very unlikely event but something you have to consider when accessing a variable that is being incremented by a background task. Now having said this I don’t know exactly when nSysTime is incremented but I have to assume it’s by the task scheduler that runs every 1mS.

These are subtle issues but in production code can drive a programmer crazy.

Anyway I’ll leave it at that and have a look at the LCD code you posted.

1 Like

Criticism is what improves code. I will take it.

I apologize. I wrote this mostly in the emulator, and apparently it does that automatically. Code has been changed.

I admit that should have been tested before publication. Code has been changed.

Sorry. It’s a habit. I will (try to) avoid placing semicolons after close brackets.

I did not know the nSysTime variable type. I appreciate the enlightenment. Code has been changed.

I have to disagree (to an extent). I believe comments should state not what code is written, but why the code was written. For instance…

float Timer_Secs(const char Timer, const bool SubtractHigherUnit = false)
{
  if (!Timer_BoundsCheck(Timer)) {return -1;}; // Can't check timer bounds at compile time (yet)
  const float Time = (nSysTime - Timers[Timer]) / 1000; // Saves a few calculations in the if block.
  if (SubtractHigherUnit)
  {
    return Time - (floor(Timer_Mins(Timer)) * 60);
    } else {
    return Time;
  }
}

Frankly, I expect programmers to be able to read and follow the logic here (no offense intended). If I were doing higher-level logic, I would probably add more comments.

I think this is really a corner case and is extremely unlikely to occur in any sane use of a VEX Cortex.

In conclusion, I really appreciate your comments. Thanks for your help!

I will probably post an update with these and any other changes deemed necessary, probably within a month. (I’m not a very definite person…) Again, thank you.

Apologies not necessary, this happens to me all the time, if a previous program has initialized the LCD I often forget to add this to new code and then realize when the cortex is power cycled.

Not a big deal, I was surprised that ROBOTC would run the code when the array index was out of bounds, normally that type of error will stomp on other variables but I guess not in this case. When debugging is enabled, ROBOTC adds bounds checking but this is a common error and sometimes I declare arrays as (for example) Timers Timers_Count+1 ] just to be safe.

Again, no need to be sorry, I was pointing this out as much for the benefit of other (perhaps) less experienced programmers who may read your code and think it’s a necessary part of the syntax.

The key file to look in for this type of information is RobotCIntrinsics.c (assuming you cannot find the info in the help system). In this case a search on nSysTime brings up this line of code

	intrinsic const long variableIndex(nSysTime,     opcdSourceSystem,        kSystemSysTime);

without going into details on what exactly this means the clue here is that it is a const long.

Writing software is an art as well as a science, we can agree to disagree. If eventiually your career involves programming you may have to adopt your employers standards and practices but for now enjoy your freedom :slight_smile: I can read and understand your code, not everyone reading it will have my experience and may wonder why you did what you did.

It is a corner case and I mention it for the benefit of others adding to their knowledge.

You are very welcome and I encourage you to post more code, the forum needs more discussion on advanced programming topics but that can only happen if students post their work.

To be honest, I thought it was. :o

I did actually check there after reading your post.

I understand.

I certainly plan on it. This has been a great learning experience for me and again I appreciate your help.

Wow this is really useful! One thing I would suggest adding is making a function that returns how much time has gone by since the last call. For instance in a PID controller, it’s sometimes recommended that you multiple the integral by how much time has gone since the last time the integral was updated.

Another comment is that it can’t be used on a PIC because there are floats and numbers above 32,767 (yes there are teams still using PIC). If you wanted to, you might want to consider making a PIC-friendly version.

Great work!

I don’t believe longs are supported by the PIC, however I’m using RobotC 2.x(I’m using a trial for the summer), so it might be supported in RobotC 3.x.

I will post an update in this thread on the 29th with the following changes:

#pragma LCD for demos
Conditional for bounds check corrected
Cleaned-up semicolons
More comments (???)
Float instances changed to longs
“Reset timer” parameter added to end of get-value functions, default false
Credits modified to reflect participation of jpearman, UnforeseenGamer, and anyone else that shows up…