F150/155 Software development

Introduction

These instructions are intended for those who want to write their own software to drive the F150/155 generators. If you want to drive one of our other generators, please email us and we will outline the differences. In this text, we will refer to the generator as a F150 but the instructions for the F155 are the same. The F150 is driven via a serial port.  It can be driven by any computer that supports a serial port (or a USB port on MAC or PC via a third party adapter). This includes some models of Palm Pilot, Pocket PC, Psion and cellular phones. 

The F150 is driven by  very simple low level byte  instructions. For each of the two channels, the instructions are:

-Run a specific frequency
-Stop running frequencies
-Change the duty cycle

Any other higher level control needs to be implemented by your software. For example, in the F100 software for PC and PalmOS, the commands sweep, dwell, converge, goto, fuzz are all implemented in the PC/Palm software.

Setting up communication with the F150

You need to setup your communication port at 19200 baud, 8 bits, no parity, one stop bit. The F150 uses only 3 wires for serial communication: transmit, receive and ground (we do not use cts/rts). So you also need to set hardware handshake to off and software handshake (xon/xoff) to off.

Example

This c++ language example code segment does this:
 -queries the generator for its ID and crystal speeds
- sends a command to run 1000 HZ on channel A
- sends a command to change channel A duty to 75%
- sends a command to run 4HZ on channel B
- sends a command to change channel B duty to 20%
- send a command to stop frequency generation on both channels


double xtals[4];
double pXtals[4];
double nw;
double Nw; 
double xtal; // channel a accumulator freq
double pXtal; // channel b accumulator freq
double nwx;
double Nwx;
my_u32 N; // main freq accumulator factor
xtals[0]=5000000.0;xtals[1]=50000000.0;xtals[2]=180000000.0; //channel a frequencies, varies with generator model
pXtals[0] = 800000;pXtals[1] = 1600000;pXtals[2]=1454545.45454545454545454545; //channel b frequencies

//Query the generator for its ID and from that, figure out the DDS xtal frequency for both channels
//here we assume you have already opened the serial port at 19200,8,N,1, no hardware/software handshake

int res;
reset();//reset generator
u8 cmd[]={120};      //id query character command is 'x'
u8 rsp[255];
writeDevice(cmd,1);  //send id command to generator
  
if (readDevice(rsp,11,100)!=11) {//read id string from generator, exit if we do not get 11 characters back
      close();
      return false;
}

//The generaotr will return a 11 character string in the format "PFG nnn X x"

if (rsp[0]!='P' || rsp[1]!='F' || rsp[2]!='G' || rsp[3]!=' ' || rsp[4]!='0'  || rsp[5]!='0') {
      //response is invalid, exit
      close();
      exit();
}
   
X=rsp[8]-0x30;
x=rsp[10]-0x30;

//now that we know the speed of each DDS crystal, lets compute a couple of variables used when sending frequency commands
nw=16777216.0; // 24 bits DDS: 2 exp 24
Nw=536870912.0;   // 29 bits DDS:  2 exp 29
xtal=50000000.0; // chan A default accumulator freq
pXtal=800000.0; // chan B default accumulator freq

if ((X>2) || (X<0))  xtal=xtals[2];
else   xtal=xtals[X];
if ((x>2) ||(x<0) ) pXtal=pXtals[2];
else   pXtal=pXtals[x];
nwx=nw/pXtal;
Nwx=Nw/xtal;

chanHigh(1);         // make sure channel B is high since we are not using it yet
setDuty (0,50.0);    //set duty on channel A to 50%
runSingle(1000.0,0); //run 1000HZ on channel A
setDuty (0,75.0);    //set duty on channel A to 75%

setDuty (1,50.0);    //set duty on channel B to 50%
runSingle(1,4.0);    //run 4HZ on channel B
setDuty (1,20.0);    /set duty on channel B to 20%
chanLow(1); //effectively stops all frequency generation

//==================================================================
//Sets the specified channel to low (0 volt) output

 bool FGen::chanLow(int channel) {
   //channel not used yet since B is always used
   u8  cmd2[]={99,0}; // c0
   writeDevice(cmd2,2);
   u8  cmd[]={110,0,0,0}; //n000
   writeDevice(cmd,4);
   return true;
 }
//==================================================================
//Set the specified channel to high (5 volt) output

bool FGen::chanHigh(int channel) {
  u8 cmd[]={99,0x01,0,0}; // c1
  switch (channel) {
 
  case 1: //channel B
    writeDevice(cmd,2);
    cmd[0]=110;cmd[1]=0;cmd[2]=0;cmd[3]=0; //n000
    writeDevice(cmd,4);
    break;
   
  case 0: //channel A
      u8 cmd4[]= {78,0,0,0};
      writeDevice(cmd4,4);
      cmd4[0]=67; // setDuty byte to 11111111
      cmd4[1]=255;
      writeDevice(cmd4,2);
      break;
  }//switch
 
  return true;
  }

//========================================================================================================= 
// Send command to run a frequency on specified channel

bool runSingle(double inFreq,int progChan) {
    switch (progChan) {
   
    case 0:  //Channel A
      //This channel is 29 bit dds but we send only 24 bits since the 5 MSB are fixed in hardware. (We do not use the full bandwidth to reduce jitter)
      N=(my_u32)((inFreq*Nwx)+.5);
      u8 *cmd;
      u8 cmd4[]= {78,(u8)((N&0xff0000)>>16),(u8)((N&0xff00)>>8),(u8)(N&0xff)};
      cmd=cmd4;
      writeDevice(cmd,4);
      break;
     
    case 1: //Channel B
      bFreq=inFreq;
      N=(my_u32)((inFreq*nwx)+.5);
      u8 cmd5[]={110,(u8)((N&0xff0000)>>16),(u8)((N&0xff00)>>8),(u8)(N&0xff)}; //n
      writeDevice(cmd5,4);
      break;
    }//switch
   
    return true;
  }


//==========================================================================================
// Send command to set duty in percentage on the specified channel

bool setDuty(int channel,double tmpDuty) {


u8 cmd[]={67,0};
switch (channel) {

case 0: // channel A
  cmd[0]=67;
  if (tmpDuty>96.875) tmpDuty=96.875;
  if (tmpDuty<3.125) tmpDuty=3.125;
  cmd[1]=(u8)((tmpDuty*32.0/100.0)+.5);
  writeDevice(cmd,2);
  return true;
  break;
 
case 1: //channel B
    if (tmpDuty<=0) tmpDuty=.39;
    if (tmpDuty>=100) tmpDuty=99.6;
    cmd[0]=99;
    cmd[1]=(u8)((tmpDuty*255.0/100.0)+.5); //rounding
    bDuty=tmpDuty;
    writeDevice(cmd,2);
    return true;
    break;
} //switch
return true;
}

//=====================================================================================
//Empty serial receive buffer on generator. Allways do this when starting a new program sequence

reset() {
   
    int res;
 
    u8 cmd[]={0,0,0,0,0,0,0,0,0,0,116}; // 116=reset DDS
    writeDevice(cmd,11);
    PurgeComm(hCom,PURGE_RXABORT&PURGE_RXCLEAR); //flush buffer on windows serial comm
    return true;
  }