; -*-asm-*- ; ; Handy macros for PIC development ; ; Most apps will use this configuration constant DEFAULT_CONFIG = _FOSC_INTOSCIO & _WDTE_OFF & _PWRTE_ON & _MCLRE_OFF & _CP_OFF & _CPD_OFF & _BOREN_OFF & _IESO_OFF & _FCMEN_OFF ; ------------------------------------------------ ; ; BASIC MACROS ; ; Switch to run at 8 MHz. ; ASSUME: Bank 1. run_at_8MHz: macro movlw 0x70 iorwf OSCCON, F endm ; ------------------------------------------------ ; ; BANK MANIPULATION ; ; SIDE EFFECTS: W and STATUS are unchanged ; ; change to a specific bank to access bank-specific registers bank0: macro bcf STATUS, RP0 bcf STATUS, RP1 endm bank1: macro bsf STATUS, RP0 bcf STATUS, RP1 endm bank2: macro bcf STATUS, RP0 bsf STATUS, RP1 endm ; short forms when we know we're already in one of: bank0, bank1 ; (ie. we don't have to avoid/turn-off bank2/3) bank0s: macro bcf STATUS, RP0 endm bank1s: macro bsf STATUS, RP0 endm ; ------------------------------------------------ ; ; INTERRUPT HANDLING ; ; If W_SAVE is not defined externally, then use register 0x70 ; NOTE: this MUST be in shared space (0x70 .. 0x7F) ifndef W_SAVE constant W_SAVE = 0x70 endif ; If S_SAVE is not defined externally, then use register 0x71. ; NOTE: the default is in shared space, but a Bank 0 register may be used ifndef S_SAVE constant S_SAVE = 0x71 endif ; The setup for an interrupt handler IH_prologue: macro ; note: using swapf to avoid altering Z flag movwf W_SAVE swapf STATUS, w clrf STATUS ; moves to Bank 0 movwf S_SAVE ; NOTE: STATUS is saved with swapped nibbles endm ; The wrapup/return for an interrupt handler IH_epilogue: macro ; Assume: still in Bank 0 swapf S_SAVE, w movwf STATUS swapf W_SAVE, f ; swap nibbles within the register swapf W_SAVE, w ; swap back, into W retfie endm ; Standard Interrupt Handler (place at 0x0004) SIH: macro ISR IH_prologue ; Everything saved. Call the given Interrupt Service Routine call ISR IH_epilogue endm ; ------------------------------------------------ ; ; COMPARATOR DISABLING ; ; None of our applications expect to use the comparators. This is a little helper ; code to use during initialization to fully disable both comparators. ; ; ASSUME: nothing; we force a switch to Bank 0 ; SIDE EFFECTS: W and STATUS *are* modified. ; EXIT: in Bank 0 ; disable_comparators: macro ; CMCON0 is in Bank 0 bank0 ; Set CMCON<2:0> to 111 to disable both comparators. movlw 0x07 iorwf CMCON0, F ; xxx -> 111 ; exit in Bank 0 endm ; ------------------------------------------------ ; ; ANALOG INPUT HANDLING ; ; ; Given an input pin, determine *if* we need to configure it for digital input ; (versus analog input). The mapping between Port(X) and AN(Y): ; RA0 -> AN0 ; RA1 -> AN1 ; RA2 -> AN2 ; RA3 -> none ; RA4 -> AN3 ; RA5 -> none ; RC0 -> AN4 ; RC1 -> AN5 ; RC2 -> AN6 ; RC3 -> AN7 ; RC4 -> none ; RC5 -> none ; ; ASSUME: Bank 1 ; SIDE EFFECTS: W and STATUS are unchanged ; disable_analog: macro PORT, PIN if PORT == PORTA if PIN == 4 bcf ANSEL, 3 ; RA4 -> AN3 else if PIN <= 2 bcf ANSEL, PIN ; RA{0:2} -> AN{0:2} endif endif else if PIN <= 3 bcf ANSEL, PIN+4 ; RC{0:3} -> AN{4:7} endif endif endm ; ; Disable ALL of the analog inputs. Everything will be digital. ; ; ASSUME: Bank 1 ; SIDE EFFECTS: W unchanged; Z will be SET ; no_analog: macro clrf ANSEL endm ; ------------------------------------------------ ifdef USING_WAIT_MACROS ; ; In some cases, we need to wait N instruction cycles (t = N/2 µs) ; ; SIDE EFFECTS: W and STATUS are unchanged ; wait_1: macro ; "wait_1" better describes intent, than "nop" nop endm wait_2: macro ; 2 instruction cycles; 1.0µs if 8Mhz goto $ + 1 endm wait_3: macro ; 3 instruction cycles; 1.5µs if 8MHz wait_2 nop endm wait_4: macro call WAIT_4_support endm wait_5: macro ; 5 instruction cycles; 2.5µs if 8MHz call WAIT_5_support endm wait_6: macro ; 6 instruction cycles; 3.0µs if 8MHz call WAIT_6_support endm wait_7: macro ; 7 instruction cycles; 3.5µs if 8MHz call WAIT_7_support endm wait_8: macro ; 8 instruction cycles; 4.0µs if 8MHz call WAIT_8_support endm wait_10: macro ; 10 instruction cycles; 5.0µs if 8MHz call WAIT_10_support endm ; see handy/wait.asm extern WAIT_10_support extern WAIT_8_support extern WAIT_7_support extern WAIT_6_support extern WAIT_5_support extern WAIT_4_support ; Blocking wait for $W * 32.6ms. See wait.asm. extern blockwait endif ; ------------------------------------------------ ; ; EEPROM ACCESS ; ; System-wide EEPROM addresses for configuration ; The I2C address (on the HOUSE network) for this slave node constant EE_I2C_ADDRESS = 0x00 ; The I2C address (on the LOCAL network) for this slave node constant EE_I2C_LOCAL = 0x01 ; Configuration mask for this node constant EE_CONFIG_MASK = 0x02 ; Which application is installed on this PIC? The registry of apps ; is located at ###gstein-specific:gdrive constant EE_APP = 0x03 ; Read the value stored at a specifed EEPROM address. Returned in W. ; ASSUME: Bank 1 read_eeprom: macro EE_ADDRESS movlw EE_ADDRESS movwf EEADR bcf EECON1, EEPGD ; select EEPROM data memory bsf EECON1, RD ; perform the read movfw EEDAT endm ; ------------------------------------------------ ; ; Analog to Digital Converter (ADC) helper macros ; ; Assume Bank 1. ADC_init_ADCS: macro ; Write 101 to ADCS<2:0> for movlw b'01010000' movwf ADCON1 endm ADC_select_pin: macro PIN ; ADFM = 0 left-justified in ADRESH/L ; VCFG = 0 use V.dd as the reference voltage ; CHS<2:0> = PIN selected channel ; GO/~DONE = 0 conversion not in progress ; ADON = 1 enable A/D converter module movlw (PIN << 2) | b'00000001' movwf ADCON0 ; ### caller should now wait for T.acq (19.72 µS) endm ADC_begin: macro ; Start the converter process bsf ADCON0, GO endm ; The process requires 11 T.ad periods, which maps to 22.0 µS ; ### how to effectively use this macro?? TBD ADC_ready: macro btfsc ADCON0, GO endm ADC_value: macro ; Return the 8 most significant bits from ADRESH. (there are two ; least-significant bits in ADRESL, which we'll ignore) movfw ADRESH endm ; ------------------------------------------------ ; Given a 10-bit value in REG16_H and REG16_L, rotate the value ; two bits right (aka divide by 4), and return the 8-bit result ; in W. ; ; Side effect: the two registers are rotated 1 bit to the right. ; the MSB of REG16_H will contain CARRY's value on entry. ; Bank: no assumptions. REG16_H and REG16_L should be in $currentBank. ; Exit: W contains <9:2> of the 10-bit value. ; rotate10_2r: macro REG16_H, REG16_L rrf REG16_H, F ; LSB is now in C. Rotate that into TEMPL's MSB. rrf REG16_L, F ; Note: TEMPL's LSB is now rotated into CARRY. It will be ; rotated into REG16_H, in the next step, but is unimportant. ; Given above: REG16_H and REG16_L have been rotated one ; bit to the right. The MSB of REG16_H is whatever CARRY ; happened to be upon entry. Below, we do not rotate the ; register; results go to W. ; Move the next LSB from REG16_H into CARRY. rrf REG16_H, W ; throw out the rotation result (to W) ; Now, rotate CARRY into the REG16_L value, and return it. rrf REG16_L, W endm ; ------------------------------------------------ ; Prepare the OPTION_REG register for TIMER0 operation. This will run the ; timer at its slowest rate: ; 1:256 scaling, which is an overflow every 32.8ms ; ; Note: this macro ENABLES PULLUPs on PORTA. ; ### maybe a future variant would not do this? needed? ; ; Note: RA2 is assumed to be used for "other purposes" rather ; than clocking/interrupts. ; ; ~RAPU: 0 to enable weak pullups on PORTA ; INTEDG: x (don't care; default is 1) ; T0CS: 0 TIMER0 uses internal instruction clock cycle ; T0SE: x (don't care; default is 1) ; PSA: 0 prescaler assigned to TIMER0 ; PS<2:0> 111 1:256 scaling ; ; EXPECT: Bank 1 prepare_timer0: macro ; Note: before untying the prescaler from the WDT (default), the ; WDT should be cleared to avoid an accidental trip. clrwdt movlw b'01010111' ; see docstring movwf OPTION_REG endm ; ------------------------------------------------