├── IMG_2182.JPG ├── IMG_2183.JPG ├── IMG_2188.JPG ├── IMG_2197.JPG ├── Teensy_time_signal_external_hardware.JPG ├── README.md └── DCF77_v0_4.ino /IMG_2182.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DD4WH/Teensy-DCF77/HEAD/IMG_2182.JPG -------------------------------------------------------------------------------- /IMG_2183.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DD4WH/Teensy-DCF77/HEAD/IMG_2183.JPG -------------------------------------------------------------------------------- /IMG_2188.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DD4WH/Teensy-DCF77/HEAD/IMG_2188.JPG -------------------------------------------------------------------------------- /IMG_2197.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DD4WH/Teensy-DCF77/HEAD/IMG_2197.JPG -------------------------------------------------------------------------------- /Teensy_time_signal_external_hardware.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DD4WH/Teensy-DCF77/HEAD/Teensy_time_signal_external_hardware.JPG -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Teensy-DCF77 2 | 3 | Receive and decode atomic clock time signals from longwave station DCF77 on 77.5kHz with minimal hardware effort (Teensy 3.x & audio board and very few external parts, see below). Fully hackable for other longwave time signal stations around the world (LF below 96kHz). 4 | 5 | https://de.wikipedia.org/wiki/DCF77 6 | 7 | https://www.ptb.de/cms/fileadmin/internet/fachabteilungen/abteilung_4/4.4_zeit_und_frequenz/4.42/dcf77.pdf 8 | 9 | 10 | Based on an idea by Frank Boesing and DD4WH 11 | 12 | https://forum.pjrc.com/threads/40102-Time-signal-LF-Receiver-Teensy-DCF77-with-minimal-hardware-effort 13 | 14 | [![Teensy DCF77 video](http://img.youtube.com/vi/-SrumhKsAKk/0.jpg)](http://www.youtube.com/watch?v=-SrumhKsAKk) 15 | 16 | HARDWARE (Option 1 - very low budget < 1$/€): 17 | - a few meters of wire soldered to the MIC INPUT (optionally add a 100nF cap) 18 | - connect GND of the MIC input to a ground connection (the heating for example): NEVER EVER USE THE GND CONNECTION OF YOUR MAINS CONNECTOR! (Do not even think about that!) 19 | --> this option could possibly not work, if you are too far away from Frankfurt or have too much local noise from all your plasma TVs, switching power supplies etc. 20 | 21 | HARDWARE (Option 2 - low budget < 5$/€) 22 | - ferrite rod with a few hundred windings --> measure inductance L 23 | - choose parallel capacitor with capacitance C, so that 1 / (2 * PI * SQRT (L * C) == 77.5kHz 24 | should be in the range of a few nF 25 | - connect the parallel circuit to the MIC input (via a 100nF cap to prevent MIC bias short circuit!) and MIC GND 26 | --> this works very well here about 500km from Frankfurt inside a building with fairly high noise level 27 | ![](https://github.com/DD4WH/Teensy-DCF77/blob/master/IMG_2183.JPG) 28 | HARDWARE (Option 3 - about 10$ / €) 29 | - buy a DCF77 receive module and connect the output to the Teensy LINEIN 30 | --> this should be a bullet-proof solution, if you are inside the 2000km circle around Frankfurt 31 | 32 | SOFTWARE: 33 | - the Teensy audio board takes the MIC input signal and digitizes it with 192ksps (so you are able to receive up to 96kHz) 34 | - that is a Direct Sampling Receiver like the really expensive and professional SDRs 35 | - the signal is bandpass-filtered around 77.5kHz 36 | - the signal is fed to a 1024-point FFT for visual inspection AND for peak analysis in order to extract the time information bits 37 | - the signal is multiplied with a local oscillator working at 76900Hz 38 | that´s the principle of a DC (direct conversion) receiver, I think. 39 | 40 | Audio: 41 | - the signal is lowpass-filtered (however, the biquads are not very good at low frequencies, be careful here!) 42 | - you can hear the audio signal from DCF77 with a 600Hz tone (77500Hz-76900Hz) 43 | 44 | 45 | The "BIN" of FFT-Data which corresponds to 77.5kHz is now used for the remaining steps: 46 | 47 | 48 | AGC: 49 | The fft-bin-value is used to generate a very slow moving average. It is compared against upper and lower constants. A too high value decreases the gain, a too low value increases it. 50 | 51 | Decoding: 52 | A factor of 10 faster moving average is used to detect the bits. The signal is reduced to 25% at the beginning of a second for 100 or 200 milliseconds. These pauses result in a decreasing average value. A decreasing of more than 90ms is detected as a bit - if the duration is more than 150ms, it is "1", otherwise a "0". 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /DCF77_v0_4.ino: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | Copyright (c) 2016, Frank Bösing, f.boesing@gmx.de & Frank DD4WH, dd4wh.swl@gmail.com 3 | 4 | Teensy DCF77 Receiver & Real Time Clock 5 | 6 | uses only minimal hardware to receive accurate time signals 7 | 8 | For information on how to setup the antenna, see here: 9 | 10 | https://github.com/DD4WH/Teensy-DCF77/wiki 11 | */ 12 | #define VERSION " v0.42" 13 | /* 14 | Permission is hereby granted, free of charge, to any person obtaining a copy 15 | of this software and associated documentation files (the "Software"), to deal 16 | in the Software without restriction, including without limitation the rights 17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the Software is 19 | furnished to do so, subject to the following conditions: 20 | 21 | The above copyright notice, development funding notice, and this permission 22 | notice shall be included in all copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 | THE SOFTWARE. 31 | 32 | */ 33 | 34 | #include 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include "font_Arial.h" 42 | 43 | time_t getTeensy3Time() 44 | { 45 | return Teensy3Clock.get(); 46 | } 47 | 48 | //#include 49 | 50 | #define BACKLIGHT_PIN 51 | #define TFT_DC 20 52 | #define TFT_CS 21 53 | #define TFT_RST 32 // 255 = unused. connect to 3.3V 54 | #define TFT_MOSI 7 55 | #define TFT_SCLK 14 56 | #define TFT_MISO 12 57 | 58 | ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO); 59 | 60 | #define SAMPLE_RATE_MIN 0 61 | #define SAMPLE_RATE_8K 0 62 | #define SAMPLE_RATE_11K 1 63 | #define SAMPLE_RATE_16K 2 64 | #define SAMPLE_RATE_22K 3 65 | #define SAMPLE_RATE_32K 4 66 | #define SAMPLE_RATE_44K 5 67 | #define SAMPLE_RATE_48K 6 68 | #define SAMPLE_RATE_88K 7 69 | #define SAMPLE_RATE_96K 8 70 | #define SAMPLE_RATE_176K 9 71 | #define SAMPLE_RATE_192K 10 72 | #define SAMPLE_RATE_MAX 10 73 | 74 | 75 | AudioInputI2S i2s_in; //xy=202,411 76 | AudioSynthWaveformSine sine1; //xy=354,249 77 | AudioFilterBiquad biquad1; //xy=394,403 78 | AudioEffectMultiply mult1; //xy=594,250 79 | AudioFilterBiquad biquad2; //xy=761,248 80 | //AudioAnalyzeFFT256 myFFT; //xy=962,434 81 | AudioAnalyzeFFT1024 myFFT; //xy=962,434 82 | AudioOutputI2S i2s_out; //xy=975,247 83 | AudioConnection patchCord1(i2s_in, 0, biquad1, 0); 84 | AudioConnection patchCord2(sine1, 0, mult1, 1); 85 | AudioConnection patchCord3(biquad1, 0, mult1, 0); 86 | AudioConnection patchCord4(biquad1, myFFT); 87 | AudioConnection patchCord5(mult1, biquad2); 88 | AudioConnection patchCord6(biquad2, 0, i2s_out, 1); 89 | AudioConnection patchCord7(biquad2, 0, i2s_out, 0); 90 | AudioControlSGTL5000 sgtl5000_1; 91 | 92 | // Metro 1 second 93 | Metro second_timer = Metro(1000); 94 | 95 | const uint16_t FFT_points = 1024; 96 | //const uint16_t FFT_points = 256; 97 | 98 | int8_t mic_gain = 38 ;//start detecting with this MIC_GAIN in dB 99 | const float bandpass_q = 10; // be careful when increasing Q, distortion can occur with higher Q because of fixed point 16bit math in the biquads 100 | const float DCF77_FREQ = 77500.0; //DCF-77 77.65 kHz 101 | // start detecting at this frequency, so that 102 | // you can hear a 600Hz tone [77.5 - 76.9 = 0.6kHz] 103 | unsigned int freq_real = DCF77_FREQ - 600; 104 | 105 | //const unsigned int sample_rate = SAMPLE_RATE_176K; 106 | //unsigned int sample_rate_real = 176400; 107 | const unsigned int sample_rate = SAMPLE_RATE_192K; 108 | unsigned int sample_rate_real = 192000; 109 | 110 | 111 | unsigned int freq_LO = 7000; 112 | float dcf_signal = 0; 113 | float dcf_threshold = 0; 114 | float dcf_med = 0; 115 | unsigned int DCF_bin;// this is the FFT bin where the 77.5kHz signal is 116 | 117 | bool timeflag = 0; 118 | const int8_t pos_x_date = 14; 119 | const int8_t pos_y_date = 68; 120 | const int8_t pos_x_time = 14; 121 | const int8_t pos_y_time = 114; 122 | uint8_t hour10_old; 123 | uint8_t hour1_old; 124 | uint8_t minute10_old; 125 | uint8_t minute1_old; 126 | uint8_t second10_old; 127 | uint8_t second1_old; 128 | uint8_t precision_flag = 0; 129 | int8_t mesz = -1; 130 | int8_t mesz_old = 0; 131 | 132 | const float displayscale = 2.5; 133 | 134 | typedef struct SR_Descriptor 135 | { 136 | const int SR_n; 137 | const char* const f1; 138 | const char* const f2; 139 | const char* const f3; 140 | const char* const f4; 141 | const float32_t x_factor; 142 | } SR_Desc; 143 | 144 | // Text and position for the FFT spectrum display scale 145 | const SR_Descriptor SR[SAMPLE_RATE_MAX + 1] = 146 | { 147 | // SR_n , f1, f2, f3, f4, x_factor = pixels per f1 kHz in spectrum display 148 | { SAMPLE_RATE_8K, " 1", " 2", " 3", " 4", 64.0}, // which means 64 pixels per 1 kHz 149 | { SAMPLE_RATE_11K, " 1", " 2", " 3", " 4", 43.1}, 150 | { SAMPLE_RATE_16K, " 2", " 4", " 6", " 8", 64.0}, 151 | { SAMPLE_RATE_22K, " 2", " 4", " 6", " 8", 43.1}, 152 | { SAMPLE_RATE_32K, "5", "10", "15", "20", 80.0}, 153 | { SAMPLE_RATE_44K, "5", "10", "15", "20", 58.05}, 154 | { SAMPLE_RATE_48K, "5", "10", "15", "20", 53.33}, 155 | { SAMPLE_RATE_88K, "10", "20", "30", "40", 58.05}, 156 | { SAMPLE_RATE_96K, "10", "20", "30", "40", 53.33}, 157 | { SAMPLE_RATE_176K, "20", "40", "60", "80", 58.05}, 158 | { SAMPLE_RATE_192K, "20", "40", "60", "80", 53.33} // which means 53.33 pixels per 20kHz 159 | }; 160 | 161 | //const int myInput = AUDIO_INPUT_LINEIN; 162 | const int myInput = AUDIO_INPUT_MIC; 163 | 164 | //const char* const Days[7] = { "Samstag", "Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag"}; 165 | const char* const Days[7] = { "Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"}; 166 | 167 | void setup(); 168 | void loop(); 169 | 170 | //========================================================================= 171 | 172 | void setup() { 173 | 174 | Serial.begin(115200); 175 | 176 | setSyncProvider(getTeensy3Time); 177 | 178 | // Audio connections require memory. 179 | AudioMemory(16); 180 | 181 | // Enable the audio shield. select input. and enable output 182 | sgtl5000_1.enable(); 183 | sgtl5000_1.inputSelect(myInput); 184 | sgtl5000_1.volume(0.9); 185 | sgtl5000_1.micGain (mic_gain); 186 | sgtl5000_1.adcHighPassFilterDisable(); // does not help too much! 187 | 188 | // Init TFT display 189 | pinMode( BACKLIGHT_PIN, OUTPUT ); 190 | analogWrite( BACKLIGHT_PIN, 1023 ); 191 | tft.begin(); 192 | tft.setRotation( 3 ); 193 | tft.fillScreen(ILI9341_BLACK); 194 | tft.setCursor(14, 7); 195 | tft.setTextColor(ILI9341_ORANGE); 196 | tft.setFont(Arial_12); 197 | tft.print("Teensy DCF77 Receiver "); tft.print(VERSION); 198 | tft.setTextColor(ILI9341_WHITE); 199 | // display_settings(); 200 | 201 | set_sample_rate (sample_rate); 202 | set_freq_LO (freq_real); 203 | // decodeTelegram( 0x8b47c0501a821b80ULL ); 204 | displayDate(); 205 | displayClock(); 206 | displayPrecisionMessage(); 207 | } // END SETUP 208 | 209 | 210 | void loop() { 211 | 212 | if (myFFT.available()) 213 | { 214 | agc(); 215 | detectBit(); 216 | spectrum(); 217 | displayClock(); 218 | } 219 | // check_processor(); 220 | } 221 | 222 | void set_mic_gain(int8_t gain) { 223 | // AudioNoInterrupts(); 224 | sgtl5000_1.micGain (mic_gain); 225 | // AudioInterrupts(); 226 | // display_settings(); 227 | } // end function set_mic_gain 228 | 229 | void set_freq_LO(int freq) { 230 | // audio lib thinks we are still in 44118sps sample rate 231 | // therefore we have to scale the frequency of the local oscillator 232 | // in accordance with the REAL sample rate 233 | freq_LO = freq * (AUDIO_SAMPLE_RATE_EXACT / sample_rate_real); 234 | // if we switch to LOWER samples rates, make sure the running LO 235 | // frequency is allowed ( < 22k) ! If not, adjust consequently, so that 236 | // LO freq never goes up 22k, also adjust the variable freq_real 237 | if (freq_LO > 22000) { 238 | freq_LO = 22000; 239 | freq_real = freq_LO * (sample_rate_real / AUDIO_SAMPLE_RATE_EXACT) + 9; 240 | } 241 | AudioNoInterrupts(); 242 | sine1.frequency(freq_LO); 243 | AudioInterrupts(); 244 | // display_settings(); 245 | } // END of function set_freq_LO 246 | 247 | void display_settings() { 248 | tft.fillRect(14, 32, 200, 17, ILI9341_BLACK); 249 | tft.setCursor(14, 32); 250 | tft.setFont(Arial_12); 251 | tft.print("gain: "); tft.print (mic_gain); 252 | tft.print(" "); 253 | tft.print("freq: "); tft.print (freq_real); 254 | tft.print(" "); 255 | tft.fillRect(232, 32, 88, 17, ILI9341_BLACK); 256 | tft.setCursor(232, 32); 257 | tft.print(" "); 258 | tft.print(sample_rate_real / 1000); tft.print("k"); 259 | } 260 | 261 | void set_sample_rate (int sr) { 262 | switch (sr) { 263 | case SAMPLE_RATE_8K: 264 | sample_rate_real = 8000; 265 | break; 266 | case SAMPLE_RATE_11K: 267 | sample_rate_real = 11025; 268 | break; 269 | case SAMPLE_RATE_16K: 270 | sample_rate_real = 16000; 271 | break; 272 | case SAMPLE_RATE_22K: 273 | sample_rate_real = 22050; 274 | break; 275 | case SAMPLE_RATE_32K: 276 | sample_rate_real = 32000; 277 | break; 278 | case SAMPLE_RATE_44K: 279 | sample_rate_real = 44100; 280 | break; 281 | case SAMPLE_RATE_48K: 282 | sample_rate_real = 48000; 283 | break; 284 | case SAMPLE_RATE_88K: 285 | sample_rate_real = 88200; 286 | break; 287 | case SAMPLE_RATE_96K: 288 | sample_rate_real = 96000; 289 | break; 290 | case SAMPLE_RATE_176K: 291 | sample_rate_real = 176400; 292 | break; 293 | case SAMPLE_RATE_192K: 294 | sample_rate_real = 192000; 295 | break; 296 | } 297 | AudioNoInterrupts(); 298 | sample_rate_real = setI2SFreq(sample_rate_real); 299 | 300 | delay(200); // this delay seems to be very essential ! 301 | set_freq_LO (freq_real); 302 | 303 | // never set the lowpass freq below 1700 (empirically derived by ear ;-)) 304 | // distortion will occur because of precision issues due to fixed point 16bit in the biquads 305 | biquad2.setLowpass(0, 1700, 0.54); 306 | biquad2.setLowpass(1, 1700, 1.3); 307 | biquad2.setLowpass(2, 1700, 0.54); 308 | biquad2.setLowpass(3, 1700, 1.3); 309 | 310 | biquad1.setBandpass(0, DCF77_FREQ * (AUDIO_SAMPLE_RATE_EXACT / sample_rate_real), bandpass_q); 311 | biquad1.setBandpass(1, DCF77_FREQ * (AUDIO_SAMPLE_RATE_EXACT / sample_rate_real), bandpass_q); 312 | biquad1.setBandpass(2, DCF77_FREQ * (AUDIO_SAMPLE_RATE_EXACT / sample_rate_real), bandpass_q); 313 | biquad1.setBandpass(3, DCF77_FREQ * (AUDIO_SAMPLE_RATE_EXACT / sample_rate_real), bandpass_q); 314 | 315 | AudioInterrupts(); 316 | delay(20); 317 | DCF_bin = round((DCF77_FREQ / (sample_rate_real / 2.0)) * (FFT_points / 2)); 318 | Serial.print("DCF77_bin number: "); Serial.println(DCF_bin); 319 | 320 | // display_settings(); 321 | prepare_spectrum_display(); 322 | 323 | } // END function set_sample_rate 324 | 325 | void prepare_spectrum_display() { 326 | int base_y = 211; 327 | int b_x = 10; 328 | int x_f = SR[sample_rate].x_factor; 329 | tft.fillRect(0, base_y, 320, 240 - base_y, ILI9341_BLACK); 330 | // tft.drawFastHLine(b_x, base_y + 2, 256, ILI9341_PURPLE); 331 | // tft.drawFastHLine(b_x, base_y + 3, 256, ILI9341_PURPLE); 332 | tft.drawFastHLine(b_x, base_y + 2, 256, ILI9341_MAROON); 333 | tft.drawFastHLine(b_x, base_y + 3, 256, ILI9341_MAROON); 334 | // vertical lines 335 | tft.drawFastVLine(b_x - 4, base_y + 1, 10, ILI9341_YELLOW); 336 | tft.drawFastVLine(b_x - 3, base_y + 1, 10, ILI9341_YELLOW); 337 | tft.drawFastVLine( x_f + b_x, base_y + 1, 10, ILI9341_YELLOW); 338 | tft.drawFastVLine( x_f + 1 + b_x, base_y + 1, 10, ILI9341_YELLOW); 339 | tft.drawFastVLine( x_f * 2 + b_x, base_y + 1, 10, ILI9341_YELLOW); 340 | tft.drawFastVLine( x_f * 2 + 1 + b_x, base_y + 1, 10, ILI9341_YELLOW); 341 | if (x_f * 3 + b_x < 256 + b_x) { 342 | tft.drawFastVLine( x_f * 3 + b_x, base_y + 1, 10, ILI9341_YELLOW); 343 | tft.drawFastVLine( x_f * 3 + 1 + b_x, base_y + 1, 10, ILI9341_YELLOW); 344 | } 345 | if (x_f * 4 + b_x < 256 + b_x) { 346 | tft.drawFastVLine( x_f * 4 + b_x, base_y + 1, 10, ILI9341_YELLOW); 347 | tft.drawFastVLine( x_f * 4 + 1 + b_x, base_y + 1, 10, ILI9341_YELLOW); 348 | } 349 | tft.drawFastVLine( x_f * 0.5 + b_x, base_y + 1, 6, ILI9341_YELLOW); 350 | tft.drawFastVLine( x_f * 1.5 + b_x, base_y + 1, 6, ILI9341_YELLOW); 351 | tft.drawFastVLine( x_f * 2.5 + b_x, base_y + 1, 6, ILI9341_YELLOW); 352 | if (x_f * 3.5 + b_x < 256 + b_x) { 353 | tft.drawFastVLine( x_f * 3.5 + b_x, base_y + 1, 6, ILI9341_YELLOW); 354 | } 355 | if (x_f * 4.5 + b_x < 256 + b_x) { 356 | tft.drawFastVLine( x_f * 4.5 + b_x, base_y + 1, 6, ILI9341_YELLOW); 357 | } 358 | // text 359 | tft.setTextColor(ILI9341_WHITE); 360 | tft.setFont(Arial_9); 361 | int text_y_offset = 16; 362 | int text_x_offset = - 5; 363 | // zero 364 | tft.setCursor (b_x + text_x_offset, base_y + text_y_offset); 365 | tft.print(0); 366 | tft.setCursor (b_x + x_f + text_x_offset, base_y + text_y_offset); 367 | tft.print(SR[sample_rate].f1); 368 | tft.setCursor (b_x + x_f * 2 + text_x_offset, base_y + text_y_offset); 369 | tft.print(SR[sample_rate].f2); 370 | tft.setCursor (b_x + x_f * 3 + text_x_offset, base_y + text_y_offset); 371 | tft.print(SR[sample_rate].f3); 372 | tft.setCursor (b_x + x_f * 4 + text_x_offset, base_y + text_y_offset); 373 | tft.print(SR[sample_rate].f4); 374 | // tft.setCursor (b_x + text_x_offset + 256, base_y + text_y_offset); 375 | tft.print(" kHz"); 376 | 377 | tft.setFont(Arial_14); 378 | } // END prepare_spectrum_display 379 | 380 | 381 | void agc() { 382 | static unsigned long tspeed = millis(); //Timer for startup 383 | 384 | const float speed_agc_start = 0.995; //initial speed AGC 385 | const float speed_agc_run = 0.9995; 386 | static float speed_agc = speed_agc_start; 387 | static unsigned long tagc = millis(); //Timer for AGC 388 | 389 | const float speed_thr = 0.995; 390 | 391 | // tft.drawFastHLine(14, 220 - dcf_med, 256, ILI9341_BLACK); 392 | tft.drawFastHLine(220, 220 - dcf_med, 46, ILI9341_BLACK); 393 | dcf_signal = (abs(myFFT.output[DCF_bin]) + abs(myFFT.output[DCF_bin + 1])) * displayscale; 394 | if (dcf_signal > 175) dcf_signal = 175; 395 | else if (dcf_med == 0) dcf_med = dcf_signal; 396 | dcf_med = (1 - speed_agc) * dcf_signal + speed_agc * dcf_med; 397 | tft.drawFastHLine(220, 220 - dcf_med, 46, ILI9341_ORANGE); 398 | 399 | tft.drawFastHLine(220, 220 - dcf_threshold, 46, ILI9341_BLACK); 400 | dcf_threshold = (1 - speed_thr) * dcf_signal + speed_thr * dcf_threshold; 401 | tft.drawFastHLine(220, 220 - dcf_threshold, 46, ILI9341_GREEN); 402 | 403 | unsigned long t = millis(); 404 | //Slow down speed after a while 405 | if ((t - tspeed > 1500) && (t - tspeed < 3500) ) { 406 | if (speed_agc < speed_agc_run) { 407 | speed_agc = speed_agc_run; 408 | Serial.printf("Set AGC-Speed %f\n", speed_agc); 409 | } 410 | } 411 | 412 | if ((t - tagc > 2221) || (speed_agc == speed_agc_start)) { 413 | tagc = t; 414 | if ((dcf_med > 160) && (mic_gain > 30)) { 415 | mic_gain--; 416 | set_mic_gain(mic_gain); 417 | Serial.printf("(Gain: %d)", mic_gain); 418 | } 419 | if ((dcf_med < 100) && (mic_gain < 58)) { 420 | mic_gain++; 421 | set_mic_gain(mic_gain); 422 | Serial.printf("(Gain: %d)", mic_gain); 423 | } 424 | } 425 | } 426 | 427 | int getParity(uint32_t value) { 428 | int par = 0; 429 | while (value) { 430 | value = value & (value - 1); 431 | par = ~par; 432 | } 433 | return par & 1; 434 | } 435 | 436 | 437 | int decodeTelegram(uint64_t telegram) { 438 | uint16_t minute, hour, day, weekday, month, year, v10; 439 | int parity; 440 | 441 | //Plausibility checks and decoding telegram 442 | //Example-Data: 0x8b47c14f468f9ec0ULL : 2016/11/20 443 | 444 | //https://de.wikipedia.org/wiki/DCF77 445 | 446 | //TODO : more plausibility-checks to prevent false positives 447 | 448 | //Check fixed - bits: 449 | if ( ((telegram & 1) != 0) || ((telegram >> 20) & 1) == 0) { 450 | Serial.println("Fixed-Bit error\n"); 451 | return 0; 452 | } 453 | 454 | //MESZ Central European Summer Time ? 455 | mesz = (telegram >> 17) & 1; 456 | if ( mesz != (~(telegram >> 18) & 1) ) { 457 | Serial.println("MESZ-Bit error\n"); 458 | return 0; 459 | } 460 | 461 | //1. decode date & date-parity-bit 462 | parity = telegram >> 58 & 0x01; 463 | if (getParity( (telegram >> 36) & 0x3fffff) != parity) return 0; 464 | year = ((telegram >> 54) & 0x0f) * 10 + ((telegram >> 50) & 0x0f); 465 | if (year < 16) return 0; 466 | 467 | month = ((telegram >> 45) & 0x0f); 468 | if (month > 9) return 0; 469 | 470 | month = ((telegram >> 49) & 0x01) * 10 + month; 471 | if ((month == 0) || (month > 12)) return 0; 472 | 473 | weekday = ((telegram >> 42) & 0x07); 474 | if (weekday == 0) return 0; 475 | 476 | day = ((telegram >> 36) & 0x0f); 477 | if (day > 9) return 0; 478 | day = ((telegram >> 40) & 0x03) * 10 + day; 479 | if ( (day == 0) || (day > 31) ) return 0;//Todo add check on 29.feb, 30/31 and more... 480 | 481 | 482 | //2. decode time & parity-bit 483 | parity = telegram >> 35 & 0x01; 484 | if (getParity( (telegram >> 29) & 0x3f) != parity) return 0; 485 | hour = (telegram >> 29 & 0x0f); 486 | if (hour > 9) return 0; 487 | v10 = (telegram >> 33 & 0x03); 488 | if (v10 > 2) return 0; 489 | hour = v10 * 10 + hour; 490 | if (hour > 23) return 0; 491 | 492 | parity = telegram >> 28 & 0x01; 493 | if (getParity( (telegram >> 21) & 0x7f ) != parity) return 0; 494 | minute = (telegram >> 21 & 0x0f); 495 | if (minute > 9) return 0; 496 | v10 = (telegram >> 25 & 0x07); 497 | if (v10 > 5) return 0; 498 | minute = v10 * 10 + minute; 499 | if (minute > 59) return 0; 500 | 501 | 502 | //All data seem to be ok. 503 | Serial.printf("Time set: %d.%d.20%d %d:%02d %s\n", day, month, year, hour, minute, mesz ? "MESZ" : "MEZ"); 504 | setTime (hour, minute, 0, day, month, year); 505 | Teensy3Clock.set(now()); 506 | displayDate(); 507 | return 1; 508 | } 509 | 510 | void displayPrecisionMessage() { 511 | if (precision_flag) { 512 | tft.fillRect(14, 32, 300, 18, ILI9341_BLACK); 513 | tft.setCursor(14, 32); 514 | tft.setFont(Arial_11); 515 | tft.setTextColor(ILI9341_GREEN); 516 | tft.print("Full precision of time and date"); 517 | tft.drawRect(290, 4, 20, 20, ILI9341_GREEN); 518 | } 519 | else 520 | { 521 | tft.fillRect(14, 32, 300, 18, ILI9341_BLACK); 522 | tft.setCursor(14, 32); 523 | tft.setFont(Arial_11); 524 | tft.setTextColor(ILI9341_RED); 525 | tft.print("Unprecise, trying to collect data"); 526 | tft.drawRect(290, 4, 20, 20, ILI9341_RED); 527 | } 528 | } // end function displayPrecisionMessage 529 | 530 | int decode(unsigned long t) { 531 | static uint64_t data = 0; 532 | static int sec = 0; 533 | static unsigned long tlastBit = 0; 534 | int bit; 535 | unsigned long m; 536 | 537 | m = millis(); 538 | if ( m - tlastBit > 1600) { 539 | Serial.printf(" End Of Telegram. Data: 0x%llx %d Bits\n", data, sec); 540 | tft.fillRect(14, 54, 59 * 5, 3, ILI9341_BLACK); 541 | if (sec == 59) { 542 | precision_flag = decodeTelegram(data); 543 | displayPrecisionMessage(); 544 | } 545 | 546 | sec = 0; 547 | data = 0; 548 | } 549 | tlastBit = m; 550 | 551 | bit = (t > 150) ? 1 : 0; 552 | Serial.print(bit); 553 | 554 | // plot horizontal bar 555 | tft.fillRect(14 + 5 * sec, 54, 3, 3, bit ? ILI9341_YELLOW : ILI9341_PURPLE); 556 | data = ( data >> 1) | ((uint64_t)bit << 58); 557 | 558 | sec++; 559 | if (sec > 59) { // just to prevent accidents with weak signals ;-) 560 | sec = 0; 561 | } 562 | return bit; 563 | } 564 | 565 | void detectBit() { 566 | static float dcf_threshold_last = 1000; 567 | static unsigned long secStart = 0; 568 | 569 | if ( dcf_threshold <= dcf_threshold_last) 570 | { 571 | if (secStart == 0) { 572 | secStart = millis(); 573 | } 574 | } 575 | 576 | else { 577 | unsigned long t = millis() - secStart; 578 | if ((secStart > 0) && (t > 90)) { 579 | int bit = decode(t); 580 | 581 | tft.fillRect(291, 5, 18, 18, ILI9341_BLACK); 582 | tft.setFont(Arial_12); 583 | tft.setTextColor(ILI9341_WHITE); 584 | tft.setCursor(295, 8); 585 | tft.print(bit); 586 | } 587 | secStart = 0; 588 | } 589 | dcf_threshold_last = dcf_threshold; 590 | } 591 | 592 | 593 | void spectrum() { // spectrum analyser code by rheslip - modified 594 | 595 | static int barm [512]; 596 | 597 | for (unsigned int x = 2; x < FFT_points / 2; x++) { 598 | int bar = abs(myFFT.output[x]) * (int)(displayscale * 2.0); 599 | if (bar > 175) bar = 175; 600 | 601 | // this is a very simple first order IIR filter to smooth the reaction of the bars 602 | bar = 0.05 * bar + 0.95 * barm[x]; 603 | tft.drawPixel(x / 2 + 10, 210 - barm[x], ILI9341_BLACK); 604 | tft.drawPixel(x / 2 + 10, 210 - bar, ILI9341_WHITE); 605 | barm[x] = bar; 606 | } 607 | } // end void spectrum 608 | 609 | 610 | 611 | int setI2SFreq(int freq) { 612 | typedef struct { 613 | uint8_t mult; 614 | uint16_t div; 615 | } tmclk; 616 | 617 | const int numfreqs = 14; 618 | const int samplefreqs[numfreqs] = { 8000, 11025, 16000, 22050, 32000, 44100, (int)44117.64706 , 48000, 88200, (int)44117.64706 * 2, 96000, 176400, (int)44117.64706 * 4, 192000}; 619 | 620 | #if (F_PLL==16000000) 621 | const tmclk clkArr[numfreqs] = {{16, 125}, {148, 839}, {32, 125}, {145, 411}, {64, 125}, {151, 214}, {12, 17}, {96, 125}, {151, 107}, {24, 17}, {192, 125}, {127, 45}, {48, 17}, {255, 83} }; 622 | #elif (F_PLL==72000000) 623 | const tmclk clkArr[numfreqs] = {{32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {128, 1125}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {128, 375}, {249, 397}, {32, 51}, {185, 271} }; 624 | #elif (F_PLL==96000000) 625 | const tmclk clkArr[numfreqs] = {{8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {32, 375}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125}, {151, 321}, {8, 17}, {64, 125} }; 626 | #elif (F_PLL==120000000) 627 | const tmclk clkArr[numfreqs] = {{32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {128, 1875}, {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {128, 625}, {178, 473}, {32, 85}, {145, 354} }; 628 | #elif (F_PLL==144000000) 629 | const tmclk clkArr[numfreqs] = {{16, 1125}, {49, 2500}, {32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {4, 51}, {32, 375}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {128, 375} }; 630 | #elif (F_PLL==168000000) 631 | const tmclk clkArr[numfreqs] = {{32, 2625}, {21, 1250}, {64, 2625}, {21, 625}, {128, 2625}, {42, 625}, {8, 119}, {64, 875}, {84, 625}, {16, 119}, {128, 875}, {168, 625}, {32, 119}, {189, 646} }; 632 | #elif (F_PLL==180000000) 633 | const tmclk clkArr[numfreqs] = {{46, 4043}, {49, 3125}, {73, 3208}, {98, 3125}, {183, 4021}, {196, 3125}, {16, 255}, {128, 1875}, {107, 853}, {32, 255}, {219, 1604}, {214, 853}, {64, 255}, {219, 802} }; 634 | #elif (F_PLL==192000000) 635 | const tmclk clkArr[numfreqs] = {{4, 375}, {37, 2517}, {8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {1, 17}, {8, 125}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125} }; 636 | #elif (F_PLL==216000000) 637 | const tmclk clkArr[numfreqs] = {{32, 3375}, {49, 3750}, {64, 3375}, {49, 1875}, {128, 3375}, {98, 1875}, {8, 153}, {64, 1125}, {196, 1875}, {16, 153}, {128, 1125}, {226, 1081}, {32, 153}, {147, 646} }; 638 | #elif (F_PLL==240000000) 639 | const tmclk clkArr[numfreqs] = {{16, 1875}, {29, 2466}, {32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {4, 85}, {32, 625}, {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {128, 625} }; 640 | #endif 641 | 642 | for (int f = 0; f < numfreqs; f++) { 643 | if ( freq == samplefreqs[f] ) { 644 | while (I2S0_MCR & I2S_MCR_DUF) ; 645 | I2S0_MDR = I2S_MDR_FRACT((clkArr[f].mult - 1)) | I2S_MDR_DIVIDE((clkArr[f].div - 1)); 646 | return round(((float)F_PLL / 256.0) * clkArr[f].mult / clkArr[f].div); //return real freq 647 | } 648 | } 649 | return 0; 650 | } 651 | 652 | void check_processor() { 653 | if (second_timer.check() == 1) { 654 | Serial.print("Proc = "); 655 | Serial.print(AudioProcessorUsage()); 656 | Serial.print(" ("); 657 | Serial.print(AudioProcessorUsageMax()); 658 | Serial.print("), Mem = "); 659 | Serial.print(AudioMemoryUsage()); 660 | Serial.print(" ("); 661 | Serial.print(AudioMemoryUsageMax()); 662 | Serial.println(")"); 663 | /* tft.fillRect(100,120,200,80,ILI9341_BLACK); 664 | tft.setCursor(10, 120); 665 | tft.setTextSize(2); 666 | tft.setTextColor(ILI9341_WHITE); 667 | tft.setFont(Arial_14); 668 | tft.print ("Proc = "); 669 | tft.setCursor(100, 120); 670 | tft.print (AudioProcessorUsage()); 671 | tft.setCursor(180, 120); 672 | tft.print (AudioProcessorUsageMax()); 673 | tft.setCursor(10, 150); 674 | tft.print ("Mem = "); 675 | tft.setCursor(100, 150); 676 | tft.print (AudioMemoryUsage()); 677 | tft.setCursor(180, 150); 678 | tft.print (AudioMemoryUsageMax()); 679 | */ 680 | AudioProcessorUsageMaxReset(); 681 | AudioMemoryUsageMaxReset(); 682 | } 683 | } // END function check_processor 684 | 685 | void displayClock() { 686 | 687 | uint8_t hour10 = hour() / 10 % 10; 688 | uint8_t hour1 = hour() % 10; 689 | uint8_t minute10 = minute() / 10 % 10; 690 | uint8_t minute1 = minute() % 10; 691 | uint8_t second10 = second() / 10 % 10; 692 | uint8_t second1 = second() % 10; 693 | uint8_t time_pos_shift = 26; 694 | tft.setFont(Arial_28); 695 | tft.setTextColor(ILI9341_WHITE); 696 | uint8_t dp = 14; 697 | 698 | if (mesz != mesz_old && mesz >= 0) { 699 | tft.setTextColor(ILI9341_ORANGE); 700 | tft.setFont(Arial_16); 701 | tft.setCursor(pos_x_date, pos_y_date+20); 702 | tft.fillRect(pos_x_date, pos_y_date+20, 150-pos_x_date, 20, ILI9341_BLACK); 703 | tft.printf((mesz==0)?"(CET)":"(CEST)"); 704 | } 705 | 706 | tft.setFont(Arial_28); 707 | tft.setTextColor(ILI9341_WHITE); 708 | 709 | // set up ":" for time display 710 | if (!timeflag) { 711 | tft.setCursor(pos_x_time + 2 * time_pos_shift, pos_y_time); 712 | tft.print(":"); 713 | tft.setCursor(pos_x_time + 4 * time_pos_shift + dp, pos_y_time); 714 | tft.print(":"); 715 | tft.setCursor(pos_x_time + 7 * time_pos_shift + 2 * dp, pos_y_time); 716 | // tft.print("UTC"); 717 | } 718 | 719 | if (hour10 != hour10_old || !timeflag) { 720 | tft.setCursor(pos_x_time, pos_y_time); 721 | tft.fillRect(pos_x_time, pos_y_time, time_pos_shift, time_pos_shift + 2, ILI9341_BLACK); 722 | if (hour10) tft.print(hour10); // do not display, if zero 723 | } 724 | if (hour1 != hour1_old || !timeflag) { 725 | tft.setCursor(pos_x_time + time_pos_shift, pos_y_time); 726 | tft.fillRect(pos_x_time + time_pos_shift, pos_y_time, time_pos_shift, time_pos_shift + 2, ILI9341_BLACK); 727 | tft.print(hour1); // always display 728 | } 729 | if (minute1 != minute1_old || !timeflag) { 730 | tft.setCursor(pos_x_time + 3 * time_pos_shift + dp, pos_y_time); 731 | tft.fillRect(pos_x_time + 3 * time_pos_shift + dp, pos_y_time, time_pos_shift, time_pos_shift + 2, ILI9341_BLACK); 732 | tft.print(minute1); // always display 733 | } 734 | if (minute10 != minute10_old || !timeflag) { 735 | tft.setCursor(pos_x_time + 2 * time_pos_shift + dp, pos_y_time); 736 | tft.fillRect(pos_x_time + 2 * time_pos_shift + dp, pos_y_time, time_pos_shift, time_pos_shift + 2, ILI9341_BLACK); 737 | tft.print(minute10); // always display 738 | } 739 | if (second10 != second10_old || !timeflag) { 740 | tft.setCursor(pos_x_time + 4 * time_pos_shift + 2 * dp, pos_y_time); 741 | tft.fillRect(pos_x_time + 4 * time_pos_shift + 2 * dp, pos_y_time, time_pos_shift, time_pos_shift + 2, ILI9341_BLACK); 742 | tft.print(second10); // always display 743 | } 744 | if (second1 != second1_old || !timeflag) { 745 | tft.setCursor(pos_x_time + 5 * time_pos_shift + 2 * dp, pos_y_time); 746 | tft.fillRect(pos_x_time + 5 * time_pos_shift + 2 * dp, pos_y_time, time_pos_shift, time_pos_shift + 2, ILI9341_BLACK); 747 | tft.print(second1); // always display 748 | } 749 | 750 | hour1_old = hour1; 751 | hour10_old = hour10; 752 | minute1_old = minute1; 753 | minute10_old = minute10; 754 | second1_old = second1; 755 | second10_old = second10; 756 | mesz_old = mesz; 757 | timeflag = 1; 758 | 759 | } // end function displayTime 760 | 761 | void displayDate() { 762 | char string99 [20]; 763 | tft.fillRect(pos_x_date, pos_y_date, 320 - pos_x_date, 20, ILI9341_BLACK); // erase old string 764 | tft.setTextColor(ILI9341_ORANGE); 765 | tft.setFont(Arial_16); 766 | tft.setCursor(pos_x_date, pos_y_date); 767 | // Date: %s, %d.%d.20%d P:%d %d", Days[weekday-1], day, month, year 768 | sprintf(string99, "%s, %02d.%02d.%04d", Days[weekday()], day(), month(), year()); 769 | tft.print(string99); 770 | } // end function displayDate 771 | --------------------------------------------------------------------------------