;************************************************************************** ; FILE: C:\PIC\ucxo\ucxo1.asm * ; CONTENTS: Firmware for "uC-controlled crystal oscillator" * ; = DCF77-locked 10 MHz timebase with a PIC16F628 * ; AUTHOR: Wolfgang Buescher, DL4YHF * ; * ; REVISIONS: (latest entry first) * ; 2005-11-26 - First experiments to divide 10 MHz down to 77.500 kHz * ; with a PIC16F628 . Saved as "ucxo1.asm" . * ; Disappointing conclusions: * ; - Too bad because this PIC internally divides the 10-MHz * ; crystal clock by four for the instruction clock. * ; Even worse, the timer is fed from this slower clock . * ; This causes four times the jitter in the output signal * ; than initially hoped (is a dsPIC better in this context?) * ; - A PIC16F628, due to its crippled instruction set, * ; the ugly bank-switching to access a few registers, * ; and the poor interrupt-context-saving mechanism * ; isn't really up to this job. * ; 77500 interrupts per second are possible, but * ; the CPU is not able to do ANYTHING ELSE then . * ; So we might as well use an endless loop * ; tuned with NOPs etc to divide the 10 MHz signal * ; down to 77.5 kHz, instead of using this tricky * ; PWM-reprogrammed-in-timer-interrupt method . * ; Baaah. * ;************************************************************************** list P=16F628 #include ; processor specific definitions ;************************************************************************** ; * ; Summary * ; * ;************************************************************************** ; Too early to document this ... ; see the notes in c:\PIC\ucxo\principle.rtf ;************************************************************************** ; * ; PIC config definitions * ; * ;************************************************************************** ; '__CONFIG' directive is used to embed configuration data within .asm file. ; The lables following the directive are located in the respective .inc file. ; See respective data sheet for additional information on configuration word. ; Don't enable the watchdog... I don't "feed" it in this program ! ; Note: In the "UXCO" project, the oscillator is a ; VOLTAGE CONTROLLED CRYSTAL OSCILLATOR ! ; From the PIC's point of view, this is the "EC" oscillator mode (in manual). ; But the damned include file calls it "_EXTCLK_OSC" . __CONFIG _CP_OFF & _WDT_OFF & _PWRTE_ON & _EXTCLK_OSC & _LVP_OFF & _BODEN_OFF & _MCLRE_OFF ; '__IDLOCS' directive may be used to set the 4 * 4(?!?) ID Location Bits . ; These shall be placed in the HEX file at addresses 0x2000...0x2003 . __IDLOCS H'1234' ; (definitions of "file" registers removed. They are defined in a header file!) ;************************************************************************** ; * ; Port assignments * ; * ;************************************************************************** PORT_A_IO equ b'0000' ; port A I/O mode (all output) PORT_B_IO equ b'00000000' ; port B I/O mode (all output) LEDS_PORT equ PORTB ; 7-segment LEDs port ENABLE_PORT equ PORTA ; display enable port ; Bitmasks to control the digit outputs have been moved to enable_table . ; YHF: Note that 'display #0' is the MOST SIGNIFICANT digit ! #define IOP_PROG_MODE PORTA,5 ; digital input signal, LOW enters programming mode #define IOP_DEBUG_OUT PORTB,0 ; digital output for debugging purposes #define IOP_DEBUG_ISR PORTB,1 ; digital output for debugging the timer-ISR ;************************************************************************** ; * ; Constants and timings * ; * ;************************************************************************** ; processor clock frequency in Hz (10MHz) CLOCK equ .10000000 PR2_VALUE equ .32 ; initial value for PR2 value (Timer 2 Period Register), decimal ; Note: the actual period is the PR2 value PLUS ONE ! ; f1 := 2.5MHz / 32 =: 78125.0000000000 kHz ; f2 := 2.5MHz / 33 =: 75757.5757575757 kHz ; The Timer2 ISR will toggle PR2 between 31 and 32 (!) ; to achieve an output frequency of 77.500000 kHz . ; See c:\PIC\ucxo\principle.rtf for details ;************************************************************************** ; * ; File register usage * ; * ;************************************************************************** ; RAM memory (general purpose registers, unfortunately not the same for PIC16F84 & PIC16F628) ; in PIC16F628: RAM from 0x20..0x7F (96 bytes, 0x20.. only accessable in Bank0) ; 0xA0..0xEF (another 80 bytes in Bank1) ; 0x120..0x14F (another 48 bytes in Bank2) ; 0x0F0..0x0FF, 0x170..0x17F , 0x1F0..0x1FF are mapped to 0x70..0x7F (same in all banks) ; So use 0x70..0x7F for context saving in the PIC16F628 and forget 0x0F0.. 0xNNN ! W_TEMP equ 0x70 ; save W register during interrupt STATUS_TEMP equ 0x71 ; save STATUS register " " IrqCnt equ 0x72 ; used in timer ISR without caring for bank bits ; ; Note on the 32-bit integer arithmetics as used in this code: ; - They begin with MOST SIGNIFICANT BYTE in memory, but... ; - Every byte location has its own label here, which makes debugging ; with Microchip's simulator much easier (point the mouse on the name ; of a variable to see what I mean !) ; RTCC_ equ 0x2C ; previous RTCC bTemp equ 0x2F ; temporary 8-bit register, ; may be overwritten in ALL subroutines ;************************************************************************** ; * ; Macros (1) * ; * ;************************************************************************** ; none (so far) ;************************************************************************** ; * ; EEPROM memory definitions * ; * ;************************************************************************** ; none (so far) ;********************************************************************** ORG 0x000 ; processor reset vector goto MainInit ; go to beginning of program ; (begin of ROM is too precious to waste for ordinary code, see below...) ;-------------------------------------------------------------------------- ; Timer 2 interrupt service routine ;-------------------------------------------------------------------------- ORG 0x004 ; Caution, shared interrupt vector ! ; Context-saving is VERY ugly with a PIC.. ; so fast interrupt handlers are almost impossible movwf W_TEMP ; copy W to temp register, could be in either bank swapf STATUS,W ; swap status to be saved into W bcf STATUS,RP0 ; change to bank 0 regardless of current bank movwf STATUS_TEMP ; save status to bank 0 register ; user ISR code goes here.. ; bsf IOP_DEBUG_ISR ; set debug pin for timer ISR ; IrqCnt = IrqCnt+8 ... movfw IrqCnt addlw .8 movwf IrqCnt ; if (IrqCnt>=31) then goto DivBy33 else IrqCnt=IrqCnt-31 ... ; Note: "sublw" is no good here, it subtracts - W ! addlw -.31 ; W := W-31, same as 'addlw 0xE1' ; if the result in W is 'negative' (like 0x1E + 0xE1 = 0xFF, which is 30dec-31dec), ; then bit7 in W is set (which we cannot poll easily) and carry is CLEAR (which we can poll). btfss STATUS, C ; carry set if IrqCnt>=31 -> skip next 'goto' (baah, what an instruction set) goto DivBy33 ; carry clear -> jump DivBy32: ; here if IrqCnt<31, don't write W-31 back to IrqCnt movlw .32-.1 ; let Timer2 divide 2.5 MHz by 32(!) now, giving 78.125 kHz goto SetW2IrqCnt DivBy33: ; REM here every 3.875-th timer interrupt.. movwf IrqCnt ; IrqCnt=IrqCnt-31 movlw .33-.1 ; let Timer2 divide 2.5 MHz by 33(!) now, giving 75.7575757575757 kHz SetW2IrqCnt: BANKSEL PR2 ;! Set register bank bits to access PR2 movwf PR2 ;! set new "Timer 2 Period value" (=frequency divisor minus one) bcf STATUS,RP0 ;! change to bank 0 bcf PIR1, TMR2IF ; clear timer2 interrupt flag ; bcf IOP_DEBUG_ISR ; reset debug pin for timer ISR ; restore context and return from interrupt : swapf STATUS_TEMP,W ; swap STATUS_TEMP register into W, sets bank to original state movwf STATUS ; move W into STATUS register swapf W_TEMP,F ; swap W_TEMP swapf W_TEMP,W ; swap W_TEMP into W (not affecting any flag in the STATUS reg) retfie ; exit interrupt routine and set the GIE bit to reenable interrupts ;************************************************************************** ; * ; Lookup tables * ; Must be at the start of the code memory to avoid crossing pages !! * ; * ;************************************************************************** ; none (so far) ;************************************************************************** ; * ; Procedures (not all of them used at the moment ! ) * ; * ;************************************************************************** ;-------------------------------------------------------------------------- ; Save a single Byte in the PIC's Data-EEPROM. ; Input parameters: ; INDF = *FSR contains byte to be written (was once EEDATA) ; w contains EEPROM address offset (i.e. "destination index") ; ;-------------------------------------------------------------------------- ; write to EEPROM data memory as explained in the 16F628 data sheet. ; EEDATA and EEADR must have been set before calling this subroutine ; (optimized for the keyer-state-machine). ; CAUTION : What the lousy datasheet DS40300B wont tell you: ; The example given there for the 16F628 is WRONG ! ; All EEPROM regs are in BANK1 for the 16F628. ; In the PIC16F84, some were in BANK0 others in BANK1.. ; In the PIC16F628, things are much different... all EEPROM regs are in BANK1 ! SaveInEEPROM: ; save "INDF" = *FSR in EEPROM[] bcf INTCON, GIE ; disable INTs ; ex: bsf STATUS, RP0 ;!; Bank1 for "EEADR" access, PIC16F628 ONLY (not F84) ; The author hoped that BANKSEL instead of bsf STATUS,.. suppresses the stupid warning ; about 'Register in operand not in bank 0. Ensure faselblah' ; - but unfortunately MPASM is too stupid for this. Grrrr. BANKSEL EEADR ;! Set bank selection bits to access EEADR+EECOM+EEDATA... movwf EEADR ;!; write into EEPROM address register (BANK1 !!) bcf STATUS, RP0 ;!; Bank0 to read "bStorageData" movfw INDF ; ; w := *FSR (read source data from BANK 0) bsf STATUS, RP0 ;!; Bank1 for "EEDATA" access, PIC16F628 ONLY (not F84) movwf EEDATA ;!; EEDATA(in BANK1) := w (BANK1; F628 only, NOT F84 !!!) bsf EECON1, WREN ;!; set WRite ENable bcf INTCON, GIE ;!; Is this REALLY required as in DS40300B Example 13-2 ? movlw 055h ;!; movwf EECON2 ;!; write 55h movlw 0AAh ;!; movwf EECON2 ;!; write AAh bsf EECON1, WR ;!; set WR bit, begin write ; wait until write access to the EEPROM is complete. SaveEW: btfsc EECON1, WR ;!; WR is cleared after completion of write goto SaveEW ;!; WR=1, write access not finished yet ; Arrived here: the EEPROM write is ready bcf EECON1, WREN ;!; disable further WRites bcf STATUS, RP0 ;!; Bank0 for normal access bsf INTCON, GIE ; enable INTs retlw 0 ; end SaveInEEPROM ;-------------------------------------------------------------------------- ; Read a single Byte from the PIC's Data-EEPROM. ; Input parameters: ; w contains EEPROM address offset (i.e. "source index") ; will *NOT* be modified to simplify block-read . ; FSR points to the memory location where the byte shall be placed. ; ; Result: ; INDF = *FSR returns the read byte ;-------------------------------------------------------------------------- ; Caution: EEDATA and EEADR have been moved from Bank0(16F84) to Bank1(16F628) ; and the example from the datasheet telling you to switch to ; bank0 to access EEDATA is rubbish (DS40300B page 93 example 13-1). EEPROM_ReadByte: ; read ONE byte from the PIC's data EEPROM movwf bTemp ; save W bcf INTCON, GIE ; disable INTs ; ex: bsf STATUS, RP0 ; Bank1 for ***ALL*** EEPROM registers in 16F628 (!) BANKSEL EEADR ; Set bank selection bits to access EEADR+EECOM+EEDATA... movwf EEADR ;! write into EEPROM address register bsf EECON1, RD ;! set "Read"-Flag for EEPROM ; why is EECON1.RD not cleared in MPLAB-sim ?!? movf EEDATA, w ;! read byte from EEPROM latch bcf STATUS, RP0 ;! normal access to Bank0 bsf INTCON, GIE ; re-enable interrupts movwf INDF ; place result in *FSR movfw bTemp ; restore W return ; back to caller ; end EEPROM_ReadByte EEPROM_Read4Byte: ; read FOUR bytes from the PIC's data EEPROM. ; Input parameters: ; w contains EEPROM address offset (i.e. "source index") ; will *NOT* be modified to simplify block-read . ; FSR points to the memory location where the byte shall be placed. call EEPROM_ReadByte ; *FSR = EEPROM[w] (usually bits 31..24) addlw 1 ; next source address incf FSR , f ; next destination address call EEPROM_ReadByte ; *FSR = EEPROM[w] (usually bits 23..16) addlw 1 ; next source address incf FSR , f ; next destination address call EEPROM_ReadByte ; *FSR = EEPROM[w] (usually bits 15..8) addlw 1 ; next source address incf FSR , f ; next destination address goto EEPROM_ReadByte ; *FSR = EEPROM[w] (usually bits 7..0) ; end EEPROM_Read4Byte ;-------------------------------------------------------------------------- ; main entry point ;-------------------------------------------------------------------------- MainInit: movlw PORT_A_IO ; initialise port A bsf STATUS, RP0 ;! setting RP0 enables access to TRIS regs movwf PORTA ;! looks like PORTA but is in fact TRISA bcf STATUS, RP0 ;! clearing RP0 enables access to PORTs clrf PORTA movlw PORT_B_IO ; initialise port B bsf STATUS, RP0 ;! setting RP0 enables access to TRIS regs movwf PORTB ;! looks like PORTB but is in fact TRISB bcf STATUS, RP0 ;! clearing RP0 enables access to PORTs clrf PORTB clrf IrqCnt ; Timer IRQ counter to toggle PR2 value ; Configure Timer 2. This is used to divide the system clock down ; to the driving signal for the synchronous detector (77.5 kHz) . ; Since Timer 2 cannot drive an output pin by itself, ; the "Capture/Compare/PWM" module is also involved here ! ; The Timer 2 "period register" sets the output frequency . ; The Capture/Compare module is programmed for a 1:1 duty cycle. ; From PIC16F628 datasheet : ; > Timer2 increments from 00h until it matches PR2 and ; > then resets to 00h on the next increment cycle. ; > The PWM period is specified by writing to the ; > PR2register. The PWM period can be calculated using ; > the following formula: ; > PWM period = [(PR2) + 1] • 4 • TOSC • ; > (TMR2 prescale value) ; > PWM frequency is defined as 1 / [PWM period]. ; > When TMR2 is equal to PR2, the following three events ; > occur on the next increment cycle: ; > • TMR2 is cleared ; > • The CCP1 pin is set (exception: if PWM duty ; > cycle = 0%, the CCP1 pin will not be set) ; > • The PWM duty cycle is latched from CCPR1L into CCPR1H ; ; The following steps should be taken when configuring the CCP module for PWM operation. ; 1. Set the PWM period by writing to the PR2 register. ; 2. Set the PWM duty cycle by writing to the CCPR1L register and CCP1CON<5:4> bits. ; 3. Make the CCP1 pin an output by clearing the TRISB<3> bit. ; 4. Set the TMR2 prescale value and enable Timer2 by writing to T2CON register. ; 5. Configure the CCP1 module for PWM operation (? ? ? - already done in step 2 ? ? ?) ; ; 1. Set the PWM period by writing to the PR2 register.... BANKSEL PR2 ;! Set register bank bits to access PR2 movlw PR2_VALUE ;! load PR2 value to PR2 register movwf PR2 ;! 1. Set the PWM period bcf STATUS, RP0 ;! clearing RP0 enables access to PORTs.... ; 2. Set the PWM duty cycle by writing to the CCPR1L register and CCP1CON<5:4> bits. movlw 0x0C ; CCP1CON: bits5..4 in PWM Mode are the two LSbs of the ; PWM duty cycle. The eight MSbs are found in CCPR1L. ; bits 3..0 : 11xx = PWM mode ... movwf CCP1CON ; Configure CCP1 for PWM movlw PR2_VALUE/2 ; half PR2 value for CCPR1L for 50 percent duty cycle movwf CCPR1L ; Set PWM duty cycle ; 3. Make the CCP1 pin an output by clearing the TRISB<3> bit. ; ( already done above, see PORT_B_IO ) ; 4. Set the TMR2 prescale value and enable Timer2 by writing to T2CON register. clrf T2CON ; 0x00 = no prescale, no postscale bsf T2CON,TMR2ON ; TIMER2 ON ; Notes (application specific): ; - The PR2 value (period register) must be reprogrammed frequently in an interrupt routine ; - but the CCP1 module doesn't generate interrupts in PWM mode ; - so the TIMER2 "Match Interrupt" interrupt is used for this purpose. ; So: enable the Timer 2 interrupt. Caution, PIE1 is in a special register bank ! BANKSEL PIE1 ;! Set register bank bits to access PIE1 bsf PIE1, TMR2IE ;! Enable the TMR2 to PR2 match interrupt bcf STATUS, RP0 ;! back to the normal register bank bcf PIR1, TMR2IF ; clear timer2 interrupt flag bsf INTCON, PEIE ; no idea why this "Peripheral Interrupt Enable" must be set too ?! bsf INTCON, GIE ; global interrupt enable ;-------------------------------------------------------------------------- ; main loop : Preparation, auto ranging, measurement, conversion, display ;-------------------------------------------------------------------------- MainLoop: bcf IOP_DEBUG_OUT ; toggle debugging output to measure time spent in ISR, ... bsf IOP_DEBUG_OUT ; ... and how much CPU time remains for main task goto MainLoop ; end of main loop END ; directive 'end of program'