From: Thomas McGahee To: PIC LIST Subject: BCD Counting Techniques & Examples Date: Friday, October 16, 1998 6:42 PM Periodically people post to the PIC list asking about how to convert from binary to BCD. Sometimes you really DO need to make such a conversion. But in *many* cases you can skip the conversion process by simply accumulating the data in the format that you eventually want it in. For the sake of those who are not already aware of such techniques, let me post this fragment of an e-mail that I sent to a PIC list member some time ago in private. I originally posted it privately (as I do *most* of my responses) because I don't want to burden other people's e-mail boxes with what *some* may consider useless litter. Neither do I want to get involved in useless discussions of which technique is "better". I use *all kinds of techniques*. What is best in one situation is not always best for *all* situations. Newcomers have a right to learn some of the things that the rest of us have found useful. Please feel free to add additional methods and techniques. Original answer to post follows (slightly edited)... **************** You mention you want to convert a binary result into BCD output. Well, I am sure that others will give you some nice hairy algorithms to do that, but I will refrain from doing so. Instead I ask you the question: What if you GATHERED the data in BCD form to begin with? Now, it is true that you cannot always do this, but the real question you have to ask yourself is: is there any reason why I *can't* gather the data in that format to begin with? (You can always collect it in *both* binary and BCD at the same time, of course!) I have built timers, for instance, where I have a set of contiguous registers used to hold the time in hours, minutes, and seconds. Instead of getting a binary result and then trying to convert *that* into the time format, I simply accumulate my seconds directly into BCD numbers. As I 'add' one to the current "TIME" I first add one to the current 'seconds' register. If it is now 10 then I set it to 0 and add one to the 'tens of seconds' register. If this register is now 6, then I set it to 0 and add one to the 'minutes' register. If this is now 10, then I set it to 0 and add one to the 'tens of minutes' register. If this is now 6, then I set it to 0 and add one to the 'hours' register. If this is now 10, then I set it to 0 and add one to the 'tens of hours' register, which is actually just a single bit that I toggle between 0 and 1. When the result is a 0, then I toggle the 'AM/PM' bit. Note that I could have also made this a 24 hour clock instead of a 12 hour am/pm style clock simply by having the carry from the hours occur normally, but also checking for when the 'hours' is 5 and the 'tens of hours' is 2. When that occurs set both the 'hours' and 'tens of hours' registers to 0. Seeing that long paragraph above may make you think that this is very involved. Believe me, it is MUCH less involved than trying to convert from binary to some other format. Note also that if your counter is a strict decimal counter, then you can implement the code quite easily using indirect addressing (FSR etc) and a simple called subroutine that adds to a register and returns the status of the 'carry' operation. If there is no carry, you are done. If there IS a carry, then increment the value in FSR. If the result is an address that is one of your count registers, then call the subroutine again. If not, you are done. You can modify the subroutine to add the value in W to the count register, instead of just 1. Note that the largest result would occur when 9+9=18. For results larger than 9 you would generate a carry flag (you save it in a bit somewhere) and then subtract 10 from the result. For 18 this would give you a carry and the number 8. Simple and easy to understand. Another good reason to implement the counting system with this set of updateable registers is that another portion of the program can at any time read the register contents and display the results, or send the results out via a serial line to another device such as a serial LCD display. **** let's get fancy **** Now, if you have implemented the ability to add numbers from 0 to 9, then realize also that you can use FSR to point to any one of the digits in your counter. So, if you wanted to add, say, 32 you would point to the units, add the 2. The routine will handle any carrys and ripple the carrys as needed. Then, to add the 30 part of the 32 you would set FSR to point to the tens register and hand the routine the number 3. Note that you cannot use this totalizer method if the count data is coming in so fast that you can't do a worst-case ripple carry (99999 to 00000) in the time between count events. To this end, do the actual program coding with an eye towards minimizing the time required for the counter to ripple all digits. ** Note that the BCD counters can be organized such that each ** 8 bit byte can contain *two* 4 bit BCD nibbles. While this ** will add something to the complexity of the programming, it ** has the good point of conserving file register useage. And, while the above ideas may not be perfectly applicable to your current projects, it is another set of possibilities that you can file away for that future time, when they may turn out to be very useful. Of course, if you think that all of this is just so much junk, then drop it gently into the trash can and think upon it no more. But I hope this helps. Fr. Tom McGahee As a quick after-thought, here is another recycled post of mine that will show newcomers actual PIC code for a multi digit decimal counter. This was originally written for Bill Pollack, and then sent to the PIC list later. I did not make any real effort to optimize the code. It is just something that I quickly wrote to meet a present need. It has been verified by running it under MPLAB. You can just copy the code and use it as-is. ********** Bill, Here is some code I wrote that allows you to increment a multi-digit decimal counter. Unlike your version, which used one byte per digit, this one packs two digits per byte. This is useful if you need to conserve registers. This is a snippit of actual code that I have running. I have left out the usual initialization stuff, since the code shown does not assume the use of any external output device. You can, of course, use it with 7 segment LEDs or LCD displays. Maybe it is something you might want to file away for later, so if you need to conserve register space, you already have some de-bugged code handy. Fr. Tom McGahee ****************** __config _XT_OSC & _wdt_off & _pwrte_on & _cp_off ;Configure pic as desired... ;I just threw that fragment in so BILL could see ;how it is used. SOME_REG EQU H'0F' ;Can be a shared register (we only need 1 bit) OVERFLOW EQU H'01' ; This is the definition for that bit. DDCTR EQU H'10' ;Can be a shared register. Temporary counter. ;The following each contain a DOUBLE digit ;Thus all 3 contain 6 digits 0 - 999,999 DIGIT3 EQU H'11' ;contains MSB (6,5) DIGIT2 EQU H'12' ;contains (4,3) DIGIT1 EQU H'13' ;contains LSB (2,1) ;The order has been chosen so that when viewed in MPLAB Simulator ; the counting will appear "normal". ; Note that when viewed in the simulator the change from 9 to 0 and a ; carry will at first be displayed as "A", since the intermediate ; results are stored in the actual digit to conserve register usage. org 0 ;Set code origin start goto setup ;We have to get past interrupt vector at 0004 org 5 ;get past interrupt vector ;Ready now to begin main user program. ;***** MAIN: ;This is a short progarm that exercises the counter. CLRF DIGIT1 ;Clear all digits initially. CLRF DIGIT2 CLRF DIGIT3 ;3 double digits in this example... BCF SOME_REG,OVERFLOW ;Clear Overflow flag initially. MAIN_LOOP: CALL INC_DIGIT_SET BTFSS SOME_REG,OVERFLOW GOTO MAINLOOP ;LOOP UNTIL OVERFLOW LP: NOP ;These NOPS are to allow me to trap ;breakpoints a bit more easily NOP GOTO LP ;ENDLESS LOOP ;***** ;MULTI-DIGIT DECIMAL COUNTER ;***** The code is set up so that it can handle ANY even number of digits INC_DIGIT_SET: BCF SOME_REG,OVERFLOW ;Reset Overflow flag MOVLW H'03' ;Do 3 double decimal digits max (6 total) ;Change this to reflect the # of double digits desired. ;You are limited only by the amount of available RAM, MOVWF DDCTR ;Keep counter in DDCTR MOVLW DIGIT1 ;Start with DIGIT1 (D3-D2-D1) MOVWF FSR ;Use Indirect Addressing! INC_RDIGIT: ;This part increments the Right side... INCF INDF,W ;So we increment Indirect & place in W MOVWF INDF ;Save updated RDIGIT in case not 10. ANDLW H'0F' ;Mask & get RDIGIT *only* SUBLW H'0A' ;Subtract 10 from it BTFSS STATUS,Z ;If it resulted in 0 we need to do more... RETURN ; otherwise we is all done. INC_LDIGIT: ;This part handles the Carry problem... MOVF INDF,W ;First recover the L and R DIGIT parts ANDLW H'F0' ;Keep the L part and reset R part to zero ADDLW H'10' ;Increment just the L DIGIT part MOVWF INDF ;Save what we got so far SUBLW H'A0' ;Subtract 10 from the L DIGIT part BTFSS STATUS,Z ;If it resulted in 0 we need to do more... RETURN ; otherwise we is all done INC_NEXT_RDIGIT: ;This gets ready to Carry on! MOVF INDF,W ;First recover both digit parts ANDLW H'0F' ;Set L DIGIT part to 0 MOVWF INDF ;Save present L R DIGIT pieces DECF FSR,F ;Point (Indirect) to NEXT DIGIT! ;* see note below DECF DDCTR,F ;Decrement DDCTR BTFSS STATUS,Z ;Check to see if it went to zero GOTO INC_RDIGIT ;If it did NOT, then keep carrying on! BSF SOME_REG,OVERFLOW ; otherwise report the OVERFLOW RETURN ; and we is (finally) all done. *********** End of program sample * Note: Change the DECF FSR,F instruction to INCF FSR,F if you order the digits as follows in RAM: DIGIT1 EQU H'11' ;contains MSB (2,1) DIGIT2 EQU H'12' ;contains (4,3) DIGIT3 EQU H'13' ;contains MSB (6,5) Instead of the ordering used in the above example, which is: DIGIT3 EQU H'11' ;contains MSB (6,5) DIGIT2 EQU H'12' ;contains (4,3) DIGIT1 EQU H'13' ;contains LSB (2,1) The ordering used in the sample program was chosen so that in the Simulator you could easily watch the digits counting up in the way you would expect to "see" them count.