memory keyer & keyboard

A few notes about this code:

  • I like modular code with small amounts of code in each file, so I typically end up with lots of files in a project.  This one is no exception.  There are lots of files, but they’re small.
  • I haven’t gotten around to actually hooking up a keyboard to this project and trying it out.  The core keyboard routines all work and have been tested elsewhere, so they’re in relatively good shape, but I have no idea if the keyboard features of this project work or not.  Let me know if you try it.
  • This was all written specifically for an Atmel ATTiny84 running at 1 MHz.  It’s easy to port to other Atmel MCUs just by making changes to interface.h and timing.h.  There might be a couple of other gotchas, but I’m not aware of them off hand. 
  • I’m posting the main morse processing code below so casual readers can readily take a peak at it.  It works for paddle, memory, and, in theory, for keyboard as well. 
  • You can download the complete source here.

volatile word m_millis;
volatile word m_delay;
volatile word m_dit_ms;
volatile word m_dah_ms;
volatile word m_elt_ms;
volatile word m_char_ms;
volatile word m_word_ms;
volatile char m_last_char;
volatile byte m_byte;
volatile byte m_wpm;
volatile byte m_weight;
volatile byte m_volume;
volatile byte m_pre_paddle;
volatile byte m_tx_record;
volatile byte m_element_count;
volatile byte m_delay_type;

ABF_Declare( completeFifo, 16 );
ABF_Declare( sendFifo, 128 );

#define PADDLE_BUFFER_SIZE 2
volatile byte m_paddleBuffer[PADDLE_BUFFER_SIZE];
volatile byte m_pb_index = 0;
volatile byte m_pb_count = 0;
volatile byte m_pb_prev = 0;

void Morse_SetSpeed( byte wpm, byte weight )
{
  if( wpm < 5 )
  {
    wpm = 5;
  }
  else if( wpm > 50 )
  {
    wpm = 50;
  }

  m_wpm = wpm;
  m_weight = weight;
  m_dit_ms = 1100 / wpm;
  m_dah_ms = (( 3 + weight ) * m_dit_ms) / 2;
  m_elt_ms = m_dit_ms - 1;
  m_char_ms = 3 * m_dit_ms - 1;
  m_word_ms = 5 * m_dit_ms - 1;
}

static inline void KeyUp()
{
    TX_PORT &= ~TX_BIT;
    PWM_SetLevel( 0 );
}

static inline void KeyDown()
{
  TX_PORT |= TX_BIT;
  PWM_SetLevel( m_volume );
}

static inline void EnqueuePaddleInput()
{
    // unoptimized paddle buffering implementation to support
    // going back and forth between paddles prematurely
    if( m_pb_count < PADDLE_BUFFER_SIZE )
    {
        if( ( PADDLE_DAH_ON ) &&
            ( m_pb_prev != MORSE_BYTE_DAH ) &&
            ( m_byte != MORSE_BYTE_DAH ) )
        {
            m_paddleBuffer[(m_pb_index+m_pb_count)%PADDLE_BUFFER_SIZE] = MORSE_BYTE_DAH;
            m_pb_prev = MORSE_BYTE_DAH;
            m_pb_count++;
        }
        if( ( PADDLE_DIT_ON ) &&
            ( m_pb_prev != MORSE_BYTE_DIT ) &&
            ( m_byte != MORSE_BYTE_DIT ) )
        {
            m_paddleBuffer[(m_pb_index+m_pb_count)%PADDLE_BUFFER_SIZE] = MORSE_BYTE_DIT;
            m_pb_prev = MORSE_BYTE_DIT;
            m_pb_count++;
        }
    }
}

static inline byte DequeuePaddleInput()
{
    byte rc = 0;

    if( m_pb_count > 0 )
    {
        rc = m_paddleBuffer[m_pb_index];
        m_pb_index = (m_pb_index+1)%PADDLE_BUFFER_SIZE;
        m_pb_count -= 1;

        if( m_pb_count == 0 )
            m_pb_prev = 0;
    }

    return rc;
}

void Morse_Init()
{
    ABF_Init( completeFifo );
    ABF_Init( sendFifo );

    // set up our "tx" pin
    TX_DDR |= TX_BIT;

    // set pull-ups on our input pins
    PADDLE_PORT |= (PADDLE_DAH|PADDLE_DIT);

    // set up PWM sound output
    PWM_Init();

    // now set up our timer overflow interrupt
    SetupTimerInterrupt();

    // todo: pull these params out of eeprom
    Morse_SetSpeed( 20, 3 );
    Morse_SetVolume( 10 );
}

// service routine for the timer interrupt
TIMER_INTERRUPT()
{ 
    // if the paddle is being touched while we're sending letters
    // then we need to stop immediately and clear the send queue
    if( !MORSE_BYTE_IS_ELEMENT(m_byte) && PADDLE_TOUCHED )
    {
        KeyUp();
        m_delay = 0;
        m_byte = 0;
        ABF_Reset( sendFifo );
        Memkey_SetPlaybackState( false );
    }

    // if m_delay is set (non-zero) then we're in the middle of
    // sending a dit, a dah or some space
    if( m_delay != 0 )
    {
        word elapsed = GetMillis() - m_millis;

        // check for paddle input
        EnqueuePaddleInput( elapsed );

        if( elapsed < m_delay )
        {
            return;
        }

        // we've waited the appropriate time now stop keying the TX
        KeyUp();

        // delay between dits and dahs within a character
        if( m_delay_type == MORSE_DT_DIT || m_delay_type == MORSE_DT_DAH )
        {
            // add some intra-element delay here
            m_delay = MORSE_ELEMENT_DELAY;
            m_delay_type = MORSE_DT_ELEMENT;
            m_millis = GetMillis();
            return;
        }
        if( !MORSE_BYTE_IS_ELEMENT(m_byte) )
        {
            m_byte <<= 1;
            if( m_byte == 0x80 )
            {
                m_byte = 0;
                // we're at the end of the morse character
                // add some intra-character delay
                m_delay = MORSE_CHAR_DELAY;
                m_delay_type = MORSE_DT_CHAR;
                m_millis = GetMillis();
                m_tx_record |= 0x80 >> m_element_count;
                ABF_Push( completeFifo, m_tx_record );
                m_tx_record = 0;
                m_element_count = 0;
                return;
            }
        }
        m_delay = 0;
    }

    if( PADDLE_TOUCHED || MORSE_BYTE_IS_ELEMENT(m_byte) )
    {
        m_byte = DequeuePaddleInput();

        // last input came from the paddle rather than keyboard
        // if we have some paddle input queued up then use that
        // otherwise, check the physical paddle for input
        if( m_byte == 0 )
        {
            if( PADDLE_DAH_ON )
                m_byte = MORSE_BYTE_DAH;
            if( PADDLE_DIT_ON )
                m_byte = MORSE_BYTE_DIT;
        }
        if( m_byte == 0 )
        {
            m_delay = 0;
            m_byte = 0;
            return;
        }
        if( m_element_count == 0 )
        {
            if( ( GetMillis() - m_millis ) > MORSE_CHAR_DELAY )
            {
                // long delay between paddle inputs
                // must be a space
                ABF_Push( completeFifo, 0 );
            }
        }
        if( m_byte == MORSE_BYTE_DAH )
        {
            m_delay = m_dah_ms;
            m_delay_type = MORSE_DT_DAH;
            m_tx_record |= 0x80 >> m_element_count++;
        }
        else if( m_byte == MORSE_BYTE_DIT )
        {
            m_delay = m_dit_ms;
            m_delay_type = MORSE_DT_DIT;
            m_element_count++;
        }
    }
    else if( m_byte == 0 )
    {
        // completed sending a character (or user stopped touching the paddle)

        if( m_element_count > 0 && ( GetMillis() - m_millis ) > MORSE_CHAR_DELAY - MORSE_ELEMENT_DELAY )
        {
            // user completed a character using the paddles
            // enqueue the completed character
            m_tx_record |= 0x80 >> m_element_count;

            ABF_Push( completeFifo, m_tx_record );
            m_millis = GetMillis();
            m_tx_record = 0;
            m_element_count = 0;
            return;
        }

        // try to get another character from the queue
        if( ABF_Empty( sendFifo ) )
        {
            // nothing in the queue
            Memkey_SetPlaybackState( false );

            return;
        }

        ABF_Pop( sendFifo, (byte*)&m_byte );

        if( m_byte == 0 )
        {
            // send some intra-word space
            m_delay = MORSE_WORD_DELAY;
            m_delay_type = MORSE_DT_WORD;
            m_millis = GetMillis();
            ABF_Push( completeFifo, 0 );
            return;
        }
    }
    else
    {
        // still in the process of sending a character from the queue
        if( m_byte & 0x80 )
        {
            m_delay = m_dah_ms;
            m_delay_type = MORSE_DT_DAH;
            m_tx_record |= 0x80 >> m_element_count++;
        }
        else
        {
            m_delay = m_dit_ms;
            m_delay_type = MORSE_DT_DIT;
            m_element_count++;
        }
    }

    KeyDown();
    m_millis = GetMillis();
    return;
}

One comment

  1. […] source is here. Posted by gorsat Filed in Uncategorized Leave a Comment […]

Leave a reply to Latest Project — Memory Keyer « Gorsat’s Blog Cancel reply