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
|