Telecnatron

radio, electronics, computing, telecnology.

rss icon

Support This Site.

Please consider helping to support this site by making a contribution.

A recent project required that an AVR microcontroller generate morse code yet still being able to do other processing while the morse is being sent. This ruled out the use of blocking delay() type functions. The solution was to implement a general-purpose, state machine driven, morse code library using a hardware timer to do the timings. The code is presented here in the hope that others will find it useful.

1. Morse Timings

In morse, all timings are integer multiples of the dot period, with the dash being 3 dots, the gap between letters being 3 dots, and the gap between words being 7 dots. By configuring a timer to expire at the dot period, the state machine can be updated at the correct rate.

The macro:

#define MORSE_WPM_DOT_MS(wpm) 1200/wpm

gives the dot period in milliseconds for the passed words-per-minute rate. This is based on the average word being of 5 characters, and, as far as I know, is the normal way for calculating words per minute.

While a message is being sent, the morse_update() function is called at a rate of this many milliseconds.

2. Initialisation and the Keyer Function

The structure morse_state_t is used to keep the configuration and state information. A pointer to it is passed to each of the library’s functions.

A user-provided function does the actual up/down keying as is configured using the macro:

MORSE_INIT_KEYER(msp, fn)

Where msp is a pointer to the morse_state_t structure, and fn is a pointer to the function.

The function has the following signature:
void keyer(unsigned char down);
And must operate such that when down is non-zero, the transmitter (or LED, or tone generator, or whatever) is turned on, and turned off when it is 0.

3. Starting The Message

The morse_send_string_XXX() functions are used to start sending the message text:

void morse_send_string(morse_state_t* morse_state,char* text);
void morse_send_string_P(morse_state_t* morse_state, const char* text);
void morse_send_string_E(morse_state_t* morse_state,char* text);

These respectively send a string which is contained in RAM, PROGMEM, or EEPROM.

The morse_is_idle() function is used to determine whether a message is currently being sent, and hence whether the morse_update() function should be being called.

4. Source Code

Source code is available from github. main.c is the example source, and morse.h and morse.c are the two library files.

5. Example

The above is probably best illustrated by way of an example, so here we have a ATMEGA8 running at 16MHz and sending the morse by blinking a LED. See main.c for the code being refered to here.

The message is repeatedly sent at 10WPM with a 4 second gap in between:

// words per minute to use
#define WPM 10
// number of seconds to wait before sending the message again
#define MSG_GAP_SEC 4

5.1. Keyer Function

The LED is active low and connected to pin D4

// LED control - note: active low
#define LED_ON()  LED_PORT &=~ _BV(LED_PIN)
#define	LED_OFF() LED_PORT |= _BV(LED_PIN)
// XXX
// XXX  LED pin is initialised in main() 
// XXX
// Function used by morse library to implement morse-key up/down
// Turn LED on when down, off otherwise.
void key_down(uint8_t down)
{
    if(down){
	LED_ON();
    }else{
	LED_OFF();
    }
}

The keyer function is initialised by:
// variable to hold morse state information
morse_state_t morse_state;
// init the keyer function
MORSE_INIT_KEYER(&morse_state, key_down);

5.2. Timer

The init_timer() configures TIMER2 to generate a compare match interrupt at a rate of 1ms.

The interrupt handler:

ISR(TIMER2_COMP_vect)
{
    ms_ticks++;
}

increments the global variable ms_ticks ever 1ms, which the main loop then uses as a tick count for its timings.

5.3. Main Loop

Note that the text being sent in contained in PROGMEM,

const char morse_text_P[] PROGMEM ="CQ CQ DE VK2AAV";

and hence the morse_send_string_P() function is being used to send the message.

// start morse message
morse_send_string_P(&morse_state, morse_text_P);
// main loop
for(;;){
    // do the morse processing here
    if(morse_is_idle(&morse_state)){
	// message is not being sent
	if(ms_ticks >= MSG_GAP_SEC * 1000 ){
	    // it's time to start sending the message again.
	    morse_send_string_P(&morse_state, morse_text_P);
	    // reset timer to zero
	    MS_TICKS_RESET();
	}
    }else{
	// message is being sent.
	if(ms_ticks >= MORSE_WPM_DOT_MS(WPM)){
	    // dot time has expired, it's time to call the state machine again
	    morse_update(&morse_state);
	    // reset timer to zero
	    MS_TICKS_RESET();
	}
    }
    // XXX other processing goes here
}

The main loop first checks to see is a message is not currently being sent, and if so, and if the 4 second gap between messages has elapsed, it starts sending the message again, and resets the tick count.

Alternatively, if the message is currently being send, and if the dot period has elapsed, morse_update() is called to update the state machine, and the tick count is rest.

6. Conclusion

The morse library presented here is relatively easy to use, and offers a major advantage over implementations that use the blocking _delay_ms() type functions, in that it allows for other processing to be done whilst the morse is being sent. Hopefully people will find this useful in various applications, please be sure to let me know if you do.
Copyright 2012 - 2024 Telecnatron CSS | Login | Privacy | Sitemap Web software by Stephen Stebbing.