Ti8x Z80 ASM Project Euler 00b
August 4, 2019
Printing Hexadecimal and Decimal Numbers
how bcalls work
Texas Instruments provided a lot of useful subroutines, dubbed
bcalls, that perform some repetitive or complicated task such as interfacing with the LCD screen. These system routines are stored in ROM, so they can only by accessed by a “callback” of sorts and an interrupt from user memory. If we unpack the bcall() macro in the standard
ti83plus.inc include file, we can see that it simply interrupts and places the address of the system routine in code memory right below the interrupting line. The interrupt vector reads off the last two bytes in the stack, which together form the 16-bit address of the line after the interrupt–where the system routine’s address is located. Then it simply dispatches to that routine. So bcalls are pretty powerful, because they mean I don’t have to write as much code and it’s a lot easier to interact with the hardware on the Ti8x because most of those functions are available in some form of bcall.
#define bcall(xxxx) rst 28h \ .dw xxxx
printing using PutS
One such bcall is
_PutS, which prints the ASCII-encoded zero-terminated string stored at the location pointed to by the HL register on the Home Screen of the LCD. Here is an example of its usage:
main: bcall(_ClrLCDFull) ld hl, 0 ld (PenCol), hl ; set cursor to (0,0) ld (CurRow), hl ; the top left corner of the LCD ld hl, yolo ; load address of string into hl bcall(_PutS) ; print the string ret yolo: .db "#YOLO", 0
That results in the following output:
There a million places where printing something would be useful, and for the greater purpose of solving and debugging the Project Euler problems, my main use for
_PutS will be for printing numbers. But everything in memory is stored as a bunch of bits, which only make sense when you apply some context or format to them. So the problem I need to solve now is a matter of juggling encodings.
When I’m doing calculations, the bits in memory represent numbers, specifically binary numbers stored in 8-bit segments. But the
_PutS subroutine assumes that the address it’s reading from is ASCII-encoded. If I ignore this discrepancy and apply
_PutS to a number stored in 8-bit binary bytes (assuming none of the bytes are zero, which would cause the printing to stop prematurely), I get a jumbled mess, that is nonetheless perfectly explicable:
Clearly, it will be necessary to format the string buffer passed to
_PutS. But translating the binary directly to ASCII-encoding would be very wasteful because each byte turns into eight ASCII characters (2^8 = 256) instead of one. It’s also hard for humans to interpret binary directly at a glance, so a standard alternative is hexadecimal. With hexadecimal, each 8-bit byte would only expand into two ASCII characters (16^2 = 256), so the number won’t overload the LCD when I try to print large numbers. Furthermore, it’s much easier to get a sense of the scale of a hexadecimal number than a binary number.
Luckily, it’s simple to convert 8-bit binary bytes to hexadecimal by simply splitting the high and low nibbles apart. Each of these nibbles is 4-bits, which is exactly enough to represent a base-16 number (2^4 = 16). Since the distinction between a hexadecimal representation in memory and binary one is just a matter of perspective, we don’t actually need to operate on the bits, but this will help us write the code that makes the hexadecimal printable.
The possible values of a single hex nibble are (0-9,A-F), which should become (30-39,65-70). Therefore, to print one hex nibble, we can add 30 to it to put it into the lower range. If it’s above 39, then it must have been A-F, in which case, we can add 25 to skip the gap between the numeral and alphabetic characters. The following code prints a single hex nibble:
Keep in mind, however, that there are two nibbles per location in memory, so these should be split up and translated separately, then placed consecutively in memory. The following code prints a single hex byte:
Since each byte turns into two hex nibbles, which need to be printed separately, then a hex number of length n will require a 2n-length buffer for the ASCII string to be printed. The following code prints a length-n hex number whose length is stored in the memory location directly before the number. It first