32-bit DWORD expressed in ASCII, uALFAT, DWORD to ascii

John Chris

Senior Member
Hi there,

I'm working with the uALFAT. I have looked through BCJKiwi's work on the uALFAT and am presently making sure I understand the subroutines used to deal with the HEX, DWORD ascii string output by the uALFAT that describes media capacity, and media free space.

I would like to eventually read a string like $00040000 (equivalent to $00 $04 $00 $00 == DWORD if I understand correctly)

It seems to me that some of the elements in the process described below are required to do this:

1) Start off with 8 ascii chars representing a hex DWORD e.g. 00040F00 (see uALFAT documentation)
2) divide into 4 groups of 2 ascii chars representing a hex byte e.g. 00, 04, 0F, 00
3) convert 2 ascii chars representing hex byte into numeric.
4) LET DWORD_pos = 0 ' Eprom pointer. Write $00 (call this mmsb) to DWORD_pos + 0. Write $04 (msb) to DWORD_pos + 1, $0F (lsb) to DWORD_pos + 2 and $00 (llsb) to DWORD_pos + 3
5) pass DWORD_pos to a subroutine that takes 4 bytes and displays an ascii string representing a 32-bit number.

Really, one would want to further process this DWORD into a) rounded number and b) a number that has units of 'megabytes of memory' as opposed to 'bytes of memory'. Here, 32-bit precision is not required, A WORD variable would be sufficient.

Perhaps the whole procedure would be simpler if one represented (or merely thought about) the DWORD as two WORD variables.

The last item in the list (5) is the one that I need help with.

Stepping away from all the complications I have presented in my description above, Can anyone propose a method to convert 4 bytes or 2 words into an ascii string representing a 32 bit decimal number?

Any thoughts / suggestions are greatly appreciated.

Chris
 
Last edited by a moderator:

John Chris

Senior Member
As a follow-up, I guess that If I understood how the DIG function works, it would be a start. Could the DIG function be used in such a way that it would work for a DWORD ?
 
Last edited by a moderator:

hippy

Technical Support
Staff member
Here's some code which will display a 32-bit number in hexadecimal. That number is stored in SFR (RAM) using four consecutive bytes starting with where 'dwordPtr' points to ...

Symbol dwordPtr = b0
Symbol byteVal = b1
Symbol nibbleVal = b2

MainProgram:
Pause 2000
dwordPtr = $50 : Gosub PutNumberToDWord
dwordPtr = $50 : Gosub PrintDWord
End

PutNumberToDWord:
Poke dwordPtr, $12 : dwordPtr = dwordPtr + 1
Poke dwordPtr, $34 : dwordPtr = dwordPtr + 1
Poke dwordPtr, $56 : dwordPtr = dwordPtr + 1
Poke dwordPtr, $78
Return

PrintDWord:
SerTxd( "$" )
Peek dwordPtr, byteVal : Gosub PrintByteVal : dwordPtr = dwordPtr + 1
Peek dwordPtr, byteVal : Gosub PrintByteVal : dwordPtr = dwordPtr + 1
Peek dwordPtr, byteVal : Gosub PrintByteVal : dwordPtr = dwordPtr + 1
Peek dwordPtr, byteVal : Gosub PrintByteVal
SerTxd( CR, LF )
Return

PrintByteVal:
nibbleVal = byteVal / $10 + "0"
If nibbleVal > "9" Then : nibbleVal = nibbleVal + 7 : End If
SerTxd( nibbleVal )
nibbleVal = byteVal & $0F + "0"
If nibbleVal > "9" Then : nibbleVal = nibbleVal + 7 : End If
SerTxd( nibbleVal )
Return
 

John Chris

Senior Member
Thank you very much for the reply. I will work through the code that you have posted. Thanks.

Chris
 
Last edited by a moderator:

John Chris

Senior Member
Thanks, this is a good routine to convert Hex numeric into an ascii representation of hex.

How does one convert this 32-bit hex numeric into an ascii representation of DECIMAL. It would seem to be a bit more complicated. One cannot treat the constituent bytes independently.

Again, I use the nomenclature: mmsb,msb,lsb,llsb

for 32-bit, the max number of digits required for an ascii representation of the largest possible value is $FF,$FF,$FF,$FF = 4294967295 => 10 digits, and therefore 10 bytes max required to store the ascii representation.

The value of the llsb can only directly affect (carry-over must be handled and is the INDIRECT effect on potentially all bytes) the bytes 2-0 of the ascii string ($FF = 255 -> three digits, bytes 2-0).
lsb: ($FF,$00 = 65280 -> 5 digits, bytes 4-0)
msb: ($FF,$00,$00 = 16711680 -> 8 digits, bytes 7-0)
mmsb: ($FF,$00,$00,$00 = 4278190080 -> 10 digits, bytes 9-0)

I would want to start with the byte value of the mmsb, get the digits associated this value alone (of which there would be 0 [$00] or 8-10 [$01 - $FF]), store these bytes sequentially. Move down to the next least significant byte and do the same. The 10 bytes storing the ascii representation of the decimal number would have to be incremented in a DO WHILE type loop to allow for carrying.

The number of bytes of memory available on a flash memory device is the the application here. Both the VNC1L and the uALFAT report the memory capacity of the connected media in a 32-bit ascii representation of hex. We, however, understand decimal - the decimal number of bytes available, or even better, the decimal number of megabytes.

This is exactly analogous to the DIG function supported by picaxe, except that here, 32-bit precision is required.

Is this beyond the Picaxe ? I'm a little stumped.

Someone who understands how the DIG function works would seem to be in a much better position than I to answer this question.

Of marginal relevance, here is the code by which I am able to keep track of the value of a memory file pointer for large (32-bit number of bytes) data files; the extent of my ability to work with 32-bit numbers.

Symbol LLSB_offset = 0
Symbol MMSB_offset = 3

IncrementCurrentPosPointer:
LET num_bytes = num_bytes_data
LET counter_b = LLSB_offset
DO
LET strtpos1 = current_position_pos + counter_b
READ strtpos1,temp1
IF counter_b = LLSB_offset THEN
LET temp2 = temp1 + num_bytes
ELSEIF counter_b > LLSB_offset THEN
LET temp2 = temp1 + 1 ' At every level but the first, increment by one - clearly num_bytes must be less than 255 bytes
ENDIF
WRITE strtpos1,temp2
IF temp2 < temp1 THEN
INC counter_b
ELSEIF temp2 >= temp1 THEN
LET counter_b = MMSB_offset ' Exit loop
ENDIF
LOOP WHILE counter_b < MMSB_offset
RETURN


Any comments, suggestions are appreciated.

Chris
 
Last edited by a moderator:

hippy

Technical Support
Staff member
Converting 32-bit to decimal is relatively easy but perhaps not so when spread over two separate 16-bit words.

Considering those two words as MSW and LSW, most significant word and least significant word, then the least significant decimal digit will be "LSW // 10", then it's a question of "LSW = LSW / 10" and "MSW = MSW / 10" and putting what's lost from MSW into LSB, repeating until zero. What's lost from MSW is "MSW // 10" but how to put that into LSW is what's got me stumped. The digits are in reversed order but that can be overcome by filling scratchpad or SFR (RAM) and then displaying in reverse order.

There should be some means of doing this so most significant digit is determined and output first, not sure how though. It could be done by repeated subtraction but there's got to be a quicker and better way. I suspect some time spent with Google will be in order.
 

hippy

Technical Support
Staff member
Courtesy of Microchip AN526 though I don't have a clue about how it works ...

$12345678 = 0305419896

That's written for 20X2 but can be converted to any other PICAXE with enough program code space.
 

Attachments

Last edited:

AllyCat

Senior Member
Hi,

An old thread, but a link from a recent post drew my attention to it.

Courtesy of Microchip AN526 though I don't have a clue about how it works ...
I think hippy was being modest because he appears to have made very significant changes to produce working code within an hour or two. :)

However, the code does have quite a large footprint, so pehaps I can offer some "simplification" and adaptation for M2 devices and other word lengths:

First, I reversed the order of the bytes so that they are in the normal PIC(axe) "low byte first" sequence. This allows the two "left-shift" loops to be combined into one. Also, the "counter" variable can be removed by using bptr to terminate the loop count. For left-shifting, I use @bptr + @bptr which is marginally faster than @bptr * 2 .

Because the bytes are accessed "indirectly" (@bptr), we want to left-shift each byte "in one hit", so two flags are needed: First store the "carry out", shift the byte, then add the "carry in" from the previous byte and finally move the "new" (out) flag to the "last" (in) position. This can be done in the "W" (working) register, removing the need for two additional flags (bytes in the original code).

I had wanted to automatically calculate within PE5, the number of bytes required for any number of input bits, but the limitation of one operator per line (and no re-use of constants) made the code unwieldy. So I have just listed the maximum bit values for typical numbers of bytes in a #REM (placed at the end for neatness). I also removed a few subroutines because I have an aversion to subroutines that take longer to execute their CALL and RETURN than the code inside them. :)

So here is my modified version:
Code:
; Binary to BCD conversion for "any" number of bits.  AllyCat, March 2014.
; Derived from code from Microchip (AN526) and hippy.

#Picaxe 20M2		; And most others
#No_Data		; EEPROM data not used

Symbol BITS 	 = 32				; Number of input bits 
Symbol BYTES	 = 4				; Number of binary bytes
Symbol BCDBYTES = 5                          ; Number of output (BCD) bytes
Symbol BASE    = 32			; Address of first data byte in RAM
Symbol BIN      = BASE			; Address of first binary byte
Symbol BCD      = BIN + BYTES	; Address of first BCD byte
Symbol BCDT	 = BCD + BCDBYTES
Symbol BCDEND	 = BCDT - 1		; Last BCD Byte address

Symbol W        = b0				; Working Register (bit-addressable)
Symbol BitCount = b1				; Loop counter

; Test Data: $12345678 = 0305419896

SetupInputData:
	bptr = BIN					; Point to Least Significant Byte
	@bptrInc = $78				; LS Byte
	@bptrInc = $56				; $34
	@bptrInc = $34				; $56
	@bptrInc = $12				; MS Byte

do
	@bptrinc = 0				; Clear the BCD data field
loop until bptr > BCDEND	

bitCount = BITS
Do
	w = 0							; Clear carry flags
	for bptr = BIN to BCDEND			; Shift Left (LS byte first)
		w = @bptr and $80 + bit7       	; Carry-Out to bit7 + move last carry from bit7 to bit0 
 	 	@bptr = @bptr + @bptr + bit0	; Shift left and add carry-in
	next bptr
  	dec bitCount
  	if bitCount = 0 then exit

	For bptr = BCD to BCDEND			; Now replace any invalid BCD nybbles
	w = @bptr + $03
	if bit3 = 0 then GOTO noadd1
		@bptr = w
noadd1:
	w = @bptr + $30
	if bit7 = 0 then GOTO noadd2
		@bptr = w
noadd2:
	Next bptr
Loop

ShowBcdDigits:			; Report the Result
	For bptr = BCDEND to BCD step -1
		W = @bptr / $10 : SerTxd( #W )
 		W = @bptr & $0F : SerTxd( #W )
	Next
	SerTxd ( CR,LF )
End

#rem
MAX		REQUIRED
INBITS		BYTES	BCDBYTES
13		2		2			
16		2		3
19		3		3
24		3		4
26		4		4
32		4		5
33		5		5
39		5		6
40		5		7
46		6		7
48		6		8
53		7		8
56		7		9
59		8		9
64		8		10	
#endrem
So how does it work? I don't claim to understand every detail, but the basic principles are quite simple. The normal method of converting from binary to BCD is that when any nybble exceeds a value of 9, a value of 6 is added to create a carry into the next nybble. One of the "tricks" used in the algorithm is that this process is performed before a left-shift (which is equivalent to multiplying by 2), so it adds only 3 to the nybble. Doing this before the left-shift simplifies the code because it keeps the carry always within the same byte.

The code "assumes" that a carry will occur (reading the current byte and adding 3 into the working register), but it only writes the modified byte back into RAM if the carry actually does occur. This is done by a despised (by some, but not me) GOTO, which skips the single line of code that (otherwise) updates the byte. In this case the GOTO is efficient and used in homage to the way that PIC machine (and Assembly) code actually handles ALL conditional expressions. :cool:

The above is done once for each nybble of each byte (the second time adding a value of $30); I did consider combining both into one stage (using a 4 byte LOOKUP), but (as I often find with PICaxe), the "brute force" structure is actually more efficient.

The other part of the algorithm repeatedly shifts the data bits leftwards, i.e. multiplying by 2 each time. Although the data field is partitioned as "Binary" and "BCD" groups of bytes, it is actually processed as a single (very large) numerical value. It starts with the Binary value located at the "low" end (and all higher bytes = 0) and repeatedly multiplies by 2 until the data has been completely shifted out of the Binary data field (which fills with zeros). So the original data value ends up multiplied by 2^32 (for 4 input bytes). However, this is easily "undone" (i.e. divided by 2^32) by simply ignoring the lowest 4 bytes of the data field.

I was a little disappointed that the algorithm needs to use as many as 9 bytes of RAM (plus two byte variables and the bptr), yet still doesn't retain the original binary value. However, most recent PICaxes have a reasonable amount of (indirect) RAM so the "footprint" of the code is acceptably small.

When testing or demonstrating the operation of the code in the simulator, a "high byte first" format can be easier to comprehend. Also the bptr can be set to a low value (overlaying the normal PICaxe variables) for easier visibility. The code can be easily modified for this (exchanging @BPTRDECs for @BPTRINCs, etc.) but I won't bother to show it here.

Cheers, Alan.
 
Last edited:

BillBWann

Member
$12345678 = 0305419896
Are you sure? I get $12345678=0304371320.

Am I wrong? Isn't $12345678=$12*2^24 + $34*2^16 + $56*2^8 +$78 = 18*2^24 + 36*2^16 + 86*2^8 +120?

I didn't really try to understand the methodologies described by Hippy & AllyCat but I decided to write my own program as an accademic exercise using the long division principles we learnt at primary school. I think that's what Hippy was originally suggesting.

My program stores the original double hex word in byte variables b24 to b27 and then pushes the remainders after dividing by 10 into the byte variables going up from 28.

Code:
#picaxe 08m2
#no_data

symbol LSW=w12
symbol MSW=w13

bptr=27		'Load hex double word $12345678 into MSW & LSW
@bptrdec=$12
@bptrdec=$24
@bptrdec=$56
@bptr=$78

bptr=28	'push remainders onto stack starting from 28

do
	b3=MSW//10
	b2=b25
	MSW=MSW/10
	
	b25=w1/10
	b3=w1//10
	b2=b24
	b24=w1/10
	
	@bptrinc=w1//10
loop until LSW=0 and MSW=0

do				'print out the decimal value
	sertxd(#@bptrdec)
loop until bptr<28
 

AllyCat

Senior Member
Hi Bill,

Isn't $12345678=$12*2^24 + $34*2^16 + $56*2^8 +$78 = 18*2^24 + 36*2^16 + 86*2^8 +120?
No, I think it's = 18*2^24 + 52*2^16 + 86*2^8 +120

My scientific calculator (with Hex - Dec, etc. conversion) is happy with hippy's (and my) result.

Cheers, Alan.
 

AllyCat

Senior Member
Hi,

Your code does seem to be an efficient way to convert a binary value to a "decimal" output string, but a few more comments and/or variable names in the code would have been nice. :)

However, I think the original aim might have been to convert binary to (packed) BCD, with the display part only for testing. Of course the individual digits can be easily "re-packed", or perhaps the code could divide by 100 and then "patch" each individual byte into BCD?

Just shows that there's more than one way to skin a Cat. ;)

Cheers, Alan.
 

hippy

Technical Support
Staff member
I think the original aim might have been to convert binary to (packed) BCD, with the display part only for testing.
Generally the only reason to convert a multi-bit value to BCD or decimal digits is to allow it to be displayed or sent via serial as a decimal representation. And likewise vice-versa for inputting data.

There's also this which may be useful, but I recall the last time I tried to use code from it I did not get the results I was expecting. Not sure if that's a bug or I forgot how to use it ...

http://www.picaxeforum.co.uk/showthread.php?18406-A-256-bit-Calculator
 

BillBWann

Member
Your code does seem to be an efficient way to convert a binary value to a "decimal" output string, but a few more comments and/or variable names in the code would have been nice. :)
Yes Alan, as I said, I hadn’t really looked closely at the previous discussion and had only tried to write my own code as a quick academic exercise and it’s not well documented. Mind you, I’m not sure how to explain it much better - I'm not good at explaining things.

Thanks for finding my error.
 

AllyCat

Senior Member
Hi,

Back in #6:

What's lost from MSW is "MSW // 10" but how to put that into LSW is what's got me stumped.
Which reminds me of the old joke about the Englishman, lost whilst driving his car in Ireland. Asking one of the locals, the conversation was:

Englishman: "Please can you tell me how to get to Kilkenny?"
Irishman: "Ah sir, if I was going to Kilkenny I wouldn't be starting from here" :)

The "problem" is that the remainder from the division (of MSW by 10) can be up to 4 bits, so we're faced with a 20-bit calculation for the LSW.

I think the method that Bill has used can be described as "maths to base 256" (does it have a proper name?). That's normally a rather strange thing to do (because it's potentially complicated), but the enormous overhead (time delay) of the PICaxe interpreter can overturn normal wisdom. A PICaxe takes hardly any longer to do a 16-bit division than move a "carry" bit between two bytes. :rolleyes:

So Bill's pragmatic solution to the "large" remainder value is to combine it with just a byte, not the whole LSW. Thus the remainder is put into the HIGH byte of a "Working" register (w1) and the "High byte of the Low Word" is copied into the LOW byte. It's here where giving the Bytes/Words "meaningful" variable names can get difficult. ;)

The division of the Working register by 10 must produce a one-byte result (because the High byte only contained a previous remainder) so that can be combined with the final (lowest) byte to calculate a final result and remainder.

So here's my annotated offering:
Code:
#picaxe 08M2			; And most others
#no_data

symbol LSW = w2		; Least Significant Word
symbol MSW = w3		; Most Significant Word
symbol W   = w1		; Working register (word)
symbol SOS = 28		; Start Of "Stack" (Most Significant Digit) for output result

symbol W.L	= b2		; Low Byte of Working register
symbol W.H	= b3		; High Byte of Working register
symbol LSW.L = b4		; Low Byte of LSW
symbol LSW.H = b5		; High Byte of LSW
symbol MSW.L = b6		; Low Byte of MSW
symbol MSW.H = b7		; High Byte of MSW
symbol MSWA  = 7		; Address of top byte of MSW

bptr = MSWA			 ; Load Hex double-word $12345678 into MSW & LSW
@bptrdec = $12			; Test data = $12345678
@bptrdec = $34
@bptrdec = $56
@bptr 	= $78

bptr = SOS				; Point to Start of Stack for the Remainders
do
	W.H = MSW // 10		; Remainder of MSW / 10 to HIGH byte of W
	W.L = LSW.H			; HIGH byte of LSW to LOW byte of W
	MSW = MSW / 10		; Divide MSW by 10 (MSW is now divided by 10)
	
	LSW.H = W / 10		; Divide W by 10 and put result in HIGH Byte of LSW 
	W.H = W // 10		; Put the Remainder into the HIGH byte of W 
	W.L = LSW.L			; Copy Low byte of LSW to Low byte of W 
	LSW.L = W / 10		; Divide LSW by 10 (LSW is now divided by 10)
	
	@bptrinc = W // 10	; Store Remainder from the division to the Stack
loop until LSW = 0 and MSW = 0

do				'print out the decimal value
	sertxd(#@bptrdec)
loop until bptr < SOS

sertxd(cr,lf,"0305419896 Should be the Result")
Cheers, Alan.
 
Last edited:

BillBWann

Member
Alan,

Thanks for going to the trouble of understanding and then succinctly describing what my program was trying to do. Yes, it’s primary school long division but instead of using decimal digits (with 10 values), it uses digits that have 256 possible values.

I hadn’t thought of you going to all this trouble, so in the mean time, as another academic exercise, I wrote a more generalised version of the program which would allows the input of an N byte long word and output it in any base (well maybe not any but up to 256 which is probably enough:)). It don't think it makes the program any more complicated but I do think that it shows more clearly how the process works and I’ve added more comments & symbols to the program– possibly to the point of excess.

So for example if N=8 (64 bit number) and your output base is 10 you could show that
$FEDCBA9876543210=018364758544493064720

or if you select base 3, then $FEDCBA9876543210=011112100120110012201202221111221022102200

As a check that the process works, you could select an output base of 16, then the poor little picaxe 08M2 after a lot of effort will print out a number pretty similar to the one you put in.

You can also select base 100 as you suggested Alan and that works too but allowing for that does complicate the print routine at the end of the program. Note that if your output base is more than 36, then the printout will look a bit strange (assuming that base 36 numbers are familiar to all of us:) ) as you’ll have then used up the entire alphabet and gone onto higher ascii characters.

Thanks again Alan.

Code:
#picaxe 08m2
#no_data

symbol PDigit=b0	'Pointer to the (base 256) digit being processed
symbol temp=b1		'temporary byte
symbol TempWlb=b2	'Low byte of temporary word TempW
symbol Rmder=b3	'Remainder after dividing digit by the output base (Obase) and high byte of TempW
symbol TempW=w1	'Temporary word
symbol MSBpointer=b4	'Pointer to the most significant non zero digit of the long word
symbol LSBpointer=b5	'Pointer to the least significant digit of the long word
symbol OBase=b6		'The base used to output the N digit long word in

Symbol N=8	'Number of digits making up the long word

OBase=10

LSBpointer=27-N+1	'Initial long word starts at b27 and continues down N bytes

bptr=27		'Load long word $FEDCBA9876543210 into memory going down from b27 - MSB first
@bptrdec=$FE
@bptrdec=$DC
@bptrdec=$BA
@bptrdec=$98
@bptrdec=$76
@bptrdec=$54
@bptrdec=$32
@bptr=$10

bptr=28		'set bptr to bottom of the remainder stack

MSBpointer=27	'point to the start of the long word

do
	Rmder=0	'set initial remainder to zero
	for PDigit=MSBpointer to LSBpointer step-1	'step through each digit of the long word
		peek PDigit,TempWlb	'get the digit
		temp=TempW/OBase		'calculate the quotient
		poke PDigit,temp		'write quotient back into long word 
		Rmder=TempW//OBase	'carry the remainder onto the next digit
	next PDigit
	@bptrinc=Rmder				'push remainder of long word/OBase onto stack
	peek MSBpointer,temp		'get the MSB of the remaining long word
	if temp=0 then: MSBpointer=MSBpointer-1:endif	'move MSB pointer down if current MSB is zero
loop until MSBpointer<LSBpointer	'Finish when the original long word is reduced to zero

do		'print out the result
	temp=@bptrdec
	if OBase=100 then
		bintoascii temp,b2,b2,b3
		sertxd (b2,b3)
	else
		if temp<10 then
			sertxd(#temp)
		else
			temp="A"-10+temp
			sertxd(temp)
		endif
	endif
loop until bptr<28
sertxd (cr,lf)
 

techElder

Well-known member
Program Demo BIN2BCD Binary to BCD Conversion

I haven't had the chance yet to put this on to my 40X2, but it does simulate well in PE6. This conversion is important to my latest work, so I'm hoping the on-chip execution time isn't too hard on me.

Thanks to AllyCat and Hippy for the code. I've been able to simplify some and improve it for my use. Hopefully someone else can put this all to good use.

Using this comes from the need to be able to put more than pure binary down a serial line. I also have to provide some human readable digits.

Code:
' Program to demonstrate a 3-byte 24-bit binary number
'     converted to a 4-byte BCD/DECIMAL number
' Texasclodhopper on the PICAXE forums adapted this from work done by
'     Hippy and AllyCat, March 2014. Derived from code from Microchip (AN526).
'     AllyCat created a Binary to BCD conversion for "any" number of bits.
'     I decided to limit the routine to the requirements of my program. Conversion
'     to other parameters should be trivial.
'
' All ram used is within the confines of b0 thru b15 so a PUSHRAM / POPRAM command
'     makes variable usage local to the routine.
'
' The other requirement is to pass a memory pointer to the area of ram where the
'     number to be converted is located. This ram area will be replaced with the
'     BCD converted number. **
'     Keep in mind the limits programmed into this routine are:
'           3 bytes for the 24-bit binary number 
'           4 bytes for the BCD number
'           ** I allowed 4 bytes here because this ram will be overwritten.
'                       You might have to allow 2 extra bytes.
'
' NOTE: The routine depends on the BIN & BCD ram areas being contiguous.

#Picaxe 40X2      ; And most others
#No_Data		; EEPROM data not used

' Next Symbols for subroutine
Symbol BITS       = 24                          ; Number of input bits 
Symbol BYTES      = 3                           ; Number of binary bytes
Symbol BCDBYTES   = 4                           ; Number of output (BCD) bytes
Symbol BIN        = $04                         ; Address of first binary byte (b4)
Symbol BCD        = $07                         ; Address of first BCD byte (b7) (BIN + BYTES)
symbol temp       = BCD + BCDBYTES              ; temp for math precedence, see next
Symbol BCDEND     = temp - 1                    ; Last BCD Byte address
Symbol regBits    = b0                          ; Working Register (bit-addressable)
Symbol bitCount   = b1                          ; Loop counter
Symbol dataAddr   = b2                          ; Address of data RAM passed

' =========================================
'     Program starts here
' =========================================

' Set binary number to be converted into RAM
b16 = $E8   ' address = $10
b17 = $DF
b18 = $05

' call the routine, passing the address of the binary number
dataAddr = $10                                  ; point to the LSB of the binary number
                                                ;     b16 in this example
gosub BIN2BCD

gosub ShowBcdDigits

end

' =========================================
'     SUBROUTINES
' =========================================

ShowBcdDigits:			; Report the Result
      bitCount = dataAddr + BYTES
	For bptr = bitCount to dataAddr step -1
		regBits = @bptr / $10 : SerTxd( #regBits ) ' High nibble
 		regBits = @bptr & $0F : SerTxd( #regBits ) ' Low nibble
	Next
	SerTxd ( CR,LF )
return


BIN2BCD:
;      Convert a 3-byte 24-bit binary number into a 4-byte BCD/DECIMAL number.
;
;      Symbol BITS       = 24                          ; Number of input bits 
;      Symbol BYTES      = 3                           ; Number of binary bytes
;      Symbol BCDBYTES   = 4                           ; Number of output (BCD) bytes
;      Symbol BIN        = $04                         ; Address of first binary byte (b4)
;      Symbol BCD        = $07                         ; Address of first BCD byte (b7)
;      symbol temp       = BCD + BCDBYTES              ; temp for math precedence, see next
;      Symbol BCDEND     = temp - 1                    ; Last BCD Byte address
;      Symbol regBits    = b0                          ; Working Register (bit-addressable)
;      Symbol bitCount   = b1                          ; Loop counter
;      Symbol dataAddr   = b2                          ; Address of data RAM passed

      pushram                                         ; Save the ram b0-b15 on the stack
      
                                                      ; transfer binary number passed, to local ram
      bptr = BIN                                      ; location to transfer to
      bitCount = dataAddr + BYTES - 1                 ; Keep track of incoming bytes
      for regBits = dataAddr to bitCount
            peek regBits, @bptrinc                    ; Transfer the peeked value
      next regBits

      bptr = BCD                                      ; location to clear
      do                                              ; bptr now pointed to BCD location
            @bptrinc = 0                              ; Clear the BCD data field
      loop until bptr > BCDEND	

      bitCount = BITS
      do
            regBits = 0                               ; Clear carry flags
      	for bptr = BIN to BCDEND                  ; Shift Left (LS byte first)
                  regBits = @bptr and $80 + bit7      ; Carry-Out to bit7 + move last carry from bit7 to bit0 
                  @bptr = @bptr + @bptr + bit0        ; Shift left and add carry-in
            next bptr
            dec bitCount
            if bitCount = 0 then exit
            for bptr = BCD to BCDEND                  ; Now replace any invalid BCD nybbles
                  regBits = @bptr + $03
                  if bit3 = 0 then GOTO noadd1
                  @bptr = regBits
                  noadd1:
                  regBits = @bptr + $30
                  if bit7 = 0 then GOTO noadd2
                  @bptr = regBits
                  noadd2:
            next bptr
      loop


	bitCount = 0                                    ; Save the BCD numbers back to the passed address
      
      For bptr = BCD to BCDEND
            regBits = dataAddr + bitCount
            poke regBits, @bptr
            inc bitCount
	Next

      popram                                          ; Return the stack to before entry

return

#rem
PUSHRAM & POPRAM save b0-b15
Utility bytes used: 3 (b0, b1 & b2) (b3 not used)
Working binary address  : (BIN) = $04 or b4
Working BCD address     : (BCD) = $07 or b7
==================================================================
MAX         BINARY      BCD/DECIMAL       Variables
INBITS      BYTES       BYTES             Used
(BITS)      (BYTES)     (BCDBYTES)        (b4-b15) (symbol names)
13          2           2
16          2           3
19          3           3
24          3           4                 BIN:b4-b6; BCD:b7-b10
26          4           4
32          4           5                 BIN:b4-b7; BCD:b8-b12
33          5           5
39          5           6
40          5           7                 BIN:b4-b8; BCD:b9-b15
46          6           7
48          6           8
53          7           8
56          7           9
59          8           9
64          8           10
#endrem

EDIT: I can now report that this routine is blazingly quick on my 40X2 at 32MHz (8MHz resonator). I couldn't tell the routine was even executing!
 
Last edited:
Top