Timing of commands: reprise

lbenson

Senior Member
Here is an update of the post of BeanieBots from April, 2004, concerning the timing of statements http://www.picaxeforum.co.uk/showthread.php?t=1128 . The rig is shown attached--two JRHackett 08M modules on a cut-down breadboard strip 11 pins high. The code to be timed is put between a "high 2" and a "low 2". In the other 08M, the time that the pulse is high is measured with pulsin. These are the timings I made:

0.77ms high 1: low 1
0.53ms high 1
0.71ms b13 = 255
0.90ms if b13 = 255 then : endif
0.94ms if b13 <> 255 then : endif
0.74ms inc b0
1.53ms pause 1 (presumably 1ms of this is the paused time)
0.68ms b0 = b1
1.33ms if b0 = 128 then : b0 = 255 : endif (after b0 has been set to 128)
0.93ms if b0 = 128 then : b0 = 255 : endif (b0 was set to 255 by prior statement)
0.94ms if b0 = 255 then truecondition : b0 = 0 : truecondition:

And then a chunk of code after b3 = 0: b2 = 250 (the "not equal" condition in the loop was +not+ true)
Code:
    do
      b0 = b2   ' get the settings of the contacts
      if b0 <> b2 then ' we have a change
        b3 = 0 
      endif
      inc b3
      pause 1
    loop until b3 >= 0
3.57ms

These timings are a bit high relative to the common citation of about 2,000 statements per second, or .5ms per statement, but they include some part of the preceeding "high 2" and trailing "low 2". Putting a string of 10 repetitions together and dividing by 10 would give a better figure.

The code of the sender is as follows:

Code:
' 08SndTim sends a pulse surrounding code to be timed
'   in another picaxe (08TimeIt)

#picaxe 08M

main:
  do
    do while pin3 = 0 : loop
    pause 100  ' debounce
    do while pin3 = 1 : loop
    pause 100  ' debounce

    b1 = 1
    serout 4,N2400_4,(#b1) ' first test
    pause 200   ' wait .2 seconds
    high 2
    high 1
    low 1
    low 2
    pause 2000  ' wait 2 seconds

    inc b1
    serout 4,N2400_4,(#b1) ' next test
    pause 200   ' wait .2 seconds
    high 2
    high 1
    low 2
    pause 2000  ' wait 2 seconds

    inc b1
    serout 4,N2400_4,(#b1) ' next test
    pause 200   ' wait .2 seconds
    high 2
    b13 = 255
    low 2
    pause 2000  ' wait 2 seconds

    inc b1
    serout 4,N2400_4,(#b1) ' next test
    pause 200   ' wait .2 seconds
    high 2
    if b13 = 255 then
    endif
    low 2
    pause 2000  ' wait 2 seconds

    inc b1
    serout 4,N2400_4,(#b1) ' next test
    pause 200   ' wait .2 seconds
    high 2
    if b13 <> 255 then
    endif
    low 2
    pause 2000  ' wait 2 seconds
    
    inc b1
    serout 4,N2400_4,(#b1) ' next test
    pause 200   ' wait .2 seconds
    high 2
    inc b0
    low 2
    pause 2000  ' wait 2 seconds
    
    inc b1
    serout 4,N2400_4,(#b1) ' next test
    pause 200   ' wait .2 seconds
    high 2
    pause 1
    low 2
    pause 2000  ' wait 2 seconds
    
    inc b1
    serout 4,N2400_4,(#b1) ' next test
    pause 200   ' wait .2 seconds
    high 2
    b0 = b1
    low 2
    pause 2000  ' wait 2 seconds

    inc b1
    serout 4,N2400_4,(#b1) ' next test
    pause 200   ' wait .2 seconds
    b0 = 128
    high 2
    if b0 = 128 then
      b0 = 255
    endif
    low 2
    pause 2000  ' wait 2 seconds

    inc b1
    serout 4,N2400_4,(#b1) ' next test
    pause 200   ' wait .2 seconds
    high 2
    if b0 = 128 then
      b0 = 255
    endif
    low 2
    pause 2000  ' wait 2 seconds

    inc b1
    serout 4,N2400_4,(#b1) ' next test
    pause 200   ' wait .2 seconds
    b0 = 255
    high 2
    if b0 = 255 then truecondition
    b0 = 0
    truecondition:
    low 2
    pause 2000  ' wait 2 seconds

    inc b1
    serout 4,N2400_4,(#b1) ' next test
    pause 200   ' wait .2 seconds
    b3 = 0
    b2 = 250
    high 2
    do
      b0 = b2   ' get the settings of the contacts
      if b0 <> b2 then ' we have a change
        b3 = 0 
      endif
      inc b3
      pause 1
    loop until b3 >= 0
    low 2

  loop
The timing code is:
Code:
' 08TimeIt reads a pulse sent another picaxe (08SndTim) and reports on the 
'   time on sertxd

#picaxe 08M

  pause 2000
  sertxd ("Starting",cr,lf)
main:
  do
    pulsin 4,1,w5           ' get time in 10s of microseconds
    if w5 > 0 then          ' we didn't time out
      w4 = w5 / 100         ' number of milliseconds
      w5 = w5 // 100        ' number of microseconds / 10
      sertxd(#w4,".",#w5,"ms",cr,lf)
    endif
  loop
Thanks to BB for the idea.
 

Attachments

BeanieBots

Moderator
Thanks for the acknowledgement.
I should point out that things (firmware) have moved on quite a bit since I did those tests. I would expect better (faster) results with many commands now.
As you say, timing over ten or more iterations would yeald better results.
 

hippy

Technical Support
Staff member
This is possibly the optimal, simplest code to use for timing tests ...

Code:
If b0 = $FFFF Then
  End : End : End
End If
Gosub SetFrequency
Do
  Toggle 2
  ' **** < command to test here >
  Toggle 2
  Pause 1000
Loop

SetFrequency:
  ' *** < force under-clocking commands here >
  Return
Anything other than testing one command at a time and the issue of positional code starts to confuse things. Even here it's not perfect. Run it with no command and you get the 'constant overhead' of the two toggles to remove from later measurements.

To get a genuine constant overhead it would be necessary to generate an interrupt during the command being excuted so the interrupt occured immediately upon its completion so latency ( to the subsequent Toggle within interrupt ) would always be consistent. This would be quite an effort to configure correctly - especially as token alignment is not public information.

One way to get better accuracy of measurement is to reduce the PICAXE speed. It will still execute the same number of cycles but the pulse will be longer. Likewise, run the measuring PICAXE at 8MHz. Take a number of PULSIN readings then average them out.

Ultimately all you can get is all times and a minimum and maximum time it takes to execute a particular command with specific values. Although that gives a ball-park figure I'm not sure how useful it is, even taking the mean, average or mid-point doesn't hold true as alignment can vary from one line to the next - in some cases the alignment may cause two lines to both take minimum or maximum times, sometimes minimum then maximum or vice-versa. You can never tell if the time of any command is going to tend towards the minimum or maximum. Alignment and command timing will change between PICAXE variants.

PICAXE timing is academically interesting but quite an effort ( 48 possibilities for a HIGH/LOW command, 64 for a simple LET assignment ) and if they aren't all done you're never really sure where any particular measurement falls on the minimum-maximum range.

The way 'commands per second' is usually measured with token interpreters is to time the frequency of the fastest I/O toggling rate, which for a PICAXE could possibly be -

Code:
If b0 <> b0 Then
  b0 = 16 : End
End If
Do
  Toggle 0
Loop
Again token alignment comes into it and this may actually not be optimal.
 

BeanieBots

Moderator
Whilst I agree that attempting to get accurate times for all the commands is rather academic, I think it is a very useful to have ball-park figures. This is particularly important to demonstrate that some commands take longer than others.
Also, timings of structures is equally important. How does "select case" compare to "branch" or a list of "if/then"?

How long does it take? is a very common question and will continue to be asked. Not everyone will appreciate that the answer is not simple. This thread would make a very good reference for such questions.
 

BCJKiwi

Senior Member
There are a number of constructs discussed in this thread;
http://www.picaxeforum.co.uk/showthread.php?t=8342

comparing the following alterntives;
Code:
The logic options reviewed are 
sequential If/ElseIf/Endif, 
select case, 
and 
Branch techniques,
combined with
LOOKUP function to get stored bit patterns from symbol statements (symbols use no program memory space),
or
direct calulation of the bit pattens via shift ( << ) and REV functions.
Have also checked to see if there is any time penalty in using ptr & @ptr compared with direct use of variables. 
The ptr/@ptr is useful as it enables the stored variables to be retained without having to worry about re-use 
of bo/b1 etc (provided you keep a proper track of the ptr addresses of course!).
If nothing else, it indicates a way to compare the timing for alterntive programming commands/constructs for the specific program requirements.
 
Last edited:

hippy

Technical Support
Staff member
These are the timings (us) I got for a HIGH opcode with various pin number arguments with ideal and non-ideal alignments ( 48 combinations ) -

Code:
HIGH  1-bit  4-bit  8-bit  16-bit  var
       253    289    326    415    301
       265    289    338    427    301
I ran the main PICAXE-08M at 125kHz, did the pulse measuring on an 18X at 8MHz. Determined min and max readings over multiple loops, and found no difference in us time units so no need to average.

Tested at 5V, 18'C. As voltage goes down or temperature rises, execution gets slightly faster, times get shorter, just one or two microseconds though.

I'd expected more variation with different alignments but I did not get any other results than these. I expect the more tokens in a command the more likely there will be more misalignments and more variances.

That the 4-bit and var times are the same is probably because I'm using the code in the posting earlier, the adverse alignment for HIGH is a beneficial alignment for the following TOGGLE which terminates the time measuring pulse. Some times here are conversely too high because the adverse effect of the TOGGLE has not been taken out. It shows just how hard it is to pin down any accurate details where any small change affects something later.

The overhead for the pulse generation I measured as 278us, so 253us + 278us = 531us, which matches with lbenson's 0.53ms. The other way to look at it is, the back half of a HIGH and the front half of a LOW plus a HIGH in between adds up to two commands, do half of 530us is the time for one command; 265us.

At around 250us for "HIGH 0" that gives near 4,000 commands per second. The latest manuals state "roughly 1,000 per second" at 4MHz, which is probably about right overall for an arbitrary program.

Running the "TOGGLE 0" in a DO-LOOP gave an average of 537us per high or low, so around 266us per command there.

I recall in earlier discussions there was some divergence of opinion on whether the basic instruction was 250us or 500us, from the measurements done by myself and lbenson I'd say 250us again ... unless we are both making the same mistake in taking measurements.
 

lbenson

Senior Member
Timing of code

As BeanieBots noted, "attempting to get accurate times for all the commands is rather academic"--for the reasons cited. My specific interest here was to be able to find the approximate timing of a section of code, so that a "pause" statement could be adjusted to provide a (nearly) fixed timing of the loop. In this test sequence I started putting a bunch of commands--all the same--to fill up the 08M's program space, or nearly so.

In the following list, the first line is the statement used and then the number of times in a row it occurred between the "high 2" and "low 2" statements. I ran each set 4 times and for single statements found no discrepancey of more than 10 microseconds (the resolution of the "pulsin" statement). As estimated by Hippy, the simplest statements, like "toggle 1" or "high 1" take a shade over 250 microseconds. This would be a rate of nearly 4000 instructions per second.

Of those few statements tested, "w1 = w1 + 256" takes the longest time, about 650 microseconds. (As BB & hippy showed, other statements take much longer.)

The final example, my piece of code, is a loop in which one "if" statement will be rarely executed in practice. I wanted to time the other statements. 100 iterations of the loop took 340 milliseconds, so each took 3.4ms. If I want each loop to take 250ms, I change "pause 1" in the loop to "pause 246". If this makes the loop timing accurate to about half a millisecond, then in 4 minutes (960 iterations--4minutes*60seconds*4loops_per_second), then the maximum time out will be about a half a second.

The timing code in post 1 can be used to time any sequence of statements which takes less time to execute than the pulsin timeout--655.36 milliseconds.

toggle 1 (100 iterations)
25.63ms
25.64ms
25.64ms
25.63ms
about .256ms per instruction

high 1 (100 iterations)
25.74ms
25.75ms
25.74ms
25.75ms
about .257ms per instruction

inc b1 (90 iterations)
41.81ms
41.81ms
41.82ms
41.81ms
about .465ms per instruction

inc w1 (90 iterations)
Starting
43.79ms
43.79ms
43.79ms
43.80ms
about .486ms per instruction

b1 = b1 + 1 (90 iterations)
Starting
41.81ms
41.82ms
41.82ms
41.82ms
about .464ms per instruction

b1 = b1 + 255 (60 iterations)
32.25ms
32.26ms
32.26ms
32.25ms
about .537ms per instruction

w1 = w1 + 256 (50 iterations)
32.48ms
32.49ms
32.49ms
32.49ms
about .650ms per instruction

code section: (1 iteration, 100 times through the loop
Code:
    b3 = 0
    b2 = 250
    high 2
    do
      b0 = b2   ' get the settings of the contacts
      if b0 <> b2 then ' we have a change
        b3 = 0 
      endif
      inc b3
      pause 1
    loop until b3 >= 100
    low 2
340.40ms
340.38ms
340.35ms
340.33ms
about 3.40ms per loop
 

hippy

Technical Support
Staff member
That's a good way to tune timing for a piece or code or loop but bear in mind that removing any code which enables the measuring can have a major effect on the code timing itself, as can altering any code before the time critical routine.

Fine tuning can come from PAUSEUS or PULSOUT to an unused pin, putting two different delays within any IF-THEN-ELSE can keep execution time through any path constant. The biggest frustration comes in having a change to the 'padding time' alter the timing of the rest - eg, a loop with a PAUSE 7 gives a 99ms loop time when you want 100ms, but going to PAUSE 8 then gives 101ms or, more annoyingly, 98ms. The later in a loop pauses can be added the better.
 

lbenson

Senior Member
One of the caveats alluded to above but not elaborated upon is the accuracy of the Precision Internal Oscillator upon which the picaxe clock is based (if running on the internal oscillator). The Microchip datasheet for the 12F683 microprocessor upon which the 08M is based says that the oscillator is " Factory calibrated to ±1%, typical". This implies that in 4 minutes, or 240 seconds, the timings from the methods used above could be off by about two seconds, plus or minus. The accuracy of the timings could theoretically be improved by poking the OSCTUNE register (0x90), but issues of calibration would arise, especially with varying temperatures. If timings of more than about 1% accuracy are required, other methods are indicated. Using the DS1307 could allow accuracy within about 1 second for extended timings, but calibration could be an issue there as well, and sub-second accuracy would require a combination of methods.
 

hippy

Technical Support
Staff member
Yes, 86,400 seconds to a day, +/-1% is 864 seconds, 15 minutes a day so using an internal oscillator isn't that reliable for long term timing.

My experience has been that this is a generally constant error ( ie fixed manufacturing tolerance ) so can be compensated for quite well with tuning pauses and I'd expect CALIBFREQ to work as well. When I tried tuning with PULSOUT I was getting just a few second losses or gains per hour which is much more tolerable and I didn't do extensive tweaking. It would be possible to use a second PICAXE and a SERIN/SEROUT to calibrate the timing automatically.

There are additional changes with temperature and voltage which would need external adjustment. A PICAXE with an external crystal is more accurate and stable but even with 1ppm error that's still a potentially reasonably large drift per day.

The 08M can have an external 32kHz watch crystal attached to give fairly reasonable time keeping. I haven't tried that with the 14M or 20M.
 
Top