Msx Assembly 101: 05 Printh
This is another post in the series about Z80 assembly (on MSX).
Previous posts:
In this post I will describe a creation of routine printh
which will
print hexadecimal representation of a byte in the accumulator register a
.
Setup
I am going to make the code which can be compiled both as a cartridge and
as a CP/M com file. The setup for this is described in the post
Using YAZE and
expaned in post Stack.
Moreover, I am going to write the code in two parts: printh
implementation
itself and some code to “test” it by running.
We will also need a routine for outputting the character with the code in the
accumulator register a
. Analogously with PutChar
macro we had before, we
will call it PutAcc
. Let us place into the new file routines.asm
we are
going to reuse later:
PutAcc: MACRO
IFDEF CARTRIDGE
call CHPUT
ENDIF
IFDEF CPM
ld e,a
CALLDOS 02h
ENDIF
ENDM
This macro will work differently in a cartridge context (calling BIOS function)
and in a CP/M context (calling DOS function). I suppose that the identifiers
CHPUT
and CALLDOS
are known, meaning that you have called the assembler
directive BIOS
or MSXDOS
before. To achieve that we are going to include
prelude.asm
file (which we are going to reuse later as well), and it will
use format.asm
, created by Makefile
, described in the previous posts.
.INCLUDE "format.asm"
IFDEF CARTRIDGE
.PAGE 1
.ROM
.BIOS
ENDIF
IFDEF CPM
.MSXDOS
ENDIF
Start: MACRO
IFDEF CARTRIDGE
ld sp, 0f380h
ENDIF
IFDEF CPM
ld [SavedStack], sp
ld sp, Stack
ENDIF
ENDM
Stop: MACRO
IFDEF CARTRIDGE
@@end:
jr @@end
ENDIF
IFDEF CPM
ld sp, [SavedStack]
ret
SavedStack:
ds 2
ds 31
Stack:
ds 1
ENDIF
ENDM
What it actually does was also described in the YAZE post: and the previous post:
We are also going to write a printh-driver.asm
which will call our (to
be created) printh
subroutine:
INCLUDE "prelude.asm"
INCLUDE "routines.asm"
ld de,@@data
ld b, 4
@@testcase:
ld a, [de]
inc de
call printh
djnz @@testcase
Stop
@@data:
db 023h, 0a4h, 05bh, 0cdh
INCLUDE "printh.asm"
It will not compile now because printh.asm
does not exist yet. We include
routines.asm
because it will be needed in printh.asm
.
Otherwise it loops (djnz
) four times through the given data and aims at
printing 23A45BCD
.
Printh: straightforward approach
Getting nibbles
Notice that one byte represents two hexadecimal digits: one encoded in upper
four bits (high nibble) and one encoded in lower four bits (low nibble). We can
get the low nibble of the accumulator register a
just by masking the
corresponding bits:
and a,1111b
To get the high nibble we first shift it to become the low nibble, and then mask it:
rra
rra
rra
rra
and a,1111b
Another approach will be to mask first (and a, 11110000b
) first and shift
afterwards.
Here rra
instruction is a faster version of rr a
(4 cycles instead of 8)
and rotates the carry flag and the accumulator bits right (in other words,
it rotates 9 bits, but it does not matter here). It was also possible to use
rrca
(which is better than rrc a
) to rotate the accumulator bits only (the
lowest bit goes to carry flag).
Printing nibbles
When we got a nibble, it has value either from 0
to 9
or from 0xa
to
0xf
. In the former case the corresponding ASCII codes of the characters from
0
to 9
are be from 0x30
to 0x39
: the nibble value plus 0x30
. In the
latter case the corresponding ASCII codes of the characters from A
to F
are
from 0x41
to 0x46
: the nibble value plus 0x37
. The most straightforward
approach to convert the nibble to the ASCII code of the corresponding character
is to examine the nibble value, and add the different constants depending on
it. Then the following subroutine outputs it using PutAcc
we have defined
above.
@@put_nibble:
cp 10
jr nc,@@hex
add 030h
@@out:
PutAcc
ret
@@hex:
add 037h
jr @@out
Putting it all together
Now we can print nibbles, and we know how to get those. Finally, it is possible
to write printh.asm
:
; print hexadecimal representation of byte in the accumulator
printh:
push af
rra
rra
rra
rra
call @@put_nibble
pop af
call @@put_nibble
ret
; put out hex digit corresponding to the accumulator
@@put_nibble:
and a,1111b
cp 10
jr nc,@@hex
add 030h
@@out:
PutAcc
ret
@@hex:
add 037h
jr @@out
We are going to destroy the accumulator by finding the high nibble, so we use
push
and then pop
to store it on the stack and then restore. The subroutine
@@put_nibble
prints the low nibble of the accumulator register.
Now you can compile the printh-driver.asm
. Warning: if you generate CP/M file
printh-driver.com
and then copy it to CP/M (for example, using YAZE’s r
command) the name of the file will be truncated to 8 characters, and the file
in CP/M will be called PRINTH-D.COM
.

“Clever” way to get ASCII code of a hexadecimal digit
There is a clever – albeit not very readable – way to get ASCII code of a hexadecimal digit. It is shown here because it allows to introduce non-trivial assembly instruction. This “clever” way does not require any conditional jumps.
BCD numbers and daa instruction
One can use on Z80 so called packed BCD
numbers. As 4 bits can
encode 16 different states, we can use the high nibble to record the first
digit of two-digit decimal number, and low nibble to record the second digit.
For example, the (decimal) number 42
can be encoded in the accumulator
register bits as 0100 0010
, and normally it means 0x42
. If we do that, we
will have problem with addition. Imagine that we want to add (decimal) 9
to
the accumulator. However, the processor works with bits, meaning hexadecimal
numbers, and we will get in the accumulator register bits 0100 1011
(0x4B
),
and the answer is (decimal) 51
, which in BCD format should be represented by
bits 0101 0001
(0x51
).
Here the instruction daa
comes to rescue. The way it exactly works is quite
complicated, and depends on the state of flags C
(carry) and H
(half-carry:
carry from the low nibble to the high), and you can find it described in the
official Zilog documentation or,
for example, here.
However, for now it is enough to know that if called after addition it will do
The Right Thing, and convert the content of accumulator register to BCD format;
if the resulting decimal will have more than two digits (and, using packed BCD,
one can encode only two decimal digits in one byte), the flag C
will be set.
Now let us look at the codes of the symbols, corresponding to the different
digits. If the digit is decimal (0
-9
), the code of the corresponding to it
character can be obtained by adding 0x30
to the digit, and if the digit is
purely hexadecimal (A
-F
, in other words 0xa
to 0xf
) the code of the
corresponding character can be obtained by adding 0x37
to it. The following
code does exactly this:
add 90h
daa
adc 40h
daa
I am not going to discuss in details how it works, but I am going to give a
couple of examples. You should know that adc
instruction adds to the
accumulator the given value and carry flag.
Example 1: decimal digit
Imagine that the accumulator register contains some value, corresponding to the
decimal digit, say 0x5
. After the first addition, it becomes 0x95
. Then
daa
instruction does not change the value in the accumulator, as it already
represents decimal 95
, but clears carry flag, as it represents two-digit
number. Now we add with adc
the value 0x40
(remember that the carry flag
is cleared) and the value in the accumulator becomes 0xD5
. It corresponds to
the decimal number 135
, so the final daa
converts it to 0x35
(and sets up
carry flag, but it does not matter here), and 0x35
is the ASCII code of
the character 5
, as it was required.
Example 2: purely hexadecimal digit
Now let us see what happens if the accumulator contains a value, corresponding
to a purely hexadecimal digit (say, 0xc
). After add
the accumulator
contains 0x9c
. How daa
will work? The low nibble 0xc
corresponds to the
decimal 12
, so it will be changed to 2
and half-carry flag will be set.
Then half-carry is added to the high nibble (9
) which becomes 0xa
, so it
corresponds to the decimal 10
, and is replaced by 0
and the carry flag
is set. The next operation (adc
) adds 0x40
plus carry flag (which is set),
so the accumulator becomes 0x43
, and the final daa
does not affect it. The
ASCII code for C
character is exactly 0x43
.
Trying the “clever” way
Now let us create printh2.com
:
printh:
push af
rra
rra
rra
rra
call @@put_nibble
pop aF
call @@put_nibble
ret
@@put_nibble:
and a,1111b
add 90h
daa
adc 40h
daa
PutAcc
ret
This is exactly like printh.com
but uses “clever” way to find the code of
the character, representing one nibble. You can try it by modifying
printh-driver.asm
to include printh2.asm
instead of printh.asm
.