close-circle
Close

PIC12LF1571 Code

A project log for Decade Flashlight

Low-power, always-on flashlight good for 10 years

Ted Yapo 12/25/2016 at 17:560 Comments

The PIC12LF1571 is programmed in assembly. The code wakes up every 16ms using the watchdog timer - this is a frequency of 62.5 Hz, above the flicker-fusion rate, so you don't notice the blinking unless the light is moving. Each time the code wakes, it pulses the LED 6 times. The PIC oscillator is set to 500 kHz to save power. The desired 8 us current pulse to the inductor is one instruction cycle long at this frequency.

A few techniques have been used in the code to enhance reliability; this code is supposed to wake up about 20 billion times over the life of the battery. First, all unused locations in the instruction memory are filled with the "reset" instruction. If the program counter gets screwed up and points to a random place, it will just reset the code and everything will start clean again. The code itself actually issues a reset instruction every 256 times it wakes - this periodically re-initializes any register settings that may have become corrupted. It might be safer to reset every time, but this adds a lot of overhead, resulting in excessive current drain and reduced efficiency.

The OSCTUNE register is used to adjust the current drain of the assembled PCB. Since the inductor has a 30% tolerance, the software may require tweaking to obtain the desired battery life. Even though the OSCTUNE register only allows +/-12% adjustment, I have so far always been able to tune the current to the desired value (40 uA). If an inductor was discovered that didn't allow this, the number of LED pulses could be changed from 6 to either 5 or 7 to roughly tune the power, then the OSCTUNE adjustment could be again used for fine tuning.

Here is the code listing. Of course, if you want the code, you should get it from the GitHub repo.

;;;
;;; ten_year_lamp.asm :
;;;    PIC12LF1571 code for (2x) LiFeS2 AA-powered LED glow marker
;;;
;;;  20161106 TCY
    
    LIST        P=12LF1571
 #include    <p12lf1571.inc>
  
  ERRORLEVEL -302
  ERRORLEVEL -305  
  ERRORLEVEL -207
  
;;;
;;; OSCTUNE_VAL: set to fine-tune current draw for compensating for
;;;              component tolerances
;  OSCTUNE_VAL  equ   0          
   OSCTUNE_VAL  equ   b'00100000'
;  OSCTUNE_VAL  equ   b'00011111'

;;;
;;; number of LED pulses per WDT timeout loop
;;; 
  N_PULSES    equ   6
  
LED_PULSE   macro
  variable  i
  i = 0
  while i < N_PULSES - 1
  movwf     LATA              ;start inductor ramp-up
  clrf      LATA              ;end inductor ramp-up
  nop                         ; 2 nops here - tuned for minimum current
  nop
  i += 1
  endw
  movwf     LATA              ;start inductor ramp-up
  clrf      LATA              ;end inductor ramp-up
  endm

;;; 
;;; I/O pin configuration
;;; 
  GATE_DRIVE_A  equ   4
  GATE_DRIVE_B  equ   5  

  __CONFIG  _CONFIG1, _FOSC_INTOSC & _WDTE_ON & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _BOREN_OFF & _CLKOUTEN_OFF
  __CONFIG _CONFIG2, _WRT_OFF & _PLLEN_OFF & _STVREN_OFF & _BORV_HI & _LPBOREN_OFF & _LVP_ON

;;;
;;; variables in Common RAM (accessable from all banks)
;;; 
  CBLOCK 0x70
    reset_counter
  ENDC  
  
  ORG     0
RESET_VEC:  
  nop
  nop
  nop
  nop
INTERRUPT_VEC:
  BANKSEL   OSCCON
  movlw     b'00111011'      ; 500 kHz MF osc
  movwf     OSCCON
  
  BANKSEL   OSCTUNE
  movlw     OSCTUNE_VAL
  movwf     OSCTUNE

  movlw     .255
  movwf     reset_counter

  BANKSEL   ANSELA
  movlw     b'00000000'     ; all digital I/O
  movwf     ANSELA

  BANKSEL   LATA
  clrf      LATA
  
  BANKSEL   TRISA
  clrf      TRISA           ; set all lines as outputs

  BANKSEL   WDTCON
  movlw     b'00001001'     ; WDT 16ms timeout    
  movwf     WDTCON        

  BANKSEL   LATA
  movlw     (1 << GATE_DRIVE_A) | (1 << GATE_DRIVE_B)
    
MAIN_LOOP:
  LED_PULSE
  sleep
  decfsz    reset_counter 
  goto      MAIN_LOOP
  reset

  ;; fill remainder of program memory with reset instructions
  fill      (reset), 0x0400-$
  END

Discussions