Full Version : Moris Code Alarm Clock (AVR ASM)
avr >>HOME & TIME & TEMPERATURE PROJECTS >>Moris Code Alarm Clock (AVR ASM)


Admin5- 04-27-2006
Cappell's Morse Code Alarm Clock Modification

(Almost) Trivial application of an AT90S2313 or ATtiny2313 in an alarm clock to change the alarm from "BEEP BEEP BEEP BEEP BEEP BEEP BEEP BEEP..." to "WAKE UP" in Morse code. This was designed in response to a request and is in daily use


Overview

The speaker is mounted on the top edge and
the AA battery holder is glued to the back. Flat ribbon
cable is used for all the interconnections.

An inexpensive alarm clock was modified by installing a small circuit board inside the clock body and attaching some wires to the clock's circuit board. The second AA cell needed to bring the voltage up to 3 volts for the micro controller is mounted on the back. The high impedance speaker was temporarily mounted on the top as I pondered where the best permanent location would be, and like many decisions that are delayed with the aid of a temporary fix, its still waiting final disposition.

Circuit

The adapter is a micro controller with a crystal clock, an input interface circuit,

and AC coupling for a speaker that is driven directly by the controller's output pins.
The 0.22 uf capacitors are ceramic monolithic.

The micro controller sleeps most of the time, consuming mere micro amps until it receives an interrupt. and wakes up, then, using timing loops, it generates timing for "WAKE UP" in Morse code. Interrupts provide a 2 kHz square wave on pins 14 and 15 that are 180 degrees out of phase with one another (when one pin is high, the other pin is low), which means that the speaker sees a square wave that is nearly 6 volts peak-to-peak (or 3 volts RMS since it is a square wave). The 100 uf capacitor in series with the speaker keeps it from drawing any DC power. Once the "WAKE UP" message has been sent, the micro controller goes back to sleep.

The digital inputs are pulled high with the chips' weak internal pull-ups to minimize power dissipation. The speaker outputs are configured as outputs (I hope that is not a surprise).

The clock module runs from a single 1.5 volt AA cell. To raise the voltage to 2.7V or greater to run the AT90S2313, I added a second AA cell that only powers the AT90S2313.

The alarm signal is taken from one of the wires from the alarm clock module to the clock's speaker. When the signal swings to ground, the 2N4401 in the alarm module sinks enough current to pull the interrupt pin low. The 39k resistor on the base of the 2N4401 sets the base current to (1.5V-0.7V)/39k = 20 micro amps. The weak pull-up on pin 6 supplies less than 85 micro amps when grounded, with 30 micro amps being typical, so the base drive is more than sufficient. Notice that this transistor's main function is to amplify the 1.5 V peak-to-peak signal from the alarm module to one that swings from ground to the micro controllers' positive power supply. The 0.22 uf capacitor cleans up the signal on the collector so that the collector goes low when the alarm signal comes in and stays low for about 2 milliseconds after the last beep.

A crystal oscillator was used because I have a whole lot of AT90S2313's and they don't have an internal oscillator. A better choice would be something like an ATtiny12 running with its internal oscillator. While on the subject of what could be better, the 100 uf capacitor can be eliminated provided that the firmware is modified to assure that pins 14 and 15, which drive the speaker, are in the same state (high, low, as inputs) when the tone is not being sounded or that at least one of them is configured as an input when the tone is not being sounded.

A note about the speaker: I used a high impedance transducer that I picked up at a surplus store. I was told that it is piezoelectric and appears to be so as it measures as greater than 20 Megohms on my ohmmeter. Be careful to use a piezoelectric transducer or other type of speaker with high enough impedance to feel comfortable that the current through it will not exceed 40 milliamps when driven by a 6 volt peak-to-peak signal. Anything measuring higher than 150 Ohms DC would fulfill this if it does not have a large shunt capacitance.

Firmware

After power-on reset, the firmware does the usual housekeeping -setting the stack pointer, initializing the I/O ports, and setting up timer 1, which is used to produce the tone for the speaker when the code is sent, and then it goes to sleep and waits for an interrupt from the alarm clock.

start:(HOUSEKEEPING CODE GOES HERE)
sleepwaithere: rjmp start

The loop above, is where the CPU spends its "foreground time", the time when its not servicing interrupts. When an interrupt occurs, it wakes and services the interrupt then returns to the "rjmp start" instruction where it goes through the housekeeping routines again to make sure everything is set up properly before going back to sleep.


The interrupt service routine is below, but with some extra comments.
CODE

int0service:
;Alarm has gone off, so wake up    
;The entire housekeeping routine is executed    
;first to make sure everything is properly set up.
clr temp
out GIMSK,temp
;Interrupt are disabled.
out MCUCR,temp
clr flagreg
 ;Clear the firmware flag resister/    ;TIMER 1 SETUP for proper tone pitch.
ldi temp,$09;Set timer 1 to reset 0000 after compare match. Prescaler = 1X.
out TCCR1B,temp
ldi temp,intcounthigh;Set compare register to establish interrupt frequency.
out OCR1AH,temp
ldi temp,intcountlow
out OCR1AL,temp
 ldi temp,$40;Enable interrupt on compare match.
out TIMSK,temp
sbi PORTB,0
cbi PORTB,1
 sei  ;Enable interrupts so that the tone can be output when code is    ;sent. ldi temp,20  ;Set a variable for 10 words per minute code speed.
mov dotlength,temp
rcall TypeMessage;Send  "WAKE UP" message in Morse Code
rjmp start  ;Go back to start (set up then sleep).


Notice that there is a lot of redundant initialization of registers and I/O ports. Its done when the processor first starts up, which is when the battery is installed, then it happens at the start of each interrupt when the alarm goes off, and then is done again at the end of each interrupt. This is for operational reliability. I have had controllers in the past that suffered reconfiguration their I/O ports without CPU intervention, when subject to high level, high frequency noise. (I won't mention the name of the manufacturer save Motorola from embarrassment). Reinitializing takes only a few additional microseconds, consumes negligible power, especially compared to that of driving the speaker for many seconds, and negligible code space since most of the flash memory in this chip is left unprogrammed. In return, I have a little piece of mind that if the $3 alarm clock sends its alarm signal on time, it will be heard and awaken the sleeper.


The section that sends the morse code is fairly primitive in operation, but relies on a neat trick to format the Morse code symbols in memory and much cleaner than the formatting methods I came up with myself. I found it on a web page by a fellow named David Robinson, and he credits N1KDO in an article in the February 1997 issue of QST. In the format, each character is encoded in a single byte, with a 1 representing a dah and a 0 representing a dit. An additional 1 is written as a sort of a "stop" bit. The characters are shifted out until the register contains only obooooooo1, and at that point, the entire word has been shifted out. For example, the letter A is encoded as "0b00000110"

Here is a view of the shift register as the code is shifted out:


"0b00000110" Before anything is send
"0b00000011" A dit is sent and the byte shifted right
"0b00000001" A dah is sent and the byte shifted again. Since the register now contains "0b00000001", the operation is complete.

The hardest part of this was transcribing the Morse Code into binary and typing it in. I thank N1KDO for the technique and David Robinson for featuring it on his web page.

Using the method above, a routine accepts ASCII characters, looks up the format of the Morse Code character, then calls the dot routine, the dash routine, or exits, as the formatted Morse is shifted out.

The routine that sends the ASCII string to be sent out was taken directly from an Atmel application note, AVR108: Setup and use of the LPM Instructions. It can be found on Atlems's web site http:www.atmel.com. You migh save time navigating Atmel's website if you just do a Google search on "DOC1233.PDF".

The routine is below:
CODE

TypeMessage:    
;Type greeting push ZL
push ZH
ldi ZH,high(2*Message)
     ;Load high part of byte address into ZH
ldi ZL,low(2*Message)
;Load low part of byte address into ZL
rcall sendromstring;Send it
pop ZH
pop ZL
ret
Message:
.db     "WAKE UP "
 .db $0A,$0D
.db     $00,$00

The message can be changed by changing the text within the quotes. Additional lines maybe used to extend the string to the capacitor of the flash memory. Just remember to keep the ".db $00,$00" at the end so the TypeMessage routine knows when to stop.

The routine sendromstring: actually sends the character off to be changed to Morse code and beeped out. A space is interpreted as one interword period.


Construction

All of the components were mounted on a small
piece of pre punched circuit board and the electrical
connections were made with a piece of ribbon cable.

The components were wired point-to-point, mostly by bending leads toward their intended connection points and soldering them. The components were placed so I could connect them with the minimum of fuss- which means as few insulated jumpers as possible.

Heat shrink tubing was used on the ribbon cable at stress points, like where it went through a hole in the alarm clock case, and at flex points, and great care was taken to keep flex stress off parts of the stranded wire that had solder wicked into them.

Link to Site: http://cappels.org/dproj/malc/alarm.html

Admin5- 04-27-2006
CODE

;RETURN TO THE MORSE CODE ALARM CLOCK PAGE.
;Copy and paste into your assemble.
;Copyright 2004 Richard Cappels, projects@cappels.org
;Program Name: Send Morse

;Version morclk041204A Corrected reversed code for the letter "D".



;Mores code message for alarm clock
;Pin 20 + 5V.
;Pin 15 High Z speaker return. Anti-phase of signal on pin 14
;Pin 14 High Z speaker output. Tone with message.
;Pin 13 Normally high, goes low during message.
;Pin 12 Normally low, goes high during message.
;Pin 10 Ground.
;Pin 6 - Message starts on negative edge on this pin.
;Pins 4 and 5 - crystal oscialltor per datasheet.
;Pin 1 may be grounded to terminate message.


;Key subroutines:


; dottime - set of delay loops to set basic dot time. Interacts with interupt routine.
;The dot time is set by adjusting the value loaded into temp at the start of the routine.
;Loading 100 into temp gives 1 wpm. Loading 1 into temp gives 100 wmp. Loadign 20 gets 5 wpm.

   
;sendromstring -send a string from Flash ROM. See TypeGreeting for example of use.


;sendSerialMorse - sends ASCII Data via serial port and as Morse Code.
;    ldi    temp,'A'
;    rcall    sendSerialMorse

;sendnumberMorseASCII -Sends binary nummber in temp as a 3 digit number via serial port and as Morse Code.    
;    ldi    temp,123       ;Send the nummber, 123
;    rcall    sendnumberMorseASCII
       
;interword - Delays one inter-word delay period    
;    rcall    interword
   
   

.include "2313def.inc"       ;Include file in same directory as project.

;03e7
      ;Interrupt timer parameters
.equ    intcountlow    =$E7   ;Low byte of number of clocks between interrupts.
.equ    intcounthigh    =$03   ;High byte of number of clocks between interrupts.


      ;UART baud rate calculation
.equ    clock = 4000000   ;clock frequency
.equ    baudrate = 9600       ;choose a baudrate
.equ    baudconstant = (clock/(16*baudrate))-1

.def    delaycount    = r2   ;Counter for delay generation
.def    dotlength    = r3   ;Number of 1/100 seconds for dot time.
.def    temp        = r16   ;General purpose scratch register.
.def    temp2        = r17   ;General purpose scratch register.
.def    h        = r22   ;Binary to decimal conversion.
.def    t        = r23  ;Binary to decmial conversion.
.def    u        = r24   ;Binary to decimal conversion.
.def    flagreg        = r25   ;Flags.


;definiton of flagreg bit assignments
;0    Status of code out bit last sent (memory used to toggle)
;1    True enables toggling of code output
;2
;3
;4
;5
;6
;7

.equ    codeport     = PORTB
.equ    codeout        = DDRB
.equ    codebit     = 2
.equ    pulsebit    = 3

;definition of I/O
;B0    + comparitor input (Reserved)
;B1    - comparitor input (Reserved)
;B2    Tone (code) output    
;B3    Anti-phase of signal on B2.    
;B4    (not assigned - configure as INPUT with weak pullup)    
;B5    (not assigned - configure as INPUT with weak pullup)    
;B6    (not assigned - configure as INPUT with weak pullup)    
;B7    (not assigned - configure as INPUT with weak pullup)    


;D0    Reserved FOR UART RECEIVE
;D1    Reserved FOR UART TRANSMIT -input has weak pullup.
;D2    (not assigned - configure as INPUT with weak pullup)
;D3    (not assigned - configure as INPUT with weak pullup)
;D4    (not assigned - configure as INPUT with weak pullup)
;D5    (not assigned - configure as INPUT with weak pullup)
;D6    (not assigned - configure as INPUT with weak pullup)
;D7    (not assigned - configure as INPUT with weak pullup)



.cseg    
.ORG $0000           ;Initializaton code
   rjmp    start
.ORG $0001
   rjmp    int0service
.ORG $0004
   rjmp    timerservice   ;Timer/counter compare interrupt handler

start:
 
   ldi    r16,RAMEND       ;Initialize Stack Pointer.
   out    spl,r16
                  ;Set PORTD.
   ldi    temp,0b00000000        
   out    DDRD,temp
   ldi    temp,0b10111111
   out    PORTD,temp
   
                  ;Set PORTB.
   ldi    temp,0b00001111        
   out    DDRB,temp
   ldi    temp,0b11110010
   out    PORTB,temp        
   
   ldi    temp,$40
   out    GIMSK,temp

   sei    
   ldi    temp,$38
   out    MCUCR,temp
   
   
   sleep
waithere:
   rjmp    start       ;Added so it would repeat if interrupt repeated.
   
   
int0service:   ;Alarm has gone off, so wake up
   
   clr    temp
   out    GIMSK,temp
   out    MCUCR,temp    
   
   clr    flagreg           ;Clear flagreg (flag register).

                  ;TIMER 1 SETUP.
   ldi    temp,$09       ;Set timer 1 to reset 0000 after compare match. Prescaler = 1X.
   out    TCCR1B,temp
   ldi    temp,intcounthigh   ;Set compare register to establish interrupt frequency.
   out    OCR1AH,temp
   ldi    temp,intcountlow
   out    OCR1AL,temp    
   ldi    temp,$40       ;Enable interrupt on compare match.
   out    TIMSK,temp
   
   
   sbi    PORTB,0
   cbi    PORTB,1
   
   sei               ;Enable interrupts.




   ldi    temp,20           ;Set for 10 words per minute
   mov    dotlength,temp
   rcall    TypeMessage       ;Send  message three times

   rjmp    start           ;added so it would repeat message each time interrupted.
   
   rcall    TypeMessage       ;Send  message three times
   rcall    TypeMessage       ;Send  message three times
   rcall    TypeMessage       ;Send  message three times
   rcall    TypeMessage       ;Send  message three times
   rcall    TypeMessage       ;Send  message three times
   rcall    TypeMessage       ;Send  message three times
   rcall    TypeMessage       ;Send  message three times
   rcall    TypeMessage       ;Send  message three times
   rcall    TypeMessage       ;Send  message three times
   rcall    TypeMessage       ;Send  message three times
   rcall    TypeMessage       ;Send  message three times
   rcall    TypeMessage       ;Send  message three times
   rcall    TypeMessage       ;Send  message three times
   rcall    TypeMessage       ;Send  message three times
   rcall    TypeMessage       ;Send  message three times
   rcall    TypeMessage       ;Send  message three times
   rcall    TypeMessage       ;Send  message three times
   rcall    TypeMessage       ;Send  message three times
   rcall    TypeMessage       ;Send  message three times

   rjmp    start           ;Go back to sleep
   


   




TypeMessage:               ;Type greeting
   push    ZL
   push    ZH
   ldi    ZH,high(2*Message)   ;Load high part of byte address into ZH
   ldi    ZL,low(2*Message)   ;Load low part of byte address into ZL
   rcall    sendromstring       ;Send it
   pop    ZH
   pop    ZL
   ret
   
   
   
Message:
   .db     "WAKE UP " "
   .db    $0A,$0D
   .db     $00,$00



sendromstring:           ;call with location of string in Z.
   push    ZL       ;Save Z on stack.
   push    ZH
srs1:
        lpm           ;Load byte from program memory into r0.
       tst    r0       ;Check if we've reached the end of the message.
       breq    finishsendstering;If so, return.
        mov    temp,r0
   rcall    sendSerialMorse;Send via serial link and as Morse Code.
        adiw    ZL,1       ;Increment Z registers
        rjmp    srs1
finishsendstering:
   pop    ZH       ;Pop Z from stack.
   pop    ZL
    rcall    interword
        ret




sendcodedcode:   ;Send Coded Morse Code
      ;Shift out morse code from coded character.
      ;Enter with code for character in temp.
   
;Description of data format from David Robinson's web page:
;"At this point I was reminded of the N1KDO NHRC-2 repeater controller published
;in February 97 QST that had Morse ID. Investigation of the assembler listing (1)
;revealed a simple conversion scheme, where all morse characters are encoded in a
;single Byte, bitwise, LSB to MSB.; ì0î = dit, ì1î = dah. The Byte is shifted out
;to the right, until only a ì1î remains. As an example 3 is encoded as binary 00111000,
;which translates to 38 in hexadecimal. "



morecode:
   cpi    temp,0b00000001
   breq    codedcodesent
   clc
   ror    temp
   brcs    senddash       ;Send a dash if lsb was a one
   rcall    dot           ;Send a dot if lsb was not a one
   rjmp    morecode
senddash:
   rcall    dash
   rjmp    morecode

codedcodesent:               ;Finished sending the coded code.
   ret



SendMorseAscii:   ;Look up coded Morse code and send, followed by rcall to interchar.
      ;Enter with ASCII character in temp. Upper-case, don't process
      ;control characters.
   
   push    ZL
   push    ZH
       
   cpi    temp,$20       ;If space character, do interword delay.
   brne    SMA1
   rcall    interword
   rjmp    lookupdone
SMA1:
   cpi    temp,$5B
   brmi    upperacse    
   andi    temp,$5F       ;Make upper-case
upperacse:
   cpi    temp,$2A    
   brmi    lookupdone

   
              ;Set up pointer into codechart.
   ldi    ZH,high(2*codechart)   ;Load high part of byte address into ZH.
   ldi    ZL,low(2*codechart)   ;Load low part of byte address into ZL.

   subi    temp,$2A       ;Removed offset from ASCII value in temp.
   add    ZL,temp           ;Add the value to the index.
   clr    temp
   adc    ZH,temp
   lpm           ;Fetch the value from the table.
   mov    temp,r0
   rcall    sendcodedcode       ;Send as Morse Code
   rcall    interchar       ;Dealy one interchar time
lookupdone:

   pop    ZH
   pop    ZL
   ret               ;Return




dot:       ;Send dot, wait one dot time.
   sbr    flagreg,0b00000010   ;Set flag to send tone.
   rcall    dottime
   cbr    flagreg,0b00000010   ;Clear flag to send tone.
   rcall    dottime
   ret
   


dash:       ;Send dash, wait one dot time.
   sbr    flagreg,0b00000010   ;Set flag to send tone.
   rcall    dottime
   rcall    dottime
   rcall    dottime
   cbr    flagreg,0b00000010   ;Clear flag to send tone.
   rcall    dottime
   ret

interchar:   ;Wait interchear period with output off -3 dot times
   rcall    dottime
   rcall    dottime
   rcall    dottime
   ret
   
interword:   ;Wait interword period with output off-6 dot times
   rcall    dottime
   rcall    dottime
   rcall    dottime
   rcall    dottime
   rcall    dottime
   rcall    dottime
   rcall    dottime
   ret



dottime:           ;Delay one dot time.
;reload values for 100 wmp with 4 MHz Crystal:
;temp = 1
;temp2 = 20
;dealycount = 0 (cleared)
;With these interrupt reload values:
;.equ    intcountlow    =$E7
;.equ    intcounthigh    =$03

;Using dotlength = 20 (decmimal), dot time is 83.37 milliseconds.
;Using dotlength = 200 (decimal), dot time is 833.64 milliseconds

   push    temp
   push    temp2
   
   mov    temp,dotlength     ;<= vary this from 1 to 100 to get 100 to 1 wpm. 20 = 5 wpm; 100 = 1
moretime3:    
   clr    delaycount
moretime2:
   ldi    temp2,20
moretime1:
   dec    temp2
   brne    moretime1
   dec    delaycount
   brne    moretime2
   dec    temp
   brne    moretime3
   
   pop    temp2
   pop    temp

   ret

          ;go back


sendSerialMorse:   ;Send ASCII Character via serial port and via Morse Code.
          ;Enter with char in temp.
           
   push    temp
    pop    temp
    rcall    SendMorseAscii
    ret            




codechart:   ;Coded Morse Code look up table. Use ASCII value -$30, so zero = 0, "A" = $11, etc.
;Note: Some ASCII characters are silent, and are coded as 0b00000001
;Also note: BT (pause) is coded for ASCII "<" and SK (end of contanct) is coded for ASCII "*",
;and End of Message is coded for ASCII "+".


;    * (SK)        + (End of Message)
.db    0b01101000,    0b00101010


;    ,(comma)    -          .          /
.db    0b01110011,0b1011110,0b01111010,0b00101001


;    0          1          2          3
.db    0b00111111,0b00111110,0b00111100,0b00111000

;    4          5          6          7
.db    0b00110000,0b00100000,0b00100001,0b00100011
   

;    8          9          :         ;
.db    0b00100111,0b00101111,0b01000111,0b01010101
     
   
;    <  (BT)        =          >          ?
.db    0b000110001,0b00000001,0b00000001,0b01001100
         
;    @          A          B          C
.db    0b00000001,0b00000110,0b00010001,0b00010101


;    D          E          F          G
.db    0b00001001,0b00000010,0b00010100,0b00001011

;    H          I          J          K
.db    0b00010000,0b00000100,0b00011110,0b00001101
 

;    L          M          N          0
.db    0b00010010,0b00000111,0b00000101,0b00001111
 
 
 ;    P          Q          R          S
.db    0b00010110,0b00011011,0b00001010,0b0001000
 
 
  ;    T          U          V          W
.db    0b00000011,0b00001100,0b00011000,0b00001110
 

  ;    X          Y          Z          [
.db    0b00011001,0b00011101,0b00010011,0b00000001
 
   
         
   
timerservice:   ;Service Timer 1
   push    temp    
   in    temp,sreg
   push    temp
   
   
   
   sbrs    flagreg,1
   rjmp    notoggle
   
      ;Toggle tone output
   sbi    codeout,codebit       ;Enable code output pin.
   mov    temp,flagreg       ;Toggle it, using flagreg 0 as memory of last one.
   andi    temp,0b00000001
   inc    temp
   andi    temp,0b00000001
   brne    codehigh
   cbi    codeport,codebit
   sbi    codeport,pulsebit
   andi    flagreg,0b11111110
   rjmp    toggledone
codehigh:
   sbi    codeport,codebit
   cbi    codeport,pulsebit
   ori    flagreg,0b00000001
toggledone:
   pop    temp
   out    sreg,temp
   pop    temp

   reti           ;Return from interrupt.
   
notoggle:   ;Don't toggle port, but delay to equalize interrupt time toggling and not toggling.
   cbi    codeout,codebit       ;Disable code out pin.
   nop
   nop
   nop
   nop
   nop
   nop
   rjmp    toggledone


.exit


Forumer™ is Voted #1 Free Forum Hosting provider
Build your own community today with the largest message board hosting company.