Morse Code Library For AVR
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.
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.
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();
}
}
// 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
Theinit_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.