Create a gist now

Instantly share code, notes, and snippets.

Arduino sketch for the Enhanced Kanga VFO, handling 20 * 4 LCD
/*
Kanga_VFO_3
Enhanced VFO System for the Arduino
with AD9850 DDS Sig Gen Module
mounted in the Kanga DDS Shield
and a LCD Display on the I2C Interface
Handles either 16 * 4 or 20 * 4 LCD format
m0xpd
August 2014
All parts available from Kanga-UK:
http://www.kanga-products.co.uk/
For further information:
http://m0xpd.blogspot.co.uk/
=======================================================
*/
#include<stdlib.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
/*=====================================================
Frequency defaults...
The system is set up to use the
136kHz, 160m, 80m, 60m, 40m, 30m, 20m, 17m, 15m, 12m
and 10m amateur bands
PLUS a continuous "signal generator" setting
You can edit the band edges and the default "start"
frequency in any band by changing the following lines... */
long BandBases[]={
135700L, 1810000L, 3500000L, 5258500L, 7000000L, 10100000L, 14000000L, 18068000L, 21000000L, 24890000L, 28000000L, 0L};
long BandTops[]={
137800L, 2000000L, 3800000L, 5406500L, 7200000L, 10150000L, 14350000L, 18168000L, 21450000L, 24990000L, 29700000L, 30000000L};
long BandCWFreqs[]={
135700L, 1836000L, 3560000L, 5258500L, 7030000L, 10106000L, 14060000L, 18086000L, 21060000L, 24906000L, 28060000L, 1000000L};
/*=====================================================
Modes and Offsets...
The system offers the following "modes"...
CW, CW(Reverse), AM, LSB, USB
each of which has its own receive offset
You can adjust the receive offset continuously
(to achieve "Receive Incremental Tuning" or "Clarifier" operation)
and you can change the offset defaults by editing the lines below... */
int ModeOffsets[]={
600, -600, 0, 0, 0};
/*=====================================================
Intermediate Frequency Offsets...
You can use offsets on Transmit and Receive
(e.g. to accomodate an IF stage)
which presently are set to zero -
but you can change them by editing the lines below... */
double RxOffset = 0;
double TxOffset = 0;
/*======================================================================
Defining CONSTANTS...
*/
//======================================
// HARDWARE: Arduino I/O Pin allocations..
// set pin numbers:
// AD9850 Module....
const int W_CLK = 2;
const int FQ_UD = 3;
const int DATA = 4;
const int RESET = 5;
// Rotary Encoder...
const int RotEncAPin = 11;
const int RotEncBPin = 12;
const int RotEncSwPin = A3;
// Pushbuttons...
const int modeSw1 = A1;
const int modeSw3 = A2;
// Transmit Input...
const int TxPin = 8;
//======================================
// Display...
// the display uses the I2C connection,
// which uses
// A4 for the Clock and
// A5 for the Data
// on the UNO etc
// (MEGA has dedicated I2C pins)
//
// Set your LCD Width here...
int lcdWidth=20;
//======================================
// SOFTWARE: defining other constants...
const int nMenus = 0x03;
byte nMenuOptions[] = {
1, 1, 4, 11};
char* MenuString[4][12]= {
{
"RiT: ", "", "", "", "", "", "", "" }
,
{
"VFO A ", "VFO B ", "", "", "", "", "", "" }
,
{
"CW ", "CWR ", "AM ", "LSB ", "USB ", "", "", "" }
,
{
"136k", "160m", " 80m", " 60m", " 40m", " 30m", " 20m", " 17m", " 15m", " 12m", " 10m", "Cont" }
};
// scaling factors for freq adjustment
const long deltaf[] = {
1L, 10L, 100L, 1000L, 10000L, 100000L, 0L, 1000000L};
//Marketing !
char* Banner=" KANGA-UK ";
// end of Constants
//=================================================================
/*=================================================================
Declare (and initialize) some VARIABLES...
*/
boolean MenuMode = false;
boolean Transmit=false;
char* TxChar;
int OffsetSign=0;
char OffsetChar='=';
char VFOChar='A';
byte MenuOption[] = {
0, 0, 0, 4, 0, 0};
byte oldVFO=MenuOption[1];
int RiT=ModeOffsets[MenuOption[2]];
long freq = BandCWFreqs[MenuOption[3]]; // try re-declaring freq as long instead of double to cure 1 Hz resolution
long freqA=freq;
long freqB=freq;
int dfindex = 3;
// Declare and initialise some Rotary Encoder Variables...
boolean OldRotEncA = true;
boolean RotEncA = true;
boolean RotEncB = true;
boolean RotEncSw = true;
boolean modeSw1State = HIGH;
boolean modeSw3State = HIGH;
boolean TxPinState = HIGH;
int Menu = 0;
int L2=17;
int BannerPos=0;
// Display positions...
byte ModePos=16;
byte TxPos=20;
byte RiTSignPos=23;
byte VFOPos=26;
byte BandPos=28;
// end of Variables
//=================================================================
// Instantiate the LCD display...
LiquidCrystal_I2C lcd(0x20,32,2);
// (I2C address = 0x20)
// (I can't make it work as a 16*4 display -
// only as a 32*2 display !! )
// If it's a 20*4, this will still work
void setup() {
lcd.init();
delay(200);
lcd.init(); // belt 'n braces !
lcd.blink();
// manage LCD width:
if (lcdWidth==20){
lcd.backlight();
L2=23;
ModePos=20;
TxPos=25;
RiTSignPos=29;
VFOPos=33;
BandPos=36;
BannerPos=2;
}
// Set up I/O pins...
pinMode(FQ_UD, OUTPUT);
pinMode(W_CLK, OUTPUT);
pinMode(DATA, OUTPUT);
pinMode(RESET, OUTPUT);
pinMode(modeSw1,INPUT);
pinMode(modeSw3,INPUT);
pinMode(TxPin,INPUT);
pinMode(RotEncAPin, INPUT);
pinMode(RotEncBPin, INPUT);
pinMode(RotEncSwPin, INPUT);
// set up pull-up resistors on inputs...
digitalWrite(modeSw1,HIGH);
digitalWrite(modeSw3,HIGH);
digitalWrite(TxPin,HIGH);
digitalWrite(RotEncAPin,HIGH);
digitalWrite(RotEncBPin,HIGH);
digitalWrite(RotEncSwPin,HIGH);
// Print opening message to the LCD...
Normal_Display();
// start up the DDS...
pulseHigh(RESET);
pulseHigh(W_CLK);
pulseHigh(FQ_UD);
// start the oscillator...
sendFrequency(freq,Transmit);
}
void loop() {
// First, we check for Transmit...
TxPinState = digitalRead(TxPin);
if (TxPinState==0){
if (Transmit==false){
Transmit=true;
sendFrequency(freq,Transmit);
setTxDisplay();
}
}
else{
// No :- we're in "Receive"
if (Transmit==true){
Transmit=false;
sendFrequency(freq,Transmit);
setTxDisplay();
}
// Read the inputs...
RotEncA = digitalRead(RotEncAPin);
RotEncB = digitalRead(RotEncBPin);
RotEncSw = digitalRead(RotEncSwPin);
modeSw1State=digitalRead(modeSw1);
modeSw3State=digitalRead(modeSw3);
if (MenuMode){
// Here we're in "Menu" mode...
if (Menu==0){
// We're in Menu 0, so read the RiT...
if ((RotEncA == HIGH)&&(OldRotEncA == LOW)){
if (RotEncB == LOW) {
RiT=constrain(RiT+1,-100000,10000);
}
else {
RiT=constrain(RiT-1,-100000,10000);
}
LCD_Display_RiT(RiT);
sendFrequency(freq,Transmit);
if (RiT>ModeOffsets[MenuOption[2]]) {
OffsetSign=1;
}
else{
if (RiT==ModeOffsets[MenuOption[2]]){
OffsetSign=0;
}
else{
OffsetSign=-1;
}
}
}
OldRotEncA=RotEncA;
}
// we're not in menu 0, so manage the menu options
// for all other menus...
if ((RotEncA == HIGH)&&(OldRotEncA == LOW)){
if (RotEncB == LOW) {
MenuOption[Menu]=constrain(MenuOption[Menu]+1,0,nMenuOptions[Menu]);
LCD_String_Display(7, 0, (MenuString[Menu][MenuOption[Menu]]));
}
else {
MenuOption[Menu]=constrain(MenuOption[Menu]-1,0,nMenuOptions[Menu]);
LCD_String_Display(7, 0, (MenuString[Menu][MenuOption[Menu]]));
}
}
OldRotEncA=RotEncA;
if (modeSw1State==LOW){
// decrement Menu
Menu=constrain(Menu-1,0,nMenus);
LCD_Int_Display(5, 0, Menu);
LCD_String_Display(7, 0, (MenuString[Menu][MenuOption[Menu]]));
delay(500);
}
if (modeSw3State==LOW){
// Increment Menu
Menu=constrain(Menu+1,0,nMenus);
LCD_Int_Display(5, 0, Menu);
LCD_String_Display(7, 0, (MenuString[Menu][MenuOption[Menu]]));
delay(500);
}
}
// End of MenuMode==1
// ========================================================================
// Beginning of normal mode (MenuMode==0)...
else{
if ((RotEncA == HIGH)&&(OldRotEncA == LOW)){
// adjust frequency
if (RotEncB == LOW) {
freq=freq+deltaf[dfindex];
freq=constrain(freq,BandBases[MenuOption[3]],BandTops[MenuOption[3]]);
}
else {
freq=freq-deltaf[dfindex];
freq=constrain(freq,BandBases[MenuOption[3]],BandTops[MenuOption[3]]);
}
LCD_Display_Freq(freq);
sendFrequency(freq,Transmit);
OldRotEncA=RotEncA;
}
if (modeSw1State==LOW){
// point to a higher digit
dfindex=constrain(dfindex+1,0,7);
LCD_Display_Freq(freq);
delay(500);
}
if (modeSw3State==LOW){
// point to a lower digit
dfindex=constrain(dfindex-1,0,7);
LCD_Display_Freq(freq);
delay(500);
}
// End of Normal Mode
}
if (RotEncSw==LOW){
// Toggle in and out of menu mode
if (MenuMode == false){
// enter menu mode
MenuMode = true;
LCD_String_Display(0, 0, "Menu ");
LCD_Int_Display(5, 0, Menu);
LCD_String_Display(7, 0, (MenuString[Menu][MenuOption[Menu]]));
}
else{
// leave menu mode
MenuMode = false;
//====================
// specific actions on leaving menus...
switch (Menu){
// Leaving RiT adjustment
case 0:
switch (OffsetSign) {
case 1:
OffsetChar='+';
break;
case -1:
OffsetChar='-';
break;
default:
OffsetChar='=';
}
break;
// Leaving VFO swap
case 1:
if(oldVFO==0){
freqA=freq;
if (MenuOption[1]==1){
freq=freqB;
}
}
else{
freqB=freq;
if (MenuOption[1]==0){
freq=freqA;
}
}
oldVFO=MenuOption[1];
break;
case 2:
// leaving mode change
RiT=ModeOffsets[MenuOption[2]];
OffsetChar='=';
break;
case 3:
// Leaving Band Change
freq=BandCWFreqs[MenuOption[3]];
freqA=freq;
freqB=freq;
MenuOption[1]=0;
}
//====================
//
sendFrequency(freq,Transmit);
Normal_Display();
}
delay(500);
// End of Toggle In and Out of Menu Mode
}
OldRotEncA=RotEncA;
// End of Receive Mode
}
// End of loop()
}
//==============================================================
// SUBROUTINES...
// new subroutine to display the frequency...
void LCD_Display_Freq(double frequency) {
// first deal with the high frequency stuff above 10 MHz
if (frequency>10000000){
lcd.setCursor(L2+1,0);
lcd.print(frequency,0);
lcd.print(" MHz ");
lcd.setCursor(L2,0);
lcd.print(floor(frequency/1e6),0);
lcd.print(".");
}
else{ // handle the LF range below 1 MHz
if(frequency<1000000){
lcd.setCursor(L2+3,0);
lcd.print(frequency,0);
lcd.print(" MHz ");
lcd.setCursor(L2+1,0);
lcd.print("0.");
}
else{ // this is the normal range, between 1 and 10 MHz
lcd.setCursor(L2, 0);
lcd.print(" ");
lcd.print(frequency,0);
lcd.print(" MHz ");
lcd.setCursor(L2+1,0);
lcd.print(floor(frequency/1e6),0);
lcd.print(".");
}
}
// establish the cursor position
int c_position=L2+8-dfindex;
lcd.setCursor(c_position, 0);
}
// subroutine to display the RiT...
void LCD_Display_RiT(int RiT) {
lcd.setCursor(5, 1);
lcd.print(RiT,DEC);
lcd.print(" Hz");
}
// subroutine to clear the RiT display...
void LCD_Clear_RiT() {
lcd.setCursor(0, 1);
lcd.print(" ");
}
// subroutine to clear the top line
void LCD_Clear_Top(){
lcd.setCursor(0,0);
if(lcdWidth==20){
lcd.print(" ");
}
else{
lcd.print(" ");
}
}
// subroutine to display a string at a specified position...
void LCD_String_Display(int c1, int c2, char Str[ ]){
lcd.setCursor(c1, c2);
lcd.print(Str);
}
// subroutine to display a number at a specified position...
void LCD_Int_Display(int c1, int c2, int num){
lcd.setCursor(c1, c2);
lcd.print(num);
}
// subroutine to display a string at a specified position...
void LCD_Char_Display(int c1, int c2, char Str){
lcd.setCursor(c1, c2);
lcd.print(Str);
}
// subroutine to set up the normal display...
void Normal_Display(){
LCD_String_Display(ModePos, 1, MenuString[2][MenuOption[2]]);
LCD_String_Display(BandPos, 1, MenuString[3][MenuOption[3]]);
switch (MenuOption[1]) {
case 0:
VFOChar='A';
break;
case 1:
VFOChar='B';
break;
default:
VFOChar=' ';
}
LCD_Char_Display(VFOPos, 1, VFOChar);
LCD_Clear_RiT();
LCD_Clear_Top();
LCD_String_Display(BannerPos, 0, Banner);
setTxDisplay();
LCD_Char_Display(RiTSignPos, 1, OffsetChar);
LCD_Display_Freq(freq);
}
// Subrouting to display the Transmit Status
void setTxDisplay(){
switch (Transmit) {
case false:
TxChar="Rx";
break;
case true:
TxChar="Tx";
break;
}
LCD_String_Display(TxPos, 1,TxChar);
// re-establish the cursor position
int c_position=25-dfindex;
lcd.setCursor(c_position, 0);
}
// Subroutine to generate a positive pulse on 'pin'...
void pulseHigh(int pin) {
digitalWrite(pin, HIGH);
digitalWrite(pin, LOW);
}
// calculate and send frequency code to DDS Module...
void sendFrequency(double frequency, boolean Tx) {
int32_t freq = (frequency-RiT-RxOffset) * 4294967295/125000000;
if (Transmit==true){
freq = (frequency-TxOffset) * 4294967295/125000000;
}
for (int b=0; b<4; b++, freq>>=8) {
shiftOut(DATA, W_CLK, LSBFIRST, freq & 0xFF);
}
shiftOut(DATA, W_CLK, LSBFIRST, 0x00);
pulseHigh(FQ_UD);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment