Hamradio2.ino
#include <Encoder.h> // https://www.pjrc.com/teensy/td_libs_Encoder.html
#include <easyRun.h> // https://github.com/bricoleau/easyRun
#include <LiquidCrystal.h>
#include "screenConfig.h" // LCD attributes
#include "frequency.h" // frequency and channel management
// Types
enum t_mode : uint8_t {PRESET, MANUAL, AUTOSCAN, NO_MODE} currentMode = PRESET;
// constants
// The Pins
const uint8_t DTPin = 2; // Encoder DT
const uint8_t CLKPin = 3; // Encoder CLK
const uint8_t SWPin = 4; // Encoder Switch (pullup activated by the library)
const uint8_t piezoPin = 5; // small audio feedback pin --- piezo --- R150 Ohms ---- GND
const uint8_t skipPin = 6; // momentary button pin --- button --- GND (pullup activated by the library)
const uint8_t squelchPin = 12; // squelch signal Indicator. HIGH means signal is
int clock = 9; // PLL Program pin
int dat = 10; // PLL Program pin
int ena = 11; // PLL Program pin
// lcd pins
const uint8_t d4Pin = A0;
const uint8_t d5Pin = A1;
const uint8_t d6Pin = A2;
const uint8_t d7Pin = A3;
const uint8_t rsPin = 7;
const uint8_t enPin = 8;
// The modes' label
const char* modeString[] = {"PRESET ", "VFO ", "SCAN "};
// instances
LiquidCrystal lcd(rsPin, enPin, d4Pin, d5Pin, d6Pin, d7Pin);
Encoder encoder(DTPin, CLKPin);
button modeButton(SWPin);
button skipButton(skipPin);
// Utility functions
void initLcd() {
lcd.begin(LCD_COL, LCD_LINES);
}
void initScreen() {
lcd.clear();
lcd.setCursor((LCD_COL - 14) / 2, 0); lcd.print(F("** N3IDS **"));
lcd.createChar (0, lock);
delay(1000);
lcd.clear();
}
void updateMode() {
static t_mode oldMode = NO_MODE;
if (currentMode != oldMode) { // refresh needed
oldMode = currentMode;
lcd.setCursor(0, 0);
lcd.print(modeString[currentMode]); // 8 characters used from cursor 0 to 7
}
}
void updateChannel() {
static uint16_t oldChannelIndex = unknownChannelIndex;
static bool oldChannelEnabled = true;
if (currentChannelIndex != unknownChannelIndex) {
if ((currentChannelIndex != oldChannelIndex) || (presetChannels[currentChannelIndex].enabled != oldChannelEnabled)) { // refresh needed
oldChannelIndex = currentChannelIndex;
oldChannelEnabled = presetChannels[currentChannelIndex].enabled;
lcd.setCursor(LCD_COL - 6, 0);
lcd.print(F(" ")); // erase 6 chars
lcd.setCursor(LCD_COL - 6, 0);
lcd.write(!presetChannels[currentChannelIndex].enabled ? '[' : ' ');
lcd.print(presetChannels[currentChannelIndex].channel);
lcd.write(!presetChannels[currentChannelIndex].enabled ? ']' : ' ');
}
} else {
if (oldChannelIndex != currentChannelIndex) {
oldChannelIndex = currentChannelIndex;
lcd.setCursor(LCD_COL - 6, 0);
lcd.print(F(" ")); // erase 6 chars
}
}
}
void updateFrequency() {
static uint32_t oldFrequency = unknownFrequencyKHz;
if (currentFrequencyKHz != oldFrequency) { // refresh needed
oldFrequency = currentFrequencyKHz;
squelchChrono = millis();
tuneToCurrentFrequencyKHz(currentFrequencyKHz);
lcd.setCursor(LCD_COL - 7, 1);
lcd.print(" "); // erase 7 chars
lcd.setCursor(LCD_COL - 7, 1);
lcd.print(currentFrequencyKHz / 1000.0, 3);
}
}
void updateSquelchStatus() {
static uint8_t oldSquelchStatus = 0xFF;
uint8_t squelchStatus = digitalRead(squelchPin);
if (squelchStatus != oldSquelchStatus) {
oldSquelchStatus = squelchStatus;
lcd.setCursor(0, 1);
if (squelchStatus == HIGH) {
lcd.write(squelchOK);
} else {
lcd.write(squelchKO);
}
}
}
void configure() {
encoder.write(0);
currentChannelIndex = defaultChannelIndex;
currentFrequencyKHz = channelIndexToFrequencyKHz(currentChannelIndex);
initScreen();
// update everything with the current values
updateMode();
updateChannel();
updateFrequency();
updateSquelchStatus();
}
void newModeSelected(t_mode __attribute__((unused)) oldMode) {
// if something mode specific needs to be done when transitioning from oldMode to currentMode, do it there
switch (currentMode) {
case PRESET: break;
case MANUAL: break;
case AUTOSCAN: break;
case NO_MODE: break; // should not happen
}
// this is done for all the modes
encoder.write(0);
updateMode();
}
void checkMode() {
t_mode oldMode = currentMode;
if (modeButton) {
switch (currentMode) {
case PRESET: currentMode = MANUAL; break;
case MANUAL: currentMode = AUTOSCAN; break;
case AUTOSCAN: currentMode = PRESET; break;
case NO_MODE: currentMode = PRESET; break; // should not happen
}
newModeSelected(oldMode); // do what's required to setup the new mode
}
}
// ------- The state Machine -------
void runPreset() {
long newEncoderPosition = encoder.read() >> 2; // divide by 2 as I get 2 ticks for one click with mine
if (newEncoderPosition != 0) {
tone(piezoPin, 300, 10);
if (newEncoderPosition > 0) { // we want to change the preset UP
while (newEncoderPosition--) currentChannelIndex = nextChannelIndex(currentChannelIndex);
} else if (newEncoderPosition < 0) { // we want to change the preset DOWN
while (newEncoderPosition++) currentChannelIndex = previousChannelIndex(currentChannelIndex);
}
currentFrequencyKHz = channelIndexToFrequencyKHz(currentChannelIndex);
encoder.write(0);
}
if ((currentChannelIndex != unknownChannelIndex) && skipButton) {
tone(piezoPin, 3000, 30);
presetChannels[currentChannelIndex].enabled = 1 - presetChannels[currentChannelIndex].enabled;
}
}
void runManual() {
long newEncoderPosition = encoder.read() >> 2; // divide by 2 as I get 2 ticks for one click with mine
if (newEncoderPosition != 0) {
tone(piezoPin, 600, 10);
if (newEncoderPosition > 0) { // we want to change the frequency UP
while (newEncoderPosition--) {
currentFrequencyKHz += frequencyStepKHz;
if (currentFrequencyKHz > maxFrequencyKHz) {
currentFrequencyKHz = maxFrequencyKHz;
break;
}
}
} else if (newEncoderPosition < 0) { // we want to change the frequency DOWN
while (newEncoderPosition++) {
currentFrequencyKHz -= frequencyStepKHz;
if (currentFrequencyKHz < minFrequencyKHz) {
currentFrequencyKHz = minFrequencyKHz;
break;
}
}
}
currentChannelIndex = channelForFrequencyKHz(currentFrequencyKHz); // if it's a known channel, make note of it
encoder.write(0);
}
if ((currentChannelIndex != unknownChannelIndex) && skipButton) {
tone(piezoPin, 3000, 30);
presetChannels[currentChannelIndex].enabled = 1 - presetChannels[currentChannelIndex].enabled;
}
}
void runAutoscan() {
if (millis() - squelchChrono >= squelchDelay) {
// we have been long enough at this frequency, check if squelch says it's not active
if (digitalRead(squelchPin) == LOW) { // HIGH if currentFrequencyKHz is active
// time to go to next active channel
currentChannelIndex = nextEnabledChannelIndex(currentChannelIndex);
currentFrequencyKHz = channelIndexToFrequencyKHz(currentChannelIndex);
}
}
}
void runMode() {
switch (currentMode) {
case PRESET: runPreset(); break;
case MANUAL: runManual(); break;
case AUTOSCAN: runAutoscan(); break;
case NO_MODE: break;
}
}
void setup() {
pinMode(clock, OUTPUT);
pinMode(dat, OUTPUT);
pinMode(ena, OUTPUT);
pinMode(squelchPin, INPUT_PULLUP); // or INPUT depending on the system you connect to
pinMode(piezoPin, OUTPUT);
Serial.begin(9600); Serial.println();
initLcd();
configure();
}
void loop() {
easyRun(); // handle buttons update
checkMode();
runMode();
updateChannel();
updateFrequency();
updateSquelchStatus();
}
Frequency.cpp
#include <Arduino.h>
#include "frequency.h"
t_channel presetChannels[] = {
{3195, true},
{3199, true},
{3203, true},
{3207, true},
{3211, true},
{3215, true},
{3219, true},
{3223, true},
{3227, true},
{3231, true},
{3235, true},
{3239, true},
{3243, true},
{3247, true},
{3251, true},
{3255, true},
{3259, true},
{3263, true},
{3267, true},
{3271, true},
{3495, true},
{3498, true},
{3501, true},
{3504, true},
{3507, true},
{3510, true},
{3513, true},
{3516, true},
{3519, true},
{3522, true},
{3525, true},
{3528, true},
{3531, true},
{3534, true},
{3537, true},
{3540, true},
{3543, true},
{3546, true},
{3549, true},
{3552, true},
{3555, true},
{3558, true},
{3561, true},
{3564, true},
{3567, true},
{3570, true},
{3573, true},
{3576, true}, // index 47 -> the default channel ?
{3579, true},
{3582, true},
{3585, true},
{3588, true},
{3591, true},
{3594, true},
{3597, true},
{3600, true},
{3603, true},
{3606, true},
{3609, true},
{3612, true},
{3615, true},
{3618, true},
{3621, true},
{3624, true},
{3627, true},
{3630, true},
{3633, true},
{3636, true},
{3639, true},
{3642, true},
{3645, true},
{3648, true},
{3651, true},
{3477, true},
{3483, true},
{3489, true},
{3465, true},
{3696, true},
};
const size_t defaultChannelIndex = 47;
size_t presetChannelsCount = sizeof presetChannels / sizeof * presetChannels;
const uint16_t unknownChannelIndex = 0xFFFF;
const uint32_t unknownFrequencyKHz = 0xFFFFFFFF;
const uint32_t frequencyStepKHz = 5; // 5KHz steps
const uint32_t squelchDelay = 100; // how long to wait in ms to decide if frequency is OK
uint32_t squelchChrono; // when we tuned to a new Frequency
uint16_t currentChannelIndex; // unknownChannelIndex if channel not in list
const uint32_t minFrequencyKHz = 140000; // we handle from 140 Mhz
const uint32_t maxFrequencyKHz = 150000; // to 150 Mhz
uint32_t currentFrequencyKHz; // frequency in KHz
uint32_t channelIndexToFrequencyKHz(uint16_t index) {
return 5ul * presetChannels[index].channel + 129135ul;
}
uint16_t frequencyKHzToChannelIndex(uint32_t freq) {
uint16_t index = 0xFFFF;
for (uint16_t i = 0; i < presetChannelsCount; i++) {
if (channelIndexToFrequencyKHz(presetChannels[i].channel) == freq) {
index = i;
break;
}
}
return index;
}
uint16_t nextChannelIndex(uint16_t anIndex) {
uint16_t nextIndex = defaultChannelIndex;
if (anIndex == unknownChannelIndex) {
nextIndex = nearestChannelToFrequencyKHz(currentFrequencyKHz);
} else {
nextIndex = (anIndex + 1) % presetChannelsCount;
}
return nextIndex;
}
uint16_t previousChannelIndex(uint16_t anIndex) {
uint16_t previousChannelIndex = defaultChannelIndex;
if (anIndex != unknownChannelIndex) {
if (anIndex == 0) previousChannelIndex = presetChannelsCount - 1;
else previousChannelIndex = anIndex - 1;
} else {
anIndex = nearestChannelToFrequencyKHz(currentFrequencyKHz);
}
return previousChannelIndex;
}
uint16_t nextEnabledChannelIndex(uint16_t anIndex) {
bool found = false;
uint16_t nextChannelIndex = anIndex;
if (anIndex == unknownChannelIndex) {
// get the channel closest to the current frequency : to do
anIndex = nearestChannelToFrequencyKHz(currentFrequencyKHz);
if (presetChannels[anIndex].enabled) {
found = true;
nextChannelIndex = anIndex;
} else {
nextChannelIndex = (anIndex + 1) % presetChannelsCount;
while (nextChannelIndex != anIndex) {
if (presetChannels[nextChannelIndex].enabled) {
found = true;
break;
} else nextChannelIndex = (nextChannelIndex + 1) % presetChannelsCount;
}
}
} else {
nextChannelIndex = (nextChannelIndex + 1) % presetChannelsCount;
while (nextChannelIndex != anIndex) {
if (presetChannels[nextChannelIndex].enabled) {
found = true;
break;
} else nextChannelIndex = (nextChannelIndex + 1) % presetChannelsCount;
}
}
return found ? nextChannelIndex : defaultChannelIndex;
}
uint16_t nearestChannelToFrequencyKHz(uint32_t freq) { // regardless if it is excluded
uint32_t delta = 0xFFFFFFFF;
uint16_t channelIndex = unknownChannelIndex;
for (uint16_t c = 0; c < presetChannelsCount; c++) {
uint32_t cFreq = channelIndexToFrequencyKHz(c);
if (abs(cFreq - freq) < delta) {
delta = abs(cFreq - freq);
channelIndex = c;
}
Serial.print(channelIndex);
}
if (channelIndex == unknownChannelIndex) channelIndex = defaultChannelIndex; // if we did not find one, return the default
return channelIndex;
}
uint16_t channelForFrequencyKHz(uint32_t freq) {
uint16_t channelIndex = unknownChannelIndex;
for (uint16_t c = 0; c < presetChannelsCount; c++) {
if (channelIndexToFrequencyKHz(c) == freq) {
channelIndex = c;
break;
}
}
return channelIndex;
}
bool isFrequencyKHzInPresetChannels(uint32_t freq) { // regardless if it is excluded
bool knownChannel = false;
for (uint16_t c = 0; c < presetChannelsCount; c++) {
knownChannel = (channelIndexToFrequencyKHz(c) == freq);
if (knownChannel) break;
}
return knownChannel;
}
// Drive the external hardware to set the frequency
void tuneToCurrentFrequencyKHz(uint32_t freqKHz) {
int clock = 9; // PLL Program pin
int dat = 10; // PLL Program pin
int ena = 11; // PLL Program pin
//pinMode(clock, OUTPUT);
//pinMode(dat, OUTPUT);
//pinMode(ena, OUTPUT);
int data = (freqKHz - 129135) / 5;
// shift out highbyte
shiftOut(dat, clock, MSBFIRST, (data >> 8));
// shift out lowbyte
shiftOut(dat, clock, MSBFIRST, data);
digitalWrite(ena, HIGH); // Bring Enable high
digitalWrite(ena, LOW); // Then back low
// TO DO
Serial.print(F("Setting Frequency to ")); Serial.print(freqKHz); Serial.println(F(" KHz"));
Serial.println(data);
}
Frequency.h
#ifndef __FREQ_H__
#define __FREQ_H__
struct __attribute__((packed)) t_channel {
const uint16_t channel: 15; // 0 to 32767
uint16_t enabled: 1; // 0 or 1
};
// constants and variables
extern t_channel presetChannels[];
extern const size_t defaultChannelIndex;
extern size_t presetChannelsCount;
extern const uint16_t unknownChannelIndex;
extern const uint32_t unknownFrequencyKHz;
extern const uint32_t frequencyStepKHz;
extern uint16_t currentChannelIndex; // unknownChannelIndex if channel not in list
extern const uint32_t minFrequencyKHz; // we handle from 140 Mhz
extern const uint32_t maxFrequencyKHz; // to 150 Mhz
extern uint32_t currentFrequencyKHz; // frequency in KHz
extern const uint32_t squelchDelay; // how long to wait in ms to decide if frequency is OK
extern uint32_t squelchChrono; // when we tuned to a new Frequency
// functions
uint32_t channelIndexToFrequencyKHz(uint16_t);
uint16_t frequencyKHzToChannelIndex(uint32_t) ;
uint16_t nearestChannelToFrequencyKHz(uint32_t);
uint16_t channelForFrequencyKHz(uint32_t);
bool isFrequencyKHzInPresetChannels(uint32_t);
uint16_t nextChannelIndex(uint16_t);
uint16_t previousChannelIndex(uint16_t);
uint16_t nextEnabledChannelIndex(uint16_t);
void tuneToCurrentFrequencyKHz(uint32_t);
#endif
screenConfig.h
#ifndef __SCREENCONFIGS__
#define __SCREENCONFIGS__
// 2004 LCD
const uint8_t LCD_COL = 16; // for 1602 use 16, for 2004 use 20
const uint8_t LCD_LINES = 2; // for 1602 use 2, for 2004 use 4
const char squelchKO = ' '; // just a space
const char squelchOK = 0x00 ; // custom defined char (lock)
uint8_t lock[] = {
0b01110,
0b10001,
0b10001,
0b11111,
0b11011,
0b11011,
0b11111,
0b00000
};
#endif