Thursday, 20 July 2017

PLAYING with the kids

Away, sort of, from radio things I have indulged myself with a robot arm kit, the meArm. But there is a tiny nagging thought at the back of my mind - that is to control a set of these arms by radio, maybe using the ubiquitous 433MHz RX TX modules, and make them dance in harmony, fun eh?

IMG 1374

The meArm sitting on my desk

So far I have done a little bit of Arduino programming to try it out, and written a library file to keep some of the functions in.

CODE "meArm.h"

// mearm.h
// v 0.5 20-7-17

#include "Servo.h"

Servo arm, left, right, grip;


// arm deg max = max, home = mid point, left = min
#define ARMMAX 180
#define ARMHOME 98
#define ARMMIN 0

// right deg min = in, max = out
#define RIGHTMIN 90
#define RIGHTMAX 180

// left deg min = in, max = out
#define LEFTMIN 50
#define LEFTMAX 140

// grip close = in, open = out
#define GRIPIN 65
#define GRIPOUT 10


// ============= BEGIN =============
void armBegin() {
  arm.attach(11);
  right.attach(10);
  left.attach(9);
  grip.attach(6);

  // start positions
  arm.write(ARMHOME);
  right.write(RIGHTMIN);
  left.write(LEFTMIN);
  grip.write(GRIPOUT);
}


// ============= ARM MOVEMENTS =============
// ARM steady move arm to pos 0-100 in ms steps
void armpos(int pos, int ms) {
  int m, r;

  pos = map(pos, 0, 100, ARMMIN, ARMMAX);
  r = arm.read();
  if (pos > r) {
    for (m = r; m != pos; m += 1) {
      arm.write(m);
      delay(ms);
    }
    return;
  }
  else if (pos < r) {
    for (m = r; m != pos; m -= 1) {
      arm.write(m);
      delay(ms);
    }
  }
  return;
}

// LEFT steady move left to pos in ms steps
void leftpos(int pos, int ms) {
  int m, r;

  pos = map(pos, 0, 100, LEFTMIN, LEFTMAX);
  r = arm.read();
  if (pos > r) {
    for (m = r; m != pos; m += 1) {
      left.write(m);
      delay(ms);
    }
    return;
  }
  else if (pos < r) {
    for (m = r; m != pos; m -= 1) {
      left.write(m);
      delay(ms);
    }
  }
  return;
}


// RIGHT steady move left to pos in ms steps
void rightpos(int pos, int ms) {
  int m, r;

  pos = map(pos, 0, 100, RIGHTMIN, RIGHTMAX);
  r = arm.read();
  if (pos > r) {
    for (m = r; m != pos; m += 1) {
      right.write(m);
      delay(ms);
    }
    return;
  }
  else if (pos < r) {
    for (m = r; m != pos; m -= 1) {
      right.write(m);
      delay(ms);
    }
  }
  return;
}

// GRIP move open close in ms steps
void grippos(int pos, int ms) {
  int m, r;

  pos = map(pos, 0, 100, GRIPOUT, GRIPIN);
  r = arm.read();
  if (pos > r) {
    for (m = r; m != pos; m += 1) {
      grip.write(m);
      delay(ms);
    }
    return;
  }
  else if (pos < r) {
    for (m = r; m != pos; m -= 1) {
      grip.write(m);
      delay(ms);
    }
  }
  return;
}

CODE FOR TESTING SERVOS

// move
// v0.5 20-7-17
// viewed from behind, range
// arm mid xx/98/xx
// right 80/xx/140
// left xx/xx/xx

#include "meArm.h"

void setup() {
  armBegin();     // attach servos, goto start positions
  delay(2000);
}

void loop() {
  armpos(80, 50); // move to 80%, 50ms steps%
  delay(5000);
  armpos(20, 50); // move to 60% in 100 ms steps
  delay(5000);

}

Tuesday, 4 July 2017

LATEST Sketches and Libraries

4 July 2017. Here's the latest update, downloadable zip file M0IFA SKetches

Screen Shot 2017 07 04 at 09 45 47

Sketches

Screen Shot 2017 07 04 at 09 47 37

Libraries used

Watch out for updates on this blog.

Monday, 26 June 2017

BASIC Tech Group - MyNews - 48 GPS working

In the last post I described how I wanted to build a GPS input to my VFO, and use this to display my Locator, from the Lat & Lon data, and calibrate the time and date of my RTC. There is a little progress I can report. the VFO has a new 3.5mm socket on the back to connect to the GPS. After a mixup, I have solved the problems of getting the right connections to the GPS and to the Arduino. Arduino pin 12 is the data input from the GPS, connected to the GPS TX output, and pin 13 is connected to the GPS RX input.

IMG 1221

VFO with 3.5mm socket for GPS input

I have some code working - see below.

Screen Shot 2017 07 03 at 10 21 52

VFO with time and date set from GPS

Power up the VFO by USB. Then plug in the GPS. push the button and there is a GPS FIX the RTC will be programmed. The display will show the RTC date time and the Maidenhead Locator. WSPR or JT65 sketches can now be loaded on the VFO and will transmit at the correct time (on UTC minute)

It may be possible to integrate this GPS calibration into the WSPR and JT65 sketches, but there may not be enough memory to do this - I have a vague plan to store the MH in EEPROM so it accessible by WSPR programs.

CODE

// DATE_TIME_MH_SET_GPS
// V2.5 read GPS time, fix & date, calc dow, set RTC, store MH
//      display RTC date & time, MH
// GPS VK-163 jack connections
// 1 tip  VCC
// 2      TX GPS  output
// 3      RX GPS  input
// 4 ring GND

#include "Oled_128X64_I2C.h"
#include "SoftwareSerial.h"
#include "Wire.h"
#include "EEPROM.h"

// Arduino pins TX GPS -> RX(12), RX GPS <- TX(13), button
#define RX 12
#define TX 13
#define SW 4

// RTC address
#define RTCADDR 0x68

// GPS data buffer
char gpsbuf[200];

// data extracted from $GPRMC, ACSII
char tm[20];        // time HHMMSS
char fix[5];        // fix A|V, init void
char dt[20];        // date YYMMDD
char la[15];        // latitude
char ns[2];         // NS
char lo[15];        // longitude
char ew[2];         // EW

// Maidenhead Locator
char mh[10];

// RTC data, decimal
byte hrs, mns, sec;
byte yr, mth, dy;
byte dow;
double lat, lon;

// Serial object
SoftwareSerial gps(RX, TX);

// ============= setup ==============
void setup() {
  // pins
  pinMode(RX, INPUT);
  pinMode(TX, OUTPUT);
  pinMode(SW, INPUT_PULLUP);

  // I2C init
  Wire.begin();

  // OLED init, I2C addr 0x3C
  oled.begin();

  // GPS serial init
  gps.begin(9600);

  // init GPS void
  strcpy(fix, "V");
}

// ============== loop ===============
void loop() {
  dispUpdate();

  // if buuton pressed, get GPS data
  if (digitalRead(SW) == LOW) {
    strcpy(fix, "V");              // set V for display update
    dispUpdate();
    
    getGPS();                      // get GPS, extract data
    
    if (strcmp(fix, "A") == 0) {   // when GPS Aquired
      setRTC();                    // set RTC date time dow
      setMH();                     // calculate MH Locator
    }
  }
  
  getRTC();                        // read RTC
}

// ========= GPS funcitons =========
// get RMC line data
void getGPS() {
  do {
    getline(gpsbuf);
  } while (strncmp(gpsbuf, "$GPRMC", 6) != 0);

  // extract strings from $GPRMC fields
  xtract(gpsbuf, 1, tm);          // time HHMMSS
  xtract(gpsbuf, 2, fix);         // fix A or V
  xtract(gpsbuf, 9, dt);          // date YYMMDD

  xtract(gpsbuf, 3, la);          // latitude
  xtract(gpsbuf, 4, ns);          // NS
  xtract(gpsbuf, 5, lo);          // longitude
  xtract(gpsbuf, 6, ew);          // EW
}

// get a line from the GPS, inc /r/n, add /0
void getline(char *out) {
  char c;
  int p;

  p = 0;                         // buffer pointer
  do {
    if (gps.available() > 0) {   // data?
      c = gps.read();            // read character
      out[p++] = c;              // put in buffer
    }
  } while ( c != '\n' );          // stop on /n
  out[p] = '\0';                 // terminate string
}

// extract field and return string in outbuf
void xtract(char *in, int field, char *out) {
  int ip = 0;                    // input buffer pointer
  int op = 0;                    // output buffer pointer
  int f = 0;                     // field counter

  while (f < field) {            // find start of field, ip
    while (in[ip++] != ',');
    f++;
  }

  while (in[ip] != ',')  {      // scan to next ','
    out[op++] = in[ip++];       // copy in to out
  }
  out[op] = '\0';               // terminate out string
}

// ================ RTC functions  SET =================
// set date and time bytes to RTC BCD
void setRTC() {
  // get GPS data in bytes, calc dow
  hrs = strtob(tm, 0);           // HH....
  mns = strtob(tm, 2);           // ..MM..
  sec = strtob(tm, 4);           // ....SS
  dy  = strtob(dt, 0);           // DD....
  mth = strtob(dt, 2);           // ..MM..
  yr  = strtob(dt, 4);           // ....YY
  dow = calcDow(yr, mth, dy);

  // program RTC
  Wire.beginTransmission(RTCADDR);
  Wire.write(0);               // next input at sec register

  Wire.write(decToBcd(sec));   // set seconds
  Wire.write(decToBcd(mns));   // set minutes
  Wire.write(decToBcd(hrs));   // set hours
  Wire.write(decToBcd(dow));   // set day of week
  Wire.write(decToBcd(dy));    // set date (1 to 31)
  Wire.write(decToBcd(mth));   // set month (1-12)
  Wire.write(decToBcd(yr));    // set year (0 to 99)
  Wire.endTransmission();
}

// convert ASCII (0-99), starting at bp, to byte
byte strtob(char *in, int bp) {
  char out[20];

  strncpy(out, in + bp, 2);     // copy 2 char
  return (byte)atoi(out);       // return byte
}

// Convert decimal to BCD
byte decToBcd(byte dec)
{
  return ( (dec / 10 * 16) + (dec % 10) );
}

// calc dow
byte calcDow(byte year, byte month, byte day)
{
  unsigned long days;
  unsigned int febs;
  unsigned int months[] =
  {
    0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 // days until 1st of month
  };

  days = year * 365;   // days up to year

  febs = year;
  if (month > 2) febs++; // number of completed Februaries

  // add in the leap days
  days += ((febs + 3) / 4);
  days -= ((febs + 99) / 100);
  days += ((febs + 399) / 400);

  days += months[month - 1] + day;

  // now we have day number such that 0000-01-01(Sat) is day 1

  return (byte)(((days + 5) % 7));
}

// ====================Maidenhead functions================
// calculate maideng=head locator from lat & lon
void setMH() {
  // extract lat * lon from GPS data
  xtract(gpsbuf, 3, la);
  xtract(gpsbuf, 4, ns);
  xtract(gpsbuf, 5, lo);
  xtract(gpsbuf, 6, ew);

  lat = convertPos(la, ns);
  lon = convertPos(lo, ew);

  findMH(mh, lat, lon);

  saveMH(); // save in EEPROM
}

// convert Lat, Lon strings to decimal +/-NS|EW
double convertPos(char *pos, char *d) {
  double pp, mm, ans;
  int dd;

  pp = atof(pos);                        // get in decimal ddmm.mmmmmmm
  dd = (int)pp / 100;                    // get degrees part
  mm = pp - (100 * dd);                  // get minutes
  ans = dd + (double)mm / 60.0;          // calc decimal degrees

  if (strcmp(d, "N") == 0 || strcmp(d, "E") == 0)  // if positive
    return ans;
  else
    return - ans; // negative
}

// find MH from lat & lon
void findMH(char *dst, double fa, double fo) {
  int a1, a2, a3;
  int o1, o2, o3;
  double rd;

  // Latitude
  rd = fa + 90.0;
  a1 = (int)(rd / 10.0);
  rd = rd - (double)a1 * 10.0;
  a2 = (int)(rd);
  rd = rd - (double)a2;
  a3 = (int)(24.0 * rd);

  // Longitude
  rd = fo + 180.0;
  o1 = (int)(rd / 20.0);
  rd = rd - (double)o1 * 20.0;
  o2 = (int)(rd / 2.0);
  rd = rd - 2.0 * (double)o2;
  o3 = (int)(12.0 * rd);

  dst[0] = (char)o1 + 'A';
  dst[1] = (char)a1 + 'A';
  dst[2] = (char)o2 + '0';
  dst[3] = (char)a2 + '0';
  dst[4] = (char)o3 + 'A';
  dst[5] = (char)a3 + 'A';
  dst[6] = '\0';
}

// save mh in EEPROM bytes 0-6
void saveMH() {
  int addr;
  byte i;

  addr = 0;
  i = 0;
  while(i < strlen(mh))                    // write MMnnMM to EEPROM
    EEPROM.write(addr++, mh[i++]);
  EEPROM.write(addr, '\0');                // terminate string
}

// ====================RTC functions READ =================
// get time from RTC, convert BCD to decimal
void getRTC() {
  // Reset the RTC register pointer
  Wire.beginTransmission(RTCADDR);
  Wire.write(0);
  Wire.endTransmission();

  // request 7 bytes from the RTC address
  Wire.requestFrom(RTCADDR, 7);

  // get the time date
  sec = bcdToDec(Wire.read()); // 0 - 59
  mns = bcdToDec(Wire.read()); // 0 - 59
  hrs = bcdToDec(Wire.read() & 0b111111); // mask 12/24 bit
  dow = bcdToDec(Wire.read()); // 0 = Sunday
  dy  = bcdToDec(Wire.read()); // 1 - 31
  mth = bcdToDec(Wire.read()); // 0 = jan
  yr  = bcdToDec(Wire.read()); // ..yy
}


// Convert BCD to decimal numbers
byte bcdToDec(byte val) {
  return ( (val / 16 * 10) + (val % 16) );
}

// ============Picture Display ===============
// picture loop
void dispUpdate() {
  oled.firstPage();
  do {
    dispMsg(55, 0, "GPS");
    if (strcmp(fix, "V") == 0) {            // no fix V
      dispMsgL(30, 15, "GET GPS");
      dispMsg(25, 45, "Press Button");
    }
    else {
      dispMsgL(30, 15, mh);                 // fix A
      dispDate(15, 32, dow, dy, mth, yr);
      dispTimeL(25, 45, hrs, mns, sec);
    }
  } while ( oled.nextPage() );
}

Saturday, 24 June 2017

BASIC Tech Group - MyNews - 47 GPS time and locator

My ADS (analog Digital Synthesiser) built using an Arduino UNO and an AD9851 chip includes a RTC DS3231 with a back up battery. The intention is to have UTC time available to software running on the Arduino UNO. So that it can generate correctly timed WSPR and JT65 output. It also includes a MMIC amplifier to output up to 10mW into 50R.

IMG 1084

Internal view of the VFO

TIME

At the moment I have a special Arduino sketch "DATE_TIME_SET_OLED" - see below - which I use to set the date and time in the RTC, it needs you to enter the date & time in the format YYMMDDWHHMMSS (W = day of week, Sunday = 1) in the Arduino Monitor window, then hit "Send" at exactly the right moment to set the correct time. Obviously this is a bit hit-and-miss, and also relies on your Computer displaying the right time to the second (my MacBook does this automatically by reading time from time.apple.com, but I have had trouble with my Windows PC which loses lock).

Anyway it seemed to me that by providing a new input/output connection to the Arduino UNO in the VFO I could send in information from a GPS receiver, extract the date and time and automatically calibrate the internal RTC. I could then also use the same connection in other sketches to input or output a couple of signals to any external device when the GPS is not used...

GPS RECEIVER

First a GPS receiver, I searched the internet and found a very low cost solution - GPS receivers that are targeted at car navigation and dash board cameras market. Like this one, VK-163 G-Mouse Headphone Wire Interface Navigation GPS,

Screen Shot 2017 06 24 at 14 50 36

GPS Receiver

It has a 4 way 3.5mm jack plug. After a considerable time fussing about with it I discovered the connections, which are,

Jack tip = VCC 3.6-5V

1st ring = GPS TX output (NMEA output data strings)

2nd ring = GPS RX Input (configuration input commands)

Jack shaft = Ground

And I wired it up to an Arduino UNO, using pin 12 as UNO RX to GPSTX, and pin 13 as UNO TX to GPS RX. />
IMG 1220

GPS wiring to Arduino UNO

The Arduino connections I used can be read in the sketch below. Basically pin 12 for data coming in, and pin 13 for any commands I may want to send out. Though I found the GPS works out of the box with 1 second updated outputs without giving any new commands, so I haven't used pin 13 in my sketch.

The results, when I insert Serial.print() commands, are that I can read the NMEA message "$GPRMC" on the serial monitor like this,

Screen Shot 2017 06 24 at 14 34 11

GPS NMEA ASCII string data for the $GPRMC message ID

Below are the sketches code for the manual set time and the GPS reception. Now all I have to do is extract the time and date info from the GPS string and program the RTC...

CODE

// DATE_TIME_SET_OLED
// V1.0 9-5-17 does not use DS3231 library
// enter YYMMDDwHHMMSS, reset/reload to repeat
// w = day-of-week 1 = mon, 01 = Jan 17 = 2017, 24 hour clock
// RTC
// SDA A4
// SCL A5
// SW 4

#include "Wire.h"
#include "Oled_128X64_I2C.h"

// RTC address
#define RTCADDR 0x68

// RTC time and date
byte doW, date, month, year;
byte hrs, mns, sec;

bool gotString;

void setup() {
  Serial.begin(9600);
  
  Wire.begin();

  oled.begin();

  gotString = false;
  
  dispUpdate();
}

void loop() {
  char inString[20] = "";
  byte j = 0;

  while (!gotString) {
    if (Serial.available()) {
      inString[j] = Serial.read();

      if (inString[j] == '\n') {
        gotString = true;

        // convert ASCII codes to bytes
        year = ((byte)inString[0] - 48) * 10 + (byte)inString[1] - 48;
        month = ((byte)inString[2] - 48) * 10 + (byte)inString[3] - 48;
        date = ((byte)inString[4] - 48) * 10 + (byte)inString[5] - 48;
        doW = ((byte)inString[6] - 48);
        hrs = ((byte)inString[7] - 48) * 10 + (byte)inString[8] - 48;
        mns = ((byte)inString[9] - 48) * 10 + (byte)inString[10] - 48;
        sec = ((byte)inString[11] - 48) * 10 + (byte)inString[12] - 48;

        setRTC();
      }
      j += 1;
    }
  }

  getRTC(); // get time
  
  dispUpdate();
}
  
// set the time int he RTC
void setRTC()
{
  // sets time and date data to DS3231
  Wire.beginTransmission(RTCADDR);
  Wire.write(0); // set next input to start at the sec register
  
  Wire.write(decToBcd(sec)); // set seconds
  Wire.write(decToBcd(mns)); // set minutes
  Wire.write(decToBcd(hrs)); // set hours
  Wire.write(decToBcd(doW)); // set day of week (1=Sunday, 7=Saturday)
  Wire.write(decToBcd(date)); // set date (1 to 31)
  Wire.write(decToBcd(month)); // set month
  Wire.write(decToBcd(year)); // set year (0 to 99)
  Wire.endTransmission();
}

// get time from RTC, convert bcd to decimal
void getRTC() {
  // Reset the RTC register pointer
  Wire.beginTransmission(RTCADDR);
  Wire.write(0x00); // output start at sec register
  Wire.endTransmission();

  // request 7 bytes from the RTC address
  Wire.requestFrom(RTCADDR, 7);

  // get the time data
  sec = bcdToDec(Wire.read()); // 0 - 59
  mns = bcdToDec(Wire.read()); // 0 - 59
  hrs = bcdToDec(Wire.read() & 0b111111); //mask 12/24 bit
  doW = bcdToDec(Wire.read()); //0 - 6 = Sunday - Saturday
  date = bcdToDec(Wire.read()); // 1 - 31
  month = bcdToDec(Wire.read()); // 0 = jan
  year = bcdToDec(Wire.read()); // 20xx
}

// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
  return( (val/10*16) + (val%10) );
}

// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
  return( (val/16*10) + (val%16) );
}

// picture loop, display init data
void dispUpdate() {
  oled.firstPage();
  do {
    if (gotString == true) {
      dispDate(15, 15, doW, date, month, year);
      dispTimeL(25, 40, hrs, mns, sec);
    }
    else {
      dispMsg(0, 15, ">> YYMMDDwHHMMSS");
    }
  } while ( oled.nextPage() );
}


MORE CODE
// GPS_READ_MSG_PRINT
// V0.4 basics of read GPS
// Jack plug/socket wiring
// tip  VCC (Y)
// 2    TX GPS (R)
// 3    RX GPS (OR)
// ring GND (BWN)

#include "SoftwareSerial.h"

// connections GPSRX -> RX, GPSTX <- TX
#define RX 12
#define TX 13

// GPS data buffer, gps message ID
char gpsbuf[200];
char MSGID[10] = "$GPRMC";

// jack GPS 3(TX) -> RX, GPS 2(RX) <- TX
SoftwareSerial gps (RX, TX);

void setup() {
  pinMode(RX, INPUT);
  pinMode(TX, OUTPUT);

  Serial.begin(9600);

  gps.begin(9600); // start GPS serial

  Serial.println("Start");
}

void loop() {
  // read MSGID line
  do {
    getline(gpsbuf);
  } while (strncmp(gpsbuf, MSGID, 6) != 0);
  
  Serial.print(gpsbuf);
}

// get a line from the GPS, inc /r/n, add /0
void getline(char *buf) {
  char c;
  int p = 0;

  do {
    if (gps.available() > 0) {
      c = gps.read();
      buf[p++] = c;
    }
  } while ( c != '\n');
  buf[p] = '\0';
}

Friday, 9 June 2017

BASIC Tech Group - MyNews - 46 THE PIXIE CHALLENGE

The PIXIE CHALLENGE

This should be fun! On eBay you will find lots of very low cost kits for a 40m Transceiver called the "Pixie". This is a simple two transistor - Oscillator and PA, and a receiver - using the PA transistor as an amplifier followed by a diode detector and LM386 amplifier IC. It is a cute and interesting design.

It took me about 3 hours to sort out the components and identify the resistors, capacitors and coils (looking like RF chokes), then to build the board. It needs a morse key, a headphone or external amplifier and loudspeaker, a 9-12V supply (I used 6 x AA batteries, you can also use a simple PP3) and an antenna. I attached a 2m length of wire as an antenna - as the challenge is intended to make a contact over a short distance - a few tens of metres. It is also better to have ground connection or radial.

Take note that from my measurements the TX on 7023kHz has lots of harmonics, for example the second harmonic is less than 30dB down, which is poor and probably not legal. The RX also seems to radiate a low level signal at the RIT higher frequency.

THE CHALLENGE

The members of the Banbury Amateur Radio Society (BARS) will be challenged to take two of our "Constructor" evenings to each build a Pixie, get it going and make a CW QSO - minimum exchange of call signs and reports with acknowledgements. First couple to make a QSO will get a prize. Simple QSO might be:

CQ DE G3YWX K
G3YWX DE G3QAB KN
G3QAB DE G3YWX UR 599 K
R UR RST 599 K
R 599 SK
To set up this challenge I purchased one of the Pixie kits here. and it arrived in a couple of weeks. The circuit is a xtal oscillator RIT tuneable a kHz or so from the XTAL frequency of 7023kHz by a varicap diode. I built it and first tested the RX using my Arduino AD9851 VFO on a frequency of 7023.00kHz.

IMG 1197

Battery (6 x AA), morse key connection, audio output and antenna. And my VFO in the small blackbox.

The RX seems to be reasonably sensitive, but an external audio amplifier is a good idea. Next I tested the TX, and connected the antenna output to my RF Meter capable of measuring RF power from a few mW to 10W.

IMG 1199

The output was around 780mW into a 50R dummy load.

Arduino keyer

Now I am lazy about morse code (and terrible at it, as are other members of BARS - thus the challenge), but I wrote a short sketch for an Arduino Uno to send a fixed short text message or a message you type in, automatically. The Arduino controls a relay from one of its outputs which in turn keys the Pixie TX.

IMG 1203

The reception was by my Elektor SDR feeding the HDSDR software, with its audio output fed to the Argo spectrum display software.

IMG 1205

IMG 1204

You can read the morse message in the Argo window.

Both software programs are running on my very low cost (£180) Windows 10 PC! I used a low cost 96kHz USB analog/digital convertor.

CODE
// PIXIE_MORSE - relay driver for sending morse message
// V1.1 9-5-17
// thanks to F0GOJ for some of the varicode
// Output to a relay, HIGH = TX
// board LED also on pin
// RELAY < PTT (5)

// relay pin
#define RELAY 5

//speed WPM
#define WPM 5

int repeat = 10000; // erpeat in 10 secs

// message to send
char msg[] = "SECRET MESSAGE GOES HERE";

// morse varicode MSB 1st, and length
byte morseVaricode[2][59] = {
  { 0, 212, 72, 0, 144, 0, 128, 120, 176, 180,
    0, 80, 204, 132, 84, 144, 248, 120, 56, 24,
    8, 0, 128, 192, 224, 240, 224, 168, 0, 136,
    0, 48, 104, 64, 128, 160, 128, 0, 32, 192,
    0, 0, 112, 160, 64, 192, 128, 224, 96, 208,
    64, 0, 128, 32, 16, 96, 144, 176, 192
  },
  { 7, 6, 5, 0, 4, 0, 4, 6, 5, 6,
    0, 5, 6, 6, 6, 5, 5, 5, 5, 5,
    5, 5, 5, 5, 5, 5, 6, 6, 0, 5,
    0, 6, 6, 2, 4, 4, 3, 1, 4, 3,
    4, 2, 4, 3, 4, 2, 2, 3, 4, 4,
    3, 3, 1, 3, 4, 3, 4, 4, 4
  }
};

void setup() {
  // relay output
  pinMode(RELAY, OUTPUT);

  // delay before start
  delay(repeat);
}

void loop() {
  sendMsg(msg);            // send CW message
  delay(repeat);           // repeat
}

// send message at wpm
void sendMsg(char *m) {
  bool val;
  byte c, n, ndx, bits, vCode;;
  int dotTime, dashTime;

  // calculate dot time
  dotTime = 1200 / WPM;                           // Duration of 1 dot
  dashTime = 3 * dotTime;                         // and dash

  //send msg in morse code
  c = 0;
  while (m[c] != '\0') {
    m[c] = toupper(m[c]);                        // u.c.just in case

    if (m[c] == ' ') {                           // catch ASCII SP
      delay(7 * dotTime);
    }
    else if (m[c] > ' ' && m[c] <= 'Z') {
      ndx = m[c] - ' ';                         // index to varicode 0-58

      vCode = morseVaricode[0][ndx];            // get CW varicode data
      bits = morseVaricode[1][ndx];             // get CW varicode length

      if (bits != 0) {                          // if not characters # % < >
        for (n = 7; n > (7 - bits); n--) {      // Send CW character, MSB(bit 7) 1st
                                                // 0 for dot, 1 for dash
          val = bitRead(vCode, n);              // look up varicode bit

          digitalWrite(RELAY, HIGH);            // send dot or dash
          if (val == 1) delay(dashTime);
          else delay(dotTime);
          digitalWrite(RELAY, LOW);

          delay(dotTime);                       // for 1 dot space between dots|dashes
        }
      }
      delay(dashTime);                          // 1 dash space between characters in a word
    }
    c++;                                        // next character in string
  }
}

The next code needs the Arduino to be connected to a serial terminal program, you can use the "serial monitor" of the Arduino IDE or your own terminal program - I use "iSerialTerm" on my MacBook.

MORE CODE
// PIXIE_MORSE_TEXT - relay driver for sending morse message
// V1.1 16-6-17
// thanks to F0GOJ for some of the varicode
// Output to a relay, HIGH = TX
// board LED also on pin
// RELAY 5 PTT

// relay pin
#define RELAY 5

//speed WPM
#define WPM 5

// message to send
char msg[40];

// morse varicode MSB 1st, and length
byte morseVaricode[2][59] = {
  { 0, 212, 72, 0, 144, 0, 128, 120, 176, 180,
    0, 80, 204, 132, 84, 144, 248, 120, 56, 24,
    8, 0, 128, 192, 224, 240, 224, 168, 0, 136,
    0, 48, 104, 64, 128, 160, 128, 0, 32, 192,
    0, 0, 112, 160, 64, 192, 128, 224, 96, 208,
    64, 0, 128, 32, 16, 96, 144, 176, 192
  },
  { 7, 6, 5, 0, 4, 0, 4, 6, 5, 6,
    0, 5, 6, 6, 6, 5, 5, 5, 5, 5,
    5, 5, 5, 5, 5, 5, 6, 6, 0, 5,
    0, 6, 6, 2, 4, 4, 3, 1, 4, 3,
    4, 2, 4, 3, 4, 2, 2, 3, 4, 4,
    3, 3, 1, 3, 4, 3, 4, 4, 4
  }
};

void setup() {
  // Serial
  Serial.begin(9600);

  // relay output
  pinMode(RELAY, OUTPUT);
}

void loop() {
  if (getMsg(msg) == true) {
    Serial.println(msg);
    sendMsg(msg);            // send CW message
  }
  clearBuf(msg);
}

// get input msg[] U.C.
bool getMsg(char *m)
{
  char ch;
  int n;

  n = 0;
  if (Serial.available() > 0) {      // if input
    delay(20);                       // let USB catch up
    while (Serial.available() > 0) { // get input
      ch = Serial.read();            // use upper case as input
      if (ch == '\n') ch = '\0';     // end of text
      m[n++] = ch;
      delay(20);                     // let USB catch up
    }
    return true;                     // got input
  }
  return false;                      // no input
}

// clear msg and buffer
void clearBuf(char *m) {
  m[0] = '\0';
  while (Serial.available() > 0) Serial.read();
}

// send message at wpm
void sendMsg(char *m) {
  bool val;
  byte c, n, ndx, bits, vCode;;
  int dotTime, dashTime;

  // calculate dot time
  dotTime = 1200 / WPM;                           // Duration of 1 dot
  dashTime = 3 * dotTime;                         // and dash

  //send msg in morse code
  c = 0;
  while (m[c] != '\0') {
    m[c] = toupper(m[c]);                        // u.c.just in case

    if (m[c] == ' ') {                           // catch ASCII SP
      delay(7 * dotTime);
    }
    else if (m[c] > ' ' && m[c] <= 'Z') {
      ndx = m[c] - ' ';                         // index to varicode 0-58

      vCode = morseVaricode[0][ndx];            // get CW varicode data
      bits = morseVaricode[1][ndx];             // get CW varicode length

      if (bits != 0) {                          // if not characters # % < >
        for (n = 7; n > (7 - bits); n--) {      // Send CW character, MSB(bit 7) 1st
          // 0 for dot, 1 for dash
          val = bitRead(vCode, n);              // look up varicode bit

          digitalWrite(RELAY, HIGH);            // send dot or dash
          if (val == 1) delay(dashTime);
          else delay(dotTime);
          digitalWrite(RELAY, LOW);

          delay(dotTime);                       // for 1 dot space between dots|dashes
        }
      }
      delay(dashTime);                          // 1 dash space between characters in a word
    }
    c++;                                        // next character in string
  }
}

Friday, 26 May 2017

BASIC Tech Group - MyNews - 45 Testing PA, WSPR, more on SWR

Finally the 20dB attenuator I ordered from China has arrived. I need this as even the lowest output setting of my VFO is too much drive power for the small 3W PA amplifier I have. So now I can turn it down to get the PA operating in its linear range. So I have been trying agin to send WSPR weak signals and am looking for a result on WSPRnet.org.

IMG 1095

Here's the set up, from right to left

- Windows PC (I am a Mac man, but this seems not so bad) running HDSDR and WSTJ software, using VB to pipe audio from one to the other.

- The SDR receiver, which is an Elektor SDR Arduino shield and an Arudino Uno.

- The VFO, which has an AD9851, RTC (using a DS3231) and a buffer amp. Software is loaded to run WSPR on 40m at 7040.1kHz, the 20dB attenuator is plugged in the back of the VFO.

The small PA amplifier module, from eBay, running 2W output from a supply of 13.8V

IMG 1097

More on SWR

In the last post I talked about an SWR meter using a normal bridge circuit. The output from the bridge FWD & REF RF was handled by two AD9307 log amplifiers and fed to the analog inputs of an Arduino Nano. This drove an OLED display showing FWD power and SWR. Fiddle as I might I could not get stable results, probably as a lot of the circuitry was on a plug-and-play breadboard. So I am having a rethink.

I have browsed the web for ages and this is what I have come up with. I will wire this up on a proper PCB to test it:

IMG 1096

I have abandoned the AD8307s and gone back to what most people use - a couple of germanium low drop diodes. The PA amplifier this will be used with is 2-3W output, so their should be enough volts to run the thing. Calibration can be done in software.

As you can see I want to retain the measurement of RF output - just the voltage on the line, which can be shown as power when the system is matched to a 50R load. For this I am using a tried and trusted AD8307 circuit which will display 10mW to 10W. This AD8307 output will also be used to trigger the auto RX/TX switching of the PA.

Wish me luck.

Monday, 15 May 2017

BASIC Tech Group - MyNews - 44 The SWR meter

As part of my planned QRP PA, giving 3.2W output on 40, 30 & 20m, I am including an SWR meter. These look simple but are far from it.

The circuit I am trying to build is based on an Arduino Nano to do the calculations and drive a OLED display, and a couple of AD8307 log detectors to measure the Forward and Reflected power outputted from a conventional circuit using tow 1:10 transformers to measure the load line current and voltage.

Screen Shot 2017 05 15 at 12 46 10

When a wave travels from TRX to ANT a forward voltage FWD is output, when a reflected wave travels from ANT to TRX a reflected voltage REF is output. That's the theory anyway. (see this plagiarism article) The actual circuit looks like this

Screen Shot 2017 05 14 at 14 42 57

So far so good.

PROGRESS SO FAR

What I have done so far is to buikd the AD8307s and Nano circuit,

IMG 1086

and write some software to display a couple of bars for FWD power, SWR, display the values and say which band I am operating on. This involves detecting (over an average of ten measurements) the AD8307 outputs in millivoltss, converting this to dBm according to the ICs slope of 25mV/dB, converting this to dBm and then milliwatts across the 50R

IMG 1085resistors.

I have found that individual calibration is needed to get the same sensitivity from each AD8307, see code below. The slopes seem to be equal, but the intercepts different. Anyway I am now getting roughly the correct SWR for a 20dB Return Loss.

And at the moment I am stuck as my SWR transformers do not seem to giving the right outputs. I am driving the TXR end from my AD9851 VFO buffer outputs with 10mW and using a load of 50R at the output, built-into my RF Meter box. And the results are WRONG - Negative SWR! Reflected power bigger than Forward power... Why?

Code

// PA_LPF_TRX_OLED
// V0.95 16-5-17 need h/w for testing/calibTFMRLOSSn
// to add TRX

#include "Oled_128X64_I2C.h"

// analog reference (mV), A/D count, read avg
// slope and TX/display trigger (mW)
#define AREF 3300
#define ADCOUNT 1023
#define READAVG 10
#define TXTRIG 2

// analog inputs, band button, band relays, TXRX relay (PTT)
#define FWDPIN A0
#define REFPIN A1
#define SW 4
#define BAND1 7
#define BAND2 6
#define PTT 5

// display variables
double mwFwd, mwRef, swr;

// display text for bands
char bandTxt[][4] = {
  "40m", "30m", "20m"
};

byte band;

bool txFlag;

void setup() {

  Serial.begin(9600);

  pinMode(SW, INPUT_PULLUP);
  pinMode(BAND1, OUTPUT);
  pinMode(BAND2, OUTPUT);
  pinMode(PTT, OUTPUT);

  // analog ref AREF
  analogReference(EXTERNAL);

  oled.begin();

  // start on 40m
  band = 0;

  // TX off
  txFlag = false;
}

void loop() {
  int aFwd, aRef, n;
  double mV, dBm;

  // read SWR bridge inputs and average
  aFwd = 0;
  aRef = 0;
  for (n = 0; n < READAVG; n++) {
    aFwd += analogRead(FWDPIN); // typ +20dB level, gives 0dB at AD8307, or 2.5V (775 d/a)
    delay(50);
    aRef += analogRead(REFPIN); // typ -10dB level, gives -30dB at AD8307, or 1.5V (465 d/a)
  }
  aFwd /= READAVG;
  aRef /= READAVG;

  // 1. Adj slope for 20dB drop (470/50R), plot
  // 2. Adj intercept for correct mW
  // aFwd to mW, slope & intercept
  mwFwd = convert(aFwd, 25.0, -86);
  mwRef = convert(aRef, 25.0, -88);

  Serial.print("F: ");
  Serial.print(aFwd);
  Serial.print("\t");
  Serial.print(mwFwd, 3);

  Serial.print("\t R: ");
  Serial.print(aRef);
  Serial.print("\t");
  Serial.println(mwRef, 3);


  // calc SWR
  swr = ( 1 + sqrt(mwRef / mwFwd) ) / (1 - sqrt(mwRef / mwFwd) ); // calc SWR

  // band change
  if (digitalRead(SW) == LOW) {
    while (!digitalRead(SW));
    if (band < 2) band++;
    else band = 0;
    bandSw();
  }

  // switch to TX at 100mW
  if (mwFwd > TXTRIG) {
    digitalWrite(PTT, HIGH); // TX = HIGH
    txFlag = true;
  }
  else {
    digitalWrite(PTT, LOW);
    txFlag = false;
  }

  dispUpdate();

  delay(50); // loop stability
}

// convert A/D count, to mW
double convert(int aIn, double sl, double cal) {
  double mV, dBm;

  mV = (double)(map(aIn, 0, ADCOUNT, 0, AREF));
  dBm = (mV / sl) + cal;
  return pow(10.0, (dBm / 10.0));

}

// band relays, wiring HIGH = relay ON
void bandSw() {
  switch (band) {
    case 0:
      digitalWrite(BAND1, HIGH);  // 40m
      digitalWrite(BAND2, HIGH);
      break;
    case 1:
      digitalWrite(BAND1, HIGH); // 30m
      digitalWrite(BAND2, LOW);
      break;
    case 2:
      digitalWrite(BAND1, LOW); // 20m
      digitalWrite(BAND2, LOW);
      break;
  }
}

//=====PICTURE LOOP
void dispUpdate() {
  oled.firstPage();
  do {
    dispMsg(20, 0, "QRP POWER AMP"); // title

    dispMsgS(0, 15, "FWD");
    dispMsgS(0, 25, "SWR");
    dispMsg(50, 35, "FWD");
    dispMsg(50, 50, "SWR");
    dispMsgL(10, 40, bandTxt[band]); // band "40m" "30m" "20m" large font

    // values display active only on TX
    if (txFlag == false) {
      dispBar(20, 15, 5, 0); // blank bars
      dispBar(20, 25, 5, 0);
    }
    else {
      dispBar(20, 15, 5, mwFwd / 50 ); // 0-5000mW = 0-100bar
      dispBar(20, 25, 5, (swr - 1) * 25); // 1-5 = 0-100bar
      if (mwFwd > 1000) {
        dispNum(80, 35, mwFwd / 1000, 1);
        dispMsg(110, 35, "W");
      }
      else {
        dispNum(80, 35, mwFwd, 0);
        dispMsg(110, 35, "mW");
      }
      dispNum(80, 50, swr, 2);
    }
  } while (oled.nextPage());
}