Close

Trial and Error

A project log for An Old Fashion Acoustic Modem for the iPhone

My son recently returned from a holiday in China. His biggest complaint was the blocking of FaceBook!

agp.cooperagp.cooper 01/25/2017 at 00:540 Comments

Trial and Error

Optimising the code is tricky as there are so many parameters. One approach is to extract the data from the Arduino and model the code in Excel. Once the Excel model faithfully mirrors the Arduino the the parameters can be tested. Here are some examples (cut down to fit the Hackaday webpage width):

First the input signal recorded from the Arduino (note the pre-amplifier bias shift):

Now with the bias removed:

Now mixed:

Now after the LP filter (and this still amazing me):

Now with the DC bias removed:

Now after non-return to zero detection:

You will note that the out put data is delayed by about 1 bit and the duty cycle is somewhat jittery, That is because the signal is 100mv pp and the background noise is about 50mv pp. If the signal is higher the duty cycle jitter disappears. The main problem with the detector is that it is sensitive to background impulse noise (false highs).

More Code improvements

Spent the last few days testing code. I think I am at the end (can't think of anything else to try). The final version consists of a bandpass, switching mixer and a low pass:

The DC bias problems at the switching mixer are solved by the bandpass filter much better than the DC bias filter.

The DC bias problems after the low pass was solved by increasing the gain (i.e. an integer maths problem).

Here are the signals:

The input signal:

After the bandpass filter:

After switch mixing:

After the lowpass filter:

And finally the demodulated signal (shown against the buad clock):

The input signal about 0.77v pp and the demodulator works down to 0.001mv pp (+/1 LSB of the ADC!).

Execution Time

The measured execution time of the ISR (including the ISR overhead of 2.6 us) was:

The total time between interrupts is 75.7 us.

A very tight fit if I want to use a 150 baud back channel as per the specification.

Here is the current code:

// Bell 202 modem
#define Fspace     1200
#define Fmark      2200
#define Fsample   13200
#define Baud       1200
#define sampleTicks  66     // sampleTicks = Fsample / Fmark * Fsample / Fspace

// 2 bit Sine table for Fsample equal to 13200 Hz 
const byte sin2Bit[sampleTicks] = {
  2,2,2,2,2,2,2,2,
  3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
  2,2,2,2,2,2,2,2,
  1,1,1,1,1,1,1,1,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  1,1,1,1,1,1,1,1
};

// Define various ADC prescaler
const byte PS_16=(1<<ADPS2);
const byte PS_32=(1<<ADPS2)|(1<<ADPS0);
const byte PS_64=(1<<ADPS2)|(1<<ADPS1);
const byte PS_128=(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);

void setup() {
  pinMode(A0,INPUT);                               // Audio input
  pinMode(12,OUTPUT);                              // Digital data output
  pinMode(11,OUTPUT);                              // Audio output bit 1
  pinMode(10,OUTPUT);                              // Audio output bit 0 
  pinMode(LED_BUILTIN,OUTPUT);                     // Test loop speed
  Serial.begin(9600);
  // Set ADC prescaler (assume a 16 MHz clock)
  ADCSRA&=~PS_128;                                 // Remove bits set by Arduino library
  ADCSRA|=PS_16;                                   // 16 prescaler (1 MHz)
  // About 17.2 us to return an ADC value but only good for 6 bits (probably more)

  cli();                                           // Disable interrupts
  // ATmega48A/PA/88A/PA/168A/PA/328/P
  // Use Timer 0 for sample rate (13.2 kHz)
  // Note delay() and millis() will no longer work (use microseconds() instead) 
  TIMSK0 = 0;                                      // Timer interrupts OFF
  TCCR0A = (2 << WGM00);                           // CTC mode
  TCCR0B = (2 << CS00);                            // prescaler 8, 2 MHz clock
  TIMSK0 = (1 << OCIE0A);                          // COMPA interrupt
  OCR0A = 151;                                     // Sample rate: 2 MHz/152 = 13.2 kHz
  sei();                                           // Enable interrupts 
}

// Total execution time 33.3 us including interrupt overhead of 2.6us
volatile byte DataIn=0;
volatile byte DataOut=1;
volatile byte baudTick=11;
ISR(TIMER0_COMPA_vect) {
  static byte Phase=0;
  static int SX0=0,SX1=0,SX2=0;
  static int BP0=0,BP1=0,BP2=0,BP3=0,BP4=0,BP5=0,BP6=0;
  static int MX0=0,MX1=0,MX2=0;
  static int LP0=0,LP1=0,LP2=0;

  // Audio output (8.6 us)
  PORTB=(PORTB&0b11110011)|(sin2Bit[Phase]<<2);
  if (DataOut==0) {
    Phase+=6;
  } else {
    Phase+=11;
  }
  if (Phase>=sampleTicks) Phase-=sampleTicks;
  // Done when baudTicks==0
  if (baudTick>0) baudTick--;
  
  // Decode the data (22.1 us)
  SX2=SX1;SX1=SX0;
  BP6=BP5;BP5=BP4;BP4=BP3;BP3=BP2;BP2=BP1;BP1=BP0;
  MX2=MX1;MX1=MX0;
  LP2=LP1;LP1=LP0;

  SX0=analogRead(A0)-512;                     // Get sample
  BP0=BP1+SX0-SX2-(BP2>>1);                   // 1625Hz (Q=1) bandpass (gain x4)
  MX0=(BP6>=0)?BP0:-BP0;                      // Switching mixer
  MX0=MX0<<2;                                 // Increase gain (x4)
  LP0=(MX0+MX1+MX1+MX2+41*LP1-13*LP2)>>5;     // 585 Hz low pass filter

  // Test correlation and set D12
  if (LP0>=0) {
    DataIn=1;  
    PORTB|=0b00010000;                        // Set D12 high
  } else {
    DataIn=0;                        
    PORTB&=0b11101111;                        // Set D12 low
  }

}

void loop() {
  static byte lastBaudTick=0;
  static byte temp=1;
  static byte i=0;
  // A test frame 0x00 and 0xFF with a start and stop bit.
  byte test[20]={1,0,0,0,0,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1};

  if (lastBaudTick!=baudTick) {
    digitalWrite(12,DataIn);
    if (baudTick==0) {
      baudTick=11;
      DataOut=temp;
    } else if (baudTick==1) {
       temp=test[i++];
       if (i>=20) i=0;
    }    
    lastBaudTick=baudTick;
    // Output loop toggle: should be almost 6600 Hz and steady
    digitalWrite(13,!digitalRead(13));

  }
}

Limits of DSP and Integer Numbers

The 1625 Hz, Q of 1 band-pass filter works exceptionally well with integer numbers. Trying to suppress the out of band frequencies has proven difficult. The coefficients of low frequency (high pass) filters are not kind (i.e. are very small) resulting in "integer DC bias" upsetting the switching mixer.

I did try doubling up the bandpass but the Q increases and upsets the demodulation and the low Q coefficients introduced high frequency noise to the demodulator (integer maths problems).

The current IIR filters are based on "Biquad" equations. Apparently State Variable (SV) filters are preferred for low frequencies. Here is the DSP verrsion of a SV filter:

(source: http://www.earlevel.com/main/2003/03/02/the-digital-state-variable-filter/)

Converting the DSP diagram into code I get:

  1. lp[n]= lp[n-1] + f * bp [n-1]
  2. hp[n] = sx[n] - lp[n] - q*bp[n-1]
  3. bp[n] = f * hp[n] + bp[n-1]
  4. nx[n] = hp[n] + lp[n]

Where sx[n] is the signal. This simplifies to:

  1. lp= lp + f * bp
  2. hp = sx- lp - q*bp
  3. bp = f * hp + bp
  4. nx = hp + lp

Plotting the filter response I get for an integer 0.5 Q notch filter (N) centred on the back channel centre frequency (434 Hz):

Forget the ripple as this is due to the both integer maths and limits of my spreadsheet model. Here is a floating point version:

So it works well but you do need to check the actual response Consider the 1 Q 1625 Hz filter:

The band pass is more like a high pass (unless my maths is wrong)!

The notch filter works well but introduces a DC bias to the switching mixer. This can be mitigated by increasing the gain before the notch filter but I think I have to wait until I get to the point of actually needing to deal with the back channel.

AlanX

Discussions