title "SBC Reset Program Using Serial Break" #include "p10f200.inc" __MAXROM D'254' ; Warn if calibration vector is overwritten. ; Used pins: ; GP0: UART TX input ; GP1: Reserved ; GP2: Reset controller (Active High) ; GP3: Enable (1)/Disable (0) Reset controller (GP2 holds current value) ; Both option pins, switchable at runtime, checked every time PIC ; wakes from sleep. ; Macros ; Check value of reset pin and toggle/clear/set it. For debugging. toggle_reset macro movlw B'00000100' xorwf GPIO, F endm clear_reset macro movlw B'11111011' andwf GPIO, F endm set_reset macro movlw B'00000100' iorwf GPIO, F endm ; Constants ; Milliseconds ifndef LONG_BREAK MIN_BREAK_LENGTH equ D'200' WDT_PRESCALER equ D'001' ; (/2, ~36ms or so.) else MIN_BREAK_LENGTH equ D'800' WDT_PRESCALER equ D'011' ; (/8, ~144ms or so.) endif ; Timer runs at 1 MHz. With 256-times prescaler, this is ~3096 Hz. Manually ; create a 256-times postscaler by counting timer overflows to get ~15.26 Hz. ; Wait the integer number of overflows closest to _but not above_ ; MIN_BREAK_LENGTH time before triggering reset. Applications talking to this ; firmware should assert break for _at least_ MIN_BREAK_LENGTH time. TIMER_OV_THRESHOLD equ (MIN_BREAK_LENGTH*D'1000')/D'256'/D'256' if TIMER_OV_THRESHOLD == 0 ; Manual claims there are user warnings, but I can't find how to make one. error "TIMER_OV_THRESHOLD is 0, timer busy wait outer loop will wait 256 times." error "Raise break length from #v(MIN_BREAK_LENGTH)." endif ; RAM TIMER_OV_COUNT equ 0x10 ; Software postscaler. CURR_TMR0 equ 0x11 ; Current timer value used for comparison. start: movwf OSCCAL ; All GPIOs are inputs on reset (TRISGPIO is all ones). ; Set GPIO GP2 to output. We can tolerate glitches on Reset, about 8us ; according to logic analyzer. This isn't really enough to do anything. ; We could add a filter cap or something if really necessary. movlw B'00001011' tris GPIO call set_wdt_prescaler ; Also enables weak pullups. btfsc STATUS, NOT_TO ; If this was a WDT reset, don't reset/disturb GPIO. clrf GPIO ; Initialize GP2 to "on" ASAP (controls an open collector/drain ; line, so an active low reset is active high from GP2's POV). call detect_space ; Only returns if a zero/space was detected. andlw B'00000001' ; If not long enough, then wait for watchdog. skpnz sleep movlw B'00000100' ; Otherwise, reset conditions have been met. Toggle reset. xorwf GPIO, F sleep ; Returns in W: ; 0- Zero/Space was NOT long enough. ; 1- Zero/Space was long enough. detect_space: movfw GPIO ; Check GP0 pin for a space (zero). andlw B'00000001' skpz ; Nothing to do if pin is set. sleep ; Wait for watchdog. movfw GPIO ; Check GP3 pin for manual override. andlw B'00001000' skpnz ; It may be useful to disable reset manually if you need break signal. sleep ; If set, go back to sleep. ; Otherwise, check that that pin is zero for long enough. ; Required sequence to change prescaler, according to datasheet. clrwdt clrf TMR0 ; Clear timer before switching prescaler (because writes will ; clear the prescaler otherwise). TMR0 should still ; be ~0x00 when we start looping because of internal ; synchronization (see Figure 6-2 of datasheet). ; Set timer prescaler to 256 (tick every 0.256 ms), use FOSC/4 as timer source. movlw B'10010111' option movlw TIMER_OV_COUNT ; Init outer loop var to 0. movwf FSR clrf INDF timer_busy_wait_outer: movlw CURR_TMR0 ; Init inner loop var to 0. movwf FSR clrf INDF timer_busy_wait_inner: movfw GPIO ; Read value of GP0. andlw B'00000001' ; If pin is set, then break. bnz space_not_long_enough movfw TMR0 ; Read timer. subwf INDF, W ; Has it elapsed to the next value yet? bz timer_busy_wait_inner ; If not, keep waiting. clrwdt ; 256/1000000 seconds isn't even close to WDT limit ; without prescaler (18 ms). incfsz INDF, F ; We assume that the timer is slow enough to only incr by ; 1 each time we check. goto timer_busy_wait_inner ; If the timer hasn't overflowed to 0, keep ; waiting. movlw TIMER_OV_COUNT ; Check that the timer overflowed an integral movwf FSR ; number of times closest to MIN_BREAK_LENGTH. incf INDF movlw TIMER_OV_THRESHOLD subwf INDF, W bz space_long_enough goto timer_busy_wait_outer space_not_long_enough: call time_zero_cleanup retlw 0 space_long_enough: call time_zero_cleanup retlw 0x01 time_zero_cleanup: ; Required sequence to change prescaler, according to datasheet. clrwdt clrf TMR0 movlw B'10011111' option clrwdt ; Set WDT prescaler to correct value based on break length settings, ; use FOSC/4 as timer source. set_wdt_prescaler: movlw B'10011000' | WDT_PRESCALER option retlw 0 __CONFIG _CP_OFF & _WDT_ON & _MCLRE_OFF ; Use bitwise AND!! __IDLOCS 0x0110 ; Version. end