; set TABs to 4 to read this source ; temp_pic-0.7.4.asm ; Copyright (c) Jan Panteltje 2009-always. ; Released into the public domain. ; Use at your own risk, no guarantees of any kind. ; temp.asm for Microchip 12F629. ; From Microchhip program: TSTAT2~1.ASM. modified for RS232 ASCII out and PIC 12F62. ; see Microchip application notes 00720c.pdf (temp sesor using watch dog timer), and (00510e.pdf for software serial out). ; This source was assembled with gpasm-0.13.5 beta, may not work with mplab. ; Procedusre to use this software: ; set ALARM_TEMP ; Program PIC. ; Connect PIC to stabilised 5V. ; Connect a terminal (via a MAX232 for example) to pin 5, 9600Bd, 1 start bit, 1 stop bit, no parity. ; Now alert pin 3 should pulse high if the SETPOINT_ON temperature is exceeded. ; Note: pin 3 is tri-state if not high, you may need a pulldown resistor in some applications. ; It should be possible to OR pin 2 of sevaral of these PIC temp sensors together. ; When ALERT_OUT is defined as not tristate, you can directly drive a logic level MOSFET with it, ; but possibly need a small capacitor to ground to keep the level for about 20 uS during port initialisation, as the port goes tristate when the watchdog timer expires. ; The MOSFET gate capacitance may in itself be enough, ; CHANGES: ; ; 0.1: ; First release ; ; 0.2 ; Removed second init. ; New binary to bcd routine ; Removed value compare, now just degrees C. ; Added negative ranges temp and setpoint support. ; ; 0.3: ; Unravelled some Michrochip spaghetti code. ; Marked subroutine area. ; Pin 2 now active via ALARM_FLAG in flags 2, in main, so you can add code. ; ; 0.4: ; Replaced some cryptic labels by more understandable ones. ; ; 0.5: ; Cleared up some code. ; ; 0.6 ; Adjusted COUNTS_PER_DEGREE_C for 5.1V zener (was for 450 for 5V) ; Adjsuted Baudrate delay to 27 (was 25) for batter match ; ; 0.7 ; Added baudrate divider to allow baudrates down to 75 Baud, default now 1200 Bd. ; Added hysteresis for alert out, so you can drive a fan or heater. ; Added option for tristate or hard pulldown (for MOSFET) alarm out on pin 2, you still need a cap and resistor to ground in case of MOSFET, as else it pulses and floats at power up. ; ; 0.7.1: ; Added invert output define. ; ; 0.7.2: ; Changed watchdog timer status tracking to flags2 ; Moved alert out processin gto subroutine 'do_alarm' for clarity. ; Allow selection of output pin ALERT signal. ; ; 0.7.3: ; That thing with OSCALL I copied from the Microcip application note is wrong for this PIC, among other things OSCCAL is in bank 1. ; Added the correct OSCCAL calibration. ; Moved do_alarm to start directly after TRISIO init, so it is always called, and the time the port is uninitialised is minimised. ; ; 0.7.4: ; Added flashing LED support. ; * USER changable * ; !!!!!!!!!!!! Please see the note about OSCCAL at line 264 and up !!!!!!!!!!!!!!!!!!!!! ; This is the threshold, above this value the alert pin 2 pulses high. SETPOINT_ON equ D'37' ; temperature threshold for alarm in degrees C. Un-comment the next line if your SETPOINT_ON is negative. ;#define NEGATIVE_SETPOINT_ON SETPOINT_OFF equ D'32' ; degrees C temp has to lower before alarm is switched off again, Un-comment the next line if your SETPOINT_OFF is negative. ;#define NEGATIVE_SETPOINT_OFF ; Uncomment the next line if you want pin 5 of the PIC to directly drive a RS232 line (without MAX232 or such). ;#define NON_INVERTING_RS232_OUT ; uncomment this if you want a a high on pin 2 when temp drops below SETPOINT_ON, use as thermostat in a heater? ;#define INVERT_OUTPUT ; BAUD_DIVIDER set to 1 for 9600 Bd, 2 for 4800 Bd, 4 for 2400 Bd, 8 for 1200 Bd, 16 for 600 Bd, 32 for 300 Bd, 64 for 150 Bd, and 128 for 75 Bd. ; Note: for 9600 Bd set BIT_DELAY to 27, or basically a bit lower then for the other baudrates, due to more relative time spend in other instructions. #define BAUD_DIVIDER D'8' ; 1200 Baud ; If you want to OR several devices with pin 2 together, uncomment the next line, if you want to drive a MOSFET with pin 2 leave it commented out, in that case you need about 4.7uF from pin 2 (connected to gate MOSFET) to ground. ;#define TRISTATE_ALARM_OFF ; uncomment this line if you use a LED on the ALERT_OUT pin and want it to flash on / off i ncase of alert. ;#define FLASHING_OUTPUT ; outout pin for alert LED ;#define ALERT_OUT 5 ; bit 5 GPIO pin 2 alert out #define ALERT_OUT 4 ; bit 4 GPIO pin 3 alert out ; end User changable ; * Calibration if you need it * ; !!!!!!!!!!!! Please see the note about OSCCAL at line 264 and up !!!!!!!!!!!!!!!!!!!!! #define COUNTS_PER_DEGREE_C D'245' ; x100 value, dot after 2. if diplayed temperatue too high at 100 C, decrease, then do COUNTS_OFFSET again, 245 is 2.45 counts / degree C. ; So if it displays 5 degrees too low, decrease COUNTS_OFSETT by 5 x 2.45 = 12 ;#define COUNTS_OFFSET D'438' ; set for correct degrees C in RS232 out at room temperature. #define COUNTS_OFFSET D'450' ; set for correct degrees C in RS232 out at room temperature. ; set baudrate, for small deviations of the internal oscillator this may need to be adapted. #define BIT_DELAY d'29' ; approx 29 for 9600 Bd, 27 for lower baudrates,depends on exact supply [zener] voltage too. ; end calibration ; * DO NOT CHANGE BEYOND THIS POINT * PROCESSOR p12f629 include ; define config fuses ; IF YOU WANT TO RUN VERIFY, BETTER HAVE COPY PROTECTION OFF ;-) __CONFIG _CPD_OFF & _WDT_ON & _PWRTE_OFF & _INTRC_OSC_NOCLKOUT & _BODEN_OFF & _MCLRE_OFF ; when !MRCLRE is asserted in INTOSC or RC mode, the internal clock oscillator is disabled. ; I/O ; output pin for RS232 #define TX_OUT 2 ; bit 2 GPIO pin 5 serial out TRUE equ 1 FALSE equ 0 MSB equ 7 SIGNED equ FALSE ; Set This To 'TRUE' if the routines for Multiplication & Division needs to be assembled as Signed Integer Routines. ; If 'FALSE' the above two routines ( D_mpy & D_div ) use unsigned arithmetic. ; define flags1 NEGATIVE_TEMPERATURE_FLAG equ D'1' FIRST_ZERO_SUPPRESSED_FLAG equ D'2' DIVIDE_BY_TEN_FLAG equ D'3' ; define flags2 ALARM_FLAG equ D'1' WATCHDOG_USED_FOR_TEMP_FLAG equ D'2' ; vars timer_0_count equ D'32' ; 2 bytes, counter for # of times tmr0 rolls (lo/hi byte) ; equ D'33' screen_register equ D'34' ; screen register for tmr0 roll over delay_counter equ D'36' tx_reg equ D'37' bit_count equ D'38' flags1 equ D'39' ; cleared on init temp equ D'40' NumH equ D'41' NumL equ D'42' TenK equ D'43' Thou equ D'44' Hund equ D'45' Tens equ D'46' Ones equ D'47' flags2 equ D'48' ; not cleared in init, saved after a watchdog reset baud_divider equ D'49' temp_c_l equ D'50' temp_c_h equ D'51' ACCaLO equ D'52' ACCaHI equ D'53' ACCbLO equ D'54' ACCbHI equ D'55' ACCcLO equ D'56' ACCcHI equ D'57' ACCdLO equ D'58' ACCdHI equ D'59' sign equ D'60' s_pclath equ D'61' s_fsr equ D'62' int_fsr_save equ D'63' temp_w equ D'64' temp_s equ D'65' ; macros to save and restore W and status register in interrupt. save_w_stat macro movwf temp_w swapf STATUS,W clrf STATUS ; extra force bank 0 clears IRP, RP1, RP0 movwf temp_s movfw PCLATH movwf s_pclath movfw FSR movwf s_fsr endm restore_w_stat macro movfw s_fsr movwf FSR movfw s_pclath movwf PCLATH swapf temp_s,W movwf STATUS swapf temp_w,F swapf temp_w,W endm ; code start org 0 goto start org 4 save_w_stat movlw '*' call tx_w int_end: movf CMCON, W ; read CMCON to end mismatch because of comparator output change ; bcf INTCON, T0IF ; timer 0 overflow interrupt flag ; bcf INTCON, GPIF ; port change interrupt flag bit ; bcf INTCON, INTF ; GP2/INT ; bcf PIR1, EEIF ; EEPROM write complete interrupt flag bcf PIR1, CMIF ; peripheral interrupts: clear comparator interrupt ; bcf PIR1, ADIF ; AD converter interrupt flag (12F675 only) ; bcf PIR1, TMR1IF ; timer 1 overflow interrupt flag restore_w_stat retfie start: ; load osc calibration for IntRC ; bit 7-2 CAL5:CAL0: 6-bit Signed Oscillator Calibration bits ; 111111 = Maximum frequency ; 100000 = Center frequency ; 000000 = Minimum frequency ; bit 1-0 Unimplemented: Read as '0' ; Uncomment as needed banksel OSCCAL ; call 0x3FF ; Get the oscillator calibration value, this assumes you did not accidently erase it, else the program will hang if not a retlwXX instruction ; But you can still set it here manually, see the dataheet. movlw B'10000000' ; midrange ; movwf OSCCAL ; Calibrate ; banksel 0 ; NOTE: =====================================writing to the option register seems to clear pin 2 GPIO5 if configured as output before this, so this needs to be done first????====================================== ; OPTION_REG bank 1, address 0x81 ; !GPPU INTEDG T0CS T0SE PSA PS2 PS1 PS0 banksel OPTION_REG movlw b'10001011' movwf OPTION_REG bsf OPTION_REG, NOT_GPPU ; no pullups banksel 0 clrf GPIO ;Init GPIO clrf flags1 ; CMCON bank 0, address 0x19 ; _ COUT _ CINV CIS CM2 CM1 CM0 ; 0 1 0 0 0 0 0 1 movlw B'00000111' ; analog in ; mode 001 differential in, out to GP2 ; CIS 1 selects GP0 in +, not used here ; GP0 comparator + input ; GP1 comparator - input ; GP2 comparator output ; ! INV movwf CMCON ; intialize I/O direction ; TRISIO bank 1, address 0x85 ; _ _ TRISIO5 TRISIO4 TRISIO3 TRISIO2 TRISIO1 TRISIO0 ; TRISIO 3 read only and reads always 1, rest R/W ; 1 = input, 0 = output banksel TRISIO movlw B'00111011' ; io port direction ; bit 7 not used ; bit 6 not used ; GP5 ALERT_OUT out, but normally tri-state configured as input pin 2 ; GP4 out free pin 3 ; GP3 Vpp / !MCRL in free pin 4 ; GP2 RS232 out pin 5 ; GP1 program clock in pin 6 ; GP0 program data in pin 7 bcf TRISIO, TX_OUT ; configure TX_OUT as output bsf TRISIO, ALERT_OUT ; configure ALERT_OUT as tristate movwf TRISIO ; set weak pullups on I/O port ; WPU bank 1 address 0x95 movlw b'00000000' ; bit 7 not used ; bit 6 not used ; bit 5 0 output ; bit 4 0 output ; bit 3 0 no pullup available on this pin ; bit 2 0 output ; bit 1 0 analog in comparator ; bit 0 0 analog in comparator movwf WPU ; comparator interrupt enable in peripheral interupt register PIE1, address 0x8c, bank 1 clrf PIE1 ; bsf PIE1, EEIE ; EEPROM write complete interrupt enable ; bsf PIE1, ADIE ; AD converter interrupt enable (12F675 only) ; bsf PIE1, TMR1IE ; timer 1 overflow interrupt enable ; bsf PIE1, CMIE ; comparator interrupt enable banksel 0 #ifndef FLASHING_OUTPUT call do_alarm #endif ; ! FLASHING_OUTPUT ; need to set this so RS232 receiver does not think it is a start bit, that receiver will sample at 1/2 bit after the zero from reset, and see a one now. #ifdef NON_INVERTING_RS232_OUT bcf GPIO, TX_OUT #else bsf GPIO, TX_OUT #endif ; check for power on reset ; About STATUS register bits: ; bit 4 TO: Time-out bit ; 1 = After power-up, CLRWDT instruction, or SLEEP instruction ; 0 = A WDT time-out occurred ; bit 3 PD: Power-down bit ; 1 = After power-up or by the CLRWDT instruction ; 0 = By execution of the SLEEP instruction ; must test condition of TO=1 to tell if power on reset. there is no sleep mode support. if not a POR, must be a WDT reset. jump to the POR or WDT routines. ; btfss STATUS, NOT_TO goto watchdog_type_test power_on_reset_handler: movlw 0x00 ; clear counters for measurement movwf timer_0_count movwf timer_0_count+1 clrf flags1 ; Set tWATCHDOG_USED_FOR_TEMP_FLAG to indicate that wdt timeouts are being used for rough temp measurements. ; This register is typically set elsewhere in a real application but for the purposes of this example, is set here. bcf flags2, WATCHDOG_USED_FOR_TEMP_FLAG ; clear wdt tracking register ; init timers clrwdt ; initialize wdt movlw 0x00 ; initialize timer0 movwf TMR0 ; and allow to free run ; delay to let tmr0 go past screen point nop nop nop nop nop ; *** IT IS HERE WE MEASURE TEMPERATURE **** ; test to see if timer0 rolls over timer_0_byte: ; count the number of tmr0's movf TMR0,W ; copy tmr0 value to working register movwf screen_register movlw 0x0A ; load masking value subwf screen_register, W ; subtraction to screen for FF -> 0 transition in tmr0 btfsc STATUS, C ; test carry flag for goto timer_0_byte ; loop back and test for FF -> 0 ; increment count lo byte incf timer_0_count, F ; incr count (lo byte) once for every tmr0 roll over btfss STATUS, Z ; test zero flag to see if need to increment hi byte of count (16 bit counter) goto timer_0_byte ; loop back and test until wdt reset ; increment count hi byte incf timer_0_count+1, F ; incr count (hi byte) once for every timer_0_count roll over goto timer_0_byte ; loop back and test until wdt reset ; test what type of interupt watchdog_type_test: ; test for wdt in temp measure or normal mode btfss flags2, WATCHDOG_USED_FOR_TEMP_FLAG goto normal_watchdog_handler ; vector to normal app wdt handler here. ; wdt temperature handler banksel 0 ; need to set this so RS232 receiver does not think it is a start bit, that receiver will sample at 1/2 bit after the zero from reset, and see a one now. #ifdef NON_INVERTING_RS232_OUT bcf GPIO, TX_OUT #else bsf GPIO, TX_OUT #endif ; calculate temp in degrees C ; temp_c = (timer_0_count - COUNTS_OFFSET) / COUNTS_PER_DEGREE_C (can become negative ). ; going to use ( (timer_0_count - COUNTS_OFFSET) * 100 ) / (COUNTS_PER_DEGREE_C * 100) to avoid floating point math. ; First test if timer_0_count < COUNTS_OFFSET, if so set sign NEGATIVE_TEMPERATURE_FLAG, and calculate COUNTS_OFFSET - timer_0_count. movfw timer_0_count ; low byte movwf ACCbLO movfw timer_0_count+1 ; high byte movwf ACCbHI movlw HIGH COUNTS_OFFSET movwf ACCaHI movlw LOW COUNTS_OFFSET movwf ACCaLO call D_sub ; Double Precision Subtraction ( ACCb - ACCa -> ACCb ) ; ACCb now timer_0_count - THRESHOLD btfsc STATUS, C ; positiv temperature goto temperature_subtract_done ; negative temperature bsf flags1, NEGATIVE_TEMPERATURE_FLAG movlw '-' call tx_w ; repeat subtraction the other way around movlw HIGH COUNTS_OFFSET movwf ACCbHI movlw LOW COUNTS_OFFSET movwf ACCbLO movfw timer_0_count ; low byte movwf ACCaLO movfw timer_0_count+1 ; high byte movwf ACCaHI call D_sub ; Double Precision Subtraction ( ACCb - ACCa -> ACCb ) ; ACCb now THRESHOLD - timer_0_count temperature_subtract_done: ; now multiply temp by 100, so we can divide by scalar * 100 to avoid floating point. ; Double Precision Multiply ( 16x16 -> 32 ) ; ( ACCb*ACCa -> ACCb,ACCc ) : 32 bit output with high word in ACCb ( ACCbHI,ACCbLO ) and low word in ACCc ( ACCcHI,ACCcLO ). ; results in ACCb(16 msb's) and ACCc(16 lsb's) movlw D'100' movwf ACCaLO movlw 0 movwf ACCaHI call D_mpyS ; Double Precision Multiply ( 16x16 -> 32 ) results in ACCb(16 msb's) and ACCc(16 lsb's) ; ACC now temp x 100 ; divide ACCc by the 100x COUNTS_PER_DEGREE_C movfw ACCcLO movwf ACCbLO movfw ACCcHI movwf ACCbHI movlw LOW COUNTS_PER_DEGREE_C movwf ACCaLO movlw HIGH COUNTS_PER_DEGREE_C movwf ACCaHI call D_divF ; Double Precision Divide ( 16/16 -> 16 ) ( ACCb/ACCa -> ACCb with remainder in ACCc ) : 16 bit output with Quotiont in ACCb (ACCbHI,ACCbLO) and Remainder in ACCc (ACCcHI,ACCcLO). ; ACCb now degrees C ; temp to temp_c movfw ACCbLO movwf temp_c_l ; will stay in range -255 to +255 movfw ACCbHI movwf temp_c_h ; Compare against specified SETPOINT_ON for alarm ; There are 4 possiblities: ; pos temp and pos setpoint process as normal temp 25 > setpoint 15 = alarm ; neg temp and pos setpoint do nothing ; pos temp and neg setpoint always alarm ; neg temp and neg setpoint process inverse temp -25 < setpoint -15 = no alarm #ifdef NEGATIVE_SETPOINT_ON ; negative setpoint btfss flags1, NEGATIVE_TEMPERATURE_FLAG ; pos temp and neg setpoint always alarm goto alarm_on ; neg temp and neg setpoint process inverse ; temperature to ACCa movfw temp_c_l movwf ACCaLO movfw temp_c_h movwf ACCaHI ; setpoint to ACCb movlw HIGH SETPOINT_ON movwf ACCbHI movlw LOW SETPOINT_ON movwf ACCbLO call D_sub ; Double Precision Subtraction ( ACCb - ACCa -> ACCb ) ; ACCb now SETPOINT_ON - temperature btfsc STATUS, C goto alarm_on goto test_alarm_off #else ; positive setpoint ; test if negative temp btfsc flags1, NEGATIVE_TEMPERATURE_FLAG ; neg temp and pos setpoint do nothing goto test_alarm_off ; positve temp, positive setpoint movlw HIGH SETPOINT_ON movwf ACCaHI movlw LOW SETPOINT_ON movwf ACCaLO call D_sub ; Double Precision Subtraction ( ACCb - ACCa -> ACCb ) ; ACCb now temprature - SETPOINT_ON btfsc STATUS, C goto alarm_on ; goto alarm_off goto test_alarm_off #endif ; end if positive setpoint test_alarm_off: ; Compare against specified SETPOINT_OFF for alarm off ; There are 4 possiblities: ; pos temp and pos setpoint off process as normal temp 15 < setpoint_off 25 = alarm off ; neg temp and pos setpoint off alarm off ; pos temp and neg setpoint off do nothing ; neg temp and neg setpoint off process inverse temp -15 > setpoint_off -25 = do nothing #ifdef NEGATIVE_SETPOINT_OFF ; negative setpoint btfss flags1, NEGATIVE_TEMPERATURE_FLAG ; pos temp and neg setpoint_off, do nothing goto end_processing ; neg temp and neg setpoint process inverse ; temperature to ACCb movfw temp_c_l movwf ACCbLO movfw temp_c_h movwf ACCbHI ; setpoint to ACCa movlw HIGH SETPOINT_OFF movwf ACCaHI movlw LOW SETPOINT_OFF movwf ACCaLO call D_sub ; Double Precision Subtraction ( ACCb - ACCa -> ACCb ) ; ACCb now temperature - SETPOINT_OFF btfsc STATUS, C goto alarm_off goto end_processing #else ; positive setpoint off ; test if negative temp btfsc flags1, NEGATIVE_TEMPERATURE_FLAG ; neg temp and pos setpoint_off, alarm off goto alarm_off ; positve temp, positive setpoint ; temperature to ACCa movfw temp_c_l movwf ACCaLO movfw temp_c_h movwf ACCaHI ; setpoint to ACCb movlw HIGH SETPOINT_OFF movwf ACCbHI movlw LOW SETPOINT_OFF movwf ACCbLO call D_sub ; Double Precision Subtraction ( ACCb - ACCa -> ACCb ) ; ACCb now SETPOINT_OFF - temperature btfsc STATUS, C goto alarm_off goto end_processing #endif ; end if positive setpoint off alarm_on: #ifdef INVERT_OUTPUT bcf flags2, ALARM_FLAG #else bsf flags2, ALARM_FLAG #endif goto end_processing alarm_off: #ifdef INVERT_OUTPUT bsf flags2, ALARM_FLAG #else bcf flags2, ALARM_FLAG #endif end_processing: banksel 0 ; print the temperature movfw temp_c_l movwf NumL ; low byte movfw temp_c_h movwf NumH ; high byte call print_16_ascii_dec movlw '°' call tx_w movlw 'C' call tx_w call tx_crlf ; soft_reset, clear conditions and reset for another rough temperature measurement clrwdt ; clear the wdt goto power_on_reset_handler ; return to reset checks non-temp measurement mode wdt handler normal_watchdog_handler: ; here if normal watchdog timeout main: ; do something here, remember to do a clrwdt from time to time. ; we hare here and in temp measurement sequentially, can use this to flash a LED. #ifdef FLASHING_OUTPUT call do_alarm #endif ; FLASHING_OUTPUT clrwdt bsf flags2, WATCHDOG_USED_FOR_TEMP_FLAG ; Ask for a temperature measurement, that will set or reset ALARM_FLAGS in flags2, flags2 and watchdog_status are saved over warm resets. wait_here_for_wd: goto wait_here_for_wd ; normal mode wdt timeout handler. since only running in rough temp measure mode, routine is just a place holder. ; ************************************************************************************************************************************************************ ; subroutines ; send_one_char ; the actual RS232 transmission routine, half-duplex, no-flow-control. ; See AN510 for an explanation tx_digit_in_w: addlw '0' ; zero tx_w: banksel 0 ; return ; movlw 'A' movwf tx_reg ; move W (char to send) to TXReg movlw 0x08 movwf bit_count ; send 8 bits ; send start bit #ifdef NON_INVERTING_RS232_OUT bsf GPIO, TX_OUT #else bcf GPIO, TX_OUT #endif nop nop nop nop call bit_delay ; send data bits send_next_bit: bcf STATUS, C rrf tx_reg, 1 ; rotate TXReg btfsc STATUS, C goto set_tx clear_tx: nop ; to get equal set/clear times #ifdef NON_INVERTING_RS232_OUT bsf GPIO, TX_OUT #else bcf GPIO, TX_OUT #endif goto ready_tx set_tx: #ifdef NON_INVERTING_RS232_OUT bcf GPIO, TX_OUT #else bsf GPIO, TX_OUT #endif goto ready_tx ready_tx: call bit_delay decfsz bit_count,1 ; decrement bit counter (8..0) goto send_next_bit ; loop for next data bit nop nop nop nop nop ; send first stop bit #ifdef NON_INVERTING_RS232_OUT bcf GPIO, TX_OUT #else bsf GPIO, TX_OUT #endif call bit_delay ; send second stop bit ; call bit_delay return ; This routine is calibrated with BIT_DELAY to 104 us, that makes BAUD_DIVIDER 1 for 9600 Bd, 2 for 4800 Bd, 4 for 2400 Bd, 8 for 1200 Bd, 16 for 600 Bd, 32 for 300 Bd, 64 for 150 Bd, and 128 for 75 Bd. bit_delay: ; prevent watchdog from interrupting serial com clrwdt ; should be called on a regular basis ; Multiply bit delay for lower baudrates. movlw BAUD_DIVIDER movwf baud_divider baud_divider_loop: ; this is the delay of about 104 uS for 9600 Bd movlw BIT_DELAY ; move baud delay constant to W movwf delay_counter ; initialize delay counter us100_delay_loop: decfsz delay_counter ; decrement delay counter goto us100_delay_loop decfsz baud_divider goto baud_divider_loop return print_16_ascii_dec: ; prints 16 bit value in registers NumH (high byte) and NumL (low byte) in ASCII decimal banksel 0 call bin_to_bcd ; Takes number in NumH NumL Returns decimal in TenK Thou Hund Tens Ones ; suppress leading zeros bsf flags1, FIRST_ZERO_SUPPRESSED_FLAG print_tenk: movfw TenK call print_nibble print_thou: movfw Thou call print_nibble print_hund: movfw Hund call print_nibble print_tens: movfw Tens call print_nibble test_dot: btfss flags1, DIVIDE_BY_TEN_FLAG goto print_ones ; force dot printing just before last digit movlw '.' call tx_w bcf flags1, DIVIDE_BY_TEN_FLAG print_ones: ; zero or not bcf flags1, FIRST_ZERO_SUPPRESSED_FLAG movfw Ones call print_nibble return print_nibble: ; print nibble in w, test for zero suppression btfss flags1, FIRST_ZERO_SUPPRESSED_FLAG goto pri_nibble_out ; test if zero andlw 0x0f btfsc STATUS, Z ; if zero return return ; if not zero print it pri_nibble_out: call tx_digit_in_w ; clear the zero suppression flag bcf flags1, FIRST_ZERO_SUPPRESSED_FLAG return tx_crlf: ; prints CR LF movlw D'13' call tx_w ; CR movlw D'10' ; LF call tx_w return ; Double Precision Subtraction ( ACCb - ACCa -> ACCb ) D_sub: ; call neg_A ; At first negate ACCa; Then add ; inline comf ACCaLO, F ; negate ACCa ( -ACCa -> ACCa ) incf ACCaLO, F btfsc STATUS, Z decf ACCaHI, F comf ACCaHI, F ; Double Precision Addition ( ACCb + ACCa -> ACCb ) D_add: movf ACCaLO, W addwf ACCbLO, F ; add lsb btfsc STATUS, C ; add in carry incf ACCbHI, F movf ACCaHI, W addwf ACCbHI, F ; add msb retlw 0 ; division macro divMac MACRO LOCAL NOCHK LOCAL NOGO bcf STATUS, C rlf ACCdLO, F rlf ACCdHI, F rlf ACCcLO, F rlf ACCcHI, F movf ACCaHI, W subwf ACCcHI, W ; check if a>c btfss STATUS, Z goto NOCHK movf ACCaLO, W subwf ACCcLO, W ; if msb equal then check lsb NOCHK: btfss STATUS,C ; carry set if c>a goto NOGO movf ACCaLO, W ; c-a into c subwf ACCcLO, F btfss STATUS, C decf ACCcHI, F movf ACCaHI, W subwf ACCcHI, F bsf STATUS, C ; shift a 1 into b (result) NOGO: rlf ACCbLO, F rlf ACCbHI, F ENDM ; Double Precision Divide ( 16/16 -> 16 ) ; ( ACCb/ACCa -> ACCb with remainder in ACCc ) : 16 bit output with Quotiont in ACCb (ACCbHI,ACCbLO) and Remainder in ACCc (ACCcHI,ACCcLO). ; NOTE : Before calling this routine, the user should make sure that the Numerator(ACCb) is greater than Denominator(ACCa). ; If the case is not true, the user should scale either Numerator or Denominator or both such that Numerator is greater than the Denominator. setup: movlw .16 ; for 16 shifts movwf temp movf ACCbHI, W ; move ACCb to ACCd movwf ACCdHI movf ACCbLO, W movwf ACCdLO clrf ACCbHI clrf ACCbLO retlw 0 D_divF: IF SIGNED CALL S_SIGN ENDIF call setup clrf ACCcHI clrf ACCcLO ; use the divMac macro 16 times divMac divMac divMac divMac divMac divMac divMac divMac divMac divMac divMac divMac divMac divMac divMac divMac ; IF SIGNED btfss sign, MSB ; check sign if negative retlw 0 goto neg_B ; negate ACCa ( -ACCa -> ACCa ) ELSE retlw 0 ENDIF ; Assemble this section only if Signed Arithmetic Needed IF SIGNED S_SIGN: movf ACCaHI,W xorwf ACCbHI,W movwf sign btfss ACCbHI, MSB ; if MSB set go & negate ACCb goto chek_A ; comf ACCbLO ; negate ACCb incf ACCbLO btfsc STATUS,Z decf ACCbHI comf ACCbHI chek_A: btfss ACCaHI, MSB ; if MSB set go & negate ACCa retlw 0 goto neg_A ENDIF ; Double Precision Multiply ( 16x16 -> 32 ) ; ( ACCb*ACCa -> ACCb,ACCc ) : 32 bit output with high word in ACCb ( ACCbHI,ACCbLO ) and low word in ACCc ( ACCcHI,ACCcLO ). ; results in ACCb(16 msb's) and ACCc(16 lsb's) D_mpyS: IF SIGNED CALL S_SIGN ENDIF call setup mloop: rrf ACCdHI, F ; rotate d right rrf ACCdLO, F btfsc STATUS, C ; need to add? call D_add rrf ACCbHI, F rrf ACCbLO, F rrf ACCcHI, F rrf ACCcLO, F decfsz temp, F ; loop until all bits checked goto mloop IF SIGNED btfss sign,MSB retlw 0 comf ACCcLO, F ; negate ACCa ( -ACCa -> ACCa ) incf ACCcLO, F btfsc STATUS, Z decf ACCcHI, F comf ACCcHI, F btfsc STATUS, Z neg_B: comf ACCbLO, F ; negate ACCb incf ACCbLO, F btfsc STATUS, Z decf ACCbHI, F comf ACCbHI, F retlw 0 ELSE retlw 0 ENDIF ; Binary-to-BCD. Written by John Payson. ; From: http://www.dattalo.com/technical/software/pic/bcd.txt bin_to_bcd: ; Takes number in NumH NumL, Returns decimal in TenK Thou Hund Tens Ones swapf NumH, w andlw 0x0F ; *** PERSONALLY, I'D REPLACE THESE 2 addlw 0xF0 ; *** LINES WITH "IORLW 11110000B" -AW movwf Thou addwf Thou, f addlw 0xE2 movwf Hund addlw 0x32 movwf Ones movf NumH, w andlw 0x0F addwf Hund, f addwf Hund, f addwf Ones, f addlw 0xE9 movwf Tens addwf Tens, f addwf Tens, f swapf NumL, w andlw 0x0F addwf Tens, f addwf Ones, f rlf Tens, f rlf Ones, f comf Ones, f rlf Ones, f movf NumL, w andlw 0x0F addwf Ones, f rlf Thou, f movlw 0x07 movwf TenK ; At this point, the original number is equal to TenK * 10000 + Thou * 1000 + Hund * 100 + Tens * 10 + Ones if those entities are regarded as two's compliment binary. ; To be precise, all of them are negative except TenK. ; Now the number needs to be normalized, but this can all be done with simple byte arithmetic. movlw 0x0A ; Ten Lb1: addwf Ones, f decf Tens, f btfss STATUS, C goto Lb1 Lb2: addwf Tens, f decf Hund, f btfss STATUS, C goto Lb2 Lb3: addwf Hund, f decf Thou, f btfss STATUS, C goto Lb3 Lb4: addwf Thou, f decf TenK, f btfss STATUS, C goto Lb4 return do_alarm: ; test for alarm banksel 0 btfss flags2, ALARM_FLAG goto no_alarm ; alarm bsf GPIO, ALERT_OUT ; alarm on banksel TRISIO bcf TRISIO, ALERT_OUT ; enable output banksel 0 return no_alarm: #ifdef TRISTATE_ALARM_OFF ; bcf GPIO, ALERT_OUT ; alarm off ; no hard pulldown, allow OR of output banksel TRISIO bsf TRISIO, ALERT_OUT ; port pin to tristate banksel 0 #else ; ! TRISTATE_ALARM_OFF ; hard pulldown banksel TRISIO bcf TRISIO, ALERT_OUT ; port pin to output banksel 0 bcf GPIO, ALERT_OUT ; alarm off #endif ; TRISTATE_ALARM_OFF return end