FSK Modem
The purpose of this project was to see if I could improve the performance of my Bell 103A2 modem. Initially to see if I could extract the baud clock to mitigate output jitter. Well I have not looked at that (have to investigate other start bit synchronisation methods as well), the FSK Modem is a good platform to test the PLL as most of the code has been written (and debugged) and it has a built in loop-back test.
All I need to do is replace the demodulation code subject to a decision on (1) a binary XOR or (2) a mixer style phase detector.
An unreasonable good results should be expected from the loop-back as the baud generator is not really asynchronous. Well this has to be tested.
The First Port
For the first port I dropped the sample frequency to 8065 Hz to match the FSK modem. I tested bothe the Anwer and Originate mark/space frequencies. This uncovered a problem for the PID version of the PLL. Although adjustment of the parameters found an acceptable performance solution, the simple low pass PLL worked better. It appears that damping factor and natural frequency are not that important performance controls!
The Second Port
The second port is based on the low pass PLL and an internal loop back test. I am on holidays so no test work, but here is the code:
// Bell 103A2 Modem
// ================
#define ModemMode false // Answer Modem -> Receives Low Channel
#define Baud 300 // Baud rate
#define TimerConst 30 // 8065 Hz sample rate
// Phase steps based on PWM frequency of 31372.5 Hz (=16000000/510)
#define BaudPhase 627 // Baud phase increment (for 31372.5 Hz clock)
#define OriFspace 2235 // Originate modem Space frequency 1070 Hz
#define OriFmark 2653 // Originate modem Mark frequency 1270 Hz
#define AnsFspace 4230 // Answer modem Space frequency 2025 Hz
#define AnsFmark 4648 // Answer modem Mark frequency 2225 Hz
#define AnsPM 7882 // Answer modem tuning word for 970 Hz
#define OriPM 15643 // Originate modem tuning word Fore 1925 Hz
// Constants
const unsigned int SR=12000; // Sample Rate
const unsigned int PL=870; // Originate PLL Low Frequency
const unsigned int PH=1825; // Answer PLL Low Frequency
const unsigned int PML=PL*65536/SR; // Originater DDS Tunning Number
const unsigned int PMH=PH*65536/SR; // Answer DDS Tunning Number
// 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);
// A 64 step byte sized sine table
const byte iSin[64] = {
128,140,153,165,177,188,199,209,
218,226,234,240,245,250,253,254,
255,254,253,250,245,240,234,226,
218,209,199,188,177,165,153,140,
128,116,103, 91, 79, 68, 57, 47,
38, 30, 22, 16, 11, 6, 3, 2,
1, 2, 3, 6, 11, 16, 22, 30,
38, 47, 57, 68, 79, 91,103,116
};
// Debug variables
// volatile int I=0;
// volatile int D0[324];
// volatile int D1[324];
// Get a signal sample
volatile bool dataAvailable=false;
volatile int SX0=0;
volatile int SX1=0;
volatile int SX2=0;
ISR(TIMER0_COMPA_vect) {
// Roll the samples
SX2=SX1;SX1=SX0;
// SX0=analogRead(A0)-512;
SX0=OCR2A-128;
// Flag new sample available
dataAvailable=true;
}
// TRANSMIT
volatile byte DataOut=1;
volatile bool baudUpdate=true;
volatile unsigned int baudTick=0;
ISR(TIMER2_OVF_vect) {
static unsigned int phase=0;
OCR2A = iSin[(phase>>10)]; // Output on pin 11
if (!ModemMode) { // Set true for Answer modem - Invert for audio loopback test
// Answer modem
if (DataOut==0) {
phase+=AnsFspace;
} else {
phase+=AnsFmark;
}
} else {
// Originate modem
if (DataOut==0) {
phase+=OriFspace;
} else {
phase+=OriFmark;
}
}
// Update baud tick
baudTick+=BaudPhase;
if (baudTick<BaudPhase) baudUpdate=true;
}
void setup() {
pinMode(A0,INPUT); // Audio input
pinMode(12,OUTPUT); // Digital data output
pinMode(11,OUTPUT); // Audio output (PWM)
pinMode(10,OUTPUT); // Digital data input
pinMode(LED_BUILTIN,OUTPUT); // Signal detected
digitalWrite(LED_BUILTIN,LOW);
// Set ADC prescaler (assume a 16 MHz clock)
ADCSRA&=~PS_128; // Remove bits set by Arduino library
ADCSRA|=PS_16; // 16 prescaler (1 MHz)
// Disable interrupts
cli();
// Use Timer 0 for sample rate (8065 Hz)
// ATmega48A/PA/88A/PA/168A/PA/328/P
TIMSK0 = 0; // Timer interrupts off
TCCR0A = (2 << WGM00); // CTC mode
TCCR0B = (3 << CS00); // prescaler 64, 16 MHz clock
TIMSK0 = (1 << OCIE0A); // COMPA interrupt
OCR0A = TimerConst; // Sample rate: 8065 Hz = 250 kHz / (30+1)
// Use Timer 2 for Audio PWM
// ATmega48A/PA/88A/PA/168A/PA/328/P
TIMSK2 = 0; // Timer interrupts off
TCCR2A = (2 << COM2A0)|(1 << WGM20); // Phase correct PWM (31.25 kHz), toggle output on OC2A (PB3/D11)
TCCR2B = (0 << WGM22)|(1 << CS20); // 16 MHz clock (no pre-scaler)
OCR2A = 128; // Set 50% duty
TIMSK2 = (1<<TOIE2); // Set interrupt on overflow (=BOTTOM)
// Enable interrupts
sei();
}
int Demodulate(bool Mode) {
static unsigned int K=3000; // PLL Gain
static unsigned int SX=0; // Signal
static unsigned int PX=0; // PLL Frequency
static unsigned int PA=0; // PLL Phase Accumulator
static unsigned int PM=0; // PLL Phase Increment
static unsigned int PD=0; // PLL Phase Detector
static unsigned int LP=0; // VCO Control
static unsigned int OP=0; // Output filter
// Bandpass filter
static unsigned int BA0=0;
static unsigned int BA1=0;
static unsigned int BA2=0;
static unsigned int BB0=0;
static unsigned int BB1=0;
static unsigned int BB2=0;
static unsigned int BP0=0;
static unsigned int BP1=0;
static unsigned int BP2=0;
// SIGNAL DECODER
BA2=BA1;BA1=BA0;
BB2=BB1;BB1=BB0;
BP2=BP1;BP1=BP0;
if (Mode) { // Set true for Answer modem
// PLL DDS TUNING NUMBER
PM=AnsPM;
// ANWSER MODEM BANDPASS FILTER (Fc = 1170 Hz)
BA0=(8*(SX0-SX2)+7*BA1-4*BA2)>>3;
BB0=(3*(BA0-BA2)+7*BB1-4*BB2)>>3;
BP0=(2*(BB0-BB2)+7*BP1-4*BP2)>>3;
} else {
// PLL DDS TUNING NUMBER
PM=OriPM;
// ORIGINATE MODEM
BA0=(8*(SX0-SX2)-BA1-4*BA2)>>3; // Bandpass (Fc = 2125 Hz)
BB0=(3*(BA0-BA2)-BB1-4*BB2)>>3;
BP0=(2*(BB0-BB2)-BP1-4*BP2)>>3;
}
// BINARY SIGNAL
SX=(BP0>=0)?1:0;
// PLL VC0
PA=PA+PM+LP;
PX=PA>>15;
// XOR PHASE DETECTOR
PD=(SX==PX)?PD=0:PD=K;
// LOOP FILTER
LP=PD+7*(LP-PD)>>3;
// OUTPUT FILTER
OP=OP+7*(OP-LP)>>3;
// Debug: Record data for post analysis
// if (I<324) {
// D0[I]=SX0;
// D1[I]=BP0;
// I++;
// }
// Return correlation flag
if (OP>=0) {
return(1);
} else {
return(0);
}
}
void loop() {
static bool Mode=ModemMode;
static byte DataIn=1;
static byte i=0;
// SEND DATA
if (baudUpdate) { // Counter has overflowed
i++;
if (i==1) DataOut=1;
if (i==2) DataOut=0;
if (i==3) DataOut=1;
if (i==4) DataOut=1;
if (i==5) DataOut=0;
if (i==6) DataOut=0;
if (i==7) DataOut=1;
if (i==8) DataOut=1;
if (i==9) DataOut=1;
if (i==10) DataOut=0;
if (i==11) DataOut=0;
if (i==12) DataOut=0;
if (i>=12) i=0;
baudUpdate=false;
// digitalWrite(10,DataOut);
}
// RECEIVE DATA
if (dataAvailable) {
// Demodulate signal and set D12
DataIn=Demodulate(Mode);
digitalWrite(12,DataIn);
// Done, wait for next sample tick
dataAvailable=false;
}
// Debug output
// if (I>=324) {
// cli();
// Serial.begin(9600);
// for (I=0;I<324;I++) {
// Serial.print(I);
// Serial.print(",");
// Serial.print(D0[I]);
// Serial.print(",");
// Serial.println(D1[I]);
// }
// Serial.flush();
// Serial.end();
// while (true);
// }
}
TBC ...
AlanX
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.