A Code Challenge

Started by Tom Becker December 12, 2005
A common task is timestamping and logging an event. I propose a
simple challenge: what is _the_ elegant code that produces an 8-byte
ASCII string in the form "HHMMSShh" (hh is _hundredths_ of a second)
from a current BX-24 RTC value?

Any takers? Tom



--- In basicx@basi..., "Tom Becker" <gtbecker@r...> wrote:

Here is a rendition that generates the characters into a buffer.

'
'' getClock
'
' Write 8 characters representing the current time beginning at
' the given address in the format HHMMSShh.
'
Sub getClock(ByVal addr as Integer)
Dim hr as Byte
Dim min as Byte
Dim sec as Single
Dim v as Byte

Call GetTime(hr, min, sec)

' hours
Call RamPoke(hr Mod 100 \ 10 Or &H30, addr + 0)
Call RamPoke(hr Mod 10 Or &H30, addr + 1)

' minutes
Call RamPoke(min Mod 100 \ 10 Or &H30, addr + 2)
Call RamPoke(min Mod 10 Or &H30, addr + 3)

' integral seconds
v = FixB(sec)
Call RamPoke(v Mod 100 \ 10 Or &H30, addr + 4)
Call RamPoke(v Mod 10 Or &H30, addr + 5)

' hundredths of a second, rounded to the nearest hundredth
v = FixB((sec - CSng(v)) * 100.0 + 0.5)
Call RamPoke(v Mod 100 \ 10 Or &H30, addr + 6)
Call RamPoke(v Mod 10 Or &H30, addr + 7)
End Sub

To print out the result, you can call it thusly:

Dim tstr as String * 8
Public Sub Main()
Do
Call getClock(MemAddress(tstr) + 2)
Debug.Print tstr
Call Delay(1.0)
Loop
End Sub


How's this? function strTimestamp() as string
dim lWork as long, strH as string*3, strM as string*3, _
strS as string*3, strF as string*3
lWork = Register.RTCtick 'read clock
strF = cStr(lWork Mod 512 * 100 \ 512 + 100) 'hundredths
lWork = lWork \ 512 'discard fraction
strS = cStr(lWork Mod 60 + 100) 'seconds
strM = cStr((lWork \ 60) Mod 60 + 100) 'minutes
strH = cStr(lWork \ 3600 + 100) 'hours
strTimestamp = mid(strH, 2, 2) & mid(strM, 2, 2) _
& mid(strS, 2, 2) & mid(strF, 2, 2)
end function
Tom



--- In basicx@basi..., "Tom Becker" <gtbecker@r...> wrote:
I made modifications to the three submitted solutions to make them
have the same invocation signature and used Mike's bxDism to
determine the code and stack size used by each. I set up Main() as
shown below and uncommented just one invocation, compiled, ran
bxDism and recorded the data.

Sub Main()
Debug.Print read_clock() ' Niel's solution
' Debug.Print readClock() ' Don's solution
' Debug.Print strTimeStamp() ' Tom's solution
End Sub

Here are the result:

Niel:
Total code size: 6208 bytes
Actual function size: 532 bytes
Stack space used: 170 bytes

Don:
Total code size: 1587 bytes
Actual function size: 597 bytes
Stack space used: 72 bytes

Tom:
Total code size: 1888
Actual function size: 326 bytes
Stack space used: 73 bytes

The 'Actual function size' data is the number of bytes of the
conversion routine itself, not counting system routines that may be
needed elsewhere in a larger program. If those system routines are
going needed anyway, the "cost" of using them is zero. In all three
cases, the size of Main() was 257 bytes.

Using the information gained from the three tests, I combined the
ideas to produce a new routine:

Function readClock2() as String
Dim tstr as String * 8
Dim lwork as Long
Dim v as Byte
Dim addr as Integer

addr = MemAddress(tstr)
lWork = Register.RTCtick 'read clock

' calculate the hundredths
v = CByte((lWork * 100 Mod 51200 + 256) \ 512)
Call RamPoke(v \ 10 Or &H30, addr + 6)
Call RamPoke(v Mod 10 Or &H30, addr + 7)

lWork = lWork \ 512 'discard fraction

' produce the seconds digits
v = CByte(lWork Mod 60)
Call RamPoke(v \ 10 Or &H30, addr + 4)
Call RamPoke(v Mod 10 Or &H30, addr + 5)

' generate the minutes digits
v = CByte(lWork \ 60 Mod 60)
Call RamPoke(v \ 10 Or &H30, addr + 2)
Call RamPoke(v Mod 10 Or &H30, addr + 3)

' generate the hours digits
v = CByte(lWork \ 3600 Mod 24)
Call RamPoke(v \ 10 Or &H30, addr + 0)
Call RamPoke(v Mod 10 Or &H30, addr + 1)
readClock2 = tstr
End Function

New:
Total code size: 1332
Actual function size: 659 bytes
Stack space used: 25 bytes

The code size could be reduced a little by introducing a helper
function but that would also increase the stack usage (probably by
13 bytes or so).

The same code compiled for the ZX-24 is 257 bytes total including
Main().

Don
http://www.zbasic.net




> Total code size: 1332
> The same code compiled for the ZX-24 is 257 bytes total...

!? Total? ZX code is six times more dense?

I like your notion of poking to avoid concatentation but I expected a
greater stack saving. I wonder if mid()= will do the same. Tom



--- In basicx@basi..., Tom Becker <gtbecker@r...> wrote:
>Total?

Yes, that was the total size. The code is smaller for two reasons.
Firstly, and the major effect in small programs, some of the often
used system library routines are actually implemented in user-space
code in BasicX. Mike calls these "externally implemented system
library functions" in his article series. In ZBasic, all system
library routines are implemented in the firmware except for one,
CStrHex().

The second factor is that the ZX pcode instruction set is richer,
allowing shorter code sequences to implement the same effect. A third
factor is that may affect code size is the optimizer. Depending on
how the code is written, the compiler may be able to simplify it by
combining values that known at compile time, substituting a shorter
equivalent code sequence, eliminating code that cannot possible by
reached, and other well-known optimization strategies.

> ZX code is six times more dense?

No. In small programs that use several externally implemented system
library routines the difference can be quite large as in this case.
If you look at the size of the readClock2() routine itself it is 659
bytes for BasicX and 209 bytes for ZBasic. I think that this is an
unusual case, perhaps somewhat of an anomaly. Normally, I would
expect ZBasic code size to be 60% to 90% of the BasicX size. In some
cases, it is possible that ZBasic code will be about the same size as
the BasicX code.

Don
http://www.zbasic.net


--- In basicx@basi..., "Don Kinzer" <dkinzer@e...> wrote:
> If you look at the size of the readClock2() routine itself it is 659
> bytes for BasicX and 209 bytes for ZBasic.

I erred. When I read the output from Mike's bxDism I took the 293
code size for BasicX to be hexadecimal but it is actually decimal.
The statement should have read:

If you look at the size of the readClock2() routine itself it is 293
bytes for BasicX and 209 bytes for ZBasic.

Don
http://www.zbasic.net



Fun exercise Tom. Whats next?
Tom Becker wrote:

> A common task is timestamping and logging an event. I propose a
> simple challenge: what is _the_ elegant code that produces an 8-byte
> ASCII string in the form "HHMMSShh" (hh is _hundredths_ of a second)
> from a current BX-24 RTC value?
>
> Any takers? > Tom > >. >
>




> ... Fun exercise, Tom. Whats next?

OK, using only code, generate accurate 60Hz (or 50Hz) three-phase
(120-degree shift) squarewaves on BX-24 pins 5, 6 and 7. Tom



--- In basicx@basi..., "Tom Becker" <gtbecker@r...> wrote:
> using only code, generate accurate 60Hz (or 50Hz) three-phase
> (120-degree shift) squarewaves on BX-24 pins 5, 6 and 7.

Measured frequency is accurate to 0.2%.

Sub Main()
Dim idx As Byte
Dim bits(1 to 6) As Byte

' prepare the output bit pattern
bits(1) = &H80
bits(2) = &Hc0
bits(3) = &He0
bits(4) = &H60
bits(5) = &H20
bits(6) = &H00
idx = 1

Register.DDRC = &Hff

' prepare the timer for 360Hz operation
Register.OCR1AH = 0
Register.OCR1AL = 19
Register.TCNT1H = 0
Register.TCNT1L = 0
Register.TIFR = &H10
Register.TCCR1A = &H00
Register.TCCR1B = &H0d

Do
' await the next timer overflow
Do Until CBool(Register.TIFR And &H10)
Loop

' output the next bit pattern
Register.PortC = bits(idx)
Register.TIFR = &H10

' prepare for the next iteration
idx = idx + 1
If (idx > 6) Then
idx = 1
End If
Loop
End Sub