Post Reply 
A small contribution and a big question (timer/keyboard)
11-29-2019, 03:42 AM
Post: #1
A small contribution and a big question (timer/keyboard)
Recently I ran into a problem of having to perform many time interval measurements and post process them. Since automating the measurement process was not possible, I decided that rather than using a stopwatch, writing down results, and later typing them in for post processing, I would write an HP Prime application combining a stopwatch and post processing in one.

Initial results were less than satisfying. It turned out that TICKS() based time measuring process yields a substantial error (about 2.5% in case of my G2).

In the web I found a note (pertaining to G1) stating that for the TICKS() command HP Prime uses processor internal clock which is not very stable, and that the real time is derived from external RTC which is very accurate, but has only 1 sec resolution.
Apparently this is also a case with G2. I accepted this fact not having time to have a closer look at the calculator board and studying i.MX 6ULL documentation.

I came up with a way if synchronizing the TICKS() derived signal with the Time() derived signal. This way the overall error of time measurement is minimized. The accuracy of the application was tested during many runs, the longest lasting over 10 hours. The error was negligible. (Keep in mind that manual starting and stopping the devices already introduced a few tenths of a second error.)

The following code implements a simple stopwatch. It's a core of the application I used for my measurements stripped of all irrelevant features, and instead equipped with very crude stopwatch user interface. Maybe somebody will find it useful.
Code:

#pragma mode(separator(.,;) integer(h64))

//prototypes
StopwatchCore();
ConvertMsToTime();
PrintTime();
RightJustifyString();
PrintMainScreen();
PrintMenuStop();
PrintMenuRun();
PrintOverfillWarning();

EXPORT STOPWATCH_HPM()
BEGIN

  LOCAL nBackground:=RGB(198,198,198);  
  LOCAL nKey;
  LOCAL lStatus:={0,0};  // list {first free line for lap time, last tick count}
  LOCAL sString;

  LOCAL sVersion:="ver. 1.11-HPM";

  // HP Prime has two sources of internal clock.
  // One is external RTC. It has good accuracy, but only 1 sec resolution (function Time() ).
  // Another is internal processor clock, and it's used for TICKS(). It has good resolution (1ms),
  //  but poor accuracy. (Measured about 2.5%.)
  // We use TICKS() for measuring tenths of a second, and every second we synchronize it to Time().

  // Resources: uses G1 and G2

  // initialization
  STARTVIEW(-1);
  PrintMainScreen(sVersion,nBackground);
  PrintMenuStop(nBackground);
  sString:=ConvertMsToTime(0);

  PrintTime(sString,nBackground);

  // main loop
  REPEAT
    nKey:=GETKEY();
    CASE
      // Enter
      IF nKey==30 THEN
        PrintMenuRun(nBackground);
        lStatus:=StopwatchCore(lStatus(1),lStatus(2),nBackground);
        PrintMenuStop(nBackground);
        IF lStatus(2)>=864000000 THEN
          PrintOverfillWarning(nBackground);
        END;
      END;
      // Del
      IF nKey==19 THEN
        PrintMainScreen(sVersion,nBackground);
        PrintMenuStop(nBackground);
        lStatus(1):=0;  // set lap counter to zero
        lStatus(2):=0;  // set starting time to zero
        sString:=ConvertMsToTime(0);
        PrintTime(sString,nBackground);
        PrintMenuStop(nBackground);  // in case overfill menu was printed
      END;
    END;
  UNTIL nKey==31; // EEX was pressed
  RETURN 0;
END;

// ***
StopwatchCore(nPreviousLaps,nPreviousTicks,nBackground)
BEGIN
  // controls stopwatch
  // nLaps: number of lap times displayed on the screen already
  // nStartTicks: time to resume counting from
  // nBackground: screen background

  LOCAL sString;
  LOCAL i,nStartTicks,nTime,nCount,nOffset;
  LOCAL nLaps:=0;
  LOCAL nKey;

  // if more than 10 days, refuse to run
  IF nPreviousTicks>=864000000 THEN
    RETURN {nPreviousLaps,nPreviousTicks};
  END;

  nTime:=Time();

  // print zero value
  sString:=ConvertMsToTime(nPreviousTicks);
  PrintTime(sString,nBackground);

  nStartTicks:=TICKS;
  nCount:=-1;
  // do first second and calculate offset
  REPEAT
    nCount:=nCount+1;
    REPEAT
      IF nTime<>Time() THEN
        nOffset:=TICKS-nStartTicks;
        nTime:=Time();
      END;
      nKey:=GETKEY();
      CASE
        IF nKey==30 AND nLaps+nPreviousLaps<13 THEN  // Enter and within capture limit
          sString:=ConvertMsToTime(TICKS-nStartTicks+nPreviousTicks);
          TEXTOUT_P(nLaps+nPreviousLaps+1+". ",4,4+(nLaps+nPreviousLaps)*14,3,RGB(0,0,0));
          TEXTOUT_P(sString,26,4+(nLaps+nPreviousLaps)*14,3,RGB(255,0,0));
          nLaps:=nLaps+1;
        END;
        IF nKey==4 OR TICKS-nStartTicks+nPreviousTicks>=864000000 THEN // Esc
          RETURN {nLaps+nPreviousLaps,TICKS-nStartTicks+nPreviousTicks};
        END;
      END;
      IF (TICKS-nStartTicks)MOD10==0 THEN
        sString:=ConvertMsToTime(TICKS-nStartTicks+nPreviousTicks);
        PrintTime(sString,nBackground);
      END;
    UNTIL TICKS-nStartTicks>=100*(nCount+1);
  UNTIL nCount<100;

  REPEAT    
    nCount:=nCount+1;
    REPEAT
      IF nTime<>Time() THEN
        nStartTicks:=TICKS-100*nCount-nOffset;  // synchronize to real time
        nTime:=Time();
      END;
      nKey:=GETKEY();
      CASE
        IF nKey==30 AND nLaps+nPreviousLaps<13 THEN  // Enter and within capture limit
          sString:=ConvertMsToTime(TICKS-nStartTicks+nPreviousTicks);
          TEXTOUT_P(nLaps+nPreviousLaps+1+". ",4,4+(nLaps+nPreviousLaps)*14,3,RGB(0,0,0));
          TEXTOUT_P(sString,26,4+(nLaps+nPreviousLaps)*14,3,RGB(255,0,0));
          nLaps:=nLaps+1;
        END;
        IF nKey==4 OR TICKS-nStartTicks+nPreviousTicks>=864000000 THEN // Esc
          RETURN {nLaps+nPreviousLaps,TICKS-nStartTicks+nPreviousTicks};
        END;
      END;
      IF (TICKS-nStartTicks)MOD10==0 THEN
        sString:=ConvertMsToTime(TICKS-nStartTicks+nPreviousTicks);
        PrintTime(sString,nBackground);
      END;
    UNTIL TICKS-nStartTicks>=100*(nCount+1);
  UNTIL 0;
END;

// ***
ConvertMsToTime(nMilliseconds)
BEGIN
  // converts number of milliseconds to string days:hrs:min:sec.trnth
  LOCAL nDays,nHours,nMinutes,nSeconds;
  LOCAL sDays,sHours,sMinutes,sSeconds;
  LOCAL sMilliseconds;

  // days
  nDays:=IP(nMilliseconds/86400000);
  nMilliseconds:=nMilliseconds-nDays*86400000;
  // hours
  nHours:=IP(nMilliseconds/3600000);
  nMilliseconds:=nMilliseconds-nHours*3600000;
  // minutes
  nMinutes:=IP(nMilliseconds/60000);
  nMilliseconds:=nMilliseconds-nMinutes*60000;
  // seconds
  nSeconds:=IP(nMilliseconds/1000);
  nMilliseconds:=nMilliseconds-nSeconds*1000;
  // tenths of seconds
  nMilliseconds:=IP(nMilliseconds/10);  // use nMilliseconds as a buffer

  IF nDays==0 THEN
    sDays:="";
  ELSE
    sDays:=STRING(nDays)+"d : ";
  END;
  IF nHours==0 AND nDays==0 THEN
    sHours:="";
  ELSE
    IF nDays<>0 AND nHours<10 THEN
      sHours:= "0"+STRING(nHours)+"h : ";
    ELSE 
      sHours:=STRING(nHours)+"h : ";
    END;
  END;
  IF nMinutes==0 AND nHours==0 AND nDays==0 THEN
    sMinutes:="";
  ELSE
    IF (nDays<>0 OR nHours<>0) AND nMinutes<10 THEN
      sMinutes:= "0"+STRING(nMinutes)+"m : ";
    ELSE
      sMinutes:=STRING(nMinutes)+"m : ";
    END;
  END;
  IF nMinutes==0 AND nHours==0 AND nDays==0 THEN
    sSeconds:=STRING(nSeconds)+".";
  ELSE
    IF nSeconds<10 THEN
      sSeconds:= "0"+STRING(nSeconds)+".";
    ELSE 
      sSeconds:=STRING(nSeconds)+".";
    END;
  END;
  IF nMilliseconds<10 THEN
    sMilliseconds:="0"+STRING(nMilliseconds)+"s";
  ELSE
    sMilliseconds:=STRING(nMilliseconds)+"s";
  END;

  RETURN sDays+sHours+sMinutes+sSeconds+sMilliseconds;
END;

// ***
PrintTime(sString,nBackground)
BEGIN
  LOCAL nX;

  DIMGROB_P(G1,1,1);
  nX:=TEXTOUT_P(sString,G1,0,0,7);
  DIMGROB_P(G2,320,23,nBackground);
  TEXTOUT_P(sString,G2,(320-nX)/2,0,7,RGB(0,0,0),320,nBackground);
  BLIT_P (G0,0,200,G2);
END;

// ***
RightJustifyString(sString,nX,nFont)
BEGIN
  // returns X coordinate of right justified string
  // uses G1
  // allocates area of 1 px since TEXTOUT_P prints faster to not allocated area

  DIMGROB_P(G1,1,1);  // reserve area of 1 pixel 
  RETURN nX-TEXTOUT_P(sString,G1,0,0,nFont);
END;

// ***
PrintMainScreen(sVersion,nBackground)
BEGIN
  RECT(nBackground);
  TEXTOUT_P("© dap, 2019",4,228,1,RGB(0,0,0));
  TEXTOUT_P(sVersion,RightJustifyString(sVersion,315,1),228,1,RGB(0,0,0));
END;

// ***
PrintMenuStop(nBackground)
BEGIN
  RECT_P(206,4,319,52,nBackground);
  TEXTOUT_P("[Enter] - start",228,4,3,RGB(255,0,168),91, nBackground);
  TEXTOUT_P("[Del] - reset",228,20,3,RGB(255,0,168),91, nBackground);
  TEXTOUT_P("[EEX] - quit",228,36,3,RGB(255,0,168),91, nBackground);
END;

// ***
PrintMenuRun(nBackground)
BEGIN
  RECT_P(226,4,319,52,nBackground);
  TEXTOUT_P("[Enter] - capture",210,4,3,RGB(255,0,168),111, nBackground);
  TEXTOUT_P("[Esc] - stop",210,20,3,RGB(255,0,168),111, nBackground);
END;

// ***
PrintOverfillWarning(nBackground)
BEGIN
  RECT_P(206,4,319,52,nBackground);
  TEXTOUT_P("Count limit",224,4,3,RGB(255,0,168),111, nBackground);
  TEXTOUT_P("exceeded,",224,20,3,RGB(255,0,168),111, nBackground);
  TEXTOUT_P("[Del] to reset",224,36,3,RGB(255,0,168),91, nBackground);
END;
(Those who would like to use it should keep in mind, though, that the battery of continuously running G2 gets drained quite fast. After 10 hours of running the battery level dropped from full to 25%. The HP Prime battery measurement system is extremely inaccurate, but, I believe, it's safe to say that if you plan to cross a boundary of a day of continuous run, use the charger to power the calculator.)

While testing the applications (both original one and the one modified for this forum) I ran into a key pressing problem. Once every hundred key presses or so (rough average) the calculator fails to recognize a key press, and misses it. I was not able to find any reason why it's happening.

If somebody knows the mechanism behind this failure and can share an explanation with me, I would appreciate it.

Darius
Find all posts by this user
Quote this message in a reply
11-29-2019, 06:24 AM
Post: #2
RE: A small contribution and a big question (timer/keyboard)
Hello,

in G2, the ticks is derived from a CPU clock (counter), itself based on the CPU buss clock, which is itself based on a PLL (Phase Locked Loop, ie a way to generate high frequency based on a lower frequency) based on the main CPU oscillator. I do NOT remember if this oscillator is based on a cristal or an oscillating circuit, but PLL are not always very accurate...
I did calibrate this on my prototype making it run 10h and I had less than 10s error at the end, so less than 0.03%... it is interesting that you are finding something different, I guess it shows variation from chip to chip, or maybe temperature sensitive...

The ticks counter of the G2 is also the clock which is used for thread switching, meaning that every ms, the CPU will check if it needs to switch from one thread to another... Luckily enough, unless USB is plugged in, prime G2 only runs 3 threads (from memory). The main app thread, the alarm thread (alarm as in internal timers) and the usb monitoring thread, which is sleeping waiting for a usb plug event...

In comparison, Prime G1 had over 10 threads!


The RTS is based on its own 32Khz crital if I remember well and should be as accurate as any watch....

Cyrille

Although I work for the HP calculator group, the views and opinions I post here are my own. I do not speak for HP.
Find all posts by this user
Quote this message in a reply
11-29-2019, 08:40 PM
Post: #3
RE: A small contribution and a big question (timer/keyboard)
Cyrille,
Thank you very much for your response.
I did some brief research and performed some experiments at the same time (...while I really should have been be doing other stuff...), and here are my conclusions/experiment results.
Disclaimer: I have not opened my G2 yet. All my conclusions are based on the analysis of the picture of G2 board I found online.

1. I modified the stopwatch code and removed RTC synchronization. Here is a relevant procedure after changes:
Code:

StopwatchCore(nPreviousLaps,nPreviousTicks,nBackground)
BEGIN
  // controls stopwatch
  // nLaps: number of lap times displayed on the screen already
  // nStartTicks: time to resume counting from
  // nBackground: screen background

  LOCAL sString;
  LOCAL i,nStartTicks;
  LOCAL nLaps:=0;
  LOCAL nKey;

  // if more than 10 days, refuse to run
  IF nPreviousTicks>=864000000 THEN
    RETURN {nPreviousLaps,nPreviousTicks};
  END;

  // print zero value
  sString:=ConvertMsToTime(nPreviousTicks);
  PrintTime(sString,nBackground);

  nStartTicks:=TICKS;

  REPEAT
    nKey:=GETKEY();
    CASE
      IF nKey==30 AND nLaps+nPreviousLaps<13 THEN  // Enter and within limit
        sString:=ConvertMsToTime(TICKS-nStartTicks+nPreviousTicks);
        TEXTOUT_P(nLaps+nPreviousLaps+1+". ",4,4+(nLaps+nPreviousLaps)*14,3,RGB(0,0,0));
        TEXTOUT_P(sString,26,4+(nLaps+nPreviousLaps)*14,3,RGB(255,0,0));
        nLaps:=nLaps+1;
      END;
      IF nKey==4 OR TICKS-nStartTicks+nPreviousTicks>=864000000 THEN // Esc
        RETURN {nLaps+nPreviousLaps,TICKS-nStartTicks+nPreviousTicks};
      END;
    END;
    IF (TICKS-nStartTicks)MOD10==0 THEN
      sString:=ConvertMsToTime(TICKS-nStartTicks+nPreviousTicks);
      PrintTime(sString,nBackground);
    END;
  UNTIL 0;
END;
I ran the code taking measurement at 1.5h and 3h. The errors were:
@ 1.5h: 2.346%
@ 3.0h: 2.346%.
(Amble stopwatch was used for time measurement. During previous tests it was compared with Casio chronometer watch, and they both ran with exactly the same speed, so we can safely assume it's accurate.)
Thus, the error seems to be stable.
Please pay attention to the sign of the error: the calculator was running faster that it should.

2. From the picture mentioned above I deduced that G2 uses two crystals: one 32K for SRTC, another for the CCM. This is consistent with the fact that MCIMX6Y2DVM05AB does not have built-in oscillator driving CCM. The crystal frequency should be 24MHz (indeed, a digit "4" can be read on the crystal's enclosure in the picture; other enclosure areas are somewhat blurry).
From what you said in your e-mail ("based on the CPU bus clock"), PLL2 is used for task switching purpose in the RTOS. It's a fixed PLL, but equipped with PFDs, so, at lest theoretically, the system frequency can be modified. (I don't think it happens, though, at least not intentionally.)

3. What can be the possible sources of the frequency error? As I see it:
a) Incorrect crystal load capacitance. Rather unlikely, and even if, then it would be a few hundred ppm, not 2.4%!
b) PLL error. The MCIMX6Y2DVM05AB data sheet does not specify phase error (or jitter) for PLL2, but again, 2.4% seems excessive.
c) Bug in the code. Either a wrong value accidentally written to a PFD, or an error in procedure retrieving time from the task switching clock. (The latter one is rather unlikely, because the calculator is running faster than it should.)
d) Bug in my code?...
...Or something yet totally different. Maybe my G2 is faulty in this respect?

Whatever it is, I was not dreaming that it was happening. And synchronization with RTC removed it. For me the case is closed.

Again, thanks for interesting info you sent in your e-mail.

Darius
Find all posts by this user
Quote this message in a reply
12-02-2019, 06:37 AM
Post: #4
RE: A small contribution and a big question (timer/keyboard)
Hello,

I first was going to write: It would be interesting to have other test your code and see by how much their calculators vary...

But then, I had a thought!
2.3%... This looks close to 2.4 to me... or to be exact to 102.4-100 See where I am going with this????

So, I looked in the source code to remember better and I realized that I confused G1 and G2 here...

In G2, the RTC is based on a 32768Hz counter, from which the time/date is derived, but also the internal "tick"... both for OS switch and for the Ticks function...

The ticks is "created" by just dividing said counter by 32 (5 bits), so it actually counts at a rate of 1024 per second! so, the 2.3% deviation is nothing more than
(1-1000/1024)*100 and is perfectly normal with a counter in 1024th of a second!

extern "C" TMillisecs PrimeGetNow() // now in 1khz clock time
{
TMillisecs t1= (SNVS->HPRTCMR<<27) + (SNVS->HPRTCLR>>5); // read the clock value divided by 32...
while (true) // need to do 2 consecutive readings to make sure that the clock did not change between the reading of the 2 registers...
{
TMillisecs t2= (SNVS->HPRTCMR<<27)+(SNVS->HPRTCLR>>5);
if (t1==t2) return t1; // if the 2 readings are identical, then we are ok
t1= t2; // else do another reading!
}
}

Cyrille

Although I work for the HP calculator group, the views and opinions I post here are my own. I do not speak for HP.
Find all posts by this user
Quote this message in a reply
12-04-2019, 05:47 PM
Post: #5
RE: A small contribution and a big question (timer/keyboard)
Cyrille,

Thanks for the explanation. Now it all makes sense.

The error value depends on what you reference it to: the HP Prime readout, or the stopwatch readout. In the latter case, after six hours of continuous running, the error stabilizes on 2.4012... %.

If I may suggest, in the next f/w revision for HP prime you should add a small note that for the h/w rev. D (G2) the real time interval returned by TICKS() is not exactly 1ms, but there is 2.4% error. The TICKS() help note might be a good place for it. It's only one sentence, but it may save a lot of time of somebody who, like me, runs into this problem. (That said, I agree that it's not a typical problem, and there may be nobody else needing this info.)

Thanks again for clearing it up for me.

Darius
Find all posts by this user
Quote this message in a reply
Post Reply 




User(s) browsing this thread: 1 Guest(s)