Learning to program the PIC16LF1823

Thread Starter

cmartinez

Joined Jan 17, 2007
8,767
Combining all our ideas, I wrote this today to sum to BCD numbers of arbitrary length.

Set FSR0 -> first operand (and result)
Set FSR1 -> second operand
Set WREG = number of bytes

call AddWBCD.

The last carry is returned for overflow information.

Should work, but not tested.


Code:
;@JoeyD -- 20230507

#define SavedC  SavedStatus,C+4
#define SavedDC SavedStatus,DC+4


*****************************************************
;** AddWBCD                                        **
;** Add (FSR0) = (FSR0) + (FSR1)                   **
;** wreg = number of BCD bytes (digits / 2)        **
;** return with final carry                        **
;** second operand is preserved                    **
;** requires registers "SavedStatus" and "bytecnt" **
;****************************************************

AddWBCD

    movwf   bytecnt              ;save number of bytes
    clrf    SavedStatus          ;preclear first carry

bcdloop

    movlw   0x66                 ;prepare for BCD addition
    addwf   indf0,f              ;fsr0 -> first addend and result

    movfw   indf1                ;get 2nd addend
    btfsc   SavedC               ;incoming carry?
    incf    indf1,w              ;yes, addend+1
    addwf   indf0,f              ;add it in

    swapf   status,w             ;save resulting C and DC
    movwf   SavedStatus

    clrw                         ; C and DC, adjustment   = 0x00
    btfss   SavedDC              ; C and NDC, adjustment  = 0xFA
    addlw   0xFA
    btfss   SavedC               ;NC and DC,  adjustment  = 0xA0
    addlw   0xA0
    addwf   indf0,f              ;NC and NDC, adjustment = 0xFA + 0xA0 = 0x9A

    incf    fsr0h,f                 ;update pointers
    incfsz  fsr0l,f
    decf    fsr0h,f

    incf    fsr1h,f      
    incfsz  fsr1l,f
    decf    fsr1h,f

    decfsz  bytecnt,f           ;do for all bytes
    goto    bcdloop

    swapf   SavedStatus,w
    movwf   status                 ;return with last carry

    return
You have very interesting coding habits, my friend. The technique you used to re-adjust the digits of the initial sum's result after adding 0x66 to it is awesome and took me a while to understand. But it saves several lines of code and delivers the correct carry for the next iteration.

Your level of experience is light years ahead of mine ... but here's a couple of observations:

I never never ever use the movfw pseudo-instruction. Using it caused me several headaches in the past because (at least for me) it can very easily be visually confused with movwf . I always use the movf File, w complete form instead. Hasn't that ever been a problem for you too?

Also, you probably not remember the whole PIC16 instruction set, but:
Code:
    incf    fsr0h,f                 ;update pointers
    incfsz  fsr0l,f
    decf    fsr0h,f

    incf    fsr1h,f      
    incfsz  fsr1l,f
    decf    fsr1h,f

can more easily be translated to
Code:
         addfsr fsr0, d'1'        ;update pointers
         addfsr fsr1, d'1'
I've learned tons from your programming techniques. Many thanks for the help.
 
Last edited:

joeyd999

Joined Jun 6, 2011
6,305
...you probably not remember the whole PIC16 instruction set...
Sure I do.

I remember the 35 instructions of the original set, which are the only 35 instructions that existed 18 years ago, the last time I wrote PIC16 .asm.

Things change, I guess.

Edit: there were only 33 instructions in the original original instructions set for the original PIC16C54, the first PIC I used waaaay back in 1992.
 
Last edited:

Thread Starter

cmartinez

Joined Jan 17, 2007
8,767
My goal is now to write a BCD subtraction routine, and it's proving to be the hell of a lot more nuanced than I thought at first.

After researching several algorithms on line, I've decided that the best approach is to use the 10's compliment technique. I've done quite a few exercises by hand trying to follow said algorithm, and so far everything seems to work.

Here's my description of said algorithm:

To calculate X = X - Y in BCD:
  1. Calculate the 10's compliment of Y by performing the next hex subtraction : Y = 0x99 - Y + 0x01
  2. Perform the hex addition of X = X + Y
  3. Adjust the lower digit of the result X if said digit is larger than 9 by adding 0x06 to X
  4. Adjust the higher digit of the result X if said digit is larger than 9 by adding 0x60 to X
  5. If the carry flag C is zero, then recalculate the result X by obtaining its 10's compliment: X = 0x99 -X +0x01
  6. If the carry flag C is zero, then the result is negative. If the carry flag C is 1, then the result is positive
I've noticed that steps 3 and 4 must be done sequentially for things to work.

I don't know yet how I'm going to translate this procedure into a routine that uses the FSR's to process arrays of bytes instead. But I'm taking things one step at a time for the moment ... baby steps.
 

joeyd999

Joined Jun 6, 2011
6,305
My goal is now to write a BCD subtraction routine, and it's proving to be the hell of a lot more nuanced than I thought at first.

After researching several algorithms on line, I've decided that the best approach is to use the 10's compliment technique. I've done quite a few exercises by hand trying to follow said algorithm, and so far everything seems to work.

Here's my description of said algorithm:

To calculate X = X - Y in BCD:
  1. Calculate the 10's compliment of Y by performing the next hex subtraction : Y = 0x99 - Y + 0x01
  2. Perform the hex addition of X = X + Y
  3. Adjust the lower digit of the result X if said digit is larger than 9 by adding 0x06 to X
  4. Adjust the higher digit of the result X if said digit is larger than 9 by adding 0x60 to X
  5. If the carry flag C is zero, then recalculate the result X by obtaining its 10's compliment: X = 0x99 -X +0x01
  6. If the carry flag C is zero, then the result is negative. If the carry flag C is 1, then the result is positive
I've noticed that steps 3 and 4 must be done sequentially for things to work.

I don't know yet how I'm going to translate this procedure into a routine that uses the FSR's to process arrays of bytes instead. But I'm taking things one step at a time for the moment ... baby steps.
You should ask yourself why you think you need to work in BCD. Generally, only financial applications -- where the cents must add up exactly -- require this.

It is far easier to do the math in binary to as much resolution as you require, and only at the end convert to BCD (or ASCII) for the final presentation of the value to the user or device that needs it.

Just my 2¢.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,767
You should ask yourself why you think you need to work in BCD. Generally, only financial applications -- where the cents must add up exactly -- require this.

It is far easier to do the math in binary to as much resolution as you require, and only at the end convert to BCD (or ASCII) for the final presentation of the value to the user or device that needs it.

Just my 2¢.
Thanks, I've already considered that ... it's just that there's a data table (that's periodically loaded from a server) that I'm working with in the MCU that is given in BCD. The MCU will be making decisions based on differences between values in the data table. The only two routines I need are addition and subtraction. Fortunately, no multiplication or division will be necessary.
 

joeyd999

Joined Jun 6, 2011
6,305
Thanks, I've already considered that ... it's just that there's a data table (that's periodically loaded from a server) that I'm working with in the MCU that is given in BCD. The MCU will be making decisions based on differences between values in the data table. The only two routines I need are addition and subtraction. Fortunately, no multiplication or division will be necessary.
I'd still convert the data on the fly and do all the important stuff in binary.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,767
I'd still convert the data on the fly and do all the important stuff in binary.
yeah ... it's been quite tempting, believe me. But the data is transmitted and received in a very specific structure... adapting what's already written would be the hell of a lot more work ...

Many thanks for helping me out here, Joey. It's always appreciated.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,767
I've just tested the addition and subtraction routines ... and, as expected, Joey's routine performed flawlessly the first time around, while my subtraction routine went off the rails and reported a bunch of nonsense ... I'm gonna try to stare at the screen for a while, see if I can debug it by myself
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,767
Ok, it seems that I've finally broken through ... Here are the addition and subtraction BCD routines. Joey's routine is practically exactly as he posted it. I just tweaked it a bit to be compatible with what my register names are, and did some indenting and lots of comments for clarity.

As for the subtraction routine, it works fine as long as the minuend is greater than the subtrahend. And if that is not the case, then a 10's complement of the reported result is the right answer. I'm sure there must be an easy fix to that ... I'm going to keep on trying and find it if I can.

In the meantime, all critiques and observations are thoroughly welcome.


Code:
;*****************************************************************************************
;  Add two BCD arrays pointed by FSR0 and FSR1 with NUMBER of bytes, storing the result 
; in the array pointed by FSR0.
;                              Add (FSR0) = (FSR0) + (FSR1)                   
;                        NUMBER = number of BCD bytes (digits / 2)    
;                               return with final carry                        
;                             second operand is preserved                    
;
;  8 MAY 2023, 9:12:04 AM: Based on a routine by JoeyD999 from 
; https://forum.allaboutcircuits.com/
;  
;                             No memory bank change takes place
;
;*****************************************************************************************
AddBCD:
       clrf PUSH01                ;PUSH01 saves the STATUS register (preclear first carry)
  
AddBCD_loop:
         movlw 0x66               ;prepare for BCD addition
         addwf INDF0, f           ;FSR0 -> first addend and result and updates STATUS flag

         movf INDF1, w            ;get 2nd addend
         btfsc PUSH01, C          ;incoming carry?
          incf INDF1, w           ;yes, addend+1
         addwf INDF0, f           ;INDF0 = INDF0 + INDF1 + C

         MovF2F STATUS, PUSH01    ;save resulting C and DC, might affect the Z flag, but 
                                  ;it's not important

         clrw                     ;C and DC, adjustment = 0x00
         btfss PUSH01, DC         ;C and not(DC), adjustment = 0xFA
          addlw 0xFA              ;cancel previous 0x06 (low nibble) addition
         btfss PUSH01, C          ;not(C) and DC, adjustment = 0xA0
          addlw 0xA0              ;cancel previous 0x60 (high nibble) addition
         addwf INDF0, f           ;not(C) and not(DC), adjustment = 0xFA + 0xA0 = 0x9A

         addfsr FSR0, d'1'        ;update pointers
         addfsr FSR1, d'1'

        decfsz NUMBER, f          ;do for all bytes
       goto AddBCD_loop

       MovF2F PUSH01, STATUS      ;return with last carry, might affect the Z flag, but 
                                  ;it's not important
    return


;*****************************************************************************************
;
;                       BCD subtraction of (FSR0) = (FSR0) - (FSR1)
;                        NUMBER = number of BCD bytes (digits / 2)    
;
;     To calculate FSR0 = FSR0 - FSR1 in BCD:
;
;      1.- Calculate the 10's complement of FSR1 by performing the next hex subtraction: 
;        cFSR1 = 0x99 - (FSR1 + notC) + 0x01
;      2.- Perform the hex addition of FSR0 = FSR0 + cFSR1
;      3.- Adjust the lower digit of the result FSR0 if said digit is larger than 9 by 
;        adding 0x06 to FSR0
;      4.- Adjust the higher digit of the result FSR0 if said digit is larger than 9 by 
;        adding 0x60 to FSR0
;      5.- If the carry flag C is zero, then the result is negative. If the carry flag 
;        C is 1, then the result is positive
;      6.- If in the end C is zero then a 10's complement must be applied to the result
;
;*****************************************************************************************
SubBCD:
       bsf PUSH01, C              ;PUSH01 saves the STATUS register (preset first carry)

SubBCD_loop:
         MovLf 0x99, GPT00        ;cFSR1 = 0x99 - (FSR1 + C) + 0x01
         movf INDF1, w

         btfss PUSH01, C          
          incf INDF1, w           ;increment subtrahend if C=0
          
         subwf GPT00, f
         incf GPT00, w            ;w now has the 10's complement of FSR1 (cFSR1)

         addwf INDF0, f           ;FSR0 = FSR0 + cFSR1
       
         
         movlw 0x66               ;check if either one of LoD or HiD is > 9 by
         addwf INDF0, f           ;adding 6 to each of them
         MovF2F STATUS, PUSH01    ;copy the status of the addition into PUSH1
                                  ;this affects the Z flag, but it's not important

                                  ;   if LoD was > 9 then DC=1
                                  ;   if HiD was > 9 then C=1
                                  
         clrw
         btfss PUSH01, C          ;undo the HiD adjustment if there was no C 
          addlw 0x60              ;reported by the previous addition                      
         btfss PUSH01, DC         ;undo the LoD adjustment if there was no DC 
          addlw 0x06              ;reported by the previous addition
         subwf INDF0, f
       
DontComplementRes:
         addfsr FSR0, d'1'        ;update pointers
         addfsr FSR1, d'1'
         
        decfsz NUMBER, f
       goto SubBCD_loop

       MovF2F PUSH01, STATUS      ;return with last carry, might affect the Z flag, but 
                                  ;it's not important
    return
 

joeyd999

Joined Jun 6, 2011
6,305
The reason the minuend must be larger than the subtrahend is that you have no way to encode negative BCD except as a 10's complement number. Same in binary. Just deal with it.

Here's some code I didn't test. It should work:

Code:
*****************************************************
;** SubWBCD                                        **
;** Subtract (FSR0) = (FSR0) + (FSR1)              **
;** wreg = number of BCD bytes (digits / 2)        **
;** return with final carry                        **
;** second operand is preserved                    **
;** requires registers "SavedStatus" and "bytecnt" **
;****************************************************

SubWBCD

    movwf   bytecnt              ;save number of bytes
    clrf    SavedStatus          ;preclear first borrow

bcdloop2

    comf    indf1,w              ;complement subtrahend
    addlw   1                    ; negate it
    btfss   SavedC               ;previous borrow?
    addlw   1                    ; pull it out
  
    addwf   indf0,f              ;add minuend to negative sutrahend

    swapf   status,w             ;save resulting C and DC (0s = borrow)
    movwf   SavedStatus
  
    clrw                         ; C and DC, adjustment   = 0x00
    btfss   SavedDC              ; C and NDC, adjustment  = 0xFA
    addlw   0xFA
    btfss   SavedC               ;NC and DC,  adjustment  = 0xA0
    addlw   0xA0
    addwf   indf0,f              ;NC and NDC, adjustment = 0xFA + 0xA0 = 0x9A

    addfsr  fsr0,1               ;update pointers
    addfsr  fsr1,1

    decfsz  bytecnt,f           ;do for all bytes
    goto    bcdloop2

    swapf   SavedStatus,w

    movwf   status              ;return with last borrow
                                ;NC indicates negative in 10s complement
    return
 
Last edited:

Thread Starter

cmartinez

Joined Jan 17, 2007
8,767
The reason the minuend must be larger than the subtrahend is that you have no way to encode negative BCD except as a 10's complement number. Same in binary. Just deal with it.

Here's some code I didn't test. It should work:

Code:
*****************************************************
;** SubWBCD                                        **
;** Subtract (FSR0) = (FSR0) + (FSR1)              **
;** wreg = number of BCD bytes (digits / 2)        **
;** return with final carry                        **
;** second operand is preserved                    **
;** requires registers "SavedStatus" and "bytecnt" **
;****************************************************

SubWBCD

    movwf   bytecnt              ;save number of bytes
    clrf    SavedStatus          ;preclear first borrow

bcdloop2

    comf    indf1,w              ;complement subtrahend
    addlw   1                    ; negate it
    btfss   SavedC               ;previous borrow?
    addlw   1                    ; pull it out

    addwf   indf0,f              ;add minuend to negative sutrahend

    swapf   status,w             ;save resulting C and DC (0s = borrow)
    movwf   SavedStatus

    clrw                         ; C and DC, adjustment   = 0x00
    btfss   SavedDC              ; C and NDC, adjustment  = 0xFA
    addlw   0xFA
    btfss   SavedC               ;NC and DC,  adjustment  = 0xA0
    addlw   0xA0
    addwf   indf0,f              ;NC and NDC, adjustment = 0xFA + 0xA0 = 0x9A

    addfsr  fsr0,1               ;update pointers
    addfsr  fsr1,1

    decfsz  bytecnt,f           ;do for all bytes
    goto    bcdloop2

    swapf   SavedStatus,w

    movwf   status              ;return with last borrow
                                ;NC indicates negative in 10s complement
    return
You've gone above and beyond the call of generosity ... thanks for sharing! :)

My code has only one line more than yours, and uses one register more than yours ... but one can never be too elegant when writing code, IMHO ... so yeah, I'm going to run and test your code against mine.

In the meantime, my conclusion (which is similar to yours) is that the best way to deal with negative numbers that result from a subtraction is to identify which one of the operands is the largest one before actually doing the calculation. And for that purpose I wrote the following routine, which I've already thoroughly tested:

Code:
;*****************************************************************************************
;                          Is FSR0 greater than or equal to FSR1?
;
;    Both FSRs point to the LSB of an array with NUMBER bytes each. Works with both Hex
;   and BCD registers as long as they're both the same type. Will preserve the original
;   values of both FSRs
;
;                               if FSR0  = FSR1  then Z = 1
;                               if FSR0 <= FSR1  then C = 1
;                               if FSR0  > FSR1  then C = 0
;
;                               Destroyed registers: CNT00
;                            No memory bank change takes place
;*****************************************************************************************
GTE2_FSR0_FSR1:
       MovF2F NUMBER, CNT00
     
       ;make both FSRs registers point to their respective arrays' MSB
      decfsz CNT00, f
       goto GTE2_Loop_00
      goto Skip_Point_To_HB       ;skip if there's only one byte in the arrays
       
GTE2_Loop_00:
        addfsr FSR0, d'1'
        addfsr FSR1, d'1'
       decfsz CNT00, f
      goto GTE2_Loop_00

Skip_Point_To_HB:
      MovF2F NUMBER, CNT00

GTE2_Comp_Loop:      
        movf INDF0, w             ;compare current bytes
        subwf INDF1, w
        btfss STATUS, Z           ;are they equal?
         goto Restore_FSRs_Loop   ;exit if they're not

        addfsr FSR0, -1d          ;point to the next byte down
        addfsr FSR1, -1d          ;this does not affect the STATUS register
       
       decfsz CNT00, f
      goto GTE2_Comp_Loop
                               
      addfsr FSR0, d'1'           ;if we reached this point, both values are equal
      addfsr FSR1, d'1'           ;set both FSR to their respective initial condition
      goto Exit_GTE2_FSR0_FSR1    ;exit this function immediately

Restore_FSRs_Loop:                ;restore the original values of both FSR's if
        addfsr FSR0, -1d          ;the previous loop exited early
        addfsr FSR1, -1d
       decfsz CNT00, f
      goto Restore_FSRs_Loop
     
      addfsr FSR0, d'1'          ;set both FSR to their respective initial condition
      addfsr FSR1, d'1'       

Exit_GTE2_FSR0_FSR1:

    return
 

joeyd999

Joined Jun 6, 2011
6,305
You've gone above and beyond the call of generosity ... thanks for sharing! :)
Not all that generous -- I only changed like five lines of the original addition code.

My code has only one line more than yours, and uses one register more than yours ... but one can never be too elegant when writing code,
I like the symmetry between the addition and subtraction routines. Good .asm code looks like art to me.

All the C stuff I've been writing lately looks like graffiti in crayon by comparison.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,767
Quick question.

I wrote a small routine that runs in the PIC16LF1823 and its purpose is to delay program execution by 10ms (yes, it's one of those blocking routines that Joey despises so much). The MCU is currently running at 32.768 kHz.

Taking into account that each instruction takes 4 clock ticks to execute, then according to my calculations I would need:

0.010s * (32,768 ticks/s)/(4 ticks/inst) = 81.92 instructions to obtain said delay. Which I rounded up to 82 instructions.

So I wrote the following code:

Code:
;*****************************************************************************************
;         Wait for 10ms when running at 32.768 kHz. That is 82 cycles (81.92)
;                                   No bank change
;*****************************************************************************************
Wait_10_ms:                           ;2 cycles to make the call

     MovLf d'25', CNT00               ;2 cycles

     ;The following loop = (25-1) x 3 + 2  cycles = 75
Wait_10_ms_Loop:  
      decfsz CNT00, f                 ;1 cycle
     goto Wait_10_ms_Loop             ;2 cycle
       
     nop                              ;1 cycle
     nop                              ;1 cycle  

    return                            ;2 cycles

The thing is that with the above function I was obtaining significantly more than 10 ms per call. About 14 ms, in fact. I know because I measured it with a scope by turning a pin on and off and calling this function in between. The discrepancy is relevant even when the extra cycles taken by the loop that turns said pin on and off are taken into account.

It wasn't until I adjusted the starting value of CNT00 to 20 that I obtained the desired timing execution.... what gives?
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,767
This is where I stopped reading.
Ok, Joey ... this routine is meant to keep a valve open in multiples of 10ms. My program does make use of interrupts for the task of keeping track of an encoder, though.

What technique would you have used instead to accomplish both goals using a single MCU? That is, keep a pin on for a set amount of time while at the same time monitoring changes at another pin .... would you have used polling instead?
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,767
All the other words were irrelevant.

Interrupts, polling, and a combination, all depending on each piece of hardware and the corresponding timing/latency requirements.

In 45 years of CPU/MCU programming, I've never had to stop a CPU for anything more than a handful of instruction cycles.
Ok, Joey ... here's what I gather from what you're saying:

  • Except for extreme cases, it is in fact perfectly possible to generate accurate timing/latency without the use of blocking routines.
  • To accomplish that, there is no single technique used but rather a combination of polling and interrupts, depending on each particular case.

I'm not some lazy dude that likes to wait and see if things are handed to him without some degree of an effort. So I'm going to go and bang my head a little bit and see if I can come up with an acceptable algorithm. Then I'll be back and post it here for posterity. That's the least that I can do, I think.

Again, thanks for all your help. It's always thoroughly appreciated.
 
Top