TITLE "Dual Frequency Divider With Sync - Richard H McCorkle, April 21, 2009" ; This is a Microchip assembler port to the 16F630 ; of code developed by Tom Van Baak for the 16C84. ; THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE ; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ; OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ; HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ; LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ; OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ; SUCH DAMAGE. ;********************************************************************* ; CPU Type & Fuses: ;********************************************************************* LIST n=58, p=PIC16F630 errorlevel 1 include P16F630.INC __CONFIG _EC_OSC & _PWRTE_ON & _WDT_OFF & _BODEN_OFF & _CPD_OFF & _CP_OFF & _MCLRE_OFF ;********************************************************************* ; Function Description: ;********************************************************************* ; This 16F630 program is designed to divide a 10 MHz or 5 MHz ; frequency source down to 1PPS that can be synchronized to a GPS ; receiver or timing standard. This program does not use TMR0, the ; pre-scaler, or interrupts. Instead it relies on the fact that ; given an accurate clock each PIC instruction takes precisely 400ns ; with a 10 MHz input or 800ns with a 5 MHz input. The main loop uses ; exactly 125 instrutions, timing the loop at 50us with a 10 MHz input ; or 100us with a 5 MHz input. ; A 10 MHz or 5 MHz frequency select input with a weak pull up is ; provided to select the input frequency. Leave open for 5 MHz or ; ground for 10 MHz operation. (0 = 10 MHz, 1 = 5 MHz) An Arm input ; and a 1PPS sync input are provided to synchronize the divider. ; The Arm pin has a weak pull up enabled allowing the use of an Arm ; pushbutton to pull the Arm pin low to stop and reset the divider. ; Once the ARM pushbutton is released the input returns high, and the ; divider resumes counting on the leading edge of 1PPS sync. The 1PPS ; output will be synchronized to the sync input to +/- 1 instruction ; time. (+/- 400ns @ 10 MHz or +/- 800ns @ 5 MHz) ; When the SQ_OUT conditional flag is disabled the program creates ; 5 - 20/80 duty cycle outputs from 10KHz to 1Hz and a separate 1PPS ; output with a 100ms duration. When the conditional flag is enabled ; the 1 KHz to 1 Hz outputs have a 50% duty cycle. With a 10 MHz input ; the program creates a 10 KHz 50% duty cycle output but when a 5 MHz ; input is used the 10 KHz output would need to be reset at 62.5 ; instructions for a 50% duty cycle. Since it can only be reset on an ; instruction time it is reset at 62 instructions giving a 10 KHz ; output with a 49.6/50.4 duty cycle when a 5 MHz input is used. The ; leading edge of all outputs is "on time" with all outputs set ; simultaneously so they perform like a synchronous counter chain. ; The 16F630 outputs have 25ma drive current so 150 ohm series ; resistors can be added to drive 50 ohm loads or external buffers ; can be used. With 150 ohm series resistors the outputs provide ; 1.25v peak to peak into a 50 ohm load. A small value bypass cap ; can be used across the resistors to improve transient time if ; desired. ; Pins RA0 and RA1 drive the LED's, RA2 is the frequency select ; input, RA3 is the Sync input, RA4 is the Arm input, and RA5 is the ; Clock input. RC0-RC4 are the 10 KHz to 1 Hz 20% or 50% duty cycle ; outputs, and RC5 is the 1PPS 100ms pulse output. ; ;********************************************************************* ; I/O Pin Assignments: ;********************************************************************* ; ; Register Bit Pin Function ; PORTA 0 (13) Green (Sync) LED ; 1 (12) Red (Arm) LED ; 2 (11) Freq Select (0 = 10MHz, 1 = 5MHz) ; 3 ( 4) Sync Input (Leading edge) ; 4 ( 3) Arm Input (0 = Arm, 1 = Run) ; 5 ( 2) Clock In ; PORTC 0 (10) 10KHz Output (20% or 50% Duty Cycle) ; 1 ( 9) 1KHz Output (on all outputs) ; 2 ( 8) 100Hz Output ; 3 ( 7) 10Hz Output ; 4 ( 6) 1Hz Output ; 5 ( 5) 1PPS Output (100ms Pulse) ; ;********************************************************************* ; Change History: ;********************************************************************* ; ; 07/05/1998 Version 4 release by Tom Van Baak ; 07/28/2005 New Construction for 16F630 (Ver 0.10) ; 06/01/2006 Change to 10M/5M dual frequency build (Ver 0.11) ; 11/26/2006 Release for public domain (Ver 1.00) ; 04/21/2009 Added Conditional for 50% duty cycle outputs (Ver 1.10) ;********************************************************************* ; Conditional Assembly: ;********************************************************************* ; comment this line out for normal 20%/80%, enable for 50% outputs #define SQ_OUT ;50% Duty Cycle Outputs ;********************************************************************* ; Define Storage Locations: ;********************************************************************* CBLOCK 0x20 Digit0 ;Decade counter registers Digit1 Digit2 Digit3 Digit4 OutByte ;Output Byte register ENDC ;********************************************************************* ; Bit Assignments: ;********************************************************************* #define Zflag STATUS,Z ; Zero flag #define Cflag STATUS,C ; Carry flag #define Green PORTA,0 ; Sync LED #define Red PORTA,1 ; Arm LED #define Fsel PORTA,2 ; Freq Mode 0= 10M #define Sync PORTA,3 ; Sync In 1= Sync #define Arm PORTA,4 ; Arm In 0= Arm #define B10K PORTC,0 ; 10K output ;********************************************************************* ; Initialization: ;********************************************************************* ;set interrupt vector and start of code org 0 ;initialize code goto start org 4 ;interrupt routine goto $ ;initialize bank 0 ports and control registers start clrf PORTA ;clear port output latches clrf PORTC movlw 0x07 ;set PORTA pins as digital (not comparator inputs) movwf CMCON ;initialize bank 1 control regs bsf STATUS,RP0 ;select bank 1 clrf OPTION_REG movlw 0x3c movwf TRISA ;set PORTA pins 0,1 as output, pins 2-5 as inputs movlw 0x14 movwf WPUA ;weak pullup on Arm & Fsel inputs clrf TRISC ;set PORTC pins as outputs bcf STATUS,RP0 ;select bank 0 Init clrf Digit0 ;clear all counters clrf Digit1 clrf Digit2 clrf Digit3 clrf Digit4 movlw 0x3f ;all outputs high ; ;********************************************************************* ; Main Program Loop: ;********************************************************************* ; The following implements a software decade divider chain much like ; a string of 7490 decade counter chips. The low-order digit is ; incremented and when it wraps the carry is propagated to the next ; higher-oder digit. In 5M mode the low order digit is incremented and ; reset each loop disabling the /2. In 10M mode the low-order digit is ; incremented from 0 to 1 to 0. All remaining digits count from 0 to 9 ; and wrap back to 0 so each next higher order digit counts at 1/10 the ; rate of the preceding digit.The STATUS Z bit is used to propagate ; carry into the next digit: Zero = carry and not Zero = no carry. ; ; For 10 MHz mode (50 us main loop) the 10k digit toggles every ; 125 instructions for a period of 100 us and a frequency of 10 kHz ; in the 50% duty cycle mode. For 20/80 outputs the 10 KHz is reset ; 50 instructions after output to give a 20% duty cycle output. ; ; For 5 MHz mode (100 us main loop) the low order divide by 2 is ; disabled, the 10 KHz digit is set high every loop and reset 25 ; instructions later and to give a 20% duty cycle output. For 50% ; duty cycle mode the 10 KHz digit is reset 62 instructions later ; giving a 10 kHz output with a 49.6/50.4% duty cycle. Loop movwf PORTC ;load C with output bits incf Digit0 ;10k counter movlw 0x01 btfss Fsel ;disable this /2 in 5M mode movlw 0x02 subwf Digit0,W Loop1 btfsc Zflag clrf Digit0 btfsc Zflag incf Digit1 ;1k counter movlw 0x0a subwf Digit1,W btfsc Zflag clrf Digit1 btfsc Zflag incf Digit2 ;100 counter movlw 0x0a subwf Digit2,W btfsc Zflag clrf Digit2 btfsc Zflag incf Digit3 ;10 counter movlw 0x0a subwf Digit3,W ifdef SQ_OUT goto $ + 1 else btfsc Fsel ;10K 20% reset in 5M mode bcf B10K endif btfsc Zflag clrf Digit3 btfsc Zflag incf Digit4 ;1 counter movlw 0x0a subwf Digit4,W btfsc Zflag clrf Digit4 ; ; Now compress digits into one byte of 20% or 50% duty cycle bits. ; For 50% duty cycle if the odometer digit is less than 5 create a ; one bit and if the odometer digit is 5 or greater create a zero bit. ; bit. For 20% duty cycle if the odometer digit is less than 2 create a ; one bit and if the odometer digit is 2 or greater create a zero bit. ; For the 1PPS output if the odometer = 0 output a 1, else output a 0 ; to give a 100ms duration 1PPS output. ; movlw 0x01 subwf Digit0,W rrf OutByte ifdef SQ_OUT movlw 0x05 else movlw 0x02 endif subwf Digit1,W rrf OutByte ifdef SQ_OUT movlw 0x05 else movlw 0x02 endif subwf Digit2,W rrf OutByte ifdef SQ_OUT movlw 0x05 else movlw 0x02 endif subwf Digit3,W rrf OutByte ifdef SQ_OUT movlw 0x05 else movlw 0x02 endif subwf Digit4,W rrf OutByte ifdef SQ_OUT goto $ + 1 else btfss Fsel ;10K 20% reset in 10M mode bcf B10K endif movlw 0x01 subwf Digit4,W rrf OutByte bsf Cflag ;shift 2 places to allign to rrf OutByte ;low 6-bits of port bsf Cflag rrf OutByte comf OutByte,W ;invert bits ; ; Add delays so that the loop takes exactly 125 cycles. ; goto $ + 1 ifdef SQ_OUT btfsc Fsel ;10K 50% reset in 5M mode bcf B10K else goto $ + 1 endif call Delay10 call Delay10 call Delay10 call Delay10 call Delay10 call Delay8 nop ; ; Check Arm signal once per loop then exit the loop with the next ; version of OutByte in W. The top of the loop writes to PORTC. ; btfsc Arm ;is Arm pin low? goto Loop ;no, continue ; ;********************************************************************* ; Arm - Sync Routine: ;********************************************************************* ; Arm-Sync protocol: ; ; 1) Divider free running (No LEDs) ; - User sets Arm pin low. ; - Stop frequency generator. ; - Turn Red LED on. ; ; 2) Divider stopped (Red LED) ; - Wait for Arm pin to go high again. ; - User sets Arm pin high. ; - Turn Green LED on. ; - Reset counter and output lines. ; ; 3) Waiting for SYNC (Both LEDs) ; - Wait for SYNC pin to go low. ; - Sync goes low. ; - Wait for SYNC pin to go high. ; - Sync goes high. ; - Turn Red LED off. ; - Resume frequency generator. ; ; 4) Divider running in sync (Green LED) ; bsf Red ;arm led on btfss Arm ;is Arm pin high? goto $ - 1 ;no, keep checking bsf Green ;sync led on clrf Digit0 ;clear all counters clrf Digit1 clrf Digit2 clrf Digit3 clrf Digit4 movlw 0x3f ;all outputs high movwf PORTC incf Digit0 ;10k counter movlw 0x01 btfss Fsel ;disable /2 in 5M mode movlw 0x02 subwf Digit0,W ; ; Resume on leading edge of SYNC pin. The PIC will be -1 to +1 ; instruction time from the actual sync edge. This results ; in closest sync with the generated 1PPS leading or lagging ; the Sync input by 1 instruction time. ; btfsc Sync ;is 1 PPS sync low? goto $ - 1 ;no, keep checking btfss Sync ;is 1 PPS sync high? goto $ - 1 ;no, keep checking ; ; Now synchronously rejoin main loop. ; bcf Red ;arm led off goto Loop1 ; ;********************************************************************* ; Delay routines ;********************************************************************* ; - To delay 1 cycle use NOP. ; - To delay 2 cycles use GOTO $+1. ; - To delay 3 cycles use NOP and GOTO $+1. ; - To delay 4 cycles use CALL Delay4. ; - To delay 10 cycles use CALL Delay10, etc. ; ; For other delays use a combination of the above. ; Delay10 goto $ + 1 Delay8 goto $ + 1 Delay6 goto $ + 1 Delay4 return ; ;********************************************************************* de "DFDIV.asm Ver 1.10, Richard H McCorkle 2009" de " " END