To sense whether the door is open or closed the circuits rely on a common type of magnetic proximity switch called magnetic contact. It's widely used in home security systems. They come in pairs with one block containing a magnet and the other housing a reed switch. The switch may be normally open or normally closed or may be a single-pole double-throw type. In the circuits below I use an NO and an NC type. Check out Honeywell's magnetic contacts catalog to see a wide variety of models. The ones I used had no brand and may have been made in Taiwan.
The circuits that were installed some four years ago:
VR = L7805 +5V voltage regulator 1000mA
MCU = Microchip PIC10F202 microcontroller
OPTO1, OPTO2 = Avago HCPL-817 optoisolator
RS = reed switch (magnetic contact)
Q1 = S9013 NPN transistor
SPKR = 8-ohm. 2-watt, 3-inch speaker
RS and magnet are fastened to the door jamb and door, respectively. When door is closed, magnet actuates RS and its contacts close. This shunts current to ground. When door is opened, RS contacts open, and current passes through LED of OPTO1 causing optotransistor to turn on. MCU then senses a low on its input pin. With this setup cutting either of the wires to RS will cause OPTO1 to be energized leading to the MCU playing the melody that indicates the door has just been opened. The drawback of this configuration is that it's always dissipating power. To minimize consumption R1 should be as large as possible but should still be able to supply sufficient current to OPTO1's LED to turn the optotransistor on.
MCU weak pull-ups are enabled in firmware so no external pull-up resistors are necessary on the optotransistors. GP3 is also the MCLR pin and is very sensitive to transients. So pcb track length to the optoisolator should be kept as short as possible.
R3 potentiometer allows adjustment of speaker volume to desired level.
When the door has been left open for a certain amount of time, MCU will start playing a different melody. The tune will be played over and over with a predetermined interval between plays until the door is closed, or until PB is momentarily pressed. PB prevents/stops the melody from playing (closing the door resets the variables and the "door has been left open" melody will play again if the door is left open once more). Firmware is such that RS switching and PB presses are detected and processed only when no melody is playing.
VR = L7805 +5V voltage regulator 1000mA
MCU = Microchip PIC10F202 microcontroller
OPTO1 = Avago HCPL-817 optoisolator
RS = reed switch (magnetic contact)
Q1 = 2N7000 MOSFET transistor
SPKR = 8-ohm. 250-mW, 2-inch speaker
In the above circuit (labeled "office circuit"), RS is open when magnet is in close proximity (door closed). This cuts off current to the optoisolator and therefore optotransistor is off. RS contacts close when the door is opened.
As for PB you may be wondering why I chose to use a normally closed momentary contact pushbutton. I actually can't recall exactly. It now strikes me as an odd (and bad) choice. It could've been due to nonavailability at that time of a normally open button. As you will see in the firmware I ran into problems with this set-up. The 5.1K pull-down resistor was too high and the internal pull-ups have to be disabled while GP3 is being read. Rather than having a pull-down resistor I should've just used the internal pull-up and connected the normally-closed PB to ground (instead of VDD). This would of course have necessitated changes in firmware--PB would now be deemed pressed if a high was sensed.
Playing melodies
To make music we need notes and to produce notes we need different frequencies. To create frequencies using a microcontroller and a speaker we can feed the speaker a square wave with a 50% duty cycle and a frequency corresponding to the note we wish to play. The following table summarizes the notes that the firmware below uses. Other frequencies from C0 to B10 can of course be utilized.Note | Pitch | Frequency f (Hz) | Period T = 1/f (microseconds) | T/2 |
---|---|---|---|---|
ti | B4 | 493.88 | 2024.8 | 1012.4 |
do | C5 | 523.25 | 1911.1 | 955.57 |
re | D5 | 587.33 | 1702.6 | 851.31 |
mi | E5 | 659.26 | 1516.8 | 758.43 |
fa | F5 | 698.46 | 1431.7 | 715.86 |
sol | G5 | 783.99 | 1275.5 | 637.76 |
la | A5 | 880.00 | 1136.4 | 568.18 |
ti | B5 | 987.77 | 1012.4 | 506.19 |
do | C6 | 1046.5 | 955.57 | 477.78 |
re | D6 | 1174.7 | 851.28 | 425.64 |
mi | E6 | 1318.5 | 758.44 | 379.22 |
In the firmware implementation the sole timer that exists in the PIC10F202 is used to determine when to switch the speaker on and off to produce the desired note. The 10F202 has a fixed internal clock frequency of 4MHz. With timer 0 prescaler set to 1:4
TMR0
rolls over to zero every 1 µs x 4 x TMR0
counts. Shifting the variables around we have: TMR0
counts = (T/2) / (1 µs x 4). Computed values are given in the following table. Pitch | number of timer 0 counts given clock = 4MHz and prescale = 1:4 |
---|---|
B4 | 253 |
C5 | 239 |
D5 | 213 |
E5 | 190 |
F5 | 179 |
G5 | 159 |
A5 | 142 |
B5 | 127 |
C6 | 119 |
D6 | 106 |
E6 | 95 |
In order to obtain the above counts
TMR0
is loaded with an initial value = 256 - TMR0
counts.By energizing the speaker for T/2 and de-energizing it for T/2 the note of the desired frequency can be played. In creating a melody, however, we also need to control how long each note is played. Thus in the firmware this time duration of play is specified with each note. Since T/2 varies for every note the duration of play must vary accordingly as well in order for each note to play for the same length of time. This is computed as follows:
L1 x T1 = L2 x T2
where
L1 = play duration of note 1
T1 = period of note 1
L2 = play duration of note 2
T2 = period of note2
In the firmware E6 is arbitrarily set to 200. This leaves some room for a few more higher notes that may be necessary for other tunes. Table below lists the computed play durations.
Pitch | Play duration of each note (E6 used as standard and arbitrarily set to 200) |
---|---|
B4 | 75 |
C5 | 79 |
D5 | 89 |
E5 | 100 |
F5 | 106 |
G5 | 119 |
A5 | 133 |
B5 | 150 |
C6 | 159 |
D6 | 178 |
E6 | 200 |
In the
PlayNote
subroutine variable LENGTH
determines how many times a note plays.Firmware
Since the firmware for the two circuits above are almost the same except for the melodies they play, I just had one program and had preprocessor defines to determine which part of the code would be assembled. By commenting out either#define home
or #define office
the desired firmware can be assembled. The time duration that a note is played is implemented through the variable
LENGTH
, or in lieu of that by using a literal, and through the variable CYCLES
. Pauses (silence) between notes when necessary are implemented using a 100ms delay routine. Because there's only one timer, timer 0 is used for both playing the notes and for polling of the switches. The prescale values are different for the two applications and so
OPTION
register is edited accordingly when switching between the two. For the office circuit OPTION
is also updated when weak pull-ups have to be temporarily disabled when the pushbutton is being polled.Firmware debounces all switch readings. The last eight readings are stored in one 8-bit variable and the switch is deemed not bouncing if the the variable is equal to 0x00 or 0xFF. Any other value means the switch is bouncing. Upon detection of a falling edge MCU plays one sequence of musical notes, and on detection of rising edge a different melody, to distinguish between opening and closing of the door.
I coded the firmware in PIC assembly using Microchip's MPLAB IDE. You'll probably be perplexed by the code since it uses a lot of homebrew defines and macros. On my computer they're in the include file "et.inc" and thus the reason for the
#include et.inc
.
; ******************************************************************************** ; ; DOOR OPENED, CLOSED, AND LEFT OPEN TOO LONG DETECTOR ; WITH CORRESPONDING MELODIES FOR EACH CONDITION ; October 2008 ; ; MPLAB IDE ; ; ******************************************************************************** processor 10F202 #include p10F202.inc #include et.inc __config _MCLRE_OFF & _CP_OFF & _WDT_OFF cblock 0x08 COUNTER:3 ; loop counters CYCLES ; determines how long the note sounds; value of 4 makes the note sound for approx 0.3sec LENGTH:2 ; number of times a frequency (note) will play so that all frequencies will play with the same time duration. NOTE ; T/2 in terms of TMR0 initial value SW_VAL ; each bit is a reed switch reading; bit0 contains the latest reading, ; bit7 the oldest; bits are shifted left during each TMR0 rollover PB_VAL ; each bit is a push button switch reading; bit0 has the latest reading; ; bits are shifted left during each TMR0 rollover FEN ; assortment of flag and enable bits for various purposes DOOR_OPEN_COUNT:2 ; counts number of elapsed 4.08ms, when count has exceeded preset level, ; chime sounds alerting door open condition endc ; ********************************************** ; !! SET THE CORRECT DEFINES !! ; is firmware for office or for home? #define office ; #define home ; ********************************************** ; ********************************************** ; change the following constants if need be ; number of seconds door is open before door-open-too-long chime plays #define open_time .30 ; time interval in seconds between door-open-too-long chime ; make sure this value is less than open_time since it will be deducted from it #define alert_interval .20 ; ********************************************** #define spkr GPIO,2 ; output speaker #define reedsw GPIO,0 ; input from reed switch / optoisolator #define pb GPIO,3 ; momentary N.O. push button; ; pressing pb prevents/stops door-open-too-long melody from playing ; macro for playing the note ; _note is the note's T/2 (half period) ; _length is the number of times the note is played key macro _note, _length movlf _note, NOTE movlf _length, LENGTH call PlayNote endm ; see wikipedia article "scientific pitch notation" for table of frequency values of notes ; B4 = ti = 493.88 Hz, period = 2.0248 ms, T/2 = 1.0124 us ; C5 = do = 523.25 Hz, period = 1.9111 ms, T/2 = 955.57 us ; D5 = re = 587.33 Hz, period = 1.7026 ms, T/2 = 851.31 us ; E5 = mi = 659.26 Hz, period = 1.5168 ms, T/2 = 758.43 us ; F5 = fa = 698.46 Hz, period = 1.4317 ms, T/2 = 715.86 us ; G5 = sol = 783.99 Hz, period = 1.2755 ms, T/2 = 637.76 us ; A5 = la = 880.00 Hz, period = 1.1364 ms, T/2 = 568.18 us ; B5 = ti = 987.77 Hz, period = 1.0124 ms, T/2 = 506.19 us ; C6 = do = 1046.5 Hz, period = 955.57 us, T/2 = 477.78 us ; D6 = re = 1174.7 Hz, period = 851.28 us, T/2 = 425.64 us ; E6 = mi = 1318.5 Hz, period = 758.44 us, T/2 = 379.22 us ; given internal clock = 4Mhz and timer0 prescaler = 1:4 ; timer zero rolls over to zero every 1microsecond x 4 x TMR0 steps ; TMR0 steps = (T/2) / (1 us x 4) ; TMR0 initial value = 256 - TMR0 steps ; the following values should be loaded into TMR0 in order to produce the above notes, respectively #define B4 .256 - .253 #define C5 .256 - .239 #define D5 .256 - .213 #define E5 .256 - .190 #define F5 .256 - .179 #define G5 .256 - .159 #define A5 .256 - .142 #define B5 .256 - .127 #define C6 .256 - .119 #define D6 .256 - .106 #define E6 .256 - .95 ; in the PlayNote subroutine LENGTH determines how many times the note will play. ; since T/2 for every note is different LENGTH must be different in order for each note to play for the same length of time ; LENGTH is computed as follows: L1 x T1 = L2 x T2 ; where L1 = length note 1, T1 = period of note 1, L2 = length of note 2, T2 = period of note2 ; arbitrarily set LENGTH for E6 = 200 (leave room for a few more higher notes) #define LB4 .75 #define LC5 .79 #define LD5 .89 #define LE5 .100 #define LF5 .106 #define LG5 .119 #define LA5 .133 #define LB5 .150 #define LC6 .159 #define LD6 .178 #define LE6 .200 #define sw_level FEN,0 ; voltage level: 1 = hi, 0 = low #define override FEN,1 ; DoorOpenTooLongChime override flag; 1 = override active, 0 = no override #define option_poll_sw b'10000011' ; value to copy to OPTION register when polling switches ; pullups enabled, prescaler to timer0, prescaler = 1:16 #define option_chime b'10000001' ; value to copy to OPTION register in chime routine ; pullups enabled, prescaler to timer0, prescaler = 1:4 #define option_no_wpu b'11000011' ; value to copy to OPTION register when polling switches but with weak pull-ups off #define tmr_init_val .11 ; initial value for timer0, given 4Mhz clock and prescaler 1:16, TMR0 will rollover every 3.92ms ; 3.92ms was chosen so that in the check_level routine open_time and alert_interval are both ; equivalent to seconds; every time DOOR_OPEN_COUNT rolls over, time elapsed = 3.92ms x 256 = 1.004sec ; ======================================================================================= ; ======================================================================================= org 0x0 movwf OSCCAL ; calibrate the chip goto initialize ; ======================================================================================= ; subroutines ; ; 10F202 can only call subroutines if they are located in first 256 memory locations ; ======================================================================================= ; --------------------------------------------------------------------------------- ; Notes are played by switching the speaker on and off at the frequency of the note. ; the note is played for a specified time as determined by LENGTH and CYCLES PlayNote: movff NOTE, TMR0 movff LENGTH, LENGTH+1 movff CYCLES, COUNTER bsf spkr loop_note dnxnotzero TMR0 goto loop_note movff NOTE, TMR0 toggle spkr decfsz LENGTH+1,f goto loop_note movff LENGTH, LENGTH+1 decfsz COUNTER,f goto loop_note bcf spkr return ; --------------------------------------------------------------------------------- ; --------------------------------------------------------------------------------- OptionChime: movlw option_chime option return ; --------------------------------------------------------------------------------- ; --------------------------------------------------------------------------------- OptionPollSw: movlw option_poll_sw option return ; --------------------------------------------------------------------------------- ; --------------------------------------------------------------------------------- OptionNoWPU: movlw option_no_wpu option return ; --------------------------------------------------------------------------------- ; ************************************************************ ; following subroutines are for office circuit melodies #ifdef office ; --------------------------------------------------------------------------------- ; the following melody is played when door is opened (falling edge detected) DoorJustOpenedChime: call OptionChime movlf .4, CYCLES key C5, LC5 key E5, LE5 key B5, .255 call OptionPollSw return ; --------------------------------------------------------------------------------- ; --------------------------------------------------------------------------------- ; the following melody is played when door is closed (rising edge detected) DoorClosed: call OptionChime movlf .2, CYCLES key A5, LA5 key F5, LF5 movlf .5, CYCLES key D5, LD5 key C5, LC5 call OptionPollSw return ; --------------------------------------------------------------------------------- ; --------------------------------------------------------------------------------- ; the following melody is played when door has been left open for too long DoorOpenTooLongChime: dnxbit1 override ; don't play chime if override button had been pressed return call OptionChime movlf .2, CYCLES movlf .3, COUNTER+2 door_open_loop1 key C5, LC5 key E5, LE5 key B5, LB5 decfsz COUNTER+2,f goto door_open_loop1 movlf .3, COUNTER+2 door_open_loop2 key C5, LC5 key E5, LE5 key A5, LA5 decfsz COUNTER+2,f goto door_open_loop2 key A5, LA5 call OptionPollSw return ; --------------------------------------------------------------------------------- #endif ; ifdef office ; ************************************************************ ; ************************************************************ ; following subroutines are for home circuit melodies #ifdef home ; --------------------------------------------------------------------------------- ; approximately 100 millisecond delay D100ms clrf COUNTER movlf .130, COUNTER+1 d100ms_loop decfsz COUNTER,f goto d100ms_loop decfsz COUNTER+1,f goto d100ms_loop return ; --------------------------------------------------------------------------------- ; --------------------------------------------------------------------------------- ; the following melody is played when door is opened (falling edge detected) ; "mary had a little lamb (partial)" melody DoorJustOpenedChime: call OptionChime movlf .3, CYCLES key B5, .255 key A5, LA5 key G5, LG5 key A5, LA5 call D100ms key B5, LB5 call D100ms key B5, LB5 call D100ms key B5, .255 call OptionPollSw return ; --------------------------------------------------------------------------------- ; --------------------------------------------------------------------------------- ; the following melody is played when door is closed (rising edge detected) DoorClosed: ; none to be played return ; --------------------------------------------------------------------------------- ; --------------------------------------------------------------------------------- ; the following melody is played when door has been left open for too long ; "mary had a little lamb (complete)" melody DoorOpenTooLongChime: dnxbit1 override ; don't play chime if override button had been pressed return call OptionChime movlf .3, CYCLES key B5, .255 key A5, LA5 key G5, LG5 key A5, LA5 call D100ms key B5, LB5 call D100ms key B5, LB5 call D100ms key B5, .255 key A5, LA5 call D100ms key A5, LA5 call D100ms key A5, .255 key B5, LB5 key D6, LD6 call D100ms key D6, .255 key B5, .255 key A5, LA5 key G5, LG5 key A5, LA5 call D100ms key B5, LB5 call D100ms key B5, LB5 call D100ms key B5, LB5 call D100ms call D100ms key B5, LB5 key A5, LA5 call D100ms key A5, LA5 key B5, LB5 key A5, LA5 key G5, .255 call OptionPollSw return ; --------------------------------------------------------------------------------- #endif ; ifdef home ; ************************************************************ ; ======================================================================================= ; main program ; ======================================================================================= ; --------------------------------------------------------------------------------- initialize: movlw b'1001' tris GPIO call OptionPollSw clrf GPIO ; speaker off clrf DOOR_OPEN_COUNT clrf DOOR_OPEN_COUNT+1 movlf 0xFF, SW_VAL ; all bits hi, door considered closed bsf sw_level ; level = hi, door considered closed movlf 0xFF, PB_VAL ; all bits hi, push button considered unpressed bcf override ; manual override flag initially off movlf tmr_init_val, TMR0 ; --------------------------------------------------------------------------------- main: poll_timer0: dnxnotzero TMR0 ; poll TMR0 until it rolls over goto poll_timer0 movlf tmr_init_val, TMR0 poll_timer0_end: ; check switch level if zero -- if it is this means that door is open ; if door has been left open for amount of time = open_time then sound the door-open-too-long chime ; and keep sounding the melody every alert_interval amount of time check_level: dnxbit1 sw_level goto check_level_end incfsz DOOR_OPEN_COUNT,f goto check_level_end incf DOOR_OPEN_COUNT+1,f dnxltlit DOOR_OPEN_COUNT+1, open_time goto check_level_end call DoorOpenTooLongChime movlf open_time - alert_interval, DOOR_OPEN_COUNT+1 check_level_end: ; poll and check reed switch; store value in 8-bit record ; if SW_VAL = 0xFF AND sw_level = 0 then let sw_level = 1, this is a rising edge detect and chime for door-just-closed is played ; if SW_VAL = 0x0 AND sw_level = 1 then let sw_level = 0, this is a falling edge detect and chime for door-just-opened is played ; if SW_VAL > 0 AND SW_VAL < 0xFF then switch is bouncing, so do nothing ; if SW_VAL = 0xFF AND sw_level = 1 then there is no level change, so do nothing ; if SW_VAL = 0 AND sw_level = 0 then there is no level change, so do nothing check_reedsw: rlf SW_VAL,f ; discard oldest switch reading bcf SW_VAL,0 ; carry bit will be moved into bit zero so clear bit0 dnxbit1 reedsw bsf SW_VAL,0 dnxeqlit SW_VAL, 0xFF goto all_bits_hi dnxzero SW_VAL goto all_bits_lo goto check_reedsw_end all_bits_hi: dnxbit1 sw_level goto check_reedsw_end bsf sw_level ; rising edge detected - door has just been closed clrf DOOR_OPEN_COUNT clrf DOOR_OPEN_COUNT+1 bcf override call DoorClosed goto check_reedsw_end all_bits_lo: dnxbit0 sw_level goto check_reedsw_end bcf sw_level ; falling edge detected - door has just been opened call DoorJustOpenedChime check_reedsw_end: ; check the override button if it's been pressed ; if door is open (i.e., sw_level = 0) AND pb is pressed (i.e., PB_VAL = 0) ; then set override flag -- this will prevent DoorOpenTooLongChime from playing check_pb: rlf PB_VAL,f ; discard oldest switch reading bcf PB_VAL,0 ; carry bit will be moved into bit zero so clear bit0 ; ************************************************************ ; the following compensates for a flaw in the circuit: ; the 5.1K pulldown resistor for PB is too high, given the 16k to 20Kohm weak pullup of the 10F202; ; When N.C. PB is pressed, 5.1K resistor cannot pull close enough to ground for GP3 to detect a low ; According to datasheet input low = 0.2Vdd max ; Firmware fix of hardware problem: ; Turn off the weak pull-up just before PB is read and restore right after the read ; solves the hardware problem without affecting anything else. #ifdef office call OptionNoWPU ; turn off weak pull ups #endif ; ************************************************************ dnxbit1 pb bsf PB_VAL,0 ; ************************************************************ #ifdef office call OptionPollSw ; restore weak pull ups #endif ; ************************************************************ dnxnotzero PB_VAL goto check_pb_end dnxbit0 sw_level bsf override check_pb_end: goto main ; --------------------------------------------------------------------------------- end ; ============================================================================== ; alternative melodies for chime ; ============================================================================== ; melody from the movie Close Encounters of the Third Kind ; -------------------------------------------------------- ; key D6, LD6 ; key E6, LE6 ; key C6, LC6 ; key E5, LE5 ; key G5, .255 ; ; melody akin to music box dancer ; ------------------------------- ; key C5, LC5 ; key E5, LE5 ; key G5, LG5 ; key C6, LC6 ; key B5, LB5 ; key A5, LA5 ; key G5, .255 ; edwardson-composed melody (part of the melodies used in the door opened/closed routines) ; ------------------------------- ; movlf .2, CYCLES ; movlf .3, COUNTER+2 ;door_open_loop1 ; key A5, LA5 ; key F5, LF5 ; key D5, LD5 ; decfsz COUNTER+2,f ; goto door_open_loop1 ; ; movlf .3, COUNTER+2 ;door_open_loop2 ; key A5, LA5 ; key E5, LE5 ; key C5, LC5 ; decfsz COUNTER+2,f ; goto door_open_loop2 ; ; movlf .4, CYCLES ; key A5, LA5
No comments:
Post a Comment