Jump to content
43oh

My version of an iambic keyer


Recommended Posts

I was asked to make this a separate post, so here it is. This is my iambic keyer.

 

In the spirit of the MSP430, whose strength is very low power sleeping, I put it to sleep at every opportunity, including between starting and stopping dits and dahs. It uses a 32 kHz clock crystal driving ACLK and TimerA, and it sleeps in LPM3 for a quick wakeup. I powered it with a coin cell and didn't bother with an on/off switch since the current draw in idle is less than a microamp. I housed it in a mint tin and it is currently my main keyer in the shack. I don't have a picture of the keyer itself, but it is built on a little piece of perf board and uses point-to-point wiring. The TX output from the chip drives a 2N7000 to key the rig. I've added an attachment with the diagram.

 

Here is the code. I use the IAR Embedded Workbench.

 

;-------------------------------------------------------------------------------
;   asmTinyKeyer3.s43 - Simple iambic keyer program with pushbutton to
;                       set speed
;
;   This is a simple iambic keyer.  If the pushbutton is held down while
;   the dit paddle is closed, the speed is incremented by one wpm.  If the
;   dah paddle is closed instead, the speed is decremented by one wpm.
;
;   If a mono plug is inserted prior to power-up (or the dah paddle is closed
;   during power-up), the keyer goes into straight key mode until power is
;   removed, keying from the dit contact.
;
;   Hardware:
;     MSP430G2211 with 32.768 clock crystal for ACLK, DCO calibrated at 1 MHz
;       for normal system clock.  Clock crystal is 12.5 pf type.
;     Pin assignments:
;       P1.0  = Dit input
;       P1.1  = Dah input
;       P1.2  = Pushbutton for speed control
;       P1.3  = Output to keying MOSFET gate
;       XIN, XOUT  = 32.768 KHz, 12.5 pf clock crystal
;       RST/NMI = 47Kohm pullup resistor, 0.0022 uF pulldown cap 
;
;   Notes:
;     When there is no paddle input, MCU is put in LPM3.  It awakens when
;     there is a paddle closure.  We must use LPM3 rather than LPM4 if a
;     clock crystal is used for Timer A, as the crystal-based oscillator does
;     not get up to speed quickly enough when the ISR is exited.
;
;     During the delays for dits and dahs, the MCU is put in LPM3 with Timer A
;     running off ACLK setting the delay time.  The timer is run in single shot
;     up mode.  The associated ISR stops the timer.  
;
;     The timer count for the current sending speed is determined from a
;     table in program flash memory.  The time for a single Morse element
;     (dit = 1 element on, 1 element off; dah = 3 elements on, 1 element off)
;     is calculated by
;
;         Element (ms) = 1200 / WPM
;
;     where WPM = sending speed in words per minute.  If the ACLK frequency
;     is changed from 32768 Hz the table will need to be recalculated.
;
;     In straight key mode, the processor is put in LPM3 between changes in
;     the key closure.
;   
;   
;   Andy Palm 2010.07.26
;-------------------------------------------------------------------------------

;-------------------Includes, defines, equates----------------------------------
#include "msp430.h"                         ; #define controlled include file

; I/O pin definitions
DIT         EQU     BIT0                    ; Dit contact input
DAH         EQU     BIT1                    ; Dah contact input
BUTTON      EQU     BIT2                    ; Speed pushbutton input
TX          EQU     BIT3                    ; Output to keying MOSFET gate

; Program constants
WPM_MIN     EQU     10                      ; WPM limits, timer count lookup
WPM_MAX     EQU     30
WPM_INIT    EQU     23                      ; Default keyer speed
                                           ; Must be between limits above
; Register variables
;   R4 = Input buffer bits
;   R5 = Current speed in wpm
;   R7 = WPM table offset

;-------------------------------------------------------------------------------
           RSEG    CSTACK                  ; Pre-declaration of segment

;-------------------Main Routine------------------------------------------------
           RSEG    CODE                    ; Place program in 'CODE' segment

Reset:      mov.w   #SFE(CSTACK), SP        ; Set up stack
           mov.w   #WDTPW+WDTHOLD, &WDTCTL ; Stop watchdog timer

; Set DCO for calibrated 1 MHz
           mov.b   &CALBC1_1MHZ, &BCSCTL1  ; Set DCO range
           mov.b   &CALDCO_1MHZ, &DCOCTL   ; Set DCO step and modulation
           mov.b   #LFXT1S_0|XCAP_3, &BCSCTL3     ; Select ACLK from LFXT1
                                           ; Set for 12.5 pf internal caps
; Configure Port P1
           clr.b   &P1OUT                  ; Set all P1 pins to ground
           bis.b   #0xFF, &P1DIR           ; Set all P1 pins to output

           bic.b   #DIT|DAH|BUTTON, &P1DIR ; Set input pins
           mov.b   #DIT|DAH|BUTTON, &P1OUT ; For pull-up direction
           mov.b   #DIT|DAH|BUTTON, &P1REN ; Pull-up on input pins

           bis.b   #DIT|DAH, &P1IES        ; Interrupt on falling edge

; Set up Timer A: Clock from ACLK/1, stopped
           mov.w   #TASSEL_1, &TACTL
           bis.w   #CCIE, &TACCTL0         ; Enable interrupts on Compare 0

main:
; Initialize varibles
           clr.w   R4                      ; Clear input buffer
           mov.w   #WPM_INIT, R5           ; Initialize speed
           call    #Load_Count             ; Timer count for one element

           call    #Straight_Key           ; Check for mono plug/straight key
Loop:
; Test input pins, set buffer bits accordingly
           bit.b   #DIT, &P1IN             ; Dit paddle closed?
           jnz     No_Dit                  ; No
           bis.b   #DIT, R4                ; Yes, set dit bit in buffer
No_Dit:
           bit.b   #DAH, &P1IN             ; Dah paddle closed?
           jnz     No_Dah                  ; No
           bis.b   #DAH, R4                ; Yes, set dah bit in buffer
No_Dah:            
           tst.w   R4                      ; If no pending output, go to sleep
           jnz     Send_Dit                ; Otherwise, process input

; Go to LPM3 sleep to wait for paddle input
           bis.b   #DIT|DAH, &P1IE         ; Enable P1 pin change interrupt
Sleep:
           clr.b   &P1IFG                  ; Clear any pending interrupts
           tst.b   &P1IFG
           jnz     Sleep
           bis.w   #LPM3|GIE, SR           ; Wait for input in LPM3

; Test input pins after awakening, set buffer accordingly
           bit.b   #DIT, &P1IN             ; Dit paddle closed?
           jnz     No_Dit1                 ; No
           bis.b   #DIT, R4                ; Yes, set dit bit in buffer
No_Dit1:
           bit.b   #DAH, &P1IN             ; Dah paddle closed?
           jnz     No_Dah1                 ; No
           bis.b   #DAH, R4                ; Yes, set dah bit in buffer
No_Dah1:            

; If dit buffer bit set, send a dit
Send_Dit:
           bit.b   #DIT, R4                ; Dit buffer set?
           jz      No_Send_Dit             ; No
           bit.b   #BUTTON, &P1IN          ; Speed change button pressed?
           jnz     Send_Dit1               ; No
           call    #Incr_Wpm               ; Yes, increment speed            
Send_Dit1:            
           bic.w   #DIT, R4                ; Reset dit buffer
           bis.b   #TX, &P1OUT             ; Key TX output
           bis.w   #MC_1|TACLR, &TACTL     ; Clear timer and start count up
           bis.w   #LPM3|GIE, SR           ; Sleep in LPM3 while waiting
           bic.b   #TX, &P1OUT             ; Unkey TX output

           bit.b   #DAH, &P1IN             ; Dah paddle closed?
           jnz     No_Dah1A                ; No
           bis.b   #DAH, R4                ; Yes, set dah bit in buffer
No_Dah1A:

           bis.w   #MC_1|TACLR, &TACTL     ; Clear timer and start count up
           bis.w   #LPM3|GIE, SR           ; Sleep in LPM3 while waiting
No_Send_Dit:

; Test input pins, set buffer accordingly
           bit.b   #DIT, &P1IN             ; Dit paddle closed?
           jnz     No_Dit2                 ; No
           bis.b   #DIT, R4                ; Yes, set dit bit in buffer
No_Dit2:
           bit.b   #DAH, &P1IN             ; Dah paddle closed?
           jnz     No_Dah2                 ; No
           bis.b   #DAH, R4                ; Yes, set dah bit in buffer
No_Dah2:

; If dah buffer bit set, send a dah
Send_Dah:
           bit.b   #DAH, R4                ; Dah buffer set?
           jz      No_Send_Dah             ; No
           bit.b   #BUTTON, &P1IN          ; Speed change button pressed?
           jnz     Send_Dah1               ; No
           call    #Decr_Wpm               ; Yes, decrement speed            
Send_Dah1:            
           bic.w   #DAH, R4                ; Reset dah buffer
           bis.b   #TX, &P1OUT             ; Key TX output
           bis.w   #MC_1|TACLR, &TACTL     ; Clear timer and start count up
           bis.w   #LPM3|GIE, SR           ; Sleep in LPM3 while waiting
           bis.w   #MC_1|TACLR, &TACTL     ; Repeat twice for a three element
           bis.w   #LPM3|GIE, SR           ; long dah
           bis.w   #MC_1|TACLR, &TACTL
           bis.w   #LPM3|GIE, SR
           bic.b   #TX, &P1OUT             ; Unkey TX output

           bit.b   #DIT, &P1IN             ; Dit paddle closed?
           jnz     No_Dit2A                ; No
           bis.b   #DIT, R4                ; Yes, set dit bit in buffer
No_Dit2A:
           bis.w   #MC_1|TACLR, &TACTL     ; Clear timer and start count up
           bis.w   #LPM3|GIE, SR           ; Sleep in LPM3 while waiting
No_Send_Dah:

           jmp     Loop

;-------------------Interrupt Service Routines----------------------------------
;-------------------------------------------------------------------------------
Input_ISR:
; Interrupt service routine for inputs on Port P1
           bic.b   #DIT|DAH, &P1IE         ; Disable P1 pin change interrupt
Input_ISR1:
           clr.b   &P1IFG                  ; Clear any pending interrupts
           tst.b   &P1IFG
           jnz     Input_ISR1

           bic.w   #LPM3|GIE, 0(SP)        ; Clear LP and GIE bits on exit
           reti

;-------------------------------------------------------------------------------
TA0_ISR:
; Interrupt service routine for TACCR0, called when TAR = TACCR0.
           bic.w   #MC_1, &TACTL           ; Stop timer
           bic.w   #LPM3|GIE, 0(SP)        ; Clear LP and GIE bits on exit
           reti

;-------------------Subroutines-------------------------------------------------
;-------------------------------------------------------------------------------
; Run in straight key mode, keying from dit paddle.
Straight_Key:
           bit.b   #DAH, &P1IN             ; Dah paddle closed?
           jz      SK1
           ret                             ; No, return to normal operation
SK1:
           bit.b   #DIT, &P1IN             ; Dit contact closed?
           jnz     SK2
           bis.b   #TX, &P1OUT             ; Yes, key TX output
           bic.b   #DIT, &P1IES            ; Interrupt on leading edge
           jmp     SK3
SK2:
           bic.b   #TX, &P1OUT             ; No, unkey TX output
           bis.b   #DIT, &P1IES            ; Interrupt on falling edge
SK3:
           bis.b   #DIT, &P1IE             ; Enable P1 pin change interrupt
SK4:
           clr.b   &P1IFG                  ; Clear any pending interrupts
           tst.b   &P1IFG
           jnz     SK4
           bis.w   #LPM3|GIE, SR           ; Wait for change in LPM3

           jmp     SK1        
           ret                             ; Should never get here

;-------------------------------------------------------------------------------
; Increment speed by one wpm in R5 and load new timer count
Incr_Wpm:
           cmp.w   #WPM_MAX, R5            ; wpm < WPM_MAX?
           jlo     Incr_Wpm1
           ret                             ; No, return
Incr_Wpm1:            
           inc.w   R5                      ; Yes, increment wpm
           call    #Load_Count             ; and load new timer count
           ret

;-------------------------------------------------------------------------------
; Decrement speed by one wpm in R5
Decr_Wpm:
           dec.w   R5
           cmp.w   #WPM_MIN, R5            ; wpm - 1 >= WPM_MIN?
           jhs     Decr_Wpm1
           inc.w   R5                      ; No, restore value
           ret                             ; and return
Decr_Wpm1:
           call    #Load_Count             ; Yes, load new timer count
           ret

;-------------------------------------------------------------------------------
; Given wpm speed in R5 load Timer A count for single time element.
; Uses R7 for calculation of table offset value.
Load_Count:
           mov.w   R5, R7                  ; Calculate table offset
           sub.w   #WPM_MIN, R7            ; = WPM - WPM_MIN
           rla.w   R7                      ; Multiply by 2 for word addr
           mov.w   WPM_Table(R7), &TACCR0  ; Load timer
           ret

;-------------------Flash RAM Data----------------------------------------------
;-------------------------------------------------------------------------------
; Table giving Timer A count for Words per Minute when using clock crystal
; at 32768 Hz for timer clock, divide by 1.  The formula for the table
; values is
;
;           Count = (1.2 / WPM) / (1 / 32768) = 39321.6 / WPM
; where
;           Length of dit/element = 1.2 / WPM  seconds
;
           RSEG    DATA16_C                ; Segment for flash data
WPM_Table:
           DW  3932 ; WPM 10
           DW  3575 ; WPM 11
           DW  3277 ; WPM 12
           DW  3025 ; WPM 13
           DW  2809 ; WPM 14
           DW  2621 ; WPM 15
           DW  2458 ; WPM 16
           DW  2313 ; WPM 17
           DW  2185 ; WPM 18
           DW  2070 ; WPM 19
           DW  1966 ; WPM 20
           DW  1872 ; WPM 21
           DW  1787 ; WPM 22
           DW  1710 ; WPM 23
           DW  1638 ; WPM 24
           DW  1573 ; WPM 25
           DW  1512 ; WPM 26
           DW  1456 ; WPM 27
           DW  1404 ; WPM 28
           DW  1356 ; WPM 29
           DW  1311 ; WPM 30

;-------------------Interrupt Vectors-------------------------------------------
; Interrupt vector table
           COMMON  INTVEC                  ; Segment for vectors in flash
           ORG     PORT1_VECTOR
           DW      Input_ISR
           ORG     TIMERA0_VECTOR
           DW      TA0_ISR
           ORG     RESET_VECTOR
           DW      Reset

           END

 

post-4636-135135508703_thumb.gif

 

The schematic has a comment about adding an on/off switch. This is only a convenience in case one wants to use a straight key through the keyer in the shack, which is unlikely (just use a Y-cable to the rig instead). The straight key feature is always there. I put it in as a feature in case I built one of these keyers inside a QRP (low-power) portable rig. Then I can insert a paddle or straight key in the same jack before power-up, and the software does the right thing when power is applied. By the way, this is an iambic keyer and I sometimes use dual paddles, but personally I prefer a single paddle which doesn't use the iambic-ness.

 

If I want to store the keyer, I slip a piece of thin plastic between the battery and the tab on the battery holder. Even without doing this, my calculations show that the battery will self-discharge long before it is used up by this keyer.

 

Cheers,

Andy N1KSN

Link to post
Share on other sites

Dear Bluehash, at your request I just took this snap of the finished keyer in my shack. Funky, eh? None of those fancy custom PCBs for me! ;-> (Actually, I hope one day to learn how to design and order PCBs, but that is on the back burner.)

 

The pushbutton is in front, the paddle and transmitter connections are in the back. The case is a mint tin. On the outside of the tin cover (not shown) I put the little sticker that came with the Launchpad. (There is a tradition in lower power amateur radio construction of putting small projects in such mint tins. They are inexpensive and block RF, both good things.)

 

Underneath the perf board is part of a plastic dummy credit card (the kind sent as junkmail) to prevent shorts to the case. A small section of the card is slipped under the battery contact if the keyer is put in storage.

 

The LED seen in the picture was put in as static protection on the gate of the 2N7000 MOSFET that keys the rig. This was added in an excess of caution and is unnecessary since the gate does not go to an outside jack. For this reason I left it off the schematic. It doesn't even light up, as it is reversed biased.

 

You can see the reset pin resistor and cap in front of the MSP4302211, along with the 32768 Hz clock crystal. Note that the crystal's case is connected to ground.

 

I have also attached a pdf file which has a page of comments on the project including the low power aspects of the software design, plus the schematic and code. It was a handout at my ham radio club.

 

Cheers,

Andy N1KSN

 

post-4636-135135509031_thumb.jpg

 

MSPTinyKeyer.pdf

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...