glennedi
Published © GPL3+

Txt to Morse

Reverse your way through a binary tree

BeginnerFull instructions provided2,655
Txt to Morse

Things used in this project

Hardware components

Arduino UNO
Arduino UNO
×1
Deek Robot data logging shield v1.0
×1
LCD Keypad shield
×1
Piezo sounder element
×1
0.1 Inch pins
×2
0.1 Inch Jumper
×1

Story

Read more

Schematics

Piezo sounder connections

Buzzer top view

Buzzer underside view with wiring annotations

How everything stacks together

Code

txt to Morse stack version

C/C++
//.txt file to morse code 
//uses standard morse binary tree for translation of letters to dots and dashes, this requires the use of a stack
//stack library source -  https://playground.arduino.cc/Code/StackArray/


//CHECK IF LCD SHIELD USES DIGITAL PIN 10 AS THIS IS USED AS CS FOR DATA LOGGING SHIELD
//CHECK TO SEE IF LCD IS ONE OF THE FAULTY ONES
//MINE HAD BOTH PROBLEMS SO I DESOLDERED AND REMOVED THE FAULTY PIN 10
//REFERENCE - https://forum.arduino.cc/index.php?topic=96747.0 (Retrieved 7/June/2019)

/*
 The circuit:
 * SD card attached to SPI bus as follows:
 ** MOSI - pin 11
 ** MISO - pin 12
 ** CLK - pin 13
 ** CS - pin 10
 * 
 * RTC connections (I2C) - Clock not used in this project:
 * gnd to gnd
 * + to +5V
 * SDA to A4
 * SCL to A5
 * 
 * LCD shield:
 * Backlight - pin 10 removed
 * B4 to D4
 * B5 to D5
 * B6 to D6
 * B7 to D7 
 * RS to D8
 * E to D9
 * 
 * LCD shield buttons
 * Analog 0
 * 
 * Buzzer 
 * GND to GND
 * + to 3
 * 
 * To change morse speed ground one of the following analog pins
 * A1 = 5 wpm
 * A2 = 13 wpm
 * A2 = 20wpm 
 * default with no analog pins grounded 5 wpm
*/
#include <SPI.h>
#include <SD.h>

const int chipSelect=10;

//my_aliases
//const int move_switch=2;
//const int enter_switch=3;
const int initialization_screen=1;
const int file_menu_screen=2;
const int file_wont_open_screen=3;
const int file_selected_screen=4;
const int display_character_screen=5;
const int print_filename_screen=6;

File root;
File myFile;

//for the LCD shield
#include <LiquidCrystal.h>

// select the pins used on the LCD panel
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
// http://www.robotshop.com/content/PDF/dfrobot-lcd-keypad-shield-schematic.pdf 

//required to debounce switch
#define MAX_CHECKS 10
volatile uint8_t Debounced_State=0;//accessed by isr and main loop code
uint8_t State[MAX_CHECKS]={0};
uint8_t Index=0;

// include stack library header.
#include <StackArray.h>

// create a stack of characters.
StackArray <int> stack;

//my_global_variables
int screen_number=initialization_screen;
bool fail=false;
bool file_exists=false;
static String my_filename="";
char my_character=' ';
  

const int sounder_pin=3;// the pin we attach our sounder to
const int duty_cycle=128;//50% duty cycle for analogWrite()

void setup() {

//initial lcd setup
  lcd.begin(16,2);   // initialize the lcd for 16 chars 2 lines, turn on backlight   
  lcd.clear();


//try to initialize sd card
  if (!SD.begin(chipSelect)) {
    fail=true;
    update_display();
    return;
  }
  fail=false;
  update_display();
  delay(5000);

  //set up sounder frequency
  TCCR2B = (TCCR2B & 0b11111000) | 0x02;

  // initialize digital pin as an output.
  pinMode(sounder_pin, OUTPUT);

  //for morse code speed selection
  pinMode(A1,INPUT_PULLUP);
  pinMode(A2,INPUT_PULLUP);
  pinMode(A3,INPUT_PULLUP);

      // initialize timer1 
  noInterrupts();           // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  OCR1A = 625;              // compare match register  16MHX/256/100HZ 
  TCCR1B |= (1 << WGM12);   // CTC mode
  TCCR1B |= (1 << CS12);    // 256 prescaler 
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
  interrupts();


}/*end of setup*/

void loop() {
  
  root = SD.open("/");
  File dir;
  dir=root;

//variables
bool count_updated=true;
bool enter_pressed=false;

static bool button_up=false;
static bool chord_ready=false;
static int Stored_State=0;
static int Action_Me=0;

//all timing is based around the length of the dot
//source for timings -> http://www.kent-engineers.com/codespeed.htm (retreived 5 March 2017)

static unsigned long unit_length=240;
if(!digitalRead(A1)){unit_length=240;}//dot time calculated for 5 words per minute
if(!digitalRead(A2)){unit_length=92;}//dot time calculated for 13 words per minute
if(!digitalRead(A3)){unit_length=60;}//dot time calculated for 20 words per minute
  
 
while(1){

    noInterrupts();           // disable all interrupts    
if (Debounced_State>Stored_State){Stored_State=Debounced_State;chord_ready=true;}//store chord and flag as ready
if (Debounced_State<Stored_State){if(chord_ready){button_up=true;Action_Me=Stored_State;}Stored_State=Debounced_State;} 
    interrupts();             // enable all interrupts

if(button_up && chord_ready)
{
chord_ready=false;
button_up=false;

if (Action_Me&1<<1){enter_pressed=true;}//select button
                          
if (Action_Me&1<<0){count_updated=true;}//down button

}
 
  if (count_updated){
  
  count_updated=false;

  File entry =  dir.openNextFile();
    if (! entry) {
      // no more files -> so go back to beginning
      entry.close();
      dir.rewindDirectory();
      my_filename="No more";
      update_display();   
    }else
    {
    my_filename=entry.name(); 
    screen_number=file_menu_screen;
    update_display();
    entry.close();}                   
                    }

if (enter_pressed){
  
  enter_pressed=false;

  screen_number=file_selected_screen;
 
  if (SD.exists(my_filename)) {file_exists=true;}else{file_exists=false;}
  
  update_display();

//read data from file
  // re-open the file for reading:
  myFile = SD.open(my_filename, FILE_READ);
  if (myFile) {

    // read from the file until there's nothing else in it:

    while (myFile.available()) 
    {
    my_character=myFile.read(); 
    screen_number=display_character_screen;
    update_display();
    if (my_character>=65 && my_character<=90){my_character+=32;};//change letters to lower case if necessary
    beep_character(my_character,unit_length);
    }

    // close the file:
    myFile.close();
  } else {
    // if the file didn't open, print an error:
    screen_number=file_wont_open_screen;
    update_display();
  }

}
                    
}
  
}/*end of loop*/

//my_functions

ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine
{

//read buttons
  uint8_t temp=0x00;
  
 if (analogRead(0) > 200 && analogRead(0) < 400){temp|=1<<0;} //btnDOWN  
 if (analogRead(0) > 600 && analogRead(0) < 800){temp|=1<<1;} //btnSELECT
 
//debounce
  uint8_t i,j;
  State[Index]= temp;
  ++Index;
  j=0xFF;
  for (i=0;i<MAX_CHECKS;i++){j=j&State[i];}
  Debounced_State=j;
  if(Index>=MAX_CHECKS){Index=0;} 
}


//my_screen_functions
void update_display(void)
{
  switch (screen_number)
  {
    case initialization_screen:lcd.clear();
                               lcd.setCursor(0,0);
                               lcd.print("Init SD...");
                               if (!fail){lcd.print("done.");}else{lcd.print("fail!");}
                               break;
    case file_menu_screen:lcd.clear();
                          lcd.setCursor(2,0);
                          lcd.print("Select file");
                          lcd.setCursor(2,1);
                          lcd.print("            ");//clear any previous filename (8.3 character format, so 12 spaces)
                          lcd.setCursor(2,1);
                          lcd.print(my_filename);
                          break;
                         
    case file_wont_open_screen:lcd.clear();
                               lcd.print("Can't open file");
                               break;
    case file_selected_screen:lcd.clear();
                              lcd.setCursor(0,0);
                              lcd.print("Opt->");
                              lcd.print(my_filename);
                              lcd.setCursor(0,1);
                              if(!file_exists){lcd.print("No file.");}
                              break;
    case display_character_screen:lcd.setCursor(7,1);
                                  lcd.write(my_character);
                                  break;                  
    }
  } 

   
//beep_character function to sound out morse
//uses functions delay() and analogWrite()
//all timing is based around the length of the dot
//source for timings -> http://www.kent-engineers.com/codespeed.htm 
//examples
//unit_length = 240, dot time calculated for 5 words per minute
//unit_length = 92, dot time calculated for 13 words per minute
//unit_length = 60, dot time calculated for 20 words per minute
//uses stack library and hardware pwm

void beep_character(char incoming_byte, unsigned long unit_length)
{

//Morse tree, intended to be processed forward, that is + from index 0
//we are going in reverse so need to use a stack to correctly sound out the dots & dashes 
const unsigned char characters_array[]={
0x00,' ',//positions 0 not used position 1 is used to return the space character
'e','t',//2
'i','a','n','m',//4
's','u','r','w','d','k','g','o',//8
'h','v','f',252,'l',228,'p','j','b','x','c','y','z','q',246,154,//16
'5','4','S','3',233,0x00,208,'2',0x00,232,'+',0x00,254,224,'J','1','6','=','/',0x00,231,0x00,'(',0x00,'7',0x00,'G',241,'8',0x00,'9','0',//32
0x00,0x00,0x00,0x00,//spaces used for alt, ctrl, fn, shift (index positions 64, 65, 66, 67)(we just check the index not what's placed there)
'$',0x00,0x00,0x00,0x00,0x00,0x00,0x00,'?','_',0x00,0x00,0x00,0x00,'"',0x00,0x00,'.',0x00,0x00,0x00,0x00,'@',0x00,0x00,0x00,39,0x00,//64 part 1
0x00,'-',0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,';','!',0x00,')',0x00,0x00,0x00,0x00,0x00,',',0x00,0x00,0x00,0x00,':',0x00,0x00,0x00,0x00,0x00,0x00,0x00,//64 part 2
};
const int characters_array_limit=127;//128 characters so 0-127 

const unsigned long dot=unit_length;
const unsigned long dash=unit_length*3;
const unsigned long intra_letter_space=unit_length;//space between dot's and dashe's within letter
const unsigned long inter_letter_space=(unit_length*3)-intra_letter_space;// space between letters, subtracts used to deal with spaces already there
const unsigned long word_space=(unit_length*7)-intra_letter_space-inter_letter_space;// space between words, subtracts used to deal with spaces already there

int i=0;

if (incoming_byte==' '){delay(word_space);return;}
 

//find character in array
    
for(i=2;i<=characters_array_limit;i++)
{
  if (incoming_byte==characters_array[i]){break;}//locate letter
  }

if (i<=characters_array_limit){
   
  //work backwards -> stopping at arrray position 1 
  //odd or even -> with binary number first bit always set if odd
  //so bitwise & index with 1, if result = 1 then index is odd 
  //in both cases push index to stack
  //then:
    //if odd prior position = (current position -1)/2  
    //if even prior position = current position/2    
  //repeat until you reach the root of binary tree, that is position 1

  while((i!=1))
            {
              if (i&1){ //odd so dash
                        stack.push(i);i=(i-1)/2; }
                      else{ //even so dot
                            stack.push(i);i=i/2;}
              }                                         
                              }
//pop each element from the stack and play dot or dash as appropriate
  while(!stack.isEmpty ())
            {
              if (stack.pop()&1){ //odd so dash
                        analogWrite(sounder_pin, duty_cycle); 
                        delay(dash);                       // wait for a while
                        analogWrite(sounder_pin,0);
                        delay(intra_letter_space);         // space between letter parts
                        i=(i-1)/2; }
                      else{ //even so dot
                            analogWrite(sounder_pin, duty_cycle);
                            delay(dot);                    // wait for a while
                            analogWrite(sounder_pin,0);
                            delay(intra_letter_space);     // space between letter parts
                            i=i/2;}
              }   delay(inter_letter_space);//space between letters                                         
  }

txt to Morse direct translation version

C/C++
//.txt file to morse code 
//uses modified morse binary tree to allow direct translation of letters to dots and dashes

//CHECK IF LCD SHIELD USES DIGITAL PIN 10 AS THIS IS USED AS CS FOR DATA LOGGING SHIELD
//CHECK TO SEE IF LCD IS ONE OF THE FAULTY ONES
//MINE HAD BOTH PROBLEMS SO I DESOLDERED AND REMOVED THE FAULTY PIN 10
//REFERENCE - https://forum.arduino.cc/index.php?topic=96747.0 (Retrieved 7/June/2019)

/*
 The circuit:
 * SD card attached to SPI bus as follows:
 ** MOSI - pin 11
 ** MISO - pin 12
 ** CLK - pin 13
 ** CS - pin 10
 * 
 * RTC connections (I2C) - Clock not used in this project:
 * gnd to gnd
 * + to +5V
 * SDA to A4
 * SCL to A5
 * 
 * LCD shield:
 * Backlight - pin 10 removed
 * B4 to D4
 * B5 to D5
 * B6 to D6
 * B7 to D7 
 * RS to D8
 * E to D9
 * 
 * LCD shield buttons
 * Analog 0
 * 
 * Buzzer 
 * GND to GND
 * + to 3
 * 
 * To change morse speed ground one of the following analog pins
 * A1 = 5 wpm
 * A2 = 13 wpm
 * A2 = 20wpm 
 * default with no analog pins grounded 5 wpm
*/
#include <SPI.h>
#include <SD.h>

const int chipSelect=10;

//my_aliases
//const int move_switch=2;
//const int enter_switch=3;
const int initialization_screen=1;
const int file_menu_screen=2;
const int file_wont_open_screen=3;
const int file_selected_screen=4;
const int display_character_screen=5;
const int print_filename_screen=6;

File root;
File myFile;

//for the LCD shield
#include <LiquidCrystal.h>

// select the pins used on the LCD panel
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
// http://www.robotshop.com/content/PDF/dfrobot-lcd-keypad-shield-schematic.pdf 

//required to debounce switch
#define MAX_CHECKS 10
volatile uint8_t Debounced_State=0;//accessed by isr and main loop code
uint8_t State[MAX_CHECKS]={0};
uint8_t Index=0;



//my_global_variables
int screen_number=initialization_screen;
bool fail=false;
bool file_exists=false;
static String my_filename="";
char my_character=' ';
  

const int sounder_pin=3;// the pin we attach our sounder to
const int duty_cycle=128;//50% duty cycle for analogWrite()

void setup() {

//initial lcd setup
  lcd.begin(16,2);   // initialize the lcd for 16 chars 2 lines, turn on backlight   
  lcd.clear();


//try to initialize sd card
  if (!SD.begin(chipSelect)) {
    fail=true;
    update_display();
    return;
  }
  fail=false;
  update_display();
  delay(5000);

  //set up sounder frequency
  TCCR2B = (TCCR2B & 0b11111000) | 0x02;

  // initialize digital pin as an output.
  pinMode(sounder_pin, OUTPUT);

  //for morse code speed selection
  pinMode(A1,INPUT_PULLUP);
  pinMode(A2,INPUT_PULLUP);
  pinMode(A3,INPUT_PULLUP);

      // initialize timer1 
  noInterrupts();           // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  OCR1A = 625;              // compare match register  16MHX/256/100HZ 
  TCCR1B |= (1 << WGM12);   // CTC mode
  TCCR1B |= (1 << CS12);    // 256 prescaler 
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
  interrupts();


}/*end of setup*/

void loop() {
  
  root = SD.open("/");
  File dir;
  dir=root;

//variables
bool count_updated=true;
bool enter_pressed=false;

static bool button_up=false;
static bool chord_ready=false;
static int Stored_State=0;
static int Action_Me=0;

//all timing is based around the length of the dot
//source for timings -> http://www.kent-engineers.com/codespeed.htm (retreived 5 March 2017)

static unsigned long unit_length=240;
if(!digitalRead(A1)){unit_length=240;}//dot time calculated for 5 words per minute
if(!digitalRead(A2)){unit_length=92;}//dot time calculated for 13 words per minute
if(!digitalRead(A3)){unit_length=60;}//dot time calculated for 20 words per minute
  
 
while(1){

    noInterrupts();           // disable all interrupts    
if (Debounced_State>Stored_State){Stored_State=Debounced_State;chord_ready=true;}//store chord and flag as ready
if (Debounced_State<Stored_State){if(chord_ready){button_up=true;Action_Me=Stored_State;}Stored_State=Debounced_State;} 
    interrupts();             // enable all interrupts

if(button_up && chord_ready)
{
chord_ready=false;
button_up=false;

if (Action_Me&1<<1){enter_pressed=true;}//select button
                          
if (Action_Me&1<<0){count_updated=true;}//down button

}
 
  if (count_updated){
  
  count_updated=false;

  File entry =  dir.openNextFile();
    if (! entry) {
      // no more files -> so go back to beginning
      entry.close();
      dir.rewindDirectory();
      my_filename="No more";
      update_display();   
    }else
    {
    my_filename=entry.name(); 
    screen_number=file_menu_screen;
    update_display();
    entry.close();}                   
                    }

if (enter_pressed){
  
  enter_pressed=false;

  screen_number=file_selected_screen;
 
  if (SD.exists(my_filename)) {file_exists=true;}else{file_exists=false;}
  
  update_display();

//read data from file
  // re-open the file for reading:
  myFile = SD.open(my_filename, FILE_READ);
  if (myFile) {

    // read from the file until there's nothing else in it:

    while (myFile.available()) 
    {
    my_character=myFile.read(); 
    screen_number=display_character_screen;
    update_display();
    if (my_character>=65 && my_character<=90){my_character+=32;};//change letters to lower case if necessary
    beep_character(my_character,unit_length);
    }

    // close the file:
    myFile.close();
  } else {
    // if the file didn't open, print an error:
    screen_number=file_wont_open_screen;
    update_display();
  }

}
                    
}
  
}/*end of loop*/

//my_functions

ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine
{

//read buttons
  uint8_t temp=0x00;
  
 if (analogRead(0) > 200 && analogRead(0) < 400){temp|=1<<0;} //btnDOWN  
 if (analogRead(0) > 600 && analogRead(0) < 800){temp|=1<<1;} //btnSELECT
 
//debounce
  uint8_t i,j;
  State[Index]= temp;
  ++Index;
  j=0xFF;
  for (i=0;i<MAX_CHECKS;i++){j=j&State[i];}
  Debounced_State=j;
  if(Index>=MAX_CHECKS){Index=0;} 
}


//my_screen_functions
void update_display(void)
{
  switch (screen_number)
  {
    case initialization_screen:lcd.clear();
                               lcd.setCursor(0,0);
                               lcd.print("Init SD...");
                               if (!fail){lcd.print("done.");}else{lcd.print("fail!");}
                               break;
    case file_menu_screen:lcd.clear();
                          lcd.setCursor(2,0);
                          lcd.print("Select file");
                          lcd.setCursor(2,1);
                          lcd.print("            ");//clear any previous filename (8.3 character format, so 12 spaces)
                          lcd.setCursor(2,1);
                          lcd.print(my_filename);
                          break;
                         
    case file_wont_open_screen:lcd.clear();
                               lcd.print("Can't open file");
                               break;
    case file_selected_screen:lcd.clear();
                              lcd.setCursor(0,0);
                              lcd.print("Opt->");
                              lcd.print(my_filename);
                              lcd.setCursor(0,1);
                              if(!file_exists){lcd.print("No file.");}
                              break;
    case display_character_screen:lcd.setCursor(7,1);
                                  lcd.write(my_character);
                                  break;                  
    }
  } 
   
//beep_character function to sound out morse
//uses functions delay() and analogWrite()
//all timing is based around the length of the dot
//source for timings -> http://www.kent-engineers.com/codespeed.htm
//examples
//unit_length = 240, dot time calculated for 5 words per minute
//unit_length = 92, dot time calculated for 13 words per minute
//unit_length = 60, dot time calculated for 20 words per minute

void beep_character(char incoming_byte, unsigned long unit_length)
{

//here the standard morse dichotic tree has been modified to allow direct translation from character look up to dots and dashes
//by rearranging the tree we eliminate the need to use a stack  
const char reverse_morse_array[]={
0x00,0x00,//positions 0 and 1 not used
101,116,//2
105,110,97,109,//4
115,100,114,103,117,107,119,111,//8
104,98,108,122,102,99,112,246,118,120,228,113,252,121,106,154,//16
53,54,0x00,55,233,231,254,56,83,47,43,71,208,72,74,57,52,61,232,0x00,13,0x00,224,0x00,51,0x00,71,241,50,0x00,49,48,//32
0x00,0x00,0x00,0x00,0x00,0x00,0x00,58,0x00,0x00,0x00,0x00,63,95,0x00,0x00,0x00,0x00,34,0x00,0x00,59,64,0x00,0x00,0x00,64,0x00,0x00,40,39,
0x00,0x00,45,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,46,33,95,0x00,40,0x00,0x00,0x00,0x00,44,0x00,33,0x00,0x00,58,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
const int reverse_morse_array_limit=127;//128 elements total so 0 to 127 

const unsigned long dot=unit_length;
const unsigned long dash=unit_length*3;
const unsigned long intra_letter_space=unit_length;//space between dot's and dashe's within letter
const unsigned long inter_letter_space=(unit_length*3)-intra_letter_space;// space between letters, subtracts used to deal with spaces already there
const unsigned long word_space=(unit_length*7)-intra_letter_space-inter_letter_space;// space between words, subtracts used to deal with spaces already there

int i=0;

if (incoming_byte==' '){delay(word_space);return;}
 

//find character in array
    
for(i=2;i<=reverse_morse_array_limit;i++)
{
  if (incoming_byte==reverse_morse_array[i]){break;}//locate letter
  }

if (i<=reverse_morse_array_limit){
   
  //work backwards -> stopping at arrray position 1
  //odd or even -> with binary number first bit always set if odd
  //so bitwise & index with 1, if result = 1 then index is odd 
  //if odd prior position = (current position -1)/2  
  //if even prior position = current position/2  
  //sound dash for odd, dot for even
  //repeat until you reach the root of binary tree, that is position 1

  while((i!=1))
            {
              if (i&1){ //odd so dash
                        analogWrite(sounder_pin, duty_cycle); 
                        delay(dash);                       // wait for a while
                        analogWrite(sounder_pin,0);
                        delay(intra_letter_space);         // space between letter parts
                        i=(i-1)/2; }
                      else{ //even so dot
                            analogWrite(sounder_pin, duty_cycle);
                            delay(dot);                    // wait for a while
                            analogWrite(sounder_pin,0);
                            delay(intra_letter_space);     // space between letter parts
                            i=i/2;}
              }   delay(inter_letter_space);//space between letters                                         
                              }

  }

Credits

glennedi

glennedi

5 projects • 23 followers

Comments