z80 Assembly: Binary-Coded Decimal

One method for displaying numbers larger than 16 bits is to convert it to Binary Coded Decimal (BCD) first, and display the result. BCD works by using four bits to store each decimal (base 10) digit of a number. The following code can convert a number to BCD, and display it. It’s currently written to convert a 24 bit number to a 10 digit BCD number, but can be modified to support anything really. It is memory ineffecient, because it uses one byte for each digit rather than storing two digits per byte. This is useful though because it makes the display routine simpler.

Conversion to BCD

This routine converts a little endian value to a little endian BCD value. It’s written for brass, a z80 assembler created by Ben Ryves.

;Converts a 24 bit unsigned int pointed to by HL to BCD
;Convert to BCD with double dabble method, see http://en.wikipedia.org/wiki/Double_dabble
;Handles up to a 10 digit number
;Input: HL - pointer to number
;Output: bcdScratch - stores BCD number.
NUM_DIGITS = 10
NUM_SRC_BYTES = 3
.var NUM_DIGITS, bcdScratch
.var NUM_SRC_BYTES, bcdSource
ConvertToBCD:
    ld de,bcdSource
    ld bc,NUM_SRC_BYTES
    ldir
    ld ix,bcdSource

    xor a
    ld hl,bcdScratch
    ld b,NUM_DIGITS
_zeroScratch:
    ld (hl),a
    inc hl
    djnz _zeroScratch

    ld b,NUM_SRC_BYTES * 8
    _bcdConvLp:
        ;Do increment
        ld c,NUM_DIGITS
        ld hl,bcdScratch
        ;Iterate through each BCD digit.
        ;If digit > 4, add 3
        _bcdIncLp:
            ld a,(hl)
            cp 5
            jr c,{@}
            add a,3
            @:
            ld (hl),a
            inc hl
            dec c
            jr nz,_bcdIncLp

        ;Shift SRC bits
        sla (ix)
        .for _off, 1, NUM_SRC_BYTES - 1
            rl (ix + _off)
        .loop

        ld c,NUM_DIGITS
        ld hl,bcdScratch
        _bcdShiftLp:
            ld a,(hl)
            rla
            bit 4,a
            jr z,{@}
            and %1111 ;Mask out high bits, since we only want the lower 4 bits for the digit
            scf       ;Set carry if bit 4 set
            @:
            ld (hl),a
            inc hl
            dec c
            jr nz,_bcdShiftLp
        djnz _bcdConvLp
    ret

Displaying BCD

Displaying BCD is quite simple, since each digit is stored within its own byte. You can simply add the char code for ‘0’ to the value to get the text char you want. This routine displays an unsigned BCD value, without leading zeroes

;Displays the BCD value at HL
;1 byte per digit
NUM_DIGITS = 10
DispBCD:
    ld de,NUM_DIGITS - 1
    add hl,de ;Go to end

    ;Skip leading zeroes, except if the value IS zero
    ld b,NUM_DIGITS - 1
_skipLeadingZeroes:
    ld a,(hl)
    or a
    jr nz,{@}
    dec hl
    djnz _skipLeadingZeroes
@:
    inc b ;B = num digits to display
_dispBCDDigits:
    ld a,(hl)
    add a,'0'
    push hl
    push bc
    
    ;Replace this call with anything that takes a char code in register A and displays it.
    ;For example, on the z80-based TI calculators, one might use b_call(_VPutMap)
    call YourCharacterDisplayRoutine
    
    pop bc
    pop hl
    dec hl
    djnz _dispBCDDigits
    ret