Jump to content
43oh

LaunchPad as Iambic Keyer


Recommended Posts

Rob, do you know anything about Ultimatic mode? I just came across it while doing some more research and I'm trying to figure out how to include it in my Iambic Mode A & B project. I've got a couple links if you're interested.

 

Cheerful regards, Mike

Link to post
Share on other sites
  • Replies 39
  • Created
  • Last Reply

Top Posters In This Topic

Top Posters In This Topic

Popular Posts

Thanks for creating this project.   After I received my Ham License I wanted to start learning CW, but using a straight key has been a challenge. I found this project inspiring and I think I now ha

Here's my first iambic keyer, it's very simple, but I am planning to add some more functions. I know, I know, my morse is little rusty, but it's been a long time and I am not used to the Bencher, I a

Here is my iambic keyer. In the spirit of the MSP430 I put it to sleep at every opportunity, including between starting and stoping dits and dahs. It uses a 32 kHz clock crystal and sleeps in LPM3 f

Posted Images

Good morning Rob,

 

It looks like several commercial keyers support A, B, and Ultimatic modes (and some other exotic modes too).

 

Here's a brief description of Ultimatic Mode from Chuck Olsen, WB9KZY.

 

There's an Ultimatic CW Keyer project article starting on page 24 of the Feb-2011 Elector.

 

Kevin Schmidt, W9CF designed an Ultimatic Adapter that you can insert between the paddles and the built-in iambic keyer in your rig. Just for fun, my version of Kevin's algorithm uses only 12 instructions (PIC);

 

  *
 *  ditnew  ___---___---_________-------____________
 *  dahnew  ______--------____---___-----___________  swnew
 *               
 *  ditold  ____---___---_________-------___________  
 *  dahold  _______--------____---___-----__________  swold
 *
 *  ditchg  ___-__-__-__-________-______-___________ 
 *  dahchg  ______-_______-___-__-__-____-__________  swnew ^= swold
 *             ^  ^  ^  ^ ^   ^  ^  ^   ^^           (changes, press or release)
 *  ditand  ___-_____-___________-__________________ 
 *  dahand  ______-___________-_____-_______________  swnew =& swold
 *             ^  ^  ^        ^  ^  ^                (new press bits)
 *  ditout  ___---___---_________---________________
 *  dahout  ______---___--____---___-----___________
 *  
 *
 *    Ultimatic Adapter Experiment, (C) 2011, Mike McLaren
 *
 *    void main()                  // main.init
 *    { ...                        // 
 *      ...                        // 
 *      while(1)                   // main.loop
 *      { swnew = ~porta;          // sample active lo switches
 *        swnew &= 0b00000011;     // on the RA0 and RA1 inputs
 *        intcon.RABIF = 0;        // clear IOC interrupt flag
 *        swnew ^= swold;          // changes, press or release
 *        swold ^= swnew;          // update switch state latch
 *        if(!(swnew &= swold))    // if no "new press" bits
 *          swnew = swold;         // use actual paddle bits
 *        portc = swnew;           // update paddle output pins
 *        sleep();                 // sleep (wait for next IOC)
 *        nop();                   //
 *      }                          // end while(1)
 *    }                            // end main()
 *
 *
 *          org     0x000
 *  init                            ; main.init
 *          ...                     ;
 *          ...                     ;
 *  loop                            ; main.loop
 *          comf    PORTA,W         ; sample active lo switches
 *          andlw   b'00000011'     ; on the RA0 and RA1 inputs
 *          bcf     INTCON,RABIF    ; clear IOC interrupt flag
 *          xorwf   swold,W         ; changes, press or release
 *          xorwf   swold,F         ; update switch state latch
 *          andwf   swold,W         ; filter out "release" bits
 *          skpnz                   ; if new press, skip, else
 *          movf    swold,W         ; use actual paddle pattern
 *          movwf   PORTC           ; update paddle output pins
 *          sleep                   ; sleep (wait for IOC) 
 *          nop                     ; 
 *          goto    loop            ;
 *
 */

Here's my MSP430 Iambic A/B algorithm (nine lines, two variables). If I can't figure out a simple, clean, and elegant way to add code to prevent the toggle for Ultimatic mode I may have to redesign the algorithm. Wish me luck.

 

      while(memory |= paddles)      // while pending paddle memories
     { if(memory & select)         // if paddles & iambic select bit
       { send(select & dah);       // send dit (0) or dah (1) + space
         memory ^= select;         // clear current paddle memory bit
         if(Mode_A) memory = 0;    // if Mode A clear both bits
       }                           //
       select ^= dit + dah;        // toggle iambic select bits
     }                             // 
     space(2);                     // force 3 dit inter-char space

More later. Cheerful regards, Mike McLaren, K8LH

Link to post
Share on other sites
  • 3 months later...

Here is my iambic keyer. In the spirit of the MSP430 I put it to sleep at every opportunity, including between starting and stoping dits and dahs. It uses a 32 kHz clock crystal and 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.

 

Cheers,

Andy N1KSN

 

;-------------------------------------------------------------------------------
;   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

Link to post
Share on other sites

Dear Mike,

 

As to your question on a sidetone: The keyer as written does not have one. However, I wrote a second version as a special project and changed the software to include a sidetone so that I could beep a piezo speaker when making speed changes (and not transmit the speed change dits or dahs).

 

To add a sidetone, I configured the watchdog timer to an interval timer with a period of 1.9 ms (one of the four options available). An interrupt service routine for the WDT toggles the sidetone output bit to produce a square wave. (This gives a rather low tone, but I like low sidetones.) The sidetone is turned on and off by manipulating the port direction bit.

 

To have more flexibility in sidetone frequency I would change the main software design to use TimerA in continuous mode, time the Morse elements with a compare on CCR0 and produce the sidetone squarewave with a compare on CCR1. Each would have its own ISR. I haven't tried this, but it should work.

 

73,

Andy N1KSN

Link to post
Share on other sites
  • 9 months later...

Thanks for creating this project.

 

After I received my Ham License I wanted to start learning CW, but using a straight key has been a challenge.

I found this project inspiring and I think I now have a better platform for learning CW. I can focus on listening to the code rather than my technique.

 

If it helps, I copied your schematic over to KiCad and etched a board for it.

I am attaching a zip folder of the files should someone else want to use them. I did use the MSP430G2211.

IambicKeyer.zip

Link to post
Share on other sites
  • 2 years later...

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...