Learning to program the PIC16LF1823

JohnInTX

Joined Jun 26, 2012
4,787
Also, the PIC10 architecture has less instructions than other more advanced chips:
For instance, there's no pagesel instruction .... nor banksel
banksel and pagesel are assembler directives, not native instructions. They cause the assembler to generate code to flip the various paging bits to select RAM and ROM banks. But yes, the 10F322 has only one bank of each so you can ignore them and delete the references to PCLATH as well since calls/gotos will cover the one bank present. I left those in so that you would be aware of them when you move up to multibank PICs.

I guess I'm too spoiled by the PUSH and POP instructions available in the 8051 ... how is IRQsaveS supposed to be declared?
Each IRQsave xxx refers to a dedicated byte in RAM. General purpose memory starts at 40h so you can use the cblock directive to assign labels to some RAM or just use EQU:
Code:
  cblock 40h
  IRQsaveS:1 ; saves status during IRQ
  IRQsaveW:1 ; saves W during IRQ
  endc

; OR
IRQsaveS equ 40h
IRQsaveW equ 41h
Add one of these constructs to your code and you can then open a Debugging->Watch window, type in the names and follow the values.

And yes, having to manually context save without push and pop and multiple register banks is one thing you'll miss..
 

OBW0549

Joined Mar 2, 2015
3,566
This thread recalls my own confusion and hair-pulling while first learning PICs some nineteen years ago. Cheer up: for anyone who's on the ball, this agony lasts only a few days or weeks before opening out into the broad, sunlit uplands of "PIC Programming Competence."

As I recall, it was things like bank and page selection, context save and restore for interrupts, the read-modify-write "gotcha", having to deal with the "backwards" logic of conditional skip instructions instead of familiar (and to me, logical) conditional branch instructions, the sparse instruction set, and the eccentricities of the assembler (and later, the linker) were all things I struggled with-- at least, briefly.

But take heart: what may now be confusing, counterintuitive and mysterious will eventually become obvious and second nature. Keep on truckin'...
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,762
A couple of questions.

1.- How could I create a structure similar to the CJNE instruction in the 8051?
2.- I'd like to use the 8-bit ADC module included in the chip. It says that an internal Vref of 1.024V, 2.048V or 4.096V can be used. Say I'm powering the chip with 3.3V and choose 1.024V as a reference. And I'd like to measure the voltage level of a signal that I know will never exceed 5.1V. So maybe I could use a simple resistor voltage divider at the ADC pin input ... but the datasheet says that it's recommended that the analog source's impedance doesn't exceed 10K ... but it also says that value could be increased by adding a capacitor at the input, with a recommended value of 0.01uF. What would happen if I were to use a 1uF cap? Could I use much a higher impedance at the voltage divider?

upload_2018-7-11_20-21-21.png

What can I do to use said ADC and keep the power being consumed at a minimum? ... something in the order of microamps ...
 

OBW0549

Joined Mar 2, 2015
3,566
... but the datasheet says that it's recommended that the analog source's impedance doesn't exceed 10K ... but it also says that value could be increased by adding a capacitor at the input, with a recommended value of 0.01uF. What would happen if I were to use a 1uF cap? Could I use much a higher impedance at the voltage divider?
There are two reasons for keeping the analog input source impedance low: to minimize acquisition time (i.e., the time required to charge up the ADC's sampling capacitor prior to each conversion), and to minimize static errors caused by the ADC's input leakage current acting through the input source impedance.

The former is not a problem if you provide enough acquisition time. The latter is important only if you want the ADC to meet all its accuracy specs to the letter.
 

JohnInTX

Joined Jun 26, 2012
4,787
CJNE (compare and jump if not = ) in midrange is done by subtraction:

Code:
; Compare a RAM register to a constant
movlw .10 ; load constant
subwf aRAMloc,W  ; compare by subtracting, W is scratch
btfss STATUS,Z ; skip if result is 0
goto ResultIsNotEq
; continue into code for = result
ResultEq:
  ; do Z code

ResultNotEq:
; do not equal code
Note that it's 'backwards' from the accumulator-centric 8051. The RAM registers hold the arguments and W is a temp register. Note that there are other variants that you can do but it usually winds up a subtract to compare values.

Moving and logical operations can be used to test values too by looking at flags after an operation (note the use of the skpnz macro for readability)
Code:
 movf aRAMloc,F ; sets Z flag if aRAMloc = 0
btfsc STATUS,Z
goto aRAMlocIs0

iorlw 0 ;  check for W==0
skpnz  ; built in macro 'skip if not Z'
goto Wwas0
 
Last edited:

JohnInTX

Joined Jun 26, 2012
4,787
You do relational comparisons by subtraction, too.
FILE or LITERAL > W NZ C
FILE or LITERAL = W Z C
FILE or LITERAL < W NZ C

Flags are after subwf RAM,W or sublw value
Code:
Typical use would be:
; Compare two RAM locations
movf aRAMloc,W
subwf aRAMloc2,W ; subtract W from F, result in W
; skip on flag(s) for desired result i.e.
skpnc ; skip if aRAMloc2 < aRAMloc
goto GTEQ
Remember that W gets subtracted from the RAM or Literal value using 2's complement arithmetic. And yes,this one is 'backwards' from what you are used to as well..
 
Last edited:

Thread Starter

cmartinez

Joined Jan 17, 2007
8,762
I'm doing some progress here ... I'm beginning to get a clearer understanding of the way this chip manages its memory, and the logic of its instructions an interrupts.

Question, in the following code if I place the lines 106 and 107:

Code:
        btfsc PIR1, TMR2IF        ;determine if a timer interrupt happened, skip to next
         call TendTimerInterrupt  ;instruction if not
inside the INT_VECTOR section, the program works fine, and flashes a LED three times every four seconds. But with the code as it is it doesn't work. What gives?

Code:
;*****************************************************************************************
;                                  11 JUL 2018, 12:39:15 PM:
;                           Generator controller for the PIC10LF322
;   The following program flashes a LED connected at RA1 for 0.005s thrice every four
;  seconds using the Timer interrupt feature. Internal oscillator is set at 31 KHz.
;
;*****************************************************************************************

       list          p=PIC10LF322                ;list directive to define processor
       #include      p10lf322.inc                ;processor specific variable definitions
       radix         dec

       ; - Flash memory self-write protection off     (default state)
       ; - Brown-out reset low trip point is selected (default state)
       ; - Low-power Brown-out Reset is enabled       (default state)
       ; - High Voltage on MCLR pin must be used for programming
       ; - Program memory code protection disabled    (default state)
       ; - MCLR pin set as digital input
       ; - Power-up Timer disabled                    (default state)
       ; - Watchdog timer disabled
       ; - Brown-out reset is enabled                 (default state)
       ; - Internal oscillator is activated
      
       __CONFIG _LVP_OFF & _MCLRE_OFF & _WDTE_OFF & _FOSC_INTOSC


; in this architecture, general purpose registers are located in addresses 40H through 7FH
TEMPVAR1 EQU 40H           ;TEMPVAR creates a direct reference to memory location 40H
TEMPVAR2 EQU 41H
TEMPVAR3 EQU 42H

;*****************************************************************************************
;                       Reset and Interrupt program memory locations
;*****************************************************************************************
RESET_VECTOR: ORG    0x000                       ;processor reset vector
              goto Init                          ;go to beginning of program

INT_VECTOR:   ORG    0x004                       ;interrupt vector location
              ;according to MaxHeadroom, the retfie instruction restarts the timer, so we
              ;will return from this interrupt immediately, in order to restart the timer
       retfie ;and affect its programmed period as little as possible

;*****************************************************************************************
;                                   Main program start
;*****************************************************************************************
Init:

       ;the reference Clock output bit CLKROE is disabled by default, we'll leave it that way
       ;set the internal oscillator frequency to 31 KHz (the lowest possible), this is done
       ;by clearing the IRCF bits, which belong in bit locations 4, 5 and 6, respecively

       movlw b'10001111'  ;the W register has been loaded with the IRCF bits as zeroes
       andwf OSCCON, f    ;AND the W register with OSCCON, storing the result in OSCCON

       clrf   INTCON      ;make sure that all interrupts are disabled for now

;************************************* I/O definitions ***********************************
       CLRF ANSELA       ;clear the analog select register, which for some stupid reason
                         ;is enabled by default ... otherwise the digital i/o function
                         ;won't work
                        

       ;banksel PORTA  ;this instruction is not necessary with the 10LF322, since it only
       ;has one bank of SFRs

       ;set all pin outputs as zeroes (low), to prevent the LED from starting up already
       ;lit before enabling the timer interrupt function
       movlw b'00000000'
       movwf LATA

       ; - set RA1 as an output, active high ... leave RA0 and RA2 as inputs ... RA3 can
       ;   ONLY be an input
       ;10 JUL 2018, 5:57:17 PM: By the advice of JohnInTx a complete movwf to TRISA should
       ;be used, instead of writing to the individual TRISAX bits
       movlw b'00001101'
       movwf TRISA
      
       ;Interrupt period is given by: PR2 * T2CKPS * TOUTPS / (Fosc/4)
       ;to get a period of one second, the following values make for the best approximation:
       ;Remember that the clock input for the timers is Fosc/4, in this case 31 KHz/4 = 7.75 KHz
       ;PR2    =  8D <- Timer2 compare and interrupt trigger value
       ;T2CKPS = 64D <- prescaling factor, 1:1 to 1:64
       ;TOUTPS = 15D <- postscaling factor, 1:1 to 1:16

       movlw d'32'
       movwf PR2
      
       ;check the structure of T2CON to understand the following instruction
       movlw b'01110011'
       movwf T2CON
      
       ;enable Timer2 to match PR2 interrupt
       bsf PIE1, TMR2IE
      
       ;enable all active peripheral interrupts
       bsf INTCON, PEIE

       ;enable all active interrupts
       bsf INTCON, GIE
      
       ;turn Timer2 On
       bsf T2CON, TMR2ON


ProgramLoop:
        btfsc PIR1, TMR2IF        ;determine if a timer interrupt happened, skip to next
         call TendTimerInterrupt  ;instruction if not
       goto ProgramLoop


;*****************************************************************************************
;             Tend to the timer interrupt and perform the flashing LED routine
;*****************************************************************************************
TendTimerInterrupt:
       ;clear the Timer2 to PR2 match interrupt flag
       bcf PIR1, TMR2IF

       ;flash the LED three times
       bsf LATA, LATA1      ;turn on the LED connected to R1              
       call Delay_0.005_sec ;wait for 0.005 seconds            
       bcf LATA, LATA1      ;turn the LED off before leaving


       movlw d'19'
       movwf TEMPVAR3
Tend_Timer_Loop_00:      
         call Delay_0.005_sec  ;wait for 0.05 seconds
        decfsz TEMPVAR3, f
       goto Tend_Timer_Loop_00

       bsf LATA, LATA1      ;turn on the LED connected to R1              
       call Delay_0.005_sec ;wait for 0.005 seconds            
       bcf LATA, LATA1      ;turn the LED off before leaving
              
              
       movlw d'19'
       movwf TEMPVAR3
Tend_Timer_Loop_01:      
         call Delay_0.005_sec  ;wait for 0.05 seconds
        decfsz TEMPVAR3, f
       goto Tend_Timer_Loop_01
      
       bsf LATA, LATA1      ;turn on the LED connected to R1              
       call Delay_0.005_sec  ;wait for 0.005 seconds            
       bcf LATA, LATA1      ;turn the LED off before leaving

   return

;*****************************************************************************************
;                             Delay for 0.005 seconds at 31 KHz
;     Remember that the "real" working frequency is 31,000/4 per instruction cycle
;*****************************************************************************************
Delay_0.005_sec:
       ;the following sequence will produce 2 + 2*10 + 3*5 = 37 cycles    
       movlw d'2'                  ;(1) load W with the value of 2
       movwf TEMPVAR1              ;(1) TEMPVAR1 now holds the value of 2

Delay_Loop_00:
            movlw d'10'            ;(1) load W with the value of 10
            movwf TEMPVAR2         ;(1) TEMPVAR2 now holds the value of 10              
Delay_Loop_01:
                decfsz TEMPVAR2, f ;(1) decrement TEMPVAR2, and skip next instruction
               goto Delay_Loop_01  ;(2) if it's zero
              decfsz TEMPVAR1, f   ;(1) decrement TEMPVAR1, skip and exit if it's zero
             goto Delay_Loop_00    ;(2)

   return

  
END
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,762
There you go, I made it work ... I copied the state of PIR1 into PIR1_COPY before leaving the interrupt servicing vector, and the functions associated with said interrupt are being executed outside of said section


Code:
;*****************************************************************************************
;                                  11 JUL 2018, 12:39:15 PM:
;                           Generator controller for the PIC10LF322
;   The following program flashes a LED connected at RA1 for 0.005s thrice every four
;  seconds using the Timer interrupt feature. Internal oscillator is set at 31 KHz.
;
;*****************************************************************************************

       list          p=PIC10LF322                ;list directive to define processor
       #include      p10lf322.inc                ;processor specific variable definitions
       radix         dec

       ; - Flash memory self-write protection off     (default state)
       ; - Brown-out reset low trip point is selected (default state)
       ; - Low-power Brown-out Reset is enabled       (default state)
       ; - High Voltage on MCLR pin must be used for programming
       ; - Program memory code protection disabled    (default state)
       ; - MCLR pin set as digital input
       ; - Power-up Timer disabled                    (default state)
       ; - Watchdog timer disabled
       ; - Brown-out reset is enabled                 (default state)
       ; - Internal oscillator is activated
     
       __CONFIG _LVP_OFF & _MCLRE_OFF & _WDTE_OFF & _FOSC_INTOSC


; in this architecture, general purpose registers are located in addresses 40H through 7FH
TEMPVAR1  EQU 40H          ;TEMPVAR1 creates a direct reference to memory location 40H
TEMPVAR2  EQU 41H
TEMPVAR3  EQU 42H
PIR1_COPY EQU 43H

;*****************************************************************************************
;                       Reset and Interrupt program memory locations
;*****************************************************************************************
RESET_VECTOR: ORG    0x000                       ;processor reset vector
              goto Init                          ;go to beginning of program

INT_VECTOR:   ORG    0x004                       ;interrupt vector location
              ;according to MaxHeadroom, the retfie instruction restarts the timer, so we
              ;will return from this interrupt immediately, in order to restart the timer
              ;and affect the programmed period as little as possible

        movf  PIR1, w          ;create a copy of the Peripheral Interrupt Request Reg 1
        movwf PIR1_COPY        ;into PIR1_COPY
        clrf  PIR1             ;clear everything, further actions will be taken outside
                               ;this routine

       retfie ;and affect its programmed period as little as possible

;*****************************************************************************************
;                                   Main program start
;*****************************************************************************************
Init:

       ;the reference Clock output bit CLKROE is disabled by default, we'll leave it that way
       ;set the internal oscillator frequency to 31 KHz (the lowest possible), this is done
       ;by clearing the IRCF bits, which belong in bit locations 4, 5 and 6, respecively

       movlw b'10001111'  ;the W register has been loaded with the IRCF bits as zeroes
       andwf OSCCON, f    ;AND the W register with OSCCON, storing the result in OSCCON

       clrf   INTCON      ;make sure that all interrupts are disabled for now

;************************************* I/O definitions ***********************************
       CLRF ANSELA       ;clear the analog select register, which for some stupid reason
                         ;is enabled by default ... otherwise the digital i/o function
                         ;won't work
                       

       ;banksel PORTA  ;this instruction is not necessary with the 10LF322, since it only
       ;has one bank of SFRs

       ;set all pin outputs as zeroes (low), to prevent the LED from starting up already
       ;lit before enabling the timer interrupt function
       movlw b'00000000'
       movwf LATA

       ; - set RA1 as an output, active high ... leave RA0 and RA2 as inputs ... RA3 can
       ;   ONLY be an input
       ;10 JUL 2018, 5:57:17 PM: By the advice of JohnInTx a complete movwf to TRISA should
       ;be used, instead of writing to the individual TRISAX bits
       movlw b'00001101'
       movwf TRISA
     
       ;Interrupt period is given by: PR2 * T2CKPS * TOUTPS / (Fosc/4)
       ;to get a period of one second, the following values make for the best approximation:
       ;Remember that the clock input for the timers is Fosc/4, in this case 31 KHz/4 = 7.75 KHz
       ;PR2    =  8D <- Timer2 compare and interrupt trigger value
       ;T2CKPS = 64D <- prescaling factor, 1:1 to 1:64
       ;TOUTPS = 15D <- postscaling factor, 1:1 to 1:16

       movlw d'32'
       movwf PR2
     
       ;check the structure of T2CON to understand the following instruction
       movlw b'01110011'
       movwf T2CON
     
       ;enable Timer2 to match PR2 interrupt
       bsf PIE1, TMR2IE
     
       ;enable all active peripheral interrupts
       bsf INTCON, PEIE

       ;enable all active interrupts
       bsf INTCON, GIE
     
       ;turn Timer2 On
       bsf T2CON, TMR2ON


ProgramLoop:
        btfsc PIR1_COPY, TMR2IF
         call TendTimerInterrupt  ;instruction if not
       goto ProgramLoop


;*****************************************************************************************
;             Tend to the timer interrupt and perform the flashing LED routine
;*****************************************************************************************
TendTimerInterrupt:
       ;clear the Timer2 to PR2 match interrupt flag
       bcf PIR1_COPY, TMR2IF

       ;flash the LED three times
       bsf LATA, LATA1      ;turn on the LED connected to R1             
       call Delay_0.005_sec ;wait for 0.005 seconds           
       bcf LATA, LATA1      ;turn the LED off before leaving


       movlw d'19'
       movwf TEMPVAR3
Tend_Timer_Loop_00:     
         call Delay_0.005_sec  ;wait for 0.05 seconds
        decfsz TEMPVAR3, f
       goto Tend_Timer_Loop_00

       bsf LATA, LATA1      ;turn on the LED connected to R1             
       call Delay_0.005_sec ;wait for 0.005 seconds           
       bcf LATA, LATA1      ;turn the LED off before leaving
             
             
       movlw d'19'
       movwf TEMPVAR3
Tend_Timer_Loop_01:     
         call Delay_0.005_sec  ;wait for 0.05 seconds
        decfsz TEMPVAR3, f
       goto Tend_Timer_Loop_01
     
       bsf LATA, LATA1      ;turn on the LED connected to R1             
       call Delay_0.005_sec  ;wait for 0.005 seconds           
       bcf LATA, LATA1      ;turn the LED off before leaving

   return

;*****************************************************************************************
;                             Delay for 0.005 seconds at 31 KHz
;     Remember that the "real" working frequency is 31,000/4 per instruction cycle
;*****************************************************************************************
Delay_0.005_sec:
       ;the following sequence will produce 2 + 2*10 + 3*5 = 37 cycles   
       movlw d'2'                  ;(1) load W with the value of 2
       movwf TEMPVAR1              ;(1) TEMPVAR1 now holds the value of 2

Delay_Loop_00:
            movlw d'10'            ;(1) load W with the value of 10
            movwf TEMPVAR2         ;(1) TEMPVAR2 now holds the value of 10             
Delay_Loop_01:
                decfsz TEMPVAR2, f ;(1) decrement TEMPVAR2, and skip next instruction
               goto Delay_Loop_01  ;(2) if it's zero
              decfsz TEMPVAR1, f   ;(1) decrement TEMPVAR1, skip and exit if it's zero
             goto Delay_Loop_00    ;(2)

   return

 
END
 

jpanhalt

Joined Jan 18, 2008
11,087
If you enable TMR2 interrupts, you don't need to poll PIR1, TMR2IF

Code:
[*]       ;enable Timer2 to match PR2 interrupt
[*]       bsf PIE1, TMR2IE
[*]   
[*]       ;enable all active peripheral interrupts
[*]       bsf INTCON, PEIE
[*]

[*]       ;enable all active interrupts
[*]       bsf INTCON, GIE
[*]   
[*]       ;turn Timer2 On
[*]       bsf T2CON, TMR2ON
[*]

[*]

[*]ProgramLoop:
[*]       btfsc PIR1_COPY, TMR2IF
[*]         call TendTimerInterrupt  ;instruction if not
[*]       goto ProgramLoop
If your program loop simply polls TMR2IF, then you don't need the hassle of an interrupt.

FWIW: Your 5 ms delay for the LED flash timer can't be seen. 50 ms will be very brief (normal blink rate can be assumed to be 20 ms or so). I would suggest something of 100 ms or so for a quickie.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,762
FWIW: Your 5 ms delay for the LED flash timer can't be seen. 50 ms will be very brief (normal blink rate can be assumed to be 20 ms or so). I would suggest something of 100 ms or so for a quickie.
Actually, the flash can be perfectly seen for those 5ms ... I suspect it has something to do with the way the retina holds on to that very brief pulse of light ... or some sort of inductance present in the LED, making the flash last longer? ... the big deal was distinguishing the time between those three flashes. I had to use a much longer delay time for the LED while off so I could actually count how many flashes were performed. That delay turned out to be the 100 ms you've just recommended.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,762
@cmartinez The interrupt flags are set whether the interrupts are enabled or not, so just simple polling can be done if needed, they just need to be reset when a int flag occurs..
Max..
Yeah, but you mentioned that the Timer was stopped when accessing the interrupt servicing routine, and restarted when the retfie instruction was executed ... so, without the interrupts being enabled, will the Timer be restarted when its interrupt flag is cleared?
 

MaxHeadRoom

Joined Jul 18, 2013
30,661
What I intended was that a subsequent interrupt routine is not performed until the existing one is done and a retfie is seen.
It has always been my understanding and findings that the timer continues running unless it is stopped when the INTF is seen to be set.
Whether polled or under interrupt. when the INTF flag is seen the timer flag is cleared in the pgm and the timer reset or turned off in the mean time.
Max.
 

JohnInTX

Joined Jun 26, 2012
4,787
Yeah, but you mentioned that the Timer was stopped when accessing the interrupt servicing routine, and restarted when the retfie instruction was executed ... so, without the interrupts being enabled, will the Timer be restarted when its interrupt flag is cleared?
I think you misunderstood. Timer 2 runs continuously and resets itself when it reaches the value in PR2 so it generates a uniform system interrupt, no stopping, no reloading just tik tik tik. All you have to do is be able to service the interrupt within the timer period. Clear the TMR2IF when you enter the service routine, process the tik then retfie.

EDIT: I see Max beat me to it. To add to his, TMR2/PR2 is unique in that you don't have to reload it. TMR0 you do and in doing so you lose a few Tcycs so it's harder to get an accurate time.

It is true that once you get an interrupt you don't get interrupted any more until retfie. If you have multiple sources of interrupts, you can mitigate some of the service latency by having each service routine return to the top of the various tests for interrupt sources instead of to the end of the tests. That way, it will stay in the interrupt section until ALL interrupt sources are serviced (no pending xxIF flags). What it buys you is that you don't go through restoring all the context and returning after servicing a timer, for example, only to immediately get re-interrupted and go through the context saving for a pending UART character, etc.
 
Last edited:
Top