NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Project Help and Ideas » morse code

April 27, 2013
by uistrol_17
uistrol_17's Avatar

hi i was wondering if anyone had tried this program, using the lcd in the kit, i also cleared the timer line in the code, when pressing the button and the lcd shows the characters on line 2, how do you keep the characters continuing onto line 3 then 4? i've been trying to for days. with the counter timer lines cleared( that originally showed on line 3) the characters will start on line 2 then continue onto line 4. can someone help me with this problem? troubled programmer 17

April 27, 2013
by pcbolt
pcbolt's Avatar

17 -

I think when the code for that project was written the Nerdkit shipped with the 2-line LCD and not the 4 line LCD the newer kits have (not positive though, I have the newer kit like yours). I looked through the source code they have posted for the USB project and they are using some raw LCD commands to move to location of the next the character placement. The "lcd.c" and "lcd.h" that are part of the newer kits have functions created to do this more easily. You can move to any location with this function call...

lcd_goto_position(0, 12);  // 0 is row 1 and  12 is column 13 (zero indexed)

You can still use this...

lcd_line_two();
lcd_write_string(PSTR("                    "));

To clear line 2 for example.

I'm not sure how you want the program to behave but the "chars" variable in the program keeps track of how many characters are printed so you can use that to decide where you want to print the next character. Keep in mind the LCD updates it's position every time you print one character so you should only have to worry about moving to new lines. The 4 line LCD's are odd because if you go past column 20 on line one it will increment to column 1 on line 3. Same with rows 2 and 4.

April 28, 2013
by uistrol_17
uistrol_17's Avatar

hey pcbolt, that is my problem, im using this code as a lab project( we can use anything online) but i'm trying to fix that problem, when line i go past line 2 the lcd increments to line 4 but i want it to continue from end of line 2 to beginning of line 3 and so on to line 4. i'm still not understanding in how to do this using the given code.

April 28, 2013
by pcbolt
pcbolt's Avatar

17 -

I just want to make sure you have the same download I was looking at. It will help as a line reference if me or anyone else needs to change something. Here is the downloaded code...

// morsedecoder.c
// for NerdKits with ATmega168
// mrobbins@mit.edu

#define F_CPU 14745600

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include <inttypes.h>
#include <stdlib.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

// PIN DEFINITIONS:
//
// PC5 -- keyer input switch (pulled to ground when pressed)
//   (was PA7)
// PC4 -- LED anode
//   (was PA1)
// PB1 -- piezo element or speaker (OC1A)

inline void wait_for_timer_tick() {
  while(!(TIFR0 & (1<<TOV0))) {
    // do nothing
  }
  // clear timer tick
  TIFR0 |= (1<<TOV0);
}
#define INTERSYMBOL_TIMEOUT 200
#define LONGSHORT_CUTOFF 60

void speaker_off() {
  TCCR1A &= ~(1<<COM1A0);
}

void speaker_on() {
  TCCR1A |= (1<<COM1A0);
}

uint8_t time_next_keypress() {
  uint16_t counter;

  // waits until the button is pressed
  // and then released. (OR timeout)
  //
  // returns timer cycles between press&release
  // or returns 255 on timeout

  // wait FOREVER until the button is released
  // (to prevent resets from triggering a new dit or dah)
  while((PINC & (1<<PC5))==0) {
  }

  // turn off LED
  PORTC &= ~(1<<PC4);
  // turn off speaker
  speaker_off();

  counter = 0;
  // wait until pressed
  while(PINC & (1<<PC5)) {
    wait_for_timer_tick();
    counter++;

    if(counter == INTERSYMBOL_TIMEOUT) {
      // timeout #1: key wasn't pressed within X timer cycles.
      // (should happen between different symbols)
      return 255;
    }
  }

  // turn on LED
  PORTC |= (1<<PC4);
  // turn on speaker
  speaker_on();

  // wait one cycle as a cheap "debouncing" mechanism
  wait_for_timer_tick();

  // wait until released
  counter = 0;
  while((PINC & (1<<PC5))==0) {
    wait_for_timer_tick();
    counter++;

    if(counter == 255) {
      // timeout #2: key wasn't released within 255 timer cycles.
      // (should happen only to reset the screen)
      return 254;
    }
  }

  // turn off LED
  PORTC &= ~(1<<PC4);
  // turn off speaker
  speaker_off();

  return counter;
}

// defining the lookup table.
// bits   7 6 5 4 3 2 1 0
// 765 define the length (0 to 7)
// 43210 define dits (0) and dahs (1)
#define MORSE_SIZE 26
#define MORSE(s, x)  ((s<<5) | x)
#define DIT(x) (x<<1)
#define DAH(x) ((x<<1) | 1)
unsigned char morse_coded[MORSE_SIZE] PROGMEM = 
{
  MORSE(2, DIT(DAH(0))),        //A
  MORSE(4, DAH(DIT(DIT(DIT(0))))),  //B
  MORSE(4, DAH(DIT(DAH(DIT(0))))),  //C
  MORSE(3, DAH(DIT(DIT(0)))),       //D
  MORSE(1, DIT(0)),         //E
  MORSE(4, DIT(DIT(DAH(DIT(0))))),  //F
  MORSE(3, DAH(DAH(DIT(0)))),       //G
  MORSE(4, DIT(DIT(DIT(DIT(0))))),  //H
  MORSE(2, DIT(DIT(0))),        //I
  MORSE(4, DIT(DAH(DAH(DAH(0))))),  //J
  MORSE(3, DAH(DIT(DAH(0)))),       //K
  MORSE(4, DIT(DAH(DIT(DIT(0))))),  //L
  MORSE(2, DAH(DAH(0))),        //M
  MORSE(2, DAH(DIT(0))),        //N
  MORSE(3, DAH(DAH(DAH(0)))),       //O
  MORSE(4, DIT(DAH(DAH(DIT(0))))),  //P
  MORSE(4, DAH(DAH(DIT(DAH(0))))),  //Q
  MORSE(3, DIT(DAH(DIT(0)))),       //R
  MORSE(3, DIT(DIT(DIT(0)))),       //S
  MORSE(1, DAH(0)),         //T
  MORSE(3, DIT(DIT(DAH(0)))),       //U
  MORSE(4, DIT(DIT(DIT(DAH(0))))),  //V
  MORSE(3, DIT(DAH(DAH(0)))),       //W
  MORSE(4, DAH(DIT(DIT(DAH(0))))),  //X
  MORSE(4, DAH(DIT(DAH(DAH(0))))),  //Y
  MORSE(4, DAH(DAH(DIT(DIT(0))))),  //Z
};
unsigned char morse_alpha[MORSE_SIZE] PROGMEM =
{ 
  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
};

unsigned char morse_lookup(unsigned char in) {
  // linearly go through the table (in program memory)
  // and find the matching one
  uint8_t i;
  unsigned char tmp;

  for(i=0; i<MORSE_SIZE; i++) {
    tmp = pgm_read_byte(&morse_coded[i]);

    if(tmp == in) {
      // matched morse character
      return pgm_read_byte(&morse_alpha[i]);
    }
  }
  return '?';
}

unsigned char bitwise_reverse(unsigned char in, uint8_t max) {
  // maps bits backwards
  // i.e. for max=5
  // in  XXX43210
  // out YYY01234
  unsigned char b = 0;

  uint8_t i;
  for(i=0; i<max; i++) {
    if(in & (1<<i))
    b |= (1<< (max-1-i) );
  }

  return b;
}

int main() {
  // enable internal pullup on PC5 (the button)
  PORTC |= (1<<PC5);

  // enable LED on PC4
  DDRC |= (1<<PC4);

  // enable piezo out on PB1
  DDRB |= (1<<PB1);

  // use Timer0 as our clock source.
  // divide the 14.7456MHz by 256, and then wait for overflow (another factor of 256)
  // so we get one overflow every 4.4ms
  // set prescale CK/256
  TCCR0A = 0;
  TCCR0B = (1<<CS02);

  // use Timer1 for buzzer sound.
  // CTC mode, WGM12
  TCCR1B |= (1<<WGM12);
  // enable output pin to toggle on match
  //TCCR1A = (1<<COM1A0); // see speaker_on()
  // toggle on 255 overflow, and get 450Hz
  OCR1AH = 0;
  OCR1AL = 255;
  // divide the 14.7456MHz by 64
  TCCR1B |= (1<<CS11) | (1<<CS10);

  // fire up the LCD
  lcd_init();
  lcd_home();

  uint8_t counter=0;
  uint8_t ditdahs=0; // counts dits and dahs along the top row
  unsigned char curchar=0, lastchar='_';
  uint8_t chars=0;   // counts chars along the bottom row
  uint8_t spacetimes=0;  // counts number of intersymbol times (before we call it a space)

  while(1) {
    counter = time_next_keypress();

    // decide what to do based on the timing
    if(counter == 254) {
      // clear everything
      lcd_home();
      lcd_write_string(PSTR("                        "));
      lcd_line_two();
      lcd_write_string(PSTR("                        "));
      ditdahs = 0;
      chars = 0;
      curchar = 0;
      lastchar = '_';
      spacetimes = 0;

    } else if(counter == 255) {

      // intersymbol timeout: clear 1st row
      lcd_home();
      lcd_write_string(PSTR("                        "));

      if(ditdahs > 0) {
        // lookup the character
        curchar = MORSE(ditdahs, bitwise_reverse(curchar, ditdahs));
        curchar = morse_lookup(curchar);

        // print it
        lcd_set_type_command();
        lcd_write_byte(0x80 + 0x40 + chars);    // move to 2nd line, chars^th column
        lcd_write_data(curchar);

        chars++;
        lastchar = curchar;
        spacetimes = 0;
      } else if(lastchar != '_') {
        spacetimes++;
        if(spacetimes == 4) {
          // as long as the last character wasn't a space, print a space 
          // (as an underscore so we can see it)
          curchar = '_';

          // print it
          lcd_set_type_command();
          lcd_write_byte(0x80 + 0x40 + chars);  // move to 2nd line, chars^th column
          lcd_write_data(curchar);

          chars++;
          lastchar = curchar;
        }
      }

      curchar = 0;
      ditdahs = 0;
    } else {

      // go to correct position
      lcd_set_type_command();
      lcd_write_byte(0x80 | ditdahs);   // move to 2nd line, chars^th column

      // dit or dah
      if(counter >= LONGSHORT_CUTOFF) {
        // dah
        lcd_write_data('-');
        curchar = DAH(curchar);
      } else {
        // dit
        lcd_write_data('.');
        curchar = DIT(curchar);
      }

      ditdahs++;
      spacetimes = 0;
    }

    // write the last timeout in the top right of the screen
    lcd_set_type_command();
    lcd_write_byte(0x80 | 21);  // go to position 21
    lcd_write_int16(counter);
    lcd_write_data(' ');
    lcd_write_data(' ');

  }

  return 0;
}

I'm going to look over it a little more and see if I can find an answer. (Little late tonight :-)

April 28, 2013
by pcbolt
pcbolt's Avatar

17 -

Ok I took a better look at the code and I think I can help. There are three places in the code you need to make some changes. First is right around line 226, comment out four lines and add one so it looks like...

//  lcd_home();
//  lcd_write_string(PSTR("                        "));
//  lcd_line_two();
//  lcd_write_string(PSTR("                        "));
lcd_clear_and_home();

Now the next two changes are the same but in two different locations. The first occurs around line 249 and the next at 264. Change that to read...

//  lcd_set_type_command();
//  lcd_write_byte(0x80 + 0x40 + chars);  // move to 2nd line, chars^th column
lcd_goto_position(((chars / 20) % 3) + 1, chars % 20);
lcd_write_data(curchar);
April 28, 2013
by uistrol_17
uistrol_17's Avatar

hey pc bolt , i just now tried that new line of code, well what happens, is that the characters do continue to the next line, but for some reason at the beginning line 3 the characters will show then disappear. This only occurs in the first four locations then at location 5 of 3rd line the charcters will stay on screen all the way through end of line 4. Also could you explain (((chars/20)%3)+1,chars%20) means? Also in regarding my question about the pushbutton, yes i am trying to use it to clear the screen for the morse program, i want to clear the screen at any time . i entered those code lines

    while(1){
         if((PINC & (1<<PC3))==0){

        lcd_clear_and_home();
        }}

after line 103 return counter, i'm not sure where to enter this while loop, it tried inserting it after line 55 but it just clears the screen with now switched turned, i think it just prevents program from running. please help

April 28, 2013
by pcbolt
pcbolt's Avatar

On line 295, change the value 21 to 15. I'm posting from a phone so I'll add more later on.

April 28, 2013
by uistrol_17
uistrol_17's Avatar

that part doesnt matter because im not displaying the timer, i noticed that these lines

  240   lcd_home();
  241  lcd_write_string(PSTR("                        "));

seem to affect line 3 spaces 1-4. if i comment out line 241, line one doesnt clear out but the characters will not disappear on line 3 space 1-4, but then again line 1 doesnt clear to show dits and dahs for each character. i figure that its the same relation as the previous problem with lines 1 and 3 connected somehow, i cant see anywhere in the code how line 1 is affecting the beginning of line 3. i'll be up late so feel free to reply

April 29, 2013
by pcbolt
pcbolt's Avatar

I thought the old LCD screen was 2x20 but it looks like its 2x25. Try shortening the number of spaces between the " " marks on this line (and all like it) to 20

lcd_write_string(PSTR("                        "));

In regards to what this does...

(((chars/20)%3)+1,chars%20)

"chars" is the variable in the program that keeps track of how many characters get printed. "chars/20" (integer division) will be 0 when "chars" is between 0 and 19, 1 when it's between 20 and 39, 2 when it's between 40 and 59 etc, so it's a way of determining which row the character will be printed on (I added 1 so it skips over line one). "chars%20" is the modulus operator and returns the remainder from an integer division. So 1/20 is 0 with a remainder of 1. This is an easy way to convert the character count to a column location. I put the %3 in there so the row numbers will always be between 1 and 3 even when "chars" goes above 60. You may want to clear the screen at this point but that's up to you.

As for the PINC code, you shouldn't use a new while loop, just place the code inside the existing while(1) loop (on line 220) ...

while(1) {
  counter = time_next_keypress();
  // decide what to do based on the timing
  if((PINC & (1<<PC3))==0){
    lcd_clear_and_home();
  }
  if(counter == 254) {
April 29, 2013
by uistrol_17
uistrol_17's Avatar

hey thanks for the tips, i do have one last question, when pressing the button or switch to clear the screen, the screen does clear, but the characters resume in the last spot from when cleared. how do you make it to where after a clear occurs, the characters begin at beginning of line 2, ive tried

lcd_goto_position(((chars / 20) % 3) + 1, chars % 20);

after clear thinking that the chars position will resume at first position i also tried using

lcd_goto_position(1,0):

please help

April 29, 2013
by pcbolt
pcbolt's Avatar

I think you just need to reset "chars" to zero. So the code from the last post should look like...

while(1) {
  counter = time_next_keypress();
  // decide what to do based on the timing
  if((PINC & (1<<PC3))==0){
    lcd_clear_and_home();
    chars = 0;
  }
  if(counter == 254) {

Everything else should be the same.

Post a Reply

Please log in to post a reply.

Did you know that an analog comparator can tell when one voltage input crosses another? Learn more...