//Using library: PinChangeInterrupt, FreqCount, SSD1306Ascii
//Please add by "Sketch/Include Library"
#include <EEPROM.h>
#include <Wire.h>
#include <FreqCount.h>
#include <SPI.h>
#include "SSD1306Ascii.h"
#include "SSD1306AsciiWire.h"
#include "PinChangeInterrupt.h"
//-------Begin hardware definition block---------------------------------------
//--------------------Encoder--------------------------------------------------
#define CLK 2
#define DT 3
#define SW 4
#define FASTROTATE 200
//---------------------OLED----------------------------------------------------
#define I2C_ADDRESS 0x3C
SSD1306AsciiWire oled;
//--------------------RDA5807 -------------------------------------------------
#define RDA5807M_RANDOM_ACCESS_ADDRESS 0x11
// Registers
#define RDA5807M_REG_CONFIG 0x02
#define RDA5807M_REG_TUNING 0x03
#define RDA5807M_REG_VOLUME 0x05
#define RDA5807M_REG_RSSI 0x0B
// FLAGS
#define RDA5807M_FLG_DHIZ 0x8000 //15 DHIZ
#define RDA5807M_FLG_DMUTE 0x4000 //14 DMUTE
#define RDA5807M_FLG_BASS 0x1000// 12 BASS
#define RDA5807M_FLG_ENABLE word(0x0001) //0 ENABLE
#define RDA5807M_FLG_TUNE word(0x0010)
#define RDA5807M_FLG_MONO 0x2000 //????
#define RDA5807M_BIT_MUTE 14
// MASKS
#define RDA5807M_CHAN_MASK 0xFFC0
#define RDA5807M_CHAN_SHIFT 6
#define RDA5807M_VOLUME_MASK word(0x000F)
#define RDA5807M_VOLUME_SHIFT 0
#define RDA5807M_RSSI_MASK 0xFE00
#define RDA5807M_RSSI_SHIFT 9
//-----------------------AD9833----------------------------------------------
#define FNC_PIN 10
//-------End hardware defenition block--------------------------------------------------
//Customizations store
struct TCust{
uint8_t iGlobalMode;
unsigned long lOutFrequency;
uint8_t iVolume;
uint16_t iManFreq;
} cust, old_cust;
//---------Global Menu defenitions-----------------------------------
String sModes[] = {"C","F"};
String sMenu[] = {"Tube","Mode","FM","Vol","Out","Frq"};
unsigned g_iMenu; //0 - Transmission, 1-Select Global Mode, 2-FM Radio (0 mode only), 3-Volume,4-Output Freq, 4-Frequency meter
const uint8_t c_CLICK = 0;
const uint8_t c_PLUS = 1;
const uint8_t c_MINUS = 2;
bool g_bIN = false; //false - not entered in menu, 1 - entered in menu
//------------------------Global Frequencies defenitions ---------------
#define FM_MIN 875 //FM radio range
#define FM_MAX 1080
const unsigned long c_MIN_OUT_FREQUENCY=100000; //Low boundary of oscillator
unsigned long g_lFrequency; // oscillating circuit frequency of tube receiver
uint8_t g_FF=4;//frequency factor
unsigned long c_lMinPossibleFrq = 100000; //Max and Min possible generation range to restrict bad frequencies
unsigned long c_lMaxPossibleFrq = 1200000;
unsigned long g_lMinFrq=0; //Current range input frequency to map on FM range
unsigned long g_lMaxFrq=0;
unsigned int g_iFMFreq; //FM frequency in Mhz*10
unsigned int g_iFMFreq_old = 1050;
//------------------Global encoder defenitions -----------------------
uint8_t g_uEncoderCurrentState;
uint8_t g_uEncoderInitState;
unsigned long g_lEncTime=0;//Times between encoder rotatates
unsigned long g_lOld_EncTime=0;
bool g_bFast=false; //Fast encoder running
//-----------------Global flags to update statuses --------------------
bool g_bUpdated = false; //to update screen if bUpdated = true;
bool g_bVolumeChanged = false;
bool g_bOutFrequencyChanged = false;
void setup() {
Serial.begin(115200);
Wire.begin();
//Menu
Serial.println( "Read custom object from EEPROM: " );
EEPROM.get( 0, cust );
if(cust.iGlobalMode != 0 and cust.iGlobalMode != 1 ) cust.iGlobalMode = 0;
if(cust.lOutFrequency < 100000 or cust.lOutFrequency > 10000000 ) cust.lOutFrequency = 465000;
if(cust.iVolume > 15 or cust.iVolume < 0 ) cust.iVolume = 15;
if(cust.iManFreq < 960 or cust.iManFreq > 1080 ) cust.iManFreq = 1050;
if (cust.iGlobalMode == 0) g_iMenu = 0;
else g_iMenu = 2;
old_cust = cust;
oled.begin(&Adafruit128x64, I2C_ADDRESS);
FreqCount.begin(1000/g_FF);
setupRDA5807();
setRDA5807frequency(cust.iManFreq);
setRDA5807volume(cust.iVolume);
pinMode(CLK, INPUT);
pinMode(DT, INPUT);
pinMode(SW, INPUT_PULLUP);
g_uEncoderInitState = digitalRead(CLK);
attachInterrupt(0, encoder_value, CHANGE);
attachInterrupt(1, encoder_value, CHANGE);
attachPCINT(digitalPinToPCINT(SW), button_press, CHANGE);
g_lMaxFrq = c_lMinPossibleFrq; //cross-initialize to fill data that are ready to compare operations (see loop)
g_lMinFrq = c_lMaxPossibleFrq;
setAD9833frequency(cust.lOutFrequency);
Serial.println("Starting....");
}
void button_press()
{
int buttonVal = digitalRead(SW);
//If we detect LOW signal, button is pressed
if (buttonVal == LOW) {
Serial.println("Button pressed!");
//mode++;
menu(c_CLICK);
g_bUpdated = true;
}
}
void drawOled()
{
oled.clear();
oled.setFont(TimesNewRoman16_bold);
oled.displayRemap(true);
//First Line
oled.set2X();
oled.print(sModes[cust.iGlobalMode]);
oled.print("/");
oled.print(sMenu[g_iMenu]);
if (g_bIN)oled.print("->");
oled.println();
switch(g_iMenu){
case 0: oled.set1X();oled.print(g_lFrequency );oled.print("->");oled.println(g_iFMFreq);
break;
case 1: oled.set2X() ;oled.print("---");oled.print(sModes[cust.iGlobalMode]); oled.print("---");
break;
case 2: oled.set2X();
if(cust.iGlobalMode==0) oled.print(g_iFMFreq );
else oled.print(cust.iManFreq);
oled.set1X();oled.print(" Mhz");
break;
case 3: oled.set2X();oled.print("Vol ") ;oled.print(cust.iVolume);
break;
case 4: oled.set2X();oled.print(int(cust.lOutFrequency/1000.));oled.set1X(); oled.print(" kHz");
break;
case 5: oled.set2X();oled.print(g_lFrequency) ; oled.set1X();oled.print(" Hz");
break;
}
}
void menu(unsigned int action)
{
unsigned long lOutFreqMult = g_bFast ? 100000 : 1000 ;
g_bFast = false;
switch (action){
case c_CLICK:
if(g_bIN)
g_bIN=false;
//Save all data
if (cust.iGlobalMode != old_cust.iGlobalMode or cust.iVolume !=old_cust.iVolume
or cust.iManFreq != old_cust.iManFreq or cust.lOutFrequency != old_cust.lOutFrequency ){
EEPROM.put(0, cust);
Serial.println(cust.iGlobalMode);Serial.println(cust.iVolume); Serial.println(cust.iManFreq); Serial.println(cust.lOutFrequency) ;
old_cust = cust;
}
else
g_bIN=true;
break;
case c_PLUS:
if(!g_bIN){
if( (cust.iGlobalMode==0 && g_iMenu<5) || (cust.iGlobalMode==1 && g_iMenu<4) ) //No Tube and Frequency in FM mode
g_iMenu++;
else
if ( cust.iGlobalMode==0 )
g_iMenu = 0;
else g_iMenu = 1;
}
else { //we are in menu
switch(g_iMenu) {
case 1: cust.iGlobalMode = 1;
break;
case 2: cust.iManFreq++;
break;
case 3: cust.iVolume = ( cust.iVolume==15 ) ? 15 : cust.iVolume+1;
g_bVolumeChanged = true;
break;
case 4: cust.lOutFrequency+= lOutFreqMult;
setAD9833frequency(cust.lOutFrequency);
g_bOutFrequencyChanged = true;
break;
default: //No menu for Frequency Meter (5)
break;
}
}
break;
case c_MINUS:
if(!g_bIN){
if( ( cust.iGlobalMode==0 && g_iMenu>0 ) or (cust.iGlobalMode==1 && g_iMenu >1 ) ) //No Tube and Frequency in FM mode
g_iMenu--;
else
if (cust.iGlobalMode==0 ) g_iMenu = 5;
else g_iMenu = 4;
}
else { //we are in menu
switch(g_iMenu) {
case 1: cust.iGlobalMode = 0;
break;
case 2: cust.iManFreq--;
break;
case 3: cust.iVolume = ( cust.iVolume==1 )? 1 : cust.iVolume-1;
g_bVolumeChanged = true;
break;
case 4:
cust.lOutFrequency = (cust.lOutFrequency > c_MIN_OUT_FREQUENCY) ? cust.lOutFrequency - lOutFreqMult : c_MIN_OUT_FREQUENCY;
setAD9833frequency(cust.lOutFrequency);
g_bOutFrequencyChanged = true;
break;
default: //No menu for Frequency Meter (5)
break;
}
}
break;
g_bUpdated = true;
}
}
void encoder_value() {
// Read the current state of CLK
g_uEncoderCurrentState = digitalRead(CLK);
// If last and current state of CLK are different, then we can be sure that the pulse occurred
if (g_uEncoderCurrentState != g_uEncoderInitState && g_uEncoderCurrentState == 1) {
// Encoder is rotating counterclockwise so we decrement the counter
if (digitalRead(DT) == g_uEncoderCurrentState) {
menu(c_PLUS);
g_bUpdated = true;
Serial.println('+');
g_lEncTime= millis();
if(g_lEncTime - g_lOld_EncTime < FASTROTATE) g_bFast = true;
g_lOld_EncTime= g_lEncTime;
} else {
// Encoder is rotating clockwise so we increment the counter
menu(c_MINUS);
g_bUpdated = true;
g_lEncTime = millis();
if(g_lEncTime - g_lOld_EncTime < FASTROTATE) g_bFast= true;
g_lOld_EncTime= g_lEncTime;
Serial.println('-');
}
}
// Remember last CLK state for next cycle
g_uEncoderInitState = g_uEncoderCurrentState;
}
void loop() {
if(cust.iGlobalMode == 0){ //Tube Substition mode
if (FreqCount.available()) {
unsigned long count = FreqCount.read();
g_lFrequency = g_FF*count;
if (g_lFrequency > c_lMinPossibleFrq && g_lFrequency < g_lMinFrq ) g_lMinFrq = g_lFrequency;
if (g_lFrequency < c_lMaxPossibleFrq && g_lFrequency > g_lMaxFrq ) g_lMaxFrq = g_lFrequency;
g_iFMFreq = FM_MIN + (g_lFrequency - g_lMinFrq ) * (float(FM_MAX-FM_MIN)/float(g_lMaxFrq-g_lMinFrq));
// iFMFreq = round(float(iFMFreq)/10)*10; //only 105 106 .. values but stability
if(g_iFMFreq != g_iFMFreq_old && g_lFrequency >= g_lMinFrq && g_lFrequency <= g_lMaxFrq ) { //to prevent parasit switching
setRDA5807frequency(g_iFMFreq);
g_iFMFreq_old = g_iFMFreq;
g_bUpdated = true;
}
}
}
if( g_bUpdated ) {
if(cust.iGlobalMode == 1) //FM radio-transmission mode
setRDA5807frequency(cust.iManFreq);
drawOled();
g_bUpdated = false;
}
if(g_bVolumeChanged) {
setRDA5807volume(cust.iVolume);
g_bVolumeChanged = false;
}
if(g_bOutFrequencyChanged){
setAD9833frequency(cust.lOutFrequency);
g_bOutFrequencyChanged = false;
}
if (g_iMenu == 5) drawOled(); //Frequncy meter mode - always update screen
}
//--------------AD9833 hardware functions --------------------------------
void setRegister(uint8_t reg, const uint16_t value) {
Wire.beginTransmission(0x11);
Wire.write(reg);
Wire.write(highByte(value));
Wire.write(lowByte(value));
Wire.endTransmission(true);
}
uint16_t getRegister(uint8_t reg) {
uint16_t result;
Wire.beginTransmission(RDA5807M_RANDOM_ACCESS_ADDRESS);
Wire.write(reg);
Wire.endTransmission(false);
Wire.requestFrom(0x11, 2, true);
result = (uint16_t)Wire.read() << 8;
result |= Wire.read();
return result;
}
void writeAD9833(uint16_t Data){
SPI.beginTransaction(SPISettings(SPI_CLOCK_DIV2, MSBFIRST, SPI_MODE2));
digitalWrite(SS, LOW);
delayMicroseconds(1);
SPI.transfer16(Data);
digitalWrite(SS, HIGH);
SPI.endTransaction();
}
void setAD9833frequency(unsigned long freq){
unsigned long x = freq * 10.73741824;
int32_t freqWord = x;
int16_t upper = (int16_t)((freqWord & 0xFFFC000) >> 14),
lower = (int16_t)(freqWord & 0x3FFF);
lower |= 0x4000 ;//FREQ0_WRITE_REG
upper |= 0x4000 ;//FREQ0_WRITE_REG
SPI.begin();
writeAD9833(0x2100);
writeAD9833(lower);
writeAD9833(upper);
writeAD9833(0xC000);
writeAD9833(0x2000);
writeAD9833(0x2000);
g_bUpdated = true;
}
void setupRDA5807()
{
uint16_t reg02h, reg03h, reg05h, reg0Bh;
// Register 02h is settings
reg02h = RDA5807M_FLG_ENABLE | RDA5807M_FLG_DHIZ | RDA5807M_FLG_DMUTE;
setRegister(RDA5807M_REG_CONFIG, reg02h);
// А потом решили еще усилить басы:
//reg02h |= RDA5807M_FLG_BASS;
//setRegister(RDA5807M_REG_CONFIG, reg02h);
reg02h |= RDA5807M_FLG_MONO; //Mono mode
setRegister(RDA5807M_REG_CONFIG, reg02h);
}
void setRDA5807frequency(uint16_t freq)
{
uint16_t reg02h, reg03h, reg05h, reg0Bh;
// Register 03h is radio station selection
// After reset the default value in register 03h is 0
// So BAND = 00 (87..108MHz), STEP = 00 (100kHz).
reg03h = (freq - 870) << RDA5807M_CHAN_SHIFT; // chan = (freq - band) / space
setRegister(RDA5807M_REG_TUNING, reg03h | RDA5807M_FLG_TUNE);
}
void setRDA5807volume(uint8_t v)
{
uint16_t reg02h, reg03h, reg05h, reg0Bh;
// Register 05h. Set the volume, do not change the other bits
reg05h = getRegister(RDA5807M_REG_VOLUME); // read the current value
reg05h &= ~RDA5807M_VOLUME_MASK; // Reset the VOLUME bits
reg05h |= v << RDA5807M_VOLUME_SHIFT; // set a new volume
setRegister(RDA5807M_REG_VOLUME, reg05h);
g_bUpdated = true;
}