/* ////////////////////////////////////////////////////////////////// ARDUINO/Genuino Project "SATHANIMOD", an FM RADIO WITH Si4713 https://www.changpuak.ch/electronics/Arduino-Amphone.php Software Version 9.9 05.12.2020 by ALEXANDER SSE FRANK USES LIBRARIES FROM ADAFRUIT. AND YES, WE BUY A LOT, THERE. https://learn.adafruit.com/adafruit-si4713-fm-radio-transmitter -with-rds-rdbs-support/test-and-usage?view=all ALSO VERY HELPFUL: https://github.com/adafruit/Adafruit-Si4713-Library/ blob/master/Adafruit_Si4713.cpp ////////////////////////////////////////////////////////////////// */ #include #include #include #include #define _BV(n) (1 << n) #define RESETPIN A3 unsigned int FMSTATION = 107600 ; // 107600 == 107.60 MHz Adafruit_Si4713 radio = Adafruit_Si4713(RESETPIN); // DISPLAY #define OLED_MOSI 8 #define OLED_CLK 9 #define OLED_DC 6 #define OLED_CS 5 #define OLED_RESET 7 // ROTARY ENCODER const int RotaryEncoder1 = 4 ; // PRESSED const int RotaryEncoder2 = 2 ; const int RotaryEncoder3 = 3 ; volatile boolean LEFT = false ; volatile boolean RIGHT = false ; volatile boolean READY = true ; // Si4713 VARIABLES float FREQ = 107.6 ; const float FREQ_MIN = 76.0 ; const float FREQ_MAX = 108.0 ; int AudioSource = 1 ; int TXPower = 109 ; const int TXPowerMax = 115 ; const int TXPowerMin = 88 ; int AFGain1 = 0 ; // BNC int AFGain2 = 0 ; // DIN 3.5 mm int AFGain3 = -20 ; // INT SINEWAVE const int AFGainMax = 31 ; const int AFGainMin = -95 ; int PreEmphase = 1 ; // 50 us int CursorPos = 5 ; Adafruit_SH1106 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS); #if (SH1106_LCDHEIGHT != 64) #error("Height incorrect, please fix Adafruit_SH1106.h!"); #endif // ///////////////////////////////////////////////////////////// // Serial Communication Routines : EEPROM & SOURCE SELECTOR // ///////////////////////////////////////////////////////////// #define EEPROM_24C01_I2CADDR 0x50 const int AFData = A1 ; const int AFClock = A0 ; const int AFLatch = 13 ; // ///////////////////////////////////////////////////////////// void UpdateNJW1194A(byte Data, byte Address) // ///////////////////////////////////////////////////////////// { digitalWrite(AFLatch, LOW) ; shiftOut(AFData, AFClock, MSBFIRST, Data) ; shiftOut(AFData, AFClock, MSBFIRST, Address) ; digitalWrite(AFLatch, HIGH) ; } // ///////////////////////////////////////////////////////////// void SelectSourceNJW1194A() // ///////////////////////////////////////////////////////////// { byte SEL1A, SEL2A, SEL1B, SEL2B ; byte ICA, ICB ; // SOURCE = BNC if(AudioSource == 0) { SEL1A = 0 ; SEL2A = 0 ; // Mute SEL1B = 3 ; SEL2B = 4 ; } // SOURCE = DIN 3.5 mm Socket if(AudioSource == 1) { SEL1A = 3 ; SEL2A = 4 ; SEL1B = 1 ; SEL2B = 2 ; } // SOURCE = INTERNAL 400 Hz and 1 kHz if(AudioSource == 2) { SEL1A = 1 ; SEL2A = 2 ; SEL1B = 1 ; SEL2B = 2 ; } ICA = (SEL1A << 5) | (SEL2A << 2) ; ICB = (SEL1B << 5) | (SEL2B << 2) ; UpdateNJW1194A(ICA, 0x40) ; UpdateNJW1194A(ICB, 0x50) ; } // ///////////////////////////////////////////////////////////// void SetVolumeNJW1194A() // ///////////////////////////////////////////////////////////// { byte VOL1A, VOL2A, VOL1B, VOL2B ; // SOURCE = BNC if(AudioSource == 0) { VOL1A = 0 ; VOL2A = 0 ; // Mute VOL1B = 2 * ( 32 - AFGain1 ) ; VOL2B = VOL1B ; } // SOURCE = DIN 3.5 mm Socket if(AudioSource == 1) { VOL1A = 64 ; VOL2A = 64 ; // 0 dB VOL1B = 2 * ( 32 - AFGain2 ) ; VOL2B = VOL1B ; } // SOURCE = INTERNAL 400 Hz and 1 kHz if(AudioSource == 2) { VOL1A = 64 ; VOL2A = 64 ; // 0 dB VOL1B = 2 * ( 32 - AFGain3 ) ; VOL2B = VOL1B ; } UpdateNJW1194A(VOL1A, 0x00) ; UpdateNJW1194A(VOL1B, 0x10) ; UpdateNJW1194A(VOL2A, 0x20) ; UpdateNJW1194A(VOL2B, 0x30) ; } // ///////////////////////////////////////////////////////////// // SUBROUTINES DISPLAY. // ///////////////////////////////////////////////////////////// // SUPPLY MONITOR const int SupplyPin = A7 ; // INPUT unsigned int SupplyRaw = 0 ; float SupplyFactor = 2.0 * 2.4 / 14.4 ; float SupplyVoltage = 0.0 ; void UpdateDisplay() { // unsigned long StartProcedure = millis() ; display.clearDisplay() ; display.setTextSize(1) ; display.setTextColor(WHITE) ; display.setCursor(74,0); display.print("* RDS *") ; display.drawLine(0, 12, 128, 12, WHITE) ; display.setTextSize(2) ; // FREQUENCY display.setCursor(0, 19) ; if (FREQ < 100.000) display.print(" ") ; if (FREQ < 10.000) display.print(" ") ; display.print(FREQ,3) ; display.setCursor(92, 19) ; display.print("MHz") ; display.drawLine(0, 39, 128, 39, WHITE) ; display.drawLine(0, 63, 128, 63, WHITE) ; display.drawLine(0, 39, 0, 63, WHITE) ; display.drawLine(127, 39, 127, 63, WHITE) ; display.drawLine(10, 39, 10, 63, WHITE) ; display.drawLine(0, 45, 10, 45, WHITE) ; display.drawLine(0, 51, 10, 51, WHITE) ; display.drawLine(0, 57, 10, 57, WHITE) ; // CURSOR if(CursorPos <= 4) display.setCursor(0, 24) ; if(CursorPos == 1) display.print(" ") ; if(CursorPos == 2) display.print(" ") ; if(CursorPos == 3) display.print(" ") ; if(CursorPos == 4) display.print(" ") ; if(CursorPos <= 4) display.print("_") ; if(CursorPos == 5) display.fillRect(2, 41, 7, 3, WHITE) ; if(CursorPos == 6) display.fillRect(2, 47, 7, 3, WHITE) ; if(CursorPos == 7) display.fillRect(2, 53, 7, 3, WHITE) ; if(CursorPos == 8) display.fillRect(2, 59, 7, 3, WHITE) ; if(CursorPos <= 5) { display.setTextSize(1) ; display.setCursor(20, 43) ; display.print("AUDIO") ; display.setCursor(20, 53) ; display.print("INPUT") ; display.setCursor(64, 44) ; display.setTextSize(2) ; if(AudioSource == 0) display.print("BNC 1") ; if(AudioSource == 1) display.print("DIN 2") ; if(AudioSource == 2) display.print("INT 3") ; } if(CursorPos == 6) { display.setTextSize(1) ; display.setCursor(20, 43) ; display.println("AUDIO") ; display.setCursor(20, 53) ; display.print("GAIN ") ; display.setCursor(60, 44) ; display.setTextSize(2) ; if(AudioSource == 0) { if(AFGain1 > 0) display.print("+") ; if(AFGain1 == 0) display.print(" ") ; display.print(AFGain1,DEC) ; } if(AudioSource == 1) { if(AFGain2 > 0) display.print("+") ; if(AFGain2 == 0) display.print(" ") ; display.print(AFGain2,DEC) ; } if(AudioSource == 2) { if(AFGain3 > 0) display.print("+") ; if(AFGain3 == 0) display.print(" ") ; display.print(AFGain3,DEC) ; } display.setCursor(99, 44) ; display.print("dB") ; } if(CursorPos == 7) { display.setTextSize(1) ; display.setCursor(20, 43) ; display.println("OUTPUT dB") ; display.setCursor(20, 53) ; display.print("VOLTAGE uV") ; display.setCursor(57, 44) ; display.setTextSize(2) ; if(TXPower < 100) ; display.print(" ") ; display.print(TXPower,DEC); // micro display.drawLine(110, 59, 109, 61, WHITE); } if(CursorPos == 8) { display.setTextSize(1) ; display.setCursor(20, 43) ; display.println("PRE-EM") ; display.setCursor(20, 53) ; display.print("PHASIS") ; display.setCursor(68, 44) ; display.setTextSize(2) ; if(PreEmphase == 0) display.print("75") ; if(PreEmphase == 1) display.print("50") ; if(PreEmphase == 2) display.print(" 0") ; // microseconds display.setCursor(99, 44) ; display.print("us") ; display.drawLine(99, 56, 97, 61, WHITE) ; display.drawLine(100, 56, 98, 61, WHITE) ; } // BATTERY VOLTAGE INDICATOR display.setTextSize(1) ; SupplyRaw = analogRead(SupplyPin) ; delay(10); SupplyRaw += analogRead(SupplyPin) ; SupplyVoltage = ( 5.0 * SupplyRaw / 1023 ) / SupplyFactor ; display.drawRect(40, 1, 18, 7, WHITE) ; display.drawRect(58, 2, 2, 5, WHITE) ; display.fillRect(57, 3, 2, 3, BLACK) ; if(SupplyVoltage >= 6.5) display.fillRect(42, 3, 2, 3, WHITE) ; if(SupplyVoltage >= 9.0) display.fillRect(45, 3, 2, 3, WHITE) ; if(SupplyVoltage >= 11.5) display.fillRect(48, 3, 2, 3, WHITE) ; if(SupplyVoltage >= 14.0) display.fillRect(51, 3, 2, 3, WHITE) ; if(SupplyVoltage >= 16.5) display.fillRect(54, 3, 2, 3, WHITE) ; // Serial.println(SupplyVoltage, 2) ; display.setCursor(0, 0) ; if(SupplyVoltage < 10.0) display.print(" ") ; display.print(SupplyVoltage,2) ; display.display(); } // ///////////////////////////////////////////////////////////// // S E T U P // ///////////////////////////////////////////////////////////// void setup() { Serial.begin(9600); // Wire.begin() ; // INIT OLED display.begin(SH1106_SWITCHCAPVCC); // SHOW STARTUP SCREEN display.clearDisplay(); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(0,0); display.print("****"); display.setCursor(33,0); display.print("SATHANIMOD"); display.setCursor(104,0); display.print("****"); display.drawLine(0, 12, 128, 12, WHITE); display.setTextSize(1); display.setCursor(0, 21); display.println("AN UKW FM RADIO"); display.setCursor(0, 33); display.println("STATION. IN MEMORIAM"); display.setCursor(0, 45); display.println("HS1AS, DUSIT PALACE."); display.setCursor(0, 57); display.println("BUILT 05.12.2020"); display.display(); delay(999) ; pinMode(RotaryEncoder1, INPUT_PULLUP); pinMode(RotaryEncoder2, INPUT_PULLUP); pinMode(RotaryEncoder3, INPUT_PULLUP); // YELLOW attachInterrupt(digitalPinToInterrupt(RotaryEncoder2), RotaryEncoderISR2, FALLING); // GREEN attachInterrupt(digitalPinToInterrupt(RotaryEncoder3), RotaryEncoderISR3, FALLING); // NJW1195 - AF Matrix pinMode(AFData, OUTPUT) ; pinMode(AFClock, OUTPUT) ; pinMode(AFLatch, OUTPUT) ; SelectSourceNJW1194A() ; SetVolumeNJW1194A() ; delay(999); // Si 4713 Serial.println("Adafruit Radio - Si4713 Test"); if (! radio.begin()) { // begin with address 0x63 (CS high default) Serial.println("Couldn't find radio?"); while (1); } radio.setProperty(SI4713_PROP_REFCLK_FREQ, 32000); // crystal is 32.000 kHz radio.setProperty(SI4713_PROP_TX_PREEMPHASIS, PreEmphase); Serial.print("\nSet TX power"); radio.setTXpower(115); // dBuV, 88-115 max Serial.print("\nTuning into "); Serial.print(FMSTATION/100); Serial.print('.'); Serial.println(FMSTATION % 100); radio.tuneFM(FMSTATION); // 107.6 MHz // This will tell you the status in case you want to read it from the chip radio.readTuneStatus(); Serial.print("\tCurr freq: "); Serial.println(radio.currFreq); Serial.print("\tCurr freqdBuV:"); Serial.println(radio.currdBuV); Serial.print("\tCurr ANTcap:"); Serial.println(radio.currAntCap); // begin the RDS/RDBS transmission radio.beginRDS(); radio.setRDSstation("AdaRadio"); radio.setRDSbuffer( "Adafruit g0th Radio!"); Serial.println("RDS on!"); UpdateDisplay() ; } // ///////////////////////////////////////////////////////////// // M A I N L O O P // ///////////////////////////////////////////////////////////// void loop() { // KEY ROTATED ? // ////////////////////////////////// if(LEFT) // ////////////////////////////////// { READY = false ; switch (CursorPos) { case 1: if(FREQ >= (FREQ_MIN + 10.0)) FREQ -= 10.0 ; FMSTATION = 100 * FREQ ; radio.tuneFM(FMSTATION); break; case 2: if(FREQ >= (FREQ_MIN + 1.0)) FREQ -= 1.0 ; FMSTATION = 100 * FREQ ; radio.tuneFM(FMSTATION); break; case 3: if(FREQ >= (FREQ_MIN + 0.1)) FREQ -= 0.1 ; FMSTATION = 100 * FREQ ; radio.tuneFM(FMSTATION); break; case 4: if(FREQ >= (FREQ_MIN + 0.05)) FREQ -= 0.05 ; FMSTATION = 100 * FREQ ; radio.tuneFM(FMSTATION); break; case 5: AudioSource -= 1 ; if(AudioSource < 0) AudioSource = 2 ; SelectSourceNJW1194A() ; SetVolumeNJW1194A() ; break; case 6: // AF GAIN if(AudioSource == 0) { AFGain1 -= 1 ; if(AFGain1 < AFGainMin) AFGain1 = AFGainMin ; } if(AudioSource == 1) { AFGain2 -= 1 ; if(AFGain2 < AFGainMin) AFGain2 = AFGainMin ; } if(AudioSource == 2) { AFGain3 -= 1 ; if(AFGain3 < AFGainMin) AFGain3 = AFGainMin ; } SetVolumeNJW1194A() ; break; case 7: TXPower -= 1 ; if(TXPower < TXPowerMin) TXPower = TXPowerMin ; radio.setTXpower(TXPower) ; // dBuV, 88-115 max break ; case 8: PreEmphase += 1 ; if(PreEmphase > 2) PreEmphase = 0 ; radio.setProperty(SI4713_PROP_TX_PREEMPHASIS, PreEmphase); break; } READY = true ; LEFT = false ; RIGHT = false ; } // ////////////////////////////////// if(RIGHT) // ////////////////////////////////// { READY = false ; switch (CursorPos) { case 1: if(FREQ <= (FREQ_MAX - 10.0)) FREQ += 10.0 ; FMSTATION = (int)(100.0 * FREQ) ; radio.tuneFM(FMSTATION); break; case 2: if(FREQ <= (FREQ_MAX - 1.0)) FREQ += 1.0 ; FMSTATION = 100 * FREQ ; radio.tuneFM(FMSTATION); break; case 3: if(FREQ <= (FREQ_MAX - 0.1)) FREQ += 0.1 ; FMSTATION = 100 * FREQ ; radio.tuneFM(FMSTATION); break; case 4: if(FREQ <= (FREQ_MAX - 0.05)) FREQ += 0.05 ; FMSTATION = 100 * FREQ ; radio.tuneFM(FMSTATION); break; case 5: AudioSource += 1 ; if(AudioSource > 2) AudioSource = 0 ; SelectSourceNJW1194A() ; SetVolumeNJW1194A() ; break ; case 6: // AF GAIN if(AudioSource == 0) { AFGain1 += 1 ; if(AFGain1 > AFGainMax) AFGain1 = AFGainMax ; } if(AudioSource == 1) { AFGain2 += 1 ; if(AFGain2 > AFGainMax) AFGain2 = AFGainMax ; } if(AudioSource == 2) { AFGain3 += 1 ; if(AFGain3 > AFGainMax) AFGain3 = AFGainMax ; } SetVolumeNJW1194A() ; break; case 7: TXPower += 1 ; if(TXPower > TXPowerMax) TXPower = TXPowerMax ; radio.setTXpower(TXPower) ; // dBuV, 88-115 max break ; case 8: PreEmphase -= 1 ; if(PreEmphase < 0) PreEmphase = 2 ; radio.setProperty(SI4713_PROP_TX_PREEMPHASIS, PreEmphase); break; } READY = true ; LEFT = false ; RIGHT = false ; } // ////////////////////////////////// // KEY PRESSED ? // ////////////////////////////////// if(digitalRead(RotaryEncoder1) == LOW) { CursorPos += 1 ; if(CursorPos > 8) CursorPos = 1 ; } UpdateDisplay() ; // 50 ms ??? delay(149) ; } // ///////////////////////////////////////////////////////////// // INTERRUPT SERVICE ROUTINES // ///////////////////////////////////////////////////////////// void RotaryEncoderISR2() { // YELLOW if(READY) { LEFT = false ; RIGHT = false ; byte autre = digitalRead(RotaryEncoder3) ; if (autre > 0) RIGHT = true ; if (autre < 1) LEFT = true ; } } void RotaryEncoderISR3() { // GREEN if(READY) { LEFT = false ; RIGHT = false ; byte autre = digitalRead(RotaryEncoder2) ; if (autre > 0) LEFT = true ; if (autre < 1) RIGHT = true ; } } // ///////////////////////////////////////////////////////////// // END OF FILE. // /////////////////////////////////////////////////////////////