subtitle "del_lib.asm" ;;; ********************************************************************* ;;; Library Name: del_lib.asm ;;; Purpose: Routines for (blocking) software delays. ;;; Revision: 0.2 ;;; Date: 03 October 2005 ;;; Author: L. Wyard-Scott, M. Cumming ;;; Copyright: Public Domain ;;; Device: PIC16F873, PIC16F874, PIC16F876, PIC16F877. ;;; PIC16F873A, PIC16F874A, PIC16F876A, PIC16F877A. ;;; ********************************************************************* ;;; Special Directions: ;;; To use these routines, include "del_lib.h" in the source code ;;; calling the routines. ;;; The crystal frequency needs to be specified on the command line: ;;; FXTAL=xx ;;; Where valid choices of FXTAL are integer representations of ;;; the crystal frequency. ;;; 3 --> 3.57945 MHz ;;; 4 --> 4.00 MHz ;;; 5 --> 5.0688 MHz ;;; 7 --> 7.15909 MHz ;;; 10 --> 10.00 MHz ;;; 12 --> 12.00 MHz ;;; 16 --> 16.00 MHz ;;; 20 --> 20.00 MHz ;;; ********************************************************************* ;;; Revision History: ;;; 0.2 - 03 October 2005 ;;; - Added support for 16F87XA devices. ;;; 0.1 - 16 March 2004 ;;; - Added conditional assembly directives to remove ;;; dependency on a particular frequency. Possible ;;; frequencies are specified above. ;;; 0.0 - 13 October 2002 ;;; - Created from routines in debug873.asm (r3.1). ;;; ********************************************************************* list ; Turn on list output. Note that device ; type needs to be specified to the ; assembler (on the command line, or through ; the MPLAB IDE). ;; Include register, bit, and other info specific to ;; the specified device. ifdef __16F873 #include messg "Assembling del_lib.asm for PIC16F873." endif ifdef __16F874 #include messg "Assembling del_lib.asm for PIC16F874." endif ifdef __16F876 #include messg "Assembling del_lib.asm for PIC16F876." endif ifdef __16F877 #include messg "Assembling del_lib.asm for PIC16F877." endif ifdef __16F873A #define A_Device #include messg "Assembling del_lib.asm for PIC16F873A." endif ifdef __16F874A #define A_Device #include messg "Assembling del_lib.asm for PIC16F874A." endif ifdef __16F876A #define A_Device #include messg "Assembling del_lib.asm for PIC16F876A." endif ifdef __16F877A #define A_Device #include messg "Assembling del_lib.asm for PIC16F877A." endif ;;; Ensure that the crystal frequency has been specified on the ;;; assembler command line. ifndef FXTAL error "Timing element frequency not specified on command line." error "Possible values for FXTAL are:" error "3 --> 3.57945 MHz" error "4 --> 4.00 MHz" error "5 --> 5.0688 MHz" error "7 --> 7.15909 MHz" error "10 --> 10.00 MHz" error "12 --> 12.00 MHz" error "16 --> 16.00 MHz" error "20 --> 20.00 MHz" endif ;;; Establish the loop delay values based on the crystal frequency. ;;; Both routines (_Wx1ms and _Wx10ms) use the same code structure. ;;; The code is quite simple, but the counting of cycles to get ;;; an accurate delay is not simple. This is what I get: ;;; ;;; TOTALDELAY = W * (OUTER*(3*INNER+4)+4) + 6 ;;; ;;; Since W is a parameter, the (OUTER*(3*INNER+4)+4) term should ;;; be the unit of the delay. The 6 cycles is overhead and there ;;; is no simple way of accommodating it without complicating matters ;;; further. ;;; ;;; Terms OUTER and INNER can be any value above zero and below 256 ;;; because they are byte values. ;;; The following values were created using a little program to ;;; search through all possible combinations and indicate the ;;; closest combinations. ;;; ;;; FOR A 1 ms DELAY ;;; fXTAL (MHz) tcyc (ns) ncyc/ms INNERCOUNT OUTERCOUNT ;;; -------------------------------------------------------------------- ;;; 3.57945 1117.49 894.86 58 5 (894~) ;;; 4.00 1000.00 1000 54 6 ;;; 5.0688 789.14 1267.2 139 3 ;;; 7.15909 558.73 1789.77 30 19 ;;; 10.00 400.00 2500 68 12 ;;; 12.00 333.33 3000 70 14 ;;; 16.00 250.00 4000 11 108 ;;; 20.00 200.00 5000 11 135 (4999~) ;;; ;;; FOR A 10ms DELAY ;;; fXTAL (MHz) tcyc (ns) ncyc/10ms INNERCOUNT OUTERCOUNT ;;; -------------------------------------------------------------------- ;;; 3.57945 1117.49 8948.6 13 208 (8948~) ;;; 4.00 1000.00 10000 15 204 ;;; 5.0688 789.14 12672 33 123 (12673~) ;;; 7.15909 558.73 17897.7 46 126 (17896~) ;;; 10.00 400.00 25000 46 176 (24996~) ;;; 12.00 333.33 30000 93 106 (30002~) ;;; 16.00 250.00 40000 66 198 ;;; 20.00 200.00 50000 167 99 (49999~) ;;; ;;; Just in case you are interested, here is the code (written in ;;; the TCL (Tool Command Language) scripting language. ;;; puts stdout "Enter the desired number of cycles (integer) --> " nonewline ;;; flush stdout ;;; ;;; set DESIRED [gets stdin] ;;; ;;; set DONE 0 ;;; set ERROR 0 ;;; ;;; while {!$DONE} { ;;; puts stdout "-------------------------------------------------" ;;; puts stdout "Searching for combinations with Error <= $ERROR" ;;; puts stdout "-------------------------------------------------" ;;; for {set INNER 1} {$INNER < 256} {incr INNER} { ;;; for {set OUTER 1} {$OUTER < 256} {incr OUTER} { ;;; set cycles [expr $OUTER*(3*$INNER + 4) + 4] ;;; if {[expr abs($cycles-$DESIRED)] <= $ERROR} { ;;; puts stdout "INNER: $INNER, OUTER: $OUTER, CYCLES: $cycles" ;;; set DONE 1 ;;; } ;;; } ;;; } ;;; incr ERROR ;;; if {!$DONE} { ;;; puts stdout "None found." ;;; } ;;; } ;;; if (FXTAL == 3) messg "3.579545 MHz timing element specified." INNERCOUNT1ms EQU 58 OUTERCOUNT1ms EQU 5 INNERCOUNT10ms EQU 13 OUTERCOUNT10ms EQU 208 endif if (FXTAL == 4) messg "4 MHz timing element specified." INNERCOUNT1ms EQU 54 OUTERCOUNT1ms EQU 6 INNERCOUNT10ms EQU 15 OUTERCOUNT10ms EQU 204 endif if (FXTAL == 5) messg "5.0688 MHz timing element specified." INNERCOUNT1ms EQU 139 OUTERCOUNT1ms EQU 3 INNERCOUNT10ms EQU 33 OUTERCOUNT10ms EQU 123 endif if (FXTAL == 7) messg "7.15909 MHz timing element specified." INNERCOUNT1ms EQU 30 OUTERCOUNT1ms EQU 19 INNERCOUNT10ms EQU 46 OUTERCOUNT10ms EQU 126 endif if (FXTAL == 10) messg "10 MHz timing element specified." INNERCOUNT1ms EQU 68 OUTERCOUNT1ms EQU 12 INNERCOUNT10ms EQU 46 OUTERCOUNT10ms EQU 176 endif if (FXTAL == 12) messg "12 MHz timing element specified." INNERCOUNT1ms EQU 70 OUTERCOUNT1ms EQU 14 INNERCOUNT10ms EQU 93 OUTERCOUNT10ms EQU 106 endif if (FXTAL == 16) messg "16 MHz timing element specified." INNERCOUNT1ms EQU 11 OUTERCOUNT1ms EQU 108 INNERCOUNT10ms EQU 66 OUTERCOUNT10ms EQU 198 endif if (FXTAL == 20) messg "20 MHz timing element specified." INNERCOUNT1ms EQU 11 OUTERCOUNT1ms EQU 135 INNERCOUNT10ms EQU 167 OUTERCOUNT10ms EQU 99 endif ;; Make sure that a *valid* crystal frequency was specified. ifndef INNERCOUNT1ms error "Invalid frequency specified: must be 3,4,5,7,10,16, or 20." endif ;;; -------------------------------------------------------------------- ;;; Assembler Equates Section. Define assembly-time constants here ;;; using the EQU assembler directive. ;;; -------------------------------------------------------------------- ;;; --------------------------------------------------------------------- ;;; Variable Address Assignments. ;;; --------------------------------------------------------------------- UDATA ; Start of the uninitialized data section. ; The following statements reserve memory, ; the address of which is allocated by the ; linker. DCOUNT1: res 1 ; Variables used to count the number DCOUNT2: res 1 ; of instruction cycles. DCOUNT3: res 1 DCOUNT4: res 1 ;; None of these variables need to be declared as global. ;; None of them should be "overlay" (UDATA_OVR), as several ;; libraries make use of these routines and a collision could ;; occur. ;;; -------------------------------------------------------------------- ;;; Macros. (File-specific) ;;; -------------------------------------------------------------------- ;;; -------------------------------------------------------------------- ;;; Subroutines. ;;; -------------------------------------------------------------------- CODE ; Start the code section. ;;; ******************************************************************** ;;; Subroutine Name: DELAY_Wx1ms ;;; Description: Delays (blocks) a multiple of 1ms increments. ;;; See above for development of the timing ;;; values. ;;; Requires: The number of increments (milliseconds) in W. ;;; Returns: None. ;;; Locations Affected: Variables DCOUNT1,2,3,4 ;;; ******************************************************************** DELAY_Wx1ms: global DELAY_Wx1ms ; Make this subroutine callable outside ; the module. banksel DCOUNT1 ; [2~] ([1~] if using MPLAB and 16F873) movwf DCOUNT4 ; [1~] Save a copy of w. movwf DCOUNT1 ; [1~] DCOUNT1 = W movlw OUTERCOUNT1ms ; [1~] movwf DCOUNT2 ; [1~] movlw INNERCOUNT1ms ; [1~] movwf DCOUNT3 ; [1~] decfsz DCOUNT3,F ; [1~,2~ on skip] goto $ - 1 ; [2~] decfsz DCOUNT2,F ; [1~,2~ on skip] goto $ - 5 ; [2~] decfsz DCOUNT1,F ; [1~,2~ on skip] goto $ - 9 ; [2~] movf DCOUNT4,W ; [1~] Restore w. return ; [2~] ;;; ******************************************************************** ;;; Subroutine Name: DELAY_Wx10ms ;;; Description: Delays (blocks) a multiple of 10ms increments. ;;; Requires: The number of increments (10 milliseconds) in W. ;;; Returns: None. ;;; Locations Affected: Variables DCOUNT1,2,3,4 ;;; ******************************************************************** DELAY_Wx10ms: global DELAY_Wx10ms ; Make this subroutine callable outside ; the module. banksel DCOUNT1 movwf DCOUNT4 ; Save a copy of w. movwf DCOUNT1 movlw OUTERCOUNT10ms movwf DCOUNT2 movlw INNERCOUNT10ms movwf DCOUNT3 decfsz DCOUNT3,F goto $ - 1 decfsz DCOUNT2,F goto $ - 5 decfsz DCOUNT1,F goto $ - 9 movf DCOUNT4,W ; Restore w. return END