diff --git a/ubitx_20/ubitx_20.ino b/ubitx_20/ubitx_20.ino new file mode 100644 index 0000000..c6aab85 --- /dev/null +++ b/ubitx_20/ubitx_20.ino @@ -0,0 +1,558 @@ +/** + * This source file is under General Public License version 3. + * + * This verision uses a built-in Si5351 library + * Most source code are meant to be understood by the compilers and the computers. + * Code that has to be hackable needs to be well understood and properly documented. + * Donald Knuth coined the term Literate Programming to indicate code that is written be + * easily read and understood. + * + * The Raduino is a small board that includes the Arduin Nano, a 16x2 LCD display and + * an Si5351a frequency synthesizer. This board is manufactured by Paradigm Ecomm Pvt Ltd + * + * To learn more about Arduino you may visit www.arduino.cc. + * + * The Arduino works by starts executing the code in a function called setup() and then it + * repeatedly keeps calling loop() forever. All the initialization code is kept in setup() + * and code to continuously sense the tuning knob, the function button, transmit/receive, + * etc is all in the loop() function. If you wish to study the code top down, then scroll + * to the bottom of this file and read your way up. + * + * Below are the libraries to be included for building the Raduino + * The EEPROM library is used to store settings like the frequency memory, caliberation data, + * callsign etc . + * + * The main chip which generates upto three oscillators of various frequencies in the + * Raduino is the Si5351a. To learn more about Si5351a you can download the datasheet + * from www.silabs.com although, strictly speaking it is not a requirment to understand this code. + * Instead, you can look up the Si5351 library written by xxx, yyy. You can download and + * install it from www.url.com to complile this file. + * The Wire.h library is used to talk to the Si5351 and we also declare an instance of + * Si5351 object to control the clocks. + */ +#include +#include + +/** + The main chip which generates upto three oscillators of various frequencies in the + Raduino is the Si5351a. To learn more about Si5351a you can download the datasheet + from www.silabs.com although, strictly speaking it is not a requirment to understand this code. + + We no longer use the standard SI5351 library because of its huge overhead due to many unused + features consuming a lot of program space. Instead of depending on an external library we now use + Jerry Gaffke's, KE7ER, lightweight standalone mimimalist "si5351bx" routines (see further down the + code). Here are some defines and declarations used by Jerry's routines: +*/ + + +/** + * We need to carefully pick assignment of pin for various purposes. + * There are two sets of completely programmable pins on the Raduino. + * First, on the top of the board, in line with the LCD connector is an 8-pin connector + * that is largely meant for analog inputs and front-panel control. It has a regulated 5v output, + * ground and six pins. Each of these six pins can be individually programmed + * either as an analog input, a digital input or a digital output. + * The pins are assigned as follows (left to right, display facing you): + * Pin 1 (Violet), A7, SPARE + * Pin 2 (Blue), A6, KEYER (DATA) + * Pin 3 (Green), +5v + * Pin 4 (Yellow), Gnd + * Pin 5 (Orange), A3, PTT + * Pin 6 (Red), A2, F BUTTON + * Pin 7 (Brown), A1, ENC B + * Pin 8 (Black), A0, ENC A + *Note: A5, A4 are wired to the Si5351 as I2C interface + * * + * Though, this can be assigned anyway, for this application of the Arduino, we will make the following + * assignment + * A2 will connect to the PTT line, which is the usually a part of the mic connector + * A3 is connected to a push button that can momentarily ground this line. This will be used for RIT/Bandswitching, etc. + * A6 is to implement a keyer, it is reserved and not yet implemented + * A7 is connected to a center pin of good quality 100K or 10K linear potentiometer with the two other ends connected to + * ground and +5v lines available on the connector. This implments the tuning mechanism + */ + +#define ENC_A (A0) +#define ENC_B (A1) +#define FBUTTON (A2) +#define PTT (A3) +#define ANALOG_KEYER (A6) +#define ANALOG_SPARE (A7) + +/** + * The Raduino board is the size of a standard 16x2 LCD panel. It has three connectors: + * + * First, is an 8 pin connector that provides +5v, GND and six analog input pins that can also be + * configured to be used as digital input or output pins. These are referred to as A0,A1,A2, + * A3,A6 and A7 pins. The A4 and A5 pins are missing from this connector as they are used to + * talk to the Si5351 over I2C protocol. + * + * Second is a 16 pin LCD connector. This connector is meant specifically for the standard 16x2 + * LCD display in 4 bit mode. The 4 bit mode requires 4 data lines and two control lines to work: + * Lines used are : RESET, ENABLE, D4, D5, D6, D7 + * We include the library and declare the configuration of the LCD panel too + */ + +#include +LiquidCrystal lcd(8,9,10,11,12,13); + +/** + * The Arduino, unlike C/C++ on a regular computer with gigabytes of RAM, has very little memory. + * We have to be very careful with variables that are declared inside the functions as they are + * created in a memory region called the stack. The stack has just a few bytes of space on the Arduino + * if you declare large strings inside functions, they can easily exceed the capacity of the stack + * and mess up your programs. + * We circumvent this by declaring a few global buffers as kitchen counters where we can + * slice and dice our strings. These strings are mostly used to control the display or handle + * the input and output from the USB port. We must keep a count of the bytes used while reading + * the serial port as we can easily run out of buffer space. This is done in the serial_in_count variable. + */ +char c[30], b[30]; +char printBuff[2][17]; //mirrors what is showing on the two lines of the display +int count = 0; //to generally count ticks, loops, etc + +/** + * The second set of 16 pins on the Raduino's bottom connector are have the three clock outputs and the digital lines to control the rig. + * This assignment is as follows : + * Pin 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 + * GND +5V CLK0 GND GND CLK1 GND GND CLK2 GND D2 D3 D4 D5 D6 D7 + * These too are flexible with what you may do with them, for the Raduino, we use them to : + * - TX_RX line : Switches between Transmit and Receive after sensing the PTT or the morse keyer + * - CW_KEY line : turns on the carrier for CW + */ + +#define TX_RX (7) +#define CW_TONE (6) +#define TX_LPF_A (5) +#define TX_LPF_B (4) +#define TX_LPF_C (3) +#define CW_KEY (2) + +/** + * These are the indices where these user changable settinngs are stored in the EEPROM + */ +#define MASTER_CAL 0 +#define LSB_CAL 4 +#define USB_CAL 8 +#define SIDE_TONE 12 +//these are ids of the vfos as well as their offset into the eeprom storage, don't change these 'magic' values +#define VFO_A 16 +#define VFO_B 20 +#define CW_SIDETONE 24 +#define CW_SPEED 28 + +/** + * The uBITX is an upconnversion transceiver. The first IF is at 45 MHz. + * The first IF frequency is not exactly at 45 Mhz but about 5 khz lower, + * this shift is due to the loading on the 45 Mhz crystal filter by the matching + * L-network used on it's either sides. + * The first oscillator works between 48 Mhz and 75 MHz. The signal is subtracted + * from the first oscillator to arriive at 45 Mhz IF. Thus, it is inverted : LSB becomes USB + * and USB becomes LSB. + * The second IF of 12 Mhz has a ladder crystal filter. If a second oscillator is used at + * 57 Mhz, the signal is subtracted FROM the oscillator, inverting a second time, and arrives + * at the 12 Mhz ladder filter thus doouble inversion, keeps the sidebands as they originally were. + * If the second oscillator is at 33 Mhz, the oscilaltor is subtracated from the signal, + * thus keeping the signal's sidebands inverted. The USB will become LSB. + * We use this technique to switch sidebands. This is to avoid placing the lsbCarrier close to + * 12 MHz where its fifth harmonic beats with the arduino's 16 Mhz oscillator's fourth harmonic + */ + +// the second oscillator should ideally be at 57 MHz, however, the crystal filter's center frequency +// is shifted down a little due to the loading from the impedance matching L-networks on either sides +#define SECOND_OSC_USB (56995000l) +#define SECOND_OSC_LSB (32995000l) +//these are the two default USB and LSB frequencies. The best frequencies depend upon your individual taste and filter shape +#define INIT_USB_FREQ (11996500l) +// limits the tuning and working range of the ubitx between 3 MHz and 30 MHz +#define LOWEST_FREQ (3000000l) +#define HIGHEST_FREQ (30000000l) + +//we directly generate the CW by programmin the Si5351 to the cw tx frequency, hence, both are different modes +//these are the parameter passed to startTx +#define TX_SSB 0 +#define TX_CW 1 + +char ritOn = 0; +char vfoActive = VFO_A; +int8_t meter_reading = 0; // a -1 on meter makes it invisible +unsigned long vfoA=7150000L, vfoB=14200000L, sideTone=800, usbCarrier; +unsigned long frequency, ritRxFrequency, ritTxFrequency; //frequency is the current frequency on the dial + +int cwSpeed = 100; //this is actuall the dot period in milliseconds +extern int32_t calibration; + +/** + * Raduino needs to keep track of current state of the transceiver. These are a few variables that do it + */ +boolean txCAT = false; //turned on if the transmitting due to a CAT command +char inTx = 0; //it is set to 1 if in transmit mode (whatever the reason : cw, ptt or cat) +char splitOn = 0; //working split, uses VFO B as the transmit frequency, (NOT IMPLEMENTED YET) +char keyDown = 0; //in cw mode, denotes the carrier is being transmitted +char isUSB = 0; //upper sideband was selected, this is reset to the default for the + //frequency when it crosses the frequency border of 10 MHz +byte menuOn = 0; //set to 1 when the menu is being displayed, if a menu item sets it to zero, the menu is exited +unsigned long cwTimeout = 0; //milliseconds to go before the cw transmit line is released and the radio goes back to rx mode +unsigned long dbgCount = 0; //not used now +unsigned char txFilter = 0; //which of the four transmit filters are in use +boolean modeCalibrate = false;//this mode of menus shows extended menus to calibrate the oscillators and choose the proper + //beat frequency +/** + * Below are the basic functions that control the uBitx. Understanding the functions before + * you start hacking around + */ + +/** + * Select the properly tx harmonic filters + * The four harmonic filters use only three relays + * the four LPFs cover 30-21 Mhz, 18 - 14 Mhz, 7-10 MHz and 3.5 to 5 Mhz + * Briefly, it works like this, + * - When KT1 is OFF, the 'off' position routes the PA output through the 30 MHz LPF + * - When KT1 is ON, it routes the PA output to KT2. Which is why you will see that + * the KT1 is on for the three other cases. + * - When the KT1 is ON and KT2 is off, the off position of KT2 routes the PA output + * to 18 MHz LPF (That also works for 14 Mhz) + * - When KT1 is On, KT2 is On, it routes the PA output to KT3 + * - KT3, when switched on selects the 7-10 Mhz filter + * - KT3 when switched off selects the 3.5-5 Mhz filter + * See the circuit to understand this + */ + +void setTXFilters(unsigned long freq){ + + if (freq > 21000000L){ // the default filter is with 35 MHz cut-off + digitalWrite(TX_LPF_A, 0); + digitalWrite(TX_LPF_B, 0); + digitalWrite(TX_LPF_C, 0); + } + else if (freq >= 14000000L){ //thrown the KT1 relay on, the 30 MHz LPF is bypassed and the 14-18 MHz LPF is allowd to go through + digitalWrite(TX_LPF_A, 1); + digitalWrite(TX_LPF_B, 0); + digitalWrite(TX_LPF_C, 0); + } + else if (freq > 7000000L){ + digitalWrite(TX_LPF_A, 1); + digitalWrite(TX_LPF_B, 1); + digitalWrite(TX_LPF_C, 0); + } + else { + digitalWrite(TX_LPF_A, 1); + digitalWrite(TX_LPF_B, 1); + digitalWrite(TX_LPF_C, 1); + } +} + +/** + * This is the most frequently called function that configures the + * radio to a particular frequeny, sideband and sets up the transmit filters + * + * The transmit filter relays are powered up only during the tx so they dont + * draw any current during rx. + * + * The carrier oscillator of the detector/modulator is permanently fixed at + * uppper sideband. The sideband selection is done by placing the second oscillator + * either 12 Mhz below or above the 45 Mhz signal thereby inverting the sidebands + * through mixing of the second local oscillator. + */ + +void setFrequency(unsigned long f){ + uint64_t osc_f; + + setTXFilters(f); + + if (isUSB){ + si5351bx_setfreq(2, SECOND_OSC_USB - usbCarrier + f); + si5351bx_setfreq(1, SECOND_OSC_USB); + } + else{ + si5351bx_setfreq(2, SECOND_OSC_LSB + usbCarrier + f); + si5351bx_setfreq(1, SECOND_OSC_LSB); + } + + frequency = f; +} + +/** + * startTx is called by the PTT, cw keyer and CAT protocol to + * put the uBitx in tx mode. It takes care of rit settings, sideband settings + * Note: In cw mode, doesnt key the radio, only puts it in tx mode + */ + +void startTx(byte txMode){ + unsigned long tx_freq = 0; + digitalWrite(TX_RX, 1); + inTx = 1; + + if (ritOn){ + //save the current as the rx frequency + ritRxFrequency = frequency; + setFrequency(ritTxFrequency); + } + + if (txMode == TX_CW){ + //turn off the second local oscillator and the bfo + si5351bx_setfreq(0, 0); + si5351bx_setfreq(1, 0); + + //shif the first oscillator to the tx frequency directly + //the key up and key down will toggle the carrier unbalancing + //the exact cw frequency is the tuned frequency + sidetone + if (isUSB) + si5351bx_setfreq(2, frequency + sideTone); + else + si5351bx_setfreq(2, frequency - sideTone); + } + updateDisplay(); +} + +void stopTx(){ + inTx = 0; + + digitalWrite(TX_RX, 0); //turn off the tx + si5351bx_setfreq(0, usbCarrier); //set back the carrier oscillator anyway, cw tx switches it off + + if (ritOn) + setFrequency(ritRxFrequency); + else + setFrequency(frequency); + + updateDisplay(); +} + +/** + * ritEnable is called with a frequency parameter that determines + * what the tx frequency will be + */ +void ritEnable(unsigned long f){ + ritOn = 1; + //save the non-rit frequency back into the VFO memory + //as RIT is a temporary shift, this is not saved to EEPROM + ritTxFrequency = f; +} + +// this is called by the RIT menu routine +void ritDisable(){ + if (ritOn){ + ritOn = 0; + setFrequency(ritTxFrequency); + updateDisplay(); + } +} + +/** + * Basic User Interface Routines. These check the front panel for any activity + */ + +/** + * The PTT is checked only if we are not already in a cw transmit session + * If the PTT is pressed, we shift to the ritbase if the rit was on + * flip the T/R line to T and update the display to denote transmission + */ + +void checkPTT(){ + //we don't check for ptt when transmitting cw + if (cwTimeout > 0) + return; + + if (digitalRead(PTT) == 0 && inTx == 0){ + startTx(TX_SSB); + delay(50); //debounce the PTT + } + + if (digitalRead(PTT) == 1 && inTx == 1) + stopTx(); +} + +void checkButton(){ + int i, t1, t2, knob, new_knob; + + //only if the button is pressed + if (!btnDown()) + return; + delay(50); + if (!btnDown()) //debounce + return; + + doMenu(); + //wait for the button to go up again + while(btnDown()) + delay(10); + delay(50);//debounce +} + + +/** + * The tuning jumps by 50 Hz on each step when you tune slowly + * As you spin the encoder faster, the jump size also increases + * This way, you can quickly move to another band by just spinning the + * tuning knob + */ + +void doTuning(){ + int s; + unsigned long prev_freq; + + s = enc_read(); + if (s){ + prev_freq = frequency; + + if (s > 10) + frequency += 200000l; + if (s > 7) + frequency += 10000l; + else if (s > 4) + frequency += 1000l; + else if (s > 2) + frequency += 500; + else if (s > 0) + frequency += 50l; + else if (s > -2) + frequency -= 50l; + else if (s > -4) + frequency -= 500l; + else if (s > -7) + frequency -= 1000l; + else if (s > -9) + frequency -= 10000l; + else + frequency -= 200000l; + + if (prev_freq < 10000000l && frequency > 10000000l) + isUSB = true; + + if (prev_freq > 10000000l && frequency < 10000000l) + isUSB = false; + + setFrequency(frequency); + updateDisplay(); + } +} + +/** + * RIT only steps back and forth by 100 hz at a time + */ +void doRIT(){ + unsigned long newFreq; + + int knob = enc_read(); + unsigned long old_freq = frequency; + + if (knob < 0) + frequency -= 100l; + else if (knob > 0) + frequency += 100; + + if (old_freq != frequency){ + setFrequency(frequency); + updateDisplay(); + } +} + +/** + * The settings are read from EEPROM. The first time around, the values may not be + * present or out of range, in this case, some intelligent defaults are copied into the + * variables. + */ +void initSettings(){ + //read the settings from the eeprom and restore them + //if the readings are off, then set defaults + EEPROM.get(MASTER_CAL, calibration); + EEPROM.get(USB_CAL, usbCarrier); + EEPROM.get(VFO_A, vfoA); + EEPROM.get(VFO_B, vfoB); + EEPROM.get(CW_SIDETONE, sideTone); + EEPROM.get(CW_SPEED, cwSpeed); + if (usbCarrier > 12010000l || usbCarrier < 11990000l) + usbCarrier = 11997000l; + if (vfoA > 35000000l || 3500000l > vfoA) + vfoA = 7150000l; + if (vfoB > 35000000l || 3500000l > vfoB) + vfoB = 14150000l; + if (sideTone < 100 || 2000 < sideTone) + sideTone = 800; + if (cwSpeed < 10 || 1000 < cwSpeed) + cwSpeed = 100; + +} + +void initPorts(){ + + analogReference(DEFAULT); + + //?? + pinMode(ENC_A, INPUT_PULLUP); + pinMode(ENC_B, INPUT_PULLUP); + pinMode(FBUTTON, INPUT_PULLUP); + + //configure the function button to use the external pull-up +// pinMode(FBUTTON, INPUT); +// digitalWrite(FBUTTON, HIGH); + + pinMode(PTT, INPUT_PULLUP); + pinMode(ANALOG_KEYER, INPUT_PULLUP); + + pinMode(CW_TONE, OUTPUT); + digitalWrite(CW_TONE, 0); + + pinMode(TX_RX,OUTPUT); + digitalWrite(TX_RX, 0); + + pinMode(TX_LPF_A, OUTPUT); + pinMode(TX_LPF_B, OUTPUT); + pinMode(TX_LPF_C, OUTPUT); + digitalWrite(TX_LPF_A, 0); + digitalWrite(TX_LPF_B, 0); + digitalWrite(TX_LPF_C, 0); + + pinMode(CW_KEY, OUTPUT); + digitalWrite(CW_KEY, 0); +} + +void setup() +{ + Serial.begin(9600); + + lcd.begin(16, 2); + + //we print this line so this shows up even if the raduino + //crashes later in the code + printLine1("uBITX v0.20"); + delay(500); + + initMeter(); //not used in this build + initSettings(); + initPorts(); + initOscillators(); + + frequency = vfoA; + setFrequency(vfoA); + updateDisplay(); + + if (btnDown()) + factory_alignment(); +} + + +/** + * The loop checks for keydown, ptt, function button and tuning. + */ + +byte flasher = 0; +void loop(){ + + cwKeyer(); + if (!txCAT) + checkPTT(); + checkButton(); + + //tune only when not tranmsitting + if (!inTx){ + if (ritOn) + doRIT(); + else + doTuning(); + } + + //we check CAT after the encoder as it might put the radio into TX + checkCAT(); +} diff --git a/ubitx_20/ubitx_cat.ino b/ubitx_20/ubitx_cat.ino new file mode 100644 index 0000000..687595c --- /dev/null +++ b/ubitx_20/ubitx_cat.ino @@ -0,0 +1,231 @@ +/** + * The CAT protocol is used by many radios to provide remote control to comptuers through + * the serial port. + * + * This is very much a work in progress. Parts of this code have been liberally + * borrowed from other GPLicensed works like hamlib. + * + * WARNING : This is an unstable version and it has worked with fldigi, + * it gives time out error with WSJTX 1.8.0 + */ + +// The next 4 functions are needed to implement the CAT protocol, which +// uses 4-bit BCD formatting. +// +byte setHighNibble(byte b,byte v) { + // Clear the high nibble + b &= 0x0f; + // Set the high nibble + return b | ((v & 0x0f) << 4); +} + +byte setLowNibble(byte b,byte v) { + // Clear the low nibble + b &= 0xf0; + // Set the low nibble + return b | (v & 0x0f); +} + +byte getHighNibble(byte b) { + return (b >> 4) & 0x0f; +} + +byte getLowNibble(byte b) { + return b & 0x0f; +} + +// Takes a number and produces the requested number of decimal digits, staring +// from the least significant digit. +// +void getDecimalDigits(unsigned long number,byte* result,int digits) { + for (int i = 0; i < digits; i++) { + // "Mask off" (in a decimal sense) the LSD and return it + result[i] = number % 10; + // "Shift right" (in a decimal sense) + number /= 10; + } +} + +// Takes a frequency and writes it into the CAT command buffer in BCD form. +// +void writeFreq(unsigned long freq,byte* cmd) { + // Convert the frequency to a set of decimal digits. We are taking 9 digits + // so that we can get up to 999 MHz. But the protocol doesn't care about the + // LSD (1's place), so we ignore that digit. + byte digits[9]; + getDecimalDigits(freq,digits,9); + // Start from the LSB and get each nibble + cmd[3] = setLowNibble(cmd[3],digits[1]); + cmd[3] = setHighNibble(cmd[3],digits[2]); + cmd[2] = setLowNibble(cmd[2],digits[3]); + cmd[2] = setHighNibble(cmd[2],digits[4]); + cmd[1] = setLowNibble(cmd[1],digits[5]); + cmd[1] = setHighNibble(cmd[1],digits[6]); + cmd[0] = setLowNibble(cmd[0],digits[7]); + cmd[0] = setHighNibble(cmd[0],digits[8]); +} + +// This function takes a frquency that is encoded using 4 bytes of BCD +// representation and turns it into an long measured in Hz. +// +// [12][34][56][78] = 123.45678? Mhz +// +unsigned long readFreq(byte* cmd) { + // Pull off each of the digits + byte d7 = getHighNibble(cmd[0]); + byte d6 = getLowNibble(cmd[0]); + byte d5 = getHighNibble(cmd[1]); + byte d4 = getLowNibble(cmd[1]); + byte d3 = getHighNibble(cmd[2]); + byte d2 = getLowNibble(cmd[2]); + byte d1 = getHighNibble(cmd[3]); + byte d0 = getLowNibble(cmd[3]); + return + (unsigned long)d7 * 100000000L + + (unsigned long)d6 * 10000000L + + (unsigned long)d5 * 1000000L + + (unsigned long)d4 * 100000L + + (unsigned long)d3 * 10000L + + (unsigned long)d2 * 1000L + + (unsigned long)d1 * 100L + + (unsigned long)d0 * 10L; +} + +/** + * Responds to all the cat commands, emulates FT-817 + */ + +void processCATCommand(byte* cmd) { + byte response[5]; + + // Debugging code, enable it to fix the cat implementation + + count++; + if (cmd[4] == 0x00){ + response[0]=0; + Serial.write(response, 1); + } + else if (cmd[4] == 0x01) { + unsigned long f = readFreq(cmd); + setFrequency(f); + updateDisplay(); + //sprintf(b, "set:%ld", f); + //printLine2(b); + + } + // Get frequency + else if (cmd[4] == 0x03){ + writeFreq(frequency,response); // Put the frequency into the buffer + if (isUSB) + response[4] = 0x01; //USB + else + response[4] = 0x00; //LSB + Serial.write(response,5); + printLine2("cat:getfreq"); + } + else if (cmd[4] == 0x07){ // set mode + if (cmd[0] == 0x00 || cmd[0] == 0x03) + isUSB = 0; + else + isUSB = 1; + response[0] = 0x00; + Serial.write(response, 1); + setFrequency(frequency); + //printLine2("cat: mode changed"); + //updateDisplay(); + } + else if (cmd[4] == 0x88){ + if (inTx){ + stopTx(); + txCAT = false; + } + else + response[0] = 0xf0; + printLine2("tx > rx"); + Serial.write(response,1); + } + else if (cmd[4] == 0x08) { // PTT On + if (!inTx) { + response[0] = 0; + txCAT = true; + startTx(TX_SSB); + updateDisplay(); + } else { + response[0] = 0xf0; + } + Serial.write(response,1); + printLine2("rx > tx"); + } + // Read TX keyed state + else if (cmd[4] == 0x10) { + if (!inTx) { + response[0] = 0; + } else { + response[0] = 0xf0; + } + Serial.write(response,1); + printLine2("cat;0x10"); + } + // PTT Off + else if (cmd[4] == 0x88) { + byte resBuf[0]; + if (inTx) { + response[0] = 0; + } else { + response[0] = 0xf0; + } + Serial.write(response,1); + printLine2("cat;0x88"); + //keyed = false; + //digitalWrite(13,LOW); + } + // Read receiver status + else if (cmd[4] == 0xe7) { + response[0] = 0x09; + Serial.write(response,1); + printLine2("cat;0xe7"); + } + else if (cmd[4] == 0xf5){ + + } + // Read receiver status + else if (cmd[4] == 0xf7) { + response[0] = 0x00; + if (inTx) { + response[0] = response[0] | 0xf0; + } + Serial.write(response,1); + printLine2("cat;0xf7"); + } + else { + //somehow, get this to print the four bytes + ultoa(*((unsigned long *)cmd), c, 16); + itoa(cmd[4], b, 16); + strcat(b, ":"); + strcat(b, c); + printLine2(b); + response[0] = 0x00; + Serial.write(response[0]); + } + +} + + + +void checkCAT(){ + static byte cat[5]; + byte i; + + if (Serial.available() < 5) + return; + + cat[4] = cat[3]; + cat[3] = cat[2]; + cat[2] = cat[0]; + for (i = 0; i < 5; i++) + cat[i] = Serial.read(); + + processCATCommand(cat); +} + + diff --git a/ubitx_20/ubitx_factory_alignment.ino b/ubitx_20/ubitx_factory_alignment.ino new file mode 100644 index 0000000..77c5e16 --- /dev/null +++ b/ubitx_20/ubitx_factory_alignment.ino @@ -0,0 +1,87 @@ + +/** + * This procedure is only for those who have a signal generator/transceiver tuned to exactly 7.150 and a dummy load + */ + +void btnWaitForClick(){ + while(!btnDown()) + delay(50); + while(btnDown()) + delay(50); + delay(50); +} + +void factory_alignment(){ + + factoryCalibration(1); + + if (calibration == 0){ + printLine2("Setup Aborted"); + return; + } + + //move it away to 7.160 for an LSB signal + setFrequency(7160000l); + updateDisplay(); + printLine2("#2 BFO"); + delay(1000); + + usbCarrier = 11994999l; + menuSetupCarrier(1); + + if (usbCarrier == 11994999l){ + printLine2("Setup Aborted"); + return; + } + + + printLine2("#3:Test 3.5MHz"); + isUSB = false; + setFrequency(3500000l); + updateDisplay(); + + while (!btnDown()){ + checkPTT(); + delay(100); + } + + btnWaitForClick(); + printLine2("#4:Test 7MHz"); + + setFrequency(7150000l); + updateDisplay(); + while (!btnDown()){ + checkPTT(); + delay(100); + } + + btnWaitForClick(); + printLine2("#5:Test 14MHz"); + + isUSB = true; + setFrequency(14000000l); + updateDisplay(); + while (!btnDown()){ + checkPTT(); + delay(100); + } + + btnWaitForClick(); + printLine2("#6:Test 28MHz"); + + setFrequency(28000000l); + updateDisplay(); + while (!btnDown()){ + checkPTT(); + delay(100); + } + + printLine2("Alignment done"); + delay(1000); + + isUSB = false; + setFrequency(7150000l); + updateDisplay(); + +} + diff --git a/ubitx_20/ubitx_keyer.ino b/ubitx_20/ubitx_keyer.ino new file mode 100644 index 0000000..3a9c86f --- /dev/null +++ b/ubitx_20/ubitx_keyer.ino @@ -0,0 +1,155 @@ +/** + * CW Keyer + * + * The CW keyer handles either a straight key or an iambic / paddle key. + * They all use just one analog input line. This is how it works. + * The analog line has the internal pull-up resistor enabled. + * When a straight key is connected, it shorts the pull-up resistor, analog input is 0 volts + * When a paddle is connected, the dot and the dash are connected to the analog pin through + * a 10K and a 2.2K resistors. These produce a 4v and a 2v input to the analog pins. + * So, the readings are as follows : + * 0v - straight key + * 1-2.5 v - paddle dot + * 2.5 to 4.5 v - paddle dash + * 2.0 to 0.5 v - dot and dash pressed + * + * The keyer is written to transparently handle all these cases + * + * Generating CW + * The CW is cleanly generated by unbalancing the front-end mixer + * and putting the local oscillator directly at the CW transmit frequency. + * The sidetone, generated by the Arduino is injected into the volume control + */ + + +// in milliseconds, this is the parameter that determines how long the tx will hold between cw key downs +#define CW_TIMEOUT (600l) +#define PADDLE_DOT 1 +#define PADDLE_DASH 2 +#define PADDLE_BOTH 3 +#define PADDLE_STRAIGHT 4 + +//we store the last padde's character +//to alternatively send dots and dashes +//when both are simultaneously pressed +char lastPaddle = 0; + + +//reads the analog keyer pin and reports the paddle +byte getPaddle(){ + int paddle = analogRead(ANALOG_KEYER); + + if (paddle > 800) // above 4v is up + return 0; + + if (paddle > 600) // 4-3v is dot + return PADDLE_DASH; + else if (paddle > 300) //1-2v is dash + return PADDLE_DOT; + else if (paddle > 50) + return PADDLE_BOTH; //both are between 1 and 2v + else + return PADDLE_STRAIGHT; //less than 1v is the straight key +} + +/** + * Starts transmitting the carrier with the sidetone + * It assumes that we have called cwTxStart and not called cwTxStop + * each time it is called, the cwTimeOut is pushed further into the future + */ +void cwKeydown(){ + keyDown = 1; //tracks the CW_KEY + tone(CW_TONE, (int)sideTone); + digitalWrite(CW_KEY, 1); + cwTimeout = millis() + CW_TIMEOUT; +} + +/** + * Stops the cw carrier transmission along with the sidetone + * Pushes the cwTimeout further into the future + */ +void cwKeyUp(){ + keyDown = 0; //tracks the CW_KEY + noTone(CW_TONE); + digitalWrite(CW_KEY, 0); + cwTimeout = millis() + CW_TIMEOUT; +} + +/** + * The keyer handles the straight key as well as the iambic key + * This module keeps looping until the user stops sending cw + * if the cwTimeout is set to 0, then it means, we have to exit the keyer loop + * Each time the key is hit the cwTimeout is pushed to a time in the future by cwKeyDown() + */ + +void cwKeyer(){ + byte paddle; + lastPaddle = 0; + + while(1){ + paddle = getPaddle(); + + // do nothing if the paddle has not been touched, unless + // we are in the cw mode and we have timed out + if (!paddle){ + if (0 < cwTimeout && cwTimeout < millis()){ + cwTimeout = 0; + keyDown = 0; + stopTx(); + } + + if (!cwTimeout) + return; + + //if a paddle was used (not a straight key) we should extend the space to be a full dash + //by adding two more dots long space (one has already been added at the end of the dot or dash) + if (cwTimeout > 0 && lastPaddle != PADDLE_STRAIGHT) + delay(cwSpeed * 2); + + // got back to the begining of the loop, if no further activity happens on the paddle or the straight key + // we will time out, and return out of this routine + delay(5); + continue; + } + + Serial.print("paddle:");Serial.println(paddle); + // if we are here, it is only because the key or the paddle is pressed + if (!inTx){ + keyDown = 0; + cwTimeout = millis() + CW_TIMEOUT; + startTx(TX_CW); + updateDisplay(); + } + + // star the transmission) + // we store the transmitted character in the lastPaddle + cwKeydown(); + if (paddle == PADDLE_DOT){ + delay(cwSpeed); + lastPaddle = PADDLE_DOT; + } + else if (paddle == PADDLE_DASH){ + delay(cwSpeed * 3); + lastPaddle = PADDLE_DASH; + } + else if (paddle == PADDLE_BOTH){ //both paddles down + //depending upon what was sent last, send the other + if (lastPaddle == PADDLE_DOT) { + delay(cwSpeed * 3); + lastPaddle = PADDLE_DASH; + }else{ + delay(cwSpeed); + lastPaddle = PADDLE_DOT; + } + } + else if (paddle == PADDLE_STRAIGHT){ + while (getPaddle() == PADDLE_STRAIGHT) + delay(1); + lastPaddle = PADDLE_STRAIGHT; + } + cwKeyUp(); + //introduce a dot long gap between characters if the keyer was used + if (lastPaddle != PADDLE_STRAIGHT) + delay(cwSpeed); + } +} diff --git a/ubitx_20/ubitx_menu.ino b/ubitx_20/ubitx_menu.ino new file mode 100644 index 0000000..fff0378 --- /dev/null +++ b/ubitx_20/ubitx_menu.ino @@ -0,0 +1,572 @@ +/** Menus + * The Radio menus are accessed by tapping on the function button. + * - The main loop() constantly looks for a button press and calls doMenu() when it detects + * a function button press. + * - As the encoder is rotated, at every 10th pulse, the next or the previous menu + * item is displayed. Each menu item is controlled by it's own function. + * - Eache menu function may be called to display itself + * - Each of these menu routines is called with a button parameter. + * - The btn flag denotes if the menu itme was clicked on or not. + * - If the menu item is clicked on, then it is selected, + * - If the menu item is NOT clicked on, then the menu's prompt is to be displayed + */ + + + +int menuBand(int btn){ + int knob = 0; + int band; + unsigned long offset; + + // band = frequency/1000000l; + // offset = frequency % 1000000l; + + if (!btn){ + printLine2("Band Select?"); + return; + } + + printLine2("Press to confirm"); + //wait for the button menu select button to be lifted) + while (btnDown()) + delay(50); + delay(50); + ritDisable(); + + while(!btnDown()){ + + knob = enc_read(); + if (knob != 0){ + /* + if (band > 3 && knob < 0) + band--; + if (band < 30 && knob > 0) + band++; + if (band > 10) + isUSB = true; + else + isUSB = false; + setFrequency(((unsigned long)band * 1000000l) + offset); */ + if (knob < 0 && frequency > 3000000l) + setFrequency(frequency - 200000l); + if (knob > 0 && frequency < 30000000l) + setFrequency(frequency + 200000l); + if (frequency > 10000000l) + isUSB = true; + else + isUSB = false; + updateDisplay(); + } + delay(20); + } + + while(btnDown()) + delay(50); + delay(50); + + printLine2(""); + updateDisplay(); + menuOn = 0; +} + +void menuVfoToggle(int btn){ + + if (!btn){ + if (vfoActive == VFO_A) + printLine2("Select VFO B? "); + else + printLine2("Select VFO A? "); + } + else { + if (vfoActive == VFO_B){ + vfoB = frequency; + EEPROM.put(VFO_B, frequency); + vfoActive = VFO_A; + printLine2("Selected VFO A "); + frequency = vfoA; + } + else { + vfoA = frequency; + EEPROM.put(VFO_A, frequency); + vfoActive = VFO_B; + printLine2("Selected VFO B "); + frequency = vfoB; + } + + ritDisable(); + setFrequency(frequency); + if (frequency >= 10000000l) + isUSB = true; + else + isUSB = false; + updateDisplay(); + printLine2(""); + delay(1000); + //exit the menu + menuOn = 0; + } +} + +void menuRitToggle(int btn){ + if (!btn){ + if (ritOn == 1) + printLine2("RIT:On, Off? "); + else + printLine2("RIT:Off, On? "); + } + else { + if (ritOn == 0){ + printLine2("RIT is ON"); + //enable RIT so the current frequency is used at transmit + ritEnable(frequency); + } + else{ + printLine2("RIT is OFF"); + ritDisable(); + } + menuOn = 0; + delay(500); + printLine2(""); + updateDisplay(); + } +} + +void menuSidebandToggle(int btn){ + if (!btn){ + if (isUSB == true) + printLine2("Select LSB?"); + else + printLine2("Select USB?"); + } + else { + if (isUSB == true){ + isUSB = false; + printLine2("LSB Selected"); + delay(500); + printLine2(""); + } + else { + isUSB = true; + printLine2("USB Selected"); + delay(500); + printLine2(""); + } + + updateDisplay(); + menuOn = 0; + } +} + +/** + * The calibration routines are not normally shown in the menu as they are rarely used + * They can be enabled by choosing this menu option + */ +void menuSetup(int btn){ + if (!btn){ + if (!modeCalibrate) + printLine2("Setup On?"); + else + printLine2("Setup Off?"); + }else { + if (!modeCalibrate){ + modeCalibrate = true; + printLine2("Setup:On "); + } + else { + modeCalibrate = false; + printLine2("Setup:Off "); + } + delay(2000); + printLine2(""); + menuOn = 0; + } +} + +void menuExit(int btn){ + + if (!btn){ + printLine2("Exit Menu? "); + } + else{ + printLine2("Exiting menu"); + delay(300); + printLine2(""); + updateDisplay(); + menuOn = 0; + } +} + +int menuCWSpeed(int btn){ + int knob = 0; + int wpm; + + wpm = 1200/cwSpeed; + + if (!btn){ + strcpy(b, "CW:"); + itoa(wpm,c, 10); + strcat(b, c); + strcat(b, "WPM Change?"); + printLine2(b); + return; + } + + printLine1("Press PTT to set"); + strcpy(b, "WPM:"); + itoa(wpm,c, 10); + strcat(b, c); + printLine2(b); + delay(300); + + while(!btnDown() && digitalRead(PTT) == HIGH){ + + knob = enc_read(); + if (knob != 0){ + if (wpm > 3 && knob < 0) + wpm--; + if (wpm < 50 && knob > 0) + wpm++; + + strcpy(b, "WPM:"); + itoa(wpm,c, 10); + strcat(b, c); + printLine2(b); + } + //abort if this button is down + if (btnDown()) + //re-enable the clock1 and clock 2 + break; + } + + //save the setting + if (digitalRead(PTT) == LOW){ + printLine2("CW Speed set!"); + cwSpeed = 1200/wpm; + EEPROM.put(CW_SPEED, cwSpeed); + delay(2000); + } + printLine2(""); + menuOn = 0; +} + + + +/** + * Take a deep breath, math(ematics) ahead + * The 25 mhz oscillator is multiplied by 35 to run the vco at 875 mhz + * This is divided by a number to generate different frequencies. + * If we divide it by 875, we will get 1 mhz signal + * So, if the vco is shifted up by 875 hz, the generated frequency of 1 mhz is shifted by 1 hz (875/875) + * At 12 Mhz, the carrier will needed to be shifted down by 12 hz for every 875 hz of shift up of the vco + * + */ + + //this is used by the si5351 routines in the ubitx_5351 file +extern int32_t calibration; +extern uint32_t si5351bx_vcoa; + +int factoryCalibration(int btn){ + int knob = 0; + int32_t prev_calibration; + + + //keep clear of any previous button press + while (btnDown()) + delay(100); + delay(100); + + if (!btn){ + printLine2("Set Calibration?"); + return 0; + } + + prev_calibration = calibration; + calibration = 0; + + isUSB = true; + + //turn off the second local oscillator and the bfo + si5351_set_calibration(calibration); + startTx(TX_CW); + si5351bx_setfreq(2, 10000000l); + + strcpy(b, "#1 10 MHz cal:"); + ltoa(calibration/8750, c, 10); + strcat(b, c); + printLine2(b); + + while (!btnDown()) + { + + if (digitalRead(PTT) == LOW && !keyDown) + cwKeydown(); + if (digitalRead(PTT) == HIGH && keyDown) + cwKeyUp(); + + knob = enc_read(); + + if (knob > 0) + calibration += 875; + else if (knob < 0) + calibration -= 875; + else + continue; //don't update the frequency or the display + + si5351_set_calibration(calibration); + si5351bx_setfreq(2, 10000000l); + strcpy(b, "#1 10 MHz cal:"); + ltoa(calibration/8750, c, 10); + strcat(b, c); + printLine2(b); + } + + cwTimeout = 0; + keyDown = 0; + stopTx(); + + printLine2("Calibration set!"); + EEPROM.put(MASTER_CAL, calibration); + initOscillators(); + setFrequency(frequency); + updateDisplay(); + + while(btnDown()) + delay(50); + delay(100); +} + +int menuSetupCalibration(int btn){ + int knob = 0; + int32_t prev_calibration; + + if (!btn){ + printLine2("Set Calibration?"); + return 0; + } + + printLine1("Set to Zero-beat,"); + printLine2("press PTT to save"); + delay(1000); + + prev_calibration = calibration; + calibration = 0; + si5351_set_calibration(calibration); + setFrequency(frequency); + + strcpy(b, "cal:"); + ltoa(calibration/8750, c, 10); + strcat(b, c); + printLine2(b); + + while (digitalRead(PTT) == HIGH && !btnDown()) + { + knob = enc_read(); + + if (knob > 0){ + calibration += 8750; + usbCarrier += 120; + } + else if (knob < 0){ + calibration -= 8750; + usbCarrier -= 120; + } + else + continue; //don't update the frequency or the display + + si5351_set_calibration(calibration); + si5351bx_setfreq(0, usbCarrier); + setFrequency(frequency); + + strcpy(b, "cal:"); + ltoa(calibration/8750, c, 10); + strcat(b, c); + printLine2(b); + } + + //save the setting + if (digitalRead(PTT) == LOW){ + printLine1("Calibration set!"); + printLine2("Set Carrier now"); + EEPROM.put(MASTER_CAL, calibration); + delay(2000); + } + else + calibration = prev_calibration; + + printLine2(""); + initOscillators(); + //si5351_set_calibration(calibration); + setFrequency(frequency); + updateDisplay(); + menuOn = 0; +} + + +void printCarrierFreq(unsigned long freq){ + + memset(c, 0, sizeof(c)); + memset(b, 0, sizeof(b)); + + ultoa(freq, b, DEC); + + strncat(c, b, 2); + strcat(c, "."); + strncat(c, &b[2], 3); + strcat(c, "."); + strncat(c, &b[5], 1); + printLine2(c); +} + +void menuSetupCarrier(int btn){ + int knob = 0; + unsigned long prevCarrier; + + if (!btn){ + printLine2("Set the BFO"); + return; + } + + prevCarrier = usbCarrier; + printLine1("Tune to best Signal"); + printLine2("PTT to confirm. "); + delay(1000); + + usbCarrier = 11995000l; + si5351bx_setfreq(0, usbCarrier); + printCarrierFreq(usbCarrier); + + //disable all clock 1 and clock 2 + while (digitalRead(PTT) == HIGH && !btnDown()) + { + knob = enc_read(); + + if (knob > 0) + usbCarrier -= 50; + else if (knob < 0) + usbCarrier += 50; + else + continue; //don't update the frequency or the display + + si5351bx_setfreq(0, usbCarrier); + printCarrierFreq(usbCarrier); + + delay(100); + } + + //save the setting + if (digitalRead(PTT) == LOW){ + printLine2("Carrier set! "); + EEPROM.put(USB_CAL, usbCarrier); + delay(1000); + } + else + usbCarrier = prevCarrier; + + si5351bx_setfreq(0, usbCarrier); + setFrequency(frequency); + updateDisplay(); + printLine2(""); + menuOn = 0; +} + +void menuSetupCwTone(int btn){ + int knob = 0; + int prev_sideTone; + + if (!btn){ + printLine2("Change CW Tone"); + return; + } + + prev_sideTone = sideTone; + printLine1("Tune CW tone"); + printLine2("PTT to confirm. "); + delay(1000); + tone(CW_TONE, sideTone); + + //disable all clock 1 and clock 2 + while (digitalRead(PTT) == LOW || !btnDown()) + { + knob = enc_read(); + + if (knob > 0 && sideTone < 2000) + sideTone += 10; + else if (knob < 0 && sideTone > 100 ) + sideTone -= 10; + else + continue; //don't update the frequency or the display + + tone(CW_TONE, sideTone); + itoa(sideTone, b, 10); + printLine2(b); + + delay(100); + } + noTone(CW_TONE); + //save the setting + if (digitalRead(PTT) == LOW){ + printLine2("Sidetone set! "); + EEPROM.put(CW_SIDETONE, usbCarrier); + delay(2000); + } + else + sideTone = prev_sideTone; + + printLine2(""); + updateDisplay(); + menuOn = 0; + } + +void doMenu(){ + int select=0, i,btnState; + + //wait for the button to be raised up + while(btnDown()) + delay(50); + delay(50); //debounce + + menuOn = 2; + + while (menuOn){ + i = enc_read(); + btnState = btnDown(); + + if (i > 0){ + if (modeCalibrate && select + i < 110) + select += i; + if (!modeCalibrate && select + i < 70) + select += i; + } + if (i < 0 && select - i >= 0) + select += i; //caught ya, i is already -ve here, so you add it + + if (select < 10) + menuBand(btnState); + else if (select < 20) + menuRitToggle(btnState); + else if (select < 30) + menuVfoToggle(btnState); + else if (select < 40) + menuSidebandToggle(btnState); + else if (select < 50) + menuCWSpeed(btnState); + else if (select < 60) + menuSetup(btnState); + else if (select < 70 && !modeCalibrate) + menuExit(btnState); + else if (select < 80 && modeCalibrate) + menuSetupCalibration(btnState); //crystal + else if (select < 90 && modeCalibrate) + menuSetupCarrier(btnState); //lsb + else if (select < 100 && modeCalibrate) + menuSetupCwTone(btnState); + else if (select < 110 && modeCalibrate) + menuExit(btnState); + } + + //debounce the button + while(btnDown()) + delay(50); + delay(50); +} + diff --git a/ubitx_20/ubitx_si5351.ino b/ubitx_20/ubitx_si5351.ino new file mode 100644 index 0000000..a5d3ed4 --- /dev/null +++ b/ubitx_20/ubitx_si5351.ino @@ -0,0 +1,116 @@ +// ************* SI5315 routines - tks Jerry Gaffke, KE7ER *********************** + +// An minimalist standalone set of Si5351 routines. +// VCOA is fixed at 875mhz, VCOB not used. +// The output msynth dividers are used to generate 3 independent clocks +// with 1hz resolution to any frequency between 4khz and 109mhz. + +// Usage: +// Call si5351bx_init() once at startup with no args; +// Call si5351bx_setfreq(clknum, freq) each time one of the +// three output CLK pins is to be updated to a new frequency. +// A freq of 0 serves to shut down that output clock. + +// The global variable si5351bx_vcoa starts out equal to the nominal VCOA +// frequency of 25mhz*35 = 875000000 Hz. To correct for 25mhz crystal errors, +// the user can adjust this value. The vco frequency will not change but +// the number used for the (a+b/c) output msynth calculations is affected. +// Example: We call for a 5mhz signal, but it measures to be 5.001mhz. +// So the actual vcoa frequency is 875mhz*5.001/5.000 = 875175000 Hz, +// To correct for this error: si5351bx_vcoa=875175000; + +// Most users will never need to generate clocks below 500khz. +// But it is possible to do so by loading a value between 0 and 7 into +// the global variable si5351bx_rdiv, be sure to return it to a value of 0 +// before setting some other CLK output pin. The affected clock will be +// divided down by a power of two defined by 2**si5351_rdiv +// A value of zero gives a divide factor of 1, a value of 7 divides by 128. +// This lightweight method is a reasonable compromise for a seldom used feature. + + +#define BB0(x) ((uint8_t)x) // Bust int32 into Bytes +#define BB1(x) ((uint8_t)(x>>8)) +#define BB2(x) ((uint8_t)(x>>16)) + +#define SI5351BX_ADDR 0x60 // I2C address of Si5351 (typical) +#define SI5351BX_XTALPF 2 // 1:6pf 2:8pf 3:10pf + +// If using 27mhz crystal, set XTAL=27000000, MSA=33. Then vco=891mhz +#define SI5351BX_XTAL 25000000 // Crystal freq in Hz +#define SI5351BX_MSA 35 // VCOA is at 25mhz*35 = 875mhz + +// User program may have reason to poke new values into these 3 RAM variables +uint32_t si5351bx_vcoa = (SI5351BX_XTAL*SI5351BX_MSA); // 25mhzXtal calibrate +uint8_t si5351bx_rdiv = 0; // 0-7, CLK pin sees fout/(2**rdiv) +uint8_t si5351bx_drive[3] = {1, 1, 1}; // 0=2ma 1=4ma 2=6ma 3=8ma for CLK 0,1,2 +uint8_t si5351bx_clken = 0xFF; // Private, all CLK output drivers off +int32_t calibration = 0; + +void i2cWrite(uint8_t reg, uint8_t val) { // write reg via i2c + Wire.beginTransmission(SI5351BX_ADDR); + Wire.write(reg); + Wire.write(val); + Wire.endTransmission(); +} + +void i2cWriten(uint8_t reg, uint8_t *vals, uint8_t vcnt) { // write array + Wire.beginTransmission(SI5351BX_ADDR); + Wire.write(reg); + while (vcnt--) Wire.write(*vals++); + Wire.endTransmission(); +} + + +void si5351bx_init() { // Call once at power-up, start PLLA + uint8_t reg; uint32_t msxp1; + Wire.begin(); + i2cWrite(149, 0); // SpreadSpectrum off + i2cWrite(3, si5351bx_clken); // Disable all CLK output drivers + i2cWrite(183, SI5351BX_XTALPF << 6); // Set 25mhz crystal load capacitance + msxp1 = 128 * SI5351BX_MSA - 512; // and msxp2=0, msxp3=1, not fractional + uint8_t vals[8] = {0, 1, BB2(msxp1), BB1(msxp1), BB0(msxp1), 0, 0, 0}; + i2cWriten(26, vals, 8); // Write to 8 PLLA msynth regs + i2cWrite(177, 0x20); // Reset PLLA (0x80 resets PLLB) + // for (reg=16; reg<=23; reg++) i2cWrite(reg, 0x80); // Powerdown CLK's + // i2cWrite(187, 0); // No fannout of clkin, xtal, ms0, ms4 +} + +void si5351bx_setfreq(uint8_t clknum, uint32_t fout) { // Set a CLK to fout Hz + uint32_t msa, msb, msc, msxp1, msxp2, msxp3p2top; + if ((fout < 500000) || (fout > 109000000)) // If clock freq out of range + si5351bx_clken |= 1 << clknum; // shut down the clock + else { + msa = si5351bx_vcoa / fout; // Integer part of vco/fout + msb = si5351bx_vcoa % fout; // Fractional part of vco/fout + msc = fout; // Divide by 2 till fits in reg + while (msc & 0xfff00000) { + msb = msb >> 1; + msc = msc >> 1; + } + msxp1 = (128 * msa + 128 * msb / msc - 512) | (((uint32_t)si5351bx_rdiv) << 20); + msxp2 = 128 * msb - 128 * msb / msc * msc; // msxp3 == msc; + msxp3p2top = (((msc & 0x0F0000) << 4) | msxp2); // 2 top nibbles + uint8_t vals[8] = { BB1(msc), BB0(msc), BB2(msxp1), BB1(msxp1), + BB0(msxp1), BB2(msxp3p2top), BB1(msxp2), BB0(msxp2) + }; + i2cWriten(42 + (clknum * 8), vals, 8); // Write to 8 msynth regs + i2cWrite(16 + clknum, 0x0C | si5351bx_drive[clknum]); // use local msynth + si5351bx_clken &= ~(1 << clknum); // Clear bit to enable clock + } + i2cWrite(3, si5351bx_clken); // Enable/disable clock +} + +void si5351_set_calibration(int32_t cal){ + si5351bx_vcoa = (SI5351BX_XTAL * SI5351BX_MSA) + cal; // apply the calibration correction factor + si5351bx_setfreq(0, usbCarrier); +} + +void initOscillators(){ + //initialize the SI5351 + si5351bx_init(); + si5351bx_vcoa = (SI5351BX_XTAL * SI5351BX_MSA) + calibration; // apply the calibration correction factor + si5351bx_setfreq(0, usbCarrier); +} + + + diff --git a/ubitx_20/ubitx_ui.ino b/ubitx_20/ubitx_ui.ino new file mode 100644 index 0000000..dbf513b --- /dev/null +++ b/ubitx_20/ubitx_ui.ino @@ -0,0 +1,230 @@ +/** + * The user interface of the ubitx consists of the encoder, the push-button on top of it + * and the 16x2 LCD display. + * The upper line of the display is constantly used to display frequency and status + * of the radio. Occasionally, it is used to provide a two-line information that is + * quickly cleared up. + */ + +//returns true if the button is pressed +int btnDown(){ + if (digitalRead(FBUTTON) == HIGH) + return 0; + else + return 1; +} + +/** + * Meter (not used in this build for anything) + * the meter is drawn using special characters. Each character is composed of 5 x 8 matrix. + * The s_meter array holds the definition of the these characters. + * each line of the array is is one character such that 5 bits of every byte + * makes up one line of pixels of the that character (only 5 bits are used) + * The current reading of the meter is assembled in the string called meter + */ + +char meter[17]; + +byte s_meter_bitmap[] = { + B00000,B00000,B00000,B00000,B00000,B00100,B00100,B11011, + B10000,B10000,B10000,B10000,B10100,B10100,B10100,B11011, + B01000,B01000,B01000,B01000,B01100,B01100,B01100,B11011, + B00100,B00100,B00100,B00100,B00100,B00100,B00100,B11011, + B00010,B00010,B00010,B00010,B00110,B00110,B00110,B11011, + B00001,B00001,B00001,B00001,B00101,B00101,B00101,B11011 +}; + + + +// initializes the custom characters +// we start from char 1 as char 0 terminates the string! +void initMeter(){ + lcd.createChar(1, s_meter_bitmap); + lcd.createChar(2, s_meter_bitmap + 8); + lcd.createChar(3, s_meter_bitmap + 16); + lcd.createChar(4, s_meter_bitmap + 24); + lcd.createChar(5, s_meter_bitmap + 32); + lcd.createChar(6, s_meter_bitmap + 40); +} + +/** + * The meter is drawn with special characters. + * character 1 is used to simple draw the blocks of the scale of the meter + * characters 2 to 6 are used to draw the needle in positions 1 to within the block + * This displays a meter from 0 to 100, -1 displays nothing + */ +void drawMeter(int8_t needle){ + int16_t best, i, s; + + if (needle < 0) + return; + + s = (needle * 4)/10; + for (i = 0; i < 8; i++){ + if (s >= 5) + meter[i] = 1; + else if (s >= 0) + meter[i] = 2 + s; + else + meter[i] = 1; + s = s - 5; + } + if (needle >= 40) + meter[i-1] = 6; + meter[i] = 0; +} + +// The generic routine to display one line on the LCD +void printLine(char linenmbr, char *c) { + if (strcmp(c, printBuff[linenmbr])) { // only refresh the display when there was a change + lcd.setCursor(0, linenmbr); // place the cursor at the beginning of the selected line + lcd.print(c); + strcpy(printBuff[linenmbr], c); + + for (byte i = strlen(c); i < 16; i++) { // add white spaces until the end of the 16 characters line is reached + lcd.print(' '); + } + } +} + +// short cut to print to the first line +void printLine1(char *c){ + printLine(1,c); +} +// short cut to print to the first line +void printLine2(char *c){ + printLine(0,c); +} + +// this builds up the top line of the display with frequency and mode +void updateDisplay() { + // tks Jack Purdum W8TEE + // replaced fsprint commmands by str commands for code size reduction + + memset(c, 0, sizeof(c)); + memset(b, 0, sizeof(b)); + + ultoa(frequency, b, DEC); + + if (inTx){ + if (cwTimeout > 0) + strcpy(c, " CW:"); + else + strcpy(c, " TX:"); + } + else { + if (ritOn) + strcpy(c, "RIT "); + else { + if (isUSB) + strcpy(c, "USB "); + else + strcpy(c, "LSB "); + } + if (vfoActive == VFO_A) // VFO A is active + strcat(c, "A:"); + else + strcat(c, "B:"); + } + + + + //one mhz digit if less than 10 M, two digits if more + if (frequency < 10000000l){ + c[6] = ' '; + c[7] = b[0]; + strcat(c, "."); + strncat(c, &b[1], 3); + strcat(c, "."); + strncat(c, &b[4], 3); + } + else { + strncat(c, b, 2); + strcat(c, "."); + strncat(c, &b[2], 3); + strcat(c, "."); + strncat(c, &b[5], 3); + } + + if (inTx) + strcat(c, " TX"); + printLine(1, c); + +/* + //now, the second line + memset(c, 0, sizeof(c)); + memset(b, 0, sizeof(b)); + + if (inTx) + strcat(c, "TX "); + else if (ritOn) + strcpy(c, "RIT"); + + strcpy(c, " \xff"); + drawMeter(meter_reading); + strcat(c, meter); + strcat(c, "\xff"); + printLine2(c);*/ +} + +int enc_prev_state = 3; + +/** + * The A7 And A6 are purely analog lines on the Arduino Nano + * These need to be pulled up externally using two 10 K resistors + * + * There are excellent pages on the Internet about how these encoders work + * and how they should be used. We have elected to use the simplest way + * to use these encoders without the complexity of interrupts etc to + * keep it understandable. + * + * The enc_state returns a two-bit number such that each bit reflects the current + * value of each of the two phases of the encoder + * + * The enc_read returns the number of net pulses counted over 50 msecs. + * If the puluses are -ve, they were anti-clockwise, if they are +ve, the + * were in the clockwise directions. Higher the pulses, greater the speed + * at which the enccoder was spun + */ + +byte enc_state (void) { + return (analogRead(ENC_A) > 500 ? 1 : 0) + (analogRead(ENC_B) > 500 ? 2: 0); +} + +int enc_read(void) { + int result = 0; + byte newState; + int enc_speed = 0; + + long stop_by = millis() + 50; + + while (millis() < stop_by) { // check if the previous state was stable + newState = enc_state(); // Get current state + + if (newState != enc_prev_state) + delay (1); + + if (enc_state() != newState || newState == enc_prev_state) + continue; + //these transitions point to the encoder being rotated anti-clockwise + if ((enc_prev_state == 0 && newState == 2) || + (enc_prev_state == 2 && newState == 3) || + (enc_prev_state == 3 && newState == 1) || + (enc_prev_state == 1 && newState == 0)){ + result--; + } + //these transitions point o the enccoder being rotated clockwise + if ((enc_prev_state == 0 && newState == 1) || + (enc_prev_state == 1 && newState == 3) || + (enc_prev_state == 3 && newState == 2) || + (enc_prev_state == 2 && newState == 0)){ + result++; + } + enc_prev_state = newState; // Record state for next pulse interpretation + enc_speed++; + delay(1); + } + return(result); +} + + diff --git a/ubitx_wiring.png b/ubitx_wiring.png new file mode 100644 index 0000000..49f903e Binary files /dev/null and b/ubitx_wiring.png differ diff --git a/ubitxv3.pdf b/ubitxv3.pdf new file mode 100644 index 0000000..9b7990e Binary files /dev/null and b/ubitxv3.pdf differ