7MHz Crystal Oscillator Design

After constructing a 40m wire dipole that works with my SDR setup, I needed to start working on a transmission system. At the heart of virtually any RF system lies a stable oscillator, and crystal oscillators are ubiquitous in many low-power (QRP) rigs simply because they are so stable. After some rough math and a lot of simulations in LTSpice, I came up with this design to give me somewhere around 7 dBm of power.

The first stage is simple Colpitts oscillator topology with a “bent” 7.03 MHz crystal resonator. The 30 pF variable capacitor (C1) provides around 2 kHz of tuning. The output stage is a common-collector (Q4) and emitter-follower (Q3) with a negative feedback loop. I forget exactly where I saw this configuration, but I thought I would try it out and see if it worked. As you might have guessed, the output is loaded with higher order harmonics resulting in a waveform that doesn’t resemble a sine wave at all. I made sure to include a simple second-order low-pass Butterworth filter on the output to filter the output.

Pictured above is the ugly constructed version of the oscillator in all it’s dead-bug style. I built it on a scrap piece of double-sided FR4 and overall it’s performance came out fairly close to what LTSpice had predicted. I got around 6 dBm of output and the second harmonic is around 29 dB down (around -23 dBm). That’s not the cleanest of signals, but it’s about right for the filter. Below is the output shown on my HP 8595E spectrum analyzer.

For the next phase, I’ll likely be adding control of the oscillator via an ATTiny chip. This will give me the ability to automate on-off keying of the device turning it into a simple CW beacon. One thing that could be improved is the current draw (~40 mA) from the emitter-follower. Basically, it’s a class A amplifier so it’s not the most efficient design in the world, but it provides enough power for driving an ADE-1 and could run off a small solar panel if I wanted to use it in the field.

Arduino: LFO Generator

here’s a treat for anyone that’s into the audio side of arduino. it’s an 8-bit two-timer based LFO Generator using timer 0/2 on an ATMega328p. i’m only using timer0 for output in this code. i’ve started implementing the LFO code into my 8-bit melotronium where timer2 is dedicated for the audio output. in the larger code base, timer0 just stores values in an unsigned integer that the other timer grabs and modulates the output mathematically. for now, this should be a good reference to anyone looking for the outline of an LFO with seven different wave forms.

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

uint8_t sineTable[] = {
  127,130,133,136,139,143,146,149,152,155,158,161,164,167,170,173,176,178,181,184,187,190,
  192,195,198,200,203,205,208,210,212,215,217,219,221,223,225,227,229,231,233,234,236,238,
  239,240,242,243,244,245,247,248,249,249,250,251,252,252,253,253,253,254,254,254,254,254,254,
  254,253,253,253,252,252,251,250,249,249,248,247,245,244,243,242,240,239,238,236,234,
  233,231,229,227,225,223,221,219,217,215,212,210,208,205,203,200,198,195,192,190,187,184,
  181,178,176,173,170,167,164,161,158,155,152,149,146,143,139,136,133,130,127,124,121,118,
  115,111,108,105,102,99,96,93,90,87,84,81,78,76,73,70,67,64,62,59,56,54,51,49,46,44,
  42,39,37,35,33,31,29,27,25,23,21,20,18,16,15,14,12,11,10,9,7,6,5,5,4,3,2,2,1,1,1,0,0,0,
  0,0,0,0,1,1,1,2,2,3,4,5,5,6,7,9,10,11,12,14,15,16,18,20,21,23,25,27,29,31, 33,35,37,39,42,
  44,46,49,51,54,56,59,62,64,67,70,73,76,78,81,84,87,90,93,96,99,102,105,108,111,115,118,121,124
};

uint8_t tWave = 128;
uint8_t sWave = 255;
uint8_t ruWave = 128;
uint8_t rdWave = 128;
uint8_t rWave = 128;

int   i = 0;
int   rate;
int   waveform;
byte  d = HIGH;

void setup() {
  pinMode(3, OUTPUT);
  setupTimer();
  OCR0A = 128;
}

void loop() {
  waveform = map(analogRead(0),0,1023,1,7);
  rate = map(analogRead(1),0,1023,255,0);
  OCR0A = rate;
}

ISR(TIMER0_COMPA_vect) {
  if(i == 255) i = 0;
  switch(waveform) {
    case 1:
      analogWrite(3,sine(i));
    break;
    case 2:
      analogWrite(3,triangle(i));
    break;
    case 3:
      analogWrite(3, square(i));
    break;
    case 4:
      analogWrite(3, rampUp(i));
    break;
    case 5:
      analogWrite(3, rampDown(i));
    break;
    case 6:
      analogWrite(3, rand(i));
    break;
    case 7:
      analogWrite(3, white(i));
    break;
  }
  i++;
}

void setupTimer() {
  cli();
/*--- TIMER2 CONFIG ---*/
  sbi(TCCR2A,WGM20);
  sbi(TCCR2A,WGM21);
  cbi(TCCR2A,WGM22);
 
  sbi(TCCR2B, CS20);
  cbi(TCCR2B, CS21);
  cbi(TCCR2B, CS22);

  sbi(TCCR2A,COM2B1);
  cbi(TCCR2A,COM2B0);
   
 /*--- TIMER0 CONFIG ---*/ 
  cbi(TCCR0B,CS00);
  cbi(TCCR0B,CS01);
  sbi(TCCR0B,CS02);
 
  sbi(TCCR0A, COM0A1);
  cbi(TCCR0A, COM0A0);
    
  cbi(TCCR0A, WGM00);
  sbi(TCCR0A, WGM01);
  cbi(TCCR0A, WGM02);

  cbi(TIFR0,OCF0A);
  sbi(TIMSK0,OCIE0A);
  sei(); 
}

int sine(int i) {
  return sineTable[i];
}

int triangle(int i) {
  if(tWave >= 255) d = LOW;
  if(tWave <= 0) d = HIGH;
  if(d == HIGH) tWave++;
  if(d == LOW) tWave--;
  return tWave; 
}

int rampUp(int i) {
  ruWave++;
  if(ruWave > 255) ruWave = 0; 
  return ruWave;
}

int rampDown(int i) {
  rdWave--;
  if(rdWave < 0) rdWave = 255;
  return rdWave;
}

int square(int i) {
  if(i >= 128) sWave = 255;
  if(i <= 127) sWave = 0;
  return sWave;
}

int rand(int i) {
  if(i == rWave) rWave = random(255);
  return rWave;
}

int white(int i) {
  return random(255);
}

Update: Here's my current code for the ATTiny85. It's not exactly bug-free as there seems to be some issue with latch-up when the speed of the LFO is high. This of course could be remedied by limiting the map() function's minimum value. I can't remember off hand if there are any other issues. Good power supply and output filtering is definitely needed to reduce noise in any additional stages of the circuit. At some point, I'll just make a separate post detailing my tremolo effect circuit in detail (so many projects and so little time!). Until then:

/*
 * Title: LFO Generator for ATTiny85 v0.12
 * Author: Abram Morphew
 * Date: 09.19.2016
 * Purpose: Low frequency oscillator with wave output controls
 */

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

uint8_t sineTable[] = {
  127,130,133,136,139,143,146,149,152,155,158,161,164,167,170,173,176,178,181,184,187,190,
  192,195,198,200,203,205,208,210,212,215,217,219,221,223,225,227,229,231,233,234,236,238,
  239,240,242,243,244,245,247,248,249,249,250,251,252,252,253,253,253,254,254,254,254,254,254,
  254,253,253,253,252,252,251,250,249,249,248,247,245,244,243,242,240,239,238,236,234,
  233,231,229,227,225,223,221,219,217,215,212,210,208,205,203,200,198,195,192,190,187,184,
  181,178,176,173,170,167,164,161,158,155,152,149,146,143,139,136,133,130,127,124,121,118,
  115,111,108,105,102,99,96,93,90,87,84,81,78,76,73,70,67,64,62,59,56,54,51,49,46,44,
  42,39,37,35,33,31,29,27,25,23,21,20,18,16,15,14,12,11,10,9,7,6,5,5,4,3,2,2,1,1,1,0,0,0,
  0,0,0,0,1,1,1,2,2,3,4,5,5,6,7,9,10,11,12,14,15,16,18,20,21,23,25,27,29,31, 33,35,37,39,42,
  44,46,49,51,54,56,59,62,64,67,70,73,76,78,81,84,87,90,93,96,99,102,105,108,111,115,118,121,124
};

uint8_t tWave = 128;
uint8_t sWave = 255;
uint8_t ruWave = 128;
uint8_t rdWave = 128;
uint8_t rWave = 128;

int   i = 0;
int   rate;
int   waveform;
byte  d = HIGH;
const uint8_t output = 1;                       // output pin #6 on ATTiny85

void setup() {
  setupTimer();
  pinMode(3, INPUT);
  pinMode(2, INPUT);
  pinMode(output, OUTPUT);
  OCR0A = 128;
}

void loop() {
  waveform = map(analogRead(2),0,1023,1,6);
  rate = map(analogRead(3),0,1023,255,0);
  OCR0A = rate;
}

ISR(TIMER0_COMPA_vect) {
  if(i == 255) i = 0;
  switch(waveform) {
    case 1:
      analogWrite(output,sine(i));
    break;
    case 2:
      analogWrite(output,triangle(i));
    break;
    case 3:
      analogWrite(output, square(i));
    break;
    case 4:
      analogWrite(output, rampUp(i));
    break;
    case 5:
      analogWrite(output, rampDown(i));
    break;
    case 6:
      analogWrite(output, rand(i));
    break;
    case 7:
      analogWrite(output, white(i));
    break;
  }
  i++;
}

void setupTimer() {
  cli(); 
 /*--- TIMER0 CONFIG ---*/  
  TCCR0A = 0b10000011;
  TCCR0B = 0b00001010;    // last 3 bits set prescalar for Timer0

  cbi(TIFR,OCF0A);
  sbi(TIMSK,OCIE0A);
  sei(); 
}

int sine(int i) {
  return sineTable[i];
}

int triangle(int i) {
  if(tWave >= 255) d = LOW;
  if(tWave <= 0) d = HIGH;
  if(d == HIGH) tWave++;
  if(d == LOW) tWave--;
  return tWave; 
}

int rampUp(int i) {
  ruWave++;
  if(ruWave > 255) ruWave = 0; 
  return ruWave;
}

int rampDown(int i) {
  rdWave--;
  if(rdWave < 0) rdWave = 255;
  return rdWave;
}

int square(int i) {
  if(i >= 128) sWave = 255;
  if(i <= 127) sWave = 0;
  return sWave;
}

int rand(int i) {
  if(i == rWave) rWave = random(255);
  return rWave;
}

int white(int i) {
  return random(255);
}