Wednesday, December 24, 2014

Generating Audio PSK31 with an Arduino (update)

This is really just an update to my previous post rather than a complete "part 2" of my discussion.  I have been tending a some rather tedious medical issues and have not been writing code for a while.  However, I have a quick update to the start of my PSK code that fixes the anomalous waveform seen here.  




The updated waveform now looks phase continuous without the glitch previously seen.



The signal sounds nice when played through amplified speakers.  Here is a screen shot from fldigi of the continuous idle signal.  Looks good and the signal is pretty clean on the waterfall display.  I still have to fix the clock timer speed to get the character time correct, but that can wait for now.



Next up is to implement character transmission.  For those following along at home, here is the updated code in its entirety.  Remember that as written this is for the ATMega2560 board.  You will need to change the PWM output pin to pin 11 for the UNO and other ATMega328 devices.

// PSK31 audio generation
// Jeff Whitlatch - ko7m

// 16 cycles of 32 samples each (512 bytes) of ramp-up sinusoid information.
// There is an extra byte at the end of the table with the value 0x80 which 
// allows the first byte to always be at the zero crossing point whether
// ramping up or down.
char zero[] = { 
  0x80,0x80,0x80,0x80,0x81,0x81,0x82,0x82,
  0x83,0x83,0x83,0x83,0x83,0x82,0x82,0x81,
  0x7F,0x7E,0x7D,0x7B,0x7A,0x79,0x78,0x77,
  0x76,0x76,0x76,0x77,0x78,0x79,0x7B,0x7D,
  0x80,0x82,0x85,0x87,0x89,0x8B,0x8D,0x8E,
  0x8F,0x8F,0x8F,0x8D,0x8C,0x89,0x86,0x83,
  0x7F,0x7C,0x78,0x75,0x71,0x6E,0x6C,0x6B,
  0x6A,0x6A,0x6B,0x6C,0x6F,0x72,0x76,0x7B,
  0x80,0x84,0x89,0x8E,0x92,0x96,0x99,0x9A,
  0x9B,0x9B,0x9A,0x98,0x94,0x90,0x8B,0x85,
  0x7F,0x79,0x73,0x6E,0x69,0x64,0x61,0x5F,
  0x5E,0x5E,0x60,0x62,0x66,0x6C,0x72,0x78,
  0x80,0x87,0x8E,0x95,0x9B,0xA0,0xA4,0xA6,
  0xA7,0xA7,0xA5,0xA2,0x9D,0x97,0x90,0x88,
  0x7F,0x77,0x6F,0x67,0x60,0x5A,0x56,0x53,
  0x52,0x52,0x55,0x59,0x5E,0x65,0x6D,0x76,
  0x80,0x89,0x92,0x9B,0xA3,0xA9,0xAE,0xB2,
  0xB3,0xB2,0xB0,0xAB,0xA5,0x9D,0x94,0x8A,
  0x7F,0x75,0x6A,0x61,0x58,0x51,0x4B,0x48,
  0x46,0x47,0x4A,0x4F,0x56,0x5F,0x69,0x74,
  0x80,0x8B,0x97,0xA1,0xAB,0xB3,0xB9,0xBD,
  0xBE,0xBD,0xBA,0xB4,0xAD,0xA3,0x98,0x8C,
  0x7F,0x73,0x66,0x5B,0x50,0x48,0x41,0x3D,
  0x3C,0x3D,0x40,0x46,0x4F,0x59,0x65,0x72,
  0x80,0x8D,0x9B,0xA7,0xB2,0xBC,0xC2,0xC7,
  0xC9,0xC8,0xC4,0xBD,0xB4,0xA9,0x9C,0x8E,
  0x7F,0x71,0x62,0x55,0x49,0x3F,0x38,0x33,
  0x31,0x33,0x37,0x3E,0x47,0x53,0x61,0x70,
  0x80,0x8F,0x9F,0xAD,0xB9,0xC4,0xCC,0xD1,
  0xD2,0xD1,0xCD,0xC5,0xBB,0xAE,0xA0,0x90,
  0x7F,0x6F,0x5F,0x50,0x42,0x37,0x2F,0x2A,
  0x28,0x29,0x2E,0x36,0x41,0x4E,0x5D,0x6E,
  0x80,0x91,0xA2,0xB2,0xC0,0xCB,0xD4,0xD9,
  0xDB,0xDA,0xD5,0xCD,0xC1,0xB3,0xA3,0x92,
  0x7F,0x6D,0x5B,0x4B,0x3C,0x30,0x27,0x21,
  0x1F,0x21,0x26,0x2F,0x3B,0x49,0x5A,0x6C,
  0x80,0x93,0xA5,0xB6,0xC6,0xD2,0xDC,0xE1,
  0xE4,0xE2,0xDC,0xD3,0xC7,0xB8,0xA6,0x93,
  0x7F,0x6C,0x58,0x46,0x37,0x2A,0x20,0x1A,
  0x18,0x19,0x1F,0x29,0x35,0x45,0x57,0x6B,
  0x80,0x94,0xA8,0xBB,0xCB,0xD8,0xE2,0xE9,
  0xEB,0xE9,0xE3,0xD9,0xCC,0xBC,0xA9,0x95,
  0x7F,0x6A,0x56,0x43,0x32,0x24,0x1A,0x13,
  0x11,0x13,0x19,0x23,0x31,0x42,0x55,0x6A,
  0x80,0x95,0xAB,0xBE,0xCF,0xDD,0xE8,0xEF,
  0xF1,0xEF,0xE9,0xDE,0xD0,0xBF,0xAB,0x96,
  0x7F,0x69,0x53,0x3F,0x2E,0x1F,0x15,0x0E,
  0x0B,0x0D,0x14,0x1F,0x2D,0x3F,0x53,0x69,
  0x80,0x96,0xAD,0xC1,0xD3,0xE2,0xED,0xF4,
  0xF6,0xF4,0xED,0xE2,0xD4,0xC2,0xAD,0x97,
  0x7F,0x68,0x52,0x3D,0x2B,0x1C,0x10,0x09,
  0x07,0x09,0x10,0x1B,0x2A,0x3C,0x51,0x68,
  0x80,0x97,0xAE,0xC3,0xD6,0xE5,0xF0,0xF7,
  0xFA,0xF8,0xF1,0xE6,0xD6,0xC4,0xAF,0x98,
  0x7F,0x67,0x50,0x3B,0x28,0x19,0x0D,0x06,
  0x04,0x06,0x0D,0x18,0x28,0x3A,0x50,0x67,
  0x80,0x98,0xAF,0xC5,0xD8,0xE7,0xF3,0xFA,
  0xFD,0xFA,0xF3,0xE8,0xD8,0xC5,0xB0,0x98,
  0x7F,0x67,0x4F,0x3A,0x27,0x17,0x0B,0x04,
  0x01,0x04,0x0B,0x17,0x26,0x39,0x4F,0x67,
  0x80,0x98,0xB0,0xC6,0xD9,0xE9,0xF4,0xFC,
  0xFE,0xFC,0xF5,0xE9,0xD9,0xC6,0xB0,0x98,
  0x7F,0x67,0x4F,0x39,0x26,0x16,0x0A,0x03,
  0x01,0x03,0x0A,0x16,0x26,0x39,0x4F,0x67,
  0x80
};

// The last 32 bytes (33 with the extra on the end) define a single cycle of 
// full amplitude sinusoid.
#define one (&zero[15*32])

// Useful macros for setting and resetting bits
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

// Variables used by the timer ISR to generate sinusoidal information.
volatile char *pbSine = &zero[0];
volatile int   cbSine = 512;
volatile char  ix     = 1;
volatile char  phase  = 1;

// PSK31 engine

// Setup timer2 with prescaler = 1, PWM mode to phase correct PWM
// See the ATMega datasheet for all the gory details

// This is not quite right for PSK31 as it is 32 us vs. 31.25 us
void timer2Setup()
{
  // Clock prescaler = 1
  sbi (TCCR2B, CS20);    // 001 = no prescaling
  cbi (TCCR2B, CS21);
  cbi (TCCR2B, CS22);

  // Phase Correct PWM
  cbi (TCCR2A, COM2A0);  // 10 = clear OC2A on compare match when up counting
  sbi (TCCR2A, COM2A1);  //      set OC2A on compare match when down counting

  // Mode 1
  sbi (TCCR2A, WGM20);   // 101 = Mode 5 uses OCR2A as top value rather than 0xff
  cbi (TCCR2A, WGM21);
}

// Timer 2 interrupt service routine (ISR).
//
// Grab the next phase point from the table and set the amplitude value
// of the sinusoid being constructed.  For a one bit, set 512 phase points
// (16 amplitudes of 32 samples each) to ramp down to zero and then immediately
// back up to full amplitude for a total of 1024 phase points.
//
// For a zero bit, there is not amplitude or phase change, so we just
// play 32 phase points of full amplitude data 32 times for a total of
// 1024 phase points.
//
// Each end of the ramp-up table starts with a zero crossing byte, so there
// is one extra byte in the table (513 entries).  Ramping up plays bytes 0 -> 511
// and ramping down plays bytes 512 -> 1 allowing each direction to start at the
// zero crossing point.
ISR(TIMER2_OVF_vect)

  // Set current amplitude value for the sine wave being constructed taking
  // care to invert the phase when processing the table in reverse order.
  OCR2A = *(pbSine)* ix * phase;
  pbSine += ix;
  if (0 == --cbSine)
  {
    cbSine = 512;

    // When we get done ramping down, phase needs to change
    if (ix < 0) phase = -phase;
    ix = -ix;
  }
}

void setup() 
{
  // PWM output for timer2 is pin 10 on the ATMega2560
  // If you use an ATMega328 (such as the UNO) you need to make this pin 11
  // See https://spreadsheets.google.com/pub?key=rtHw_R6eVL140KS9_G8GPkA&gid=0
  pinMode(10, OUTPUT);      // Timer 2 PWM output on mega256 is pin 10

  // Set up timer2 to a phase correct 32kHz clock
  timer2Setup();

  sbi (TIMSK2,TOIE2);    // Enable timer 2.
}

void loop() 
{

}

No comments:

Post a Comment