By: Comments Off

WSPR on 6m (50MHz)

I decided to let the IC756 listen to 6m while I was working today. ​

​NG0R and VE1JF via WSPR on 6m

​NG0R and VE1JF via WSPR on 6m

For several hours nothing was heard at 50.293MHz. At the end of my work day I stopped by the shack PC to discover that I had heard VE1JF in three different time periods. It appears that he was running 20 watts so that is pretty nice for 6m. It appears that we might have had a couple of brief band openings that only a mode like this would capture. ​

73 de NG0R

By: Comments Off

Arduino Keyer

I am starting to think about Field Day 2013. Part of that is working through the various gear & antenna choices which is pretty simple with the 2012 addition of an Elecraft KX3.​

I started thinking about how I want to handle keyboard CW & PSK this year without having to bring along a netbook or laptop. Being able to send the QSO exchange with a keyboard makes life much more comfortable & enjoyable.​

I am exploring using an Arduino (aka: AVR) as the potential back bone of the keyer interface for CW & PSK31. ​

​A screen capture showing a partial keyer demo

​A screen capture showing a partial keyer demo

I hooked up a small speaker to an Arduino Nano and generated a tone in a couple of minutes. It took about another 10 minutes to variablize some code. From there I built a Dit & Dah Function. That worked well enough that I then built a set of functions for A-Z and 0-9. ​ So a little more than an hour from when I started I have basic morse code library built and functioning.

We will see where this goes next. I need to interface it to the KX3 and test that. After that I probably need to test hooking up a keyboard. If that all pans out I will look building some function key macros. If that all continues to work I will consider hooking up a 20x4 LCD and building a simple user interface & some feedback messages.​

I am not positive that this is the route that I am really headed yet. But it was an enjoyable 60 minutes or so to produce some good sounding morse code.​

73 de NG0R

By: Comments Off

Whispering

I have the radios on in the shack today...

​20m WSRP received on an Icom756 + Ubuntu 12.04

​20m WSRP received on an Icom756 + Ubuntu 12.04

​Stations that I heard today on 20m & 30m

​Stations that I heard today on 20m & 30m

I tried something slightly different today. I used two radios and two computers to receive two different bands at the same time.​

​Icom 756 + desktop running Ubuntu 12.04 on 20m
​Elecraft KX3 + netbook running Ubuntu 12.04 on 30m

Normally the shack is quiet during the day because I am using it as my office. Today I turned on the radios and computers and let them run with the audio turned down.​

Kind of a fun end to the day to see what I was able to capture.​

73 de NG0R

By: Comments Off

Looking a little like a VFO (4)

​Ok, it looks like a LOT like a VFO now...

​Arduino Nano + I2C 20x4 LCD + rotary encoder + AD9851 

On Friday I spent about four hours tweaking the code. I had a couple of bugs that I wanted to shake out before moving the next phase of the project.

Saturday was spent hitting one of the local radio club breakfasts, then the monthly club meeting, followed by running errands.  I spent some time Saturday evening looking at some old code that I had from Peter VK2TPM and Jeff KO7M.  I was trying to decide how I wanted to handle reading out the serial bits to the DDS. (I could understand Peter's code pretty well.  Jeff's code is more efficent but harder for a noob like me to understand.) 

Sunday morning was spent running errands (groceries). By the time I got home was feeling pretty guilty about my lack of progress (Sat & Sun morning) so I decided to get back to it.  ​It took me about an hour to move some code fragments from my 2012 project into my new project. I also changed many of the variables and object names to be consistent with the the current project. 

Surprisingly it worked pretty much right away when I fired it up. After the initial test I moved around one block of code to minimize how often I update the DDS. (If the dial doesn't move, the DDS does not need to be updated which saves a LOT of clock cycles for other future things.)​​

​AD9851 with a 7MHz signal on the spectrum analyzer. (5MHz horizontal steps)

​AD9851 with a 7MHz signal on the spectrum analyzer. (5MHz horizontal steps)

​The next step was to put it on the spectrum analyzer, oscope, frequency counter, and oscope to look at the signal.  The picture of the spectrum analyzer shows a 7.040MHz signal on the left with 5MHz divisions out to 45MHz on the right side of the screen. The harmonics are 40-50dB (or more) down from the fundamental frequency.​

​AD9851 on the oscope at 7.040MHz

​AD9851 on the oscope at 7.040MHz

​The picture on the oscope was pretty boring. It looked pretty good overall. One open question might be some better bypass filtering related to ground. But since this is on a breadboard with flying leads I am pretty happy with it as a prototype.

My counter(s) do not have a 10MHz reference so I take their exact accuracy with a grain of salt. Typically I don't need 1-10Hz level accuracy so they are close enough. In this case I do need an accurate reference if I want to adjust the true timing of the 30MHz clock on the DDS board.  It is not overly critical unless you want to operate modes like WSPR or QRSS which need to be accurate to within a couple of Hz. My initial testing was indicating that I was +/- about 60Hz with my non-referenced gear.​

RF measured power levels

​My next experiment was to measure the power of the DDS over a range of frequencies. It looks like it does not have much gain from 1.8 - 5MHz. From 6 - 26MHz it is running 4 - 6dBm or 2.5 - 4mW of power. From 26 - 30MHz it is running about 2dBm or 2mW. That is quite a bit of power from the DDS and is a nice starting point for real RF projects.

​I am pretty pleased with the project. I figure that I have about 12 hours or so into the project. Most of that is simply because I don't know the language and I am having to look up the syntax as I go. This is going to be starting point for a HF rig. Clearly this is just one building block out of several that I will need in the future but this is the building block that I have been contemplating for a while. 

​The code below is not exciting or even highly optimized at this point but it is fully functional. Since I have leveraged the internet for a lot of my learning I thought that I should share my code in case someone else needs help getting starting. (Thanks to folks like Jeff,  Eldon, Jason, and Peter that have helped me in various ways with my projects.) The sketch below is about 8k and should provide a fully functioning signal generator between 1.8 - 30MHz.

​73 de NG0R

/* 
   
   This sketch will setup an Arduino (Nano in my example)
   with the an I2C 20x4 LCD and a AD9851 DDS module (from ebay)
   and a rotary encoder.
   
   The encoder can be spun from 1.8MHz to 30MHz. 
   When you push the encoder push button it will allow to
   change the decade position of the number being changed/tuned.
   
   This code is heavily commented so that you can try to 
   understand what is going on.
   
   This tries to avoid the use of the delay command since that is
   a blocking statement. Use of the millis function and variable
   checking as a timer are used instead if/when needed.
   
   The base sketch takes about 8k of memory.
   
   This could be easily used a the builing block for a HF radio.
    
   -------------------
   
   DDS AD9850/AD9850 info from:
   Mike Bowthorpe
   http://www.ladyada.net/rant/2007/02/cotw-ltc6903/ 
   http://www.geocities.com/leon_heller/dds.html
   Function for sending the byte word by Peter Marks http://marxy.org

       
   
   ---Encoder info---
   https://forum.sparkfun.com/viewtopic.php?p=65052

   http://hifiduino.files.wordpress.com/2010/10/rotarynodebounce.jpg

   read a rotary encoder with interrupts
   Encoder hooked up with common to GROUND,
   Encoder Pin A to pin 2, 
   Encoder Pin B to pin 4 (or pin 3 see below)
   
   uses Arduino pullups on A & B channel outputs
   turning on the pullups saves having to hook up resistors 
   to the A & B channel outputs
   
   
   ---I2C LCD INFO---
   http://arduino-info.wikispaces.com/LCD-Blue-I2C
   YWROBOT
   LCD header pins/cable color code
   VCC = Red         -- 5v
   GND = Black       -- Gnd   
   SDA = Yellow      -- A4
   SCL = Green/White -- A5

*/ 

//---Libraries
#include <Wire.h>                                       // I2C library
#include <LiquidCrystal_I2C.h>                          // I2C LCD library
LiquidCrystal_I2C lcd(0x27,20,4);                       // set the LCD address to 0x27 for a 20 chars and 4 line display

//--Define constants
#define encoder0PinA  2                        // Setup the encoder pins
#define encoder0PinB  4                        // Setup the encoder pins
#define encoderbtn 6                           // encoder pushbutton
#define DDS_CLOCK 180000000                    // 30MHz x 6 (onboard rock)


//---define variables
volatile long encoder0Pos = 10000000;          // setup a value to count with
volatile long oldencoder0Pos = encoder0Pos ;   // used to compare
int varVal;                                    // variable for reading the pin status
int varHz = 1;                                 // 1=Hz, 2=KHz, 3=10KHz, 4=100KHz, 5=MHz tuning step mode
int long varMult = 1;                          // used to plug in as the multiplier
char varBuf1[8];                               // used to convert an int to a string array for the freq display
char varBuf2[10];                              // used to format the freq display with commas      
unsigned long varCurrentMillis;                // current time via the Millis function
long varPreviousMillis = 0;                    // used as a timer to avoid bounce
long varInterval = 500;                        // interval used with a timer (milliseconds)
byte ddsLOAD = 8;                              // AD9851 LOAD   Arduino D8
byte ddsCLOCK = 9;                             // AD9851 CLOCK  Arduino D9
byte ddsDATA = 10;                             // AD9851 FQ_UD  Ardunio D10
long varTuning_word;                           // Used to hold the word for the DDS 



void setup() { 
//---Setup encoder  
  pinMode(encoderbtn, INPUT);
  pinMode(encoder0PinA, INPUT); 
  digitalWrite(encoder0PinA, HIGH);       // turn on pullup resistor
  pinMode(encoder0PinB, INPUT); 
  digitalWrite(encoder0PinB, HIGH);       // turn on pullup resistor
  attachInterrupt(0, doEncoder, CHANGE);  // encoder pin on interrupt 0 - pin 2
//---Initial screen setup   
  lcd.init();                             // initialize the lcd 
  lcd.backlight();                        // turn on the lcd backlight
  lcd.clear();                            // good practice to make sure that display is clear
  lcd.setCursor(0,0);                     // column,row
  lcd.print("Freq: "); 
  sprintf(varBuf1,"%8lu", encoder0Pos);   // convert initial freq for display
  sprintf(varBuf2,"%1c%1c,%1c%1c%1c,%1c%1c%1c", varBuf1[0], varBuf1[1], varBuf1[2], varBuf1[3], varBuf1[4], varBuf1[5], varBuf1[6], varBuf1[7], varBuf1[8]);
  lcd.print(varBuf2);
  lcd.setCursor(18,0);                    // column,row
  lcd.print("Hz");                        // prints out step; should variablize
//---setup pins for AD9851
  pinMode (ddsDATA, OUTPUT);              // sets pin 10 as OUPUT
  pinMode (ddsCLOCK, OUTPUT);             // sets pin 9 as OUTPUT
  pinMode (ddsLOAD, OUTPUT);              // sets pin 8 as OUTPUT
  sendFrequency(encoder0Pos);             // send the initial freq to the DDSvoid loop(){
  encoderStatus();                        // check for encoder button press 
  lcdStatus();                            // function for lcd updates
  if (encoder0Pos != oldencoder0Pos)      // only update the DDS if the freq has changed 
    sendFrequency(encoder0Pos);           // function to update the DDS
}



void sendFrequency(long encoder0Pos){                        // function to update the DDS
  varTuning_word = (encoder0Pos * pow(2, 32)) / DDS_CLOCK;   // set value for the DDS
  digitalWrite (ddsLOAD, LOW);                               // take load pin low
  //--start loop--
  for(int i = 0; i < 32; i++)                                // loop through the bits
  {
    if ((varTuning_word & 1) == 1)                           // test for binary 1
      outOne();                                              // function to send 1 serial
    else
      outZero();                                             // function to send 0 serial
    varTuning_word = varTuning_word >> 1;                    
  }
  //--end loop--
  byte_out(0x09);                                            // send the end command
  digitalWrite (ddsLOAD, HIGH);                              // take the load pin high
}




void byte_out(unsigned char byte)         // spin through a byte (8 bits)
{                                         // send it a bit a time
  int i;

  for (i = 0; i < 8; i++)
  {
    if ((byte & 1) == 1)
      outOne();
    else
      outZero();
    byte = byte >> 1;
  }
}




void outOne(){                            // send 1 to the DDS         
  digitalWrite(ddsCLOCK, LOW);            // set the ddsCLOCK pin low
  digitalWrite(ddsDATA, HIGH);            // set the ddsDATA pin high
  digitalWrite(ddsCLOCK, HIGH);           // set the ddsCLOCK pin HIGH
  digitalWrite(ddsDATA, LOW);             // set the ddsDATA ping low
}



void outZero(){                           // send 0 to the DDS
  digitalWrite(ddsCLOCK, LOW);            // set the ddsCLOCK pin low
  digitalWrite(ddsDATA, LOW);             // set the ddsDATA pin low
  digitalWrite(ddsCLOCK, HIGH);           // set the ddsCLOCK pin high
}



void encoderStatus (){                    // this is checking for the button press and setting the MHZ, KHz, Hz cursor
  varCurrentMillis = millis();            // set variable = time
  if(varCurrentMillis - varPreviousMillis > varInterval) {
     varPreviousMillis = varCurrentMillis;   
     varVal = digitalRead(encoderbtn);    // read input value and store it in val
     if (varVal == LOW) {                 // check if the button is pressed
        if (varHz == 6) {                 // 1 Hz, 2 100Hz, 3 KHz, 4 10KHz, 5 100KHz, 6 MHz 
        varHz = 0;                        // reset from MHz to Hz  
        }     
      varHz = varHz + 1;                  // move one position
     }
  }
}





void lcdStatus (){
    sprintf(varBuf1,"%8lu", encoder0Pos);        
    sprintf(varBuf2,"%1c%1c,%1c%1c%1c,%1c%1c%1c", varBuf1[0], varBuf1[1], varBuf1[2], varBuf1[3], varBuf1[4], varBuf1[5], varBuf1[6], varBuf1[7], varBuf1[8]);
    if (encoder0Pos != oldencoder0Pos){
      lcd.setCursor(6,0);                   // column,row
      lcd.print(varBuf2);                   // prints out the freq
    }
 
   switch (varHz) {
    case 1:                                 // if 1 move the cursor to Hz
      lcd.setCursor(18,0);                  // column,row
      lcd.print("H1");                      // prints out step
      varMult = 1;        
      break;
    case 2:                                 // if 2 move the cursor to 100 Hz
      lcd.setCursor(18,0);                  // column,row
      lcd.print("H2");                      // prints out step
      //varMult = 1000/2;                   // original encoder with detents
      varMult = 100;                        // AA0ZZ encoder, no detents
      break;
    case 3:                                 // if 2 move the cursor to KHz
      lcd.setCursor(18,0);                  // column,row
      lcd.print("K1");                      // prints out step
      //varMult = 1000/2;                   // original encoder with detents
      varMult = 1000;                       // AA0ZZ encoder, no detents
      break;
     case 4:                                // if 3 move the cursor to 10KHz
      lcd.setCursor(18,0);                  // column,row
      lcd.print("K2");                      // prints out step
      //varMult = 10000/2;                  // original encoder with detents
      varMult = 10000;                      // AA0ZZ encoder, no detents
      break;  
    case 5:                                 // if 4 move the cursor to 100KHz
      lcd.setCursor(18,0);                  // column,row
      lcd.print("K3");                      // prints out step
      //varMult = 100000/2;                 // original encoder with detents
      varMult = 100000;                     // AA0ZZ encoder, no detents
      break;  
    case 6:                                 // if 5 move the cursor to MHz
      lcd.setCursor(18,0);                  // column,row
      lcd.print("MH");                      // prints out step
      //varMult = 500000;                   // original encoder with detents 
      varMult = 1000000;                    // AA0ZZ encoder, no detents
      break;
    }
    if (encoder0Pos <= 1800000) {            // lower bounds limit
        encoder0Pos  = 1800000;
    }
    if (encoder0Pos >= 30000000) {           // upper bounds limit
        encoder0Pos  = 30000000;
    }
}





void doEncoder(){
 oldencoder0Pos = encoder0Pos;                // reset the variables so that we can compare them next time 
  if (digitalRead(encoder0PinA) == HIGH) {    // found a low-to-high on channel A
    if (digitalRead(encoder0PinB) == LOW) {   // check channel B to see which way
                                              // encoder is turning
      encoder0Pos = encoder0Pos - varMult;    // CCW
    } 
    else {
      encoder0Pos = encoder0Pos + varMult;    // CW
    }
  }
  else                                        // found a high-to-low on channel A
  { 
    if (digitalRead(encoder0PinB) == LOW) {   // check channel B to see which way
                                              // encoder is turning  
      encoder0Pos = encoder0Pos + varMult;    // CW
    } 
    else {
      encoder0Pos = encoder0Pos - varMult;    // CCW
    }
  }
 }

/*  to read the other two transitions - just use another attachInterrupt()
in the setup and duplicate the doEncoder function into say, 
doEncoderA and doEncoderB. 
You also need to move the other encoder wire over to pin 3 (interrupt 1). 
*/ 

By: Comments Off

Encoder swap

Sweet...​

​A sweet rotary encoder setup for testing

​A sweet rotary encoder setup for testing

The rotary encoder that I have been using has been ok, but just that, ok.  I have largely minimized bounce via some capacitors. I also have the push button portion setup the same way. The encoder works ok but leaves me wanting more.​

The existing encoder has two pulses per detent about about 20 detents per revolution. The detents suck. It is more than for every 1 detent count one pulse or adjust the math kind of deal. Even with the hardware debounce (I could also add software debounce) it is not 100% stable combined with the 2 pulses per detent it is clearly not the encoder that I want for my VFO. (No surprise there.)

I shared my thoughts with AA0ZZ and he shared several stories about his battles trying to find the ideal encoder (functionality vs. price vs. availability.) Craig has some nice encoders in his projects. (I own several AA0ZZ designed projects.) I asked if Craig would share a couple of them from his secret stash for the right price. (Yesterday I ordered some different encoders from Arrow to test as prototype candidates. They should be here on Monday.)

The picture above is one of those secret stash encoders. I am going to swap out my current encoder with the secret stash today and then retest my latest version of code. ​I have a couple of items in the code that I want to tweak (lower bound issue, moving the cursor, cursor blink timing.)  I took today (Friday) off as a mental health day from work.  My goal is to spend a couple of hours bonding with the project today.​

73 de NG0R

By: Comments Off

Looking a little like a VFO (3)

A resistor, a capacitor, and a few minutes of code

​Nano + I2C 20x4 LCD + encoder

​Nano + I2C 20x4 LCD + encoder

​I setup some simple hardware debounce and then dropped in some code to read the push button the rotary encoder. The hardware debounce works fine so I am happy with the results.

The next step will be to write some code to use the encoder push button to change the cursor between the Hz, KHz, and MHz fields of the frequency display.​

73 de NG0R

By: Comments Off

Snowshoeing after work

Now that we have an extra hour of day light (spring forward over the weekend) I decided that it was time to go outside after work.  I drove to a local county park and put on some snowshoes (xmas present from my mother) and put Max on the leash for some exercise.​

​Max the wonder dog

​Max the wonder dog

The snow is moderately deep after a couple of recent storms but crusty and packed after some rain and sunny weather. The trail had been groomed recently so it in good shape for a hike.​

It was a nice way to transition from my work day to my family time after work.​

By: Comments Off

Looking a little like a VFO (2)

​A VFO looking display. Nano + I2C 20x4 LCD + encoder

​A VFO looking display. Nano + I2C 20x4 LCD + encoder

While talking with some other QRP & builder types on EchoLink tonight I made a few more tweaks to the code. I trimmed about 700-800 bytes.  I think that the next step is to implement the push-button portion of the encoder to change the tuning between MHz, KHz, and Hz ranges.​

This was a very positive end to the weekend and positive start to the work week.​

73 de NG0R

By: Comments Off

Looking a little like a VFO

​Debugging the format of the frequency display. &nbsp;Nano + I2C 20x4 LCD + encoder

​Debugging the format of the frequency display.  Nano + I2C 20x4 LCD + encoder

It took me a while to figure it out. I was having a hard time trying to convert a long unsigned integer (10000000) into something that looked like a typical frequency display (10,000,000).  Since I am not a C/C++ guru it took me a while to figure it out.​

I came up with four potential approaches. Ultimately it came down to figuring out how to make the sprintf command work properly on the Arduino. It is a very powerful command assuming that you understand the arguments that you need to use. There are lots of references for standard PC platforms but not all of that is implemented in the library for the Arduino. I did most of my reading and experimenting saturday night and then came back at it with a fresh mind and point of view Sunday afternoon once my errands were complete for the day.​

​With a fresh mind it only took me about 10 minutes to bang out some code that worked. The only real challenge was the argument for the data type conversion. As time permits I will need to come back to this and trim down the code as I think that I can make it a bit more lean.​

  char buf2[10];
  sprintf(buf2,"%1c%1c,%1c%1c%1c,%1c%1c%1c", buf1[0], buf1[1], buf1[2], buf1[3], buf1[4], buf1[5], buf1[6], buf1[7], buf1[8]);

​It is not glamorous but it works.  This is a pretty basic and important part of the VFO so it was an important step for me. I have a couple more items that I want to add to this and then I can move to the DDS code.

73 de NG0R

By: Comments Off

Arduino time

It has been a while since I have had a chance to write any code for the Arduino. I pulled out another breadboard, a Nano, some header pins, an encoder, and a I2C 20x4 LCD.​

​Arduino Nano + I2C 20x4 LCD + rotary encoder

​Arduino Nano + I2C 20x4 LCD + rotary encoder

Right now I am just making sure that the I am getting text on the LCD. If time permits this afternoon I would like to write & test some code that would use the encoder spinning to increase/decrease a number. It would be another building block in my VFO for the rig that I am trying to design in my spare time. (Nonexistent space time.) ​​

​A quick sketch to test the I2C interface to the LCD.

​A quick sketch to test the I2C interface to the LCD.

I am trying to break down the project so that if I have 30-60 minutes of time that I can work on a small module.​

73 de NG0R