A small contribution and a big question (timer/keyboard)
11-29-2019, 03:42 AM
Post: #1
 DrDarius Junior Member Posts: 41 Joined: Oct 2019
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
11-29-2019, 06:24 AM
Post: #2
 cyrille de brébisson Senior Member Posts: 1,009 Joined: Dec 2013
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.
11-29-2019, 08:40 PM
Post: #3
 DrDarius Junior Member Posts: 41 Joined: Oct 2019
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
12-02-2019, 06:37 AM
Post: #4
 cyrille de brébisson Senior Member Posts: 1,009 Joined: Dec 2013
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.
12-04-2019, 05:47 PM
Post: #5
 DrDarius Junior Member Posts: 41 Joined: Oct 2019
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
 « Next Oldest | Next Newest »

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