DCF77 Radio Time Signal

Posted on
  • Hi,

    Thought you might find this interesting. I'll try and post it into a module/tutorial soon, but this code decodes the time signal from the DCF77 time transmissions which cover europe on 77.5kHz.

    Someone's selling receivers on eBay here or you can almost certainly rip one out of an old radio clock.

    var minuteData = "";
    var lastData = getTime();
    
    // Decode 4 bits into a number
    function decode4(s) {
      return (0|s[0])*1 + (0|s[1])*2 + (0|s[2])*4 + (0|s[3])*8;
    } 
    
    // xor all items in s and return the result (for parity checks)
    function xor(s) {
      var r = 0;
      for (var i=0;i<s.length;i++) r^=s[i];
      return r;
    }
    
    // decode the DCF77 time transmission
    function decodeMinute(d) {
      console.log(d, d.length);
      
      if (xor(d.substr(21,7))!=d[28]) { 
        console.log("Bad minutes");
        return;
      }
      var minute = decode4(d.substr(21,4)) + decode4(d.substr(25,3))*10;
      if (xor(d.substr(29,6))!=d[35]) { 
        console.log("Bad hours");
        return;
      }
      var hour = decode4(d.substr(29,4)) + decode4(d.substr(33,2))*10;
      if (xor(d.substr(36,22))!=d[58]) { 
        console.log("Bad date");
        return;
      }
      var day = decode4(d.substr(36,4)) + decode4(d.substr(40,2))*10;
      var doy = decode4(d.substr(42,3));
      var month = decode4(d.substr(45,4)) + decode4(d.substr(49,1))*10;
      var year = decode4(d.substr(50,4)) + decode4(d.substr(54,4))*10;
      console.log(hour+":"+minute+", "+day+"/"+month+"/"+year);
      
      var date = new Date(2000+year, month-1, day, hour, minute, 0, 0);
      console.log(date.toString());
    }
    
    function onSec(e) {  
      // Work out what bit we got
      var d = e.time-e.lastTime;
      var bit = (d<0.15)?0:1;
      // if we had a 2 sec gap then it's the beginning of a minute
      if (e.time-lastData > 1.5) {
        decodeMinute(minuteData);
        minuteData = "";
      }
      lastData = e.time;  
      // now add this bit of data
      minuteData += bit;
    }
    
    setWatch(onSec, dataPin, { edge:"falling", repeat:true, debounce:75 });
    
  • Hi Gordon,

    i use the receiver from Conrad. It is a little bit different from the one on Pollin.

    i tried your sample code:

    require("DCF77").connect(B5, function(err, date, info) {
      if (err)
        console.log("Invalid time received: " + err);
      else
        console.log(date.toString(), info);
    });
    
    

    But i haven't got any valid results. So i tried the dcf77 library from (http://thijs.elenbaas.net/2012/04/arduino-dcf77-radio-clock-receiver-library/)
    with an arduino one.

    At the beginning i didn't get valid result there either, but then i tested his debug code.
    i saw that his pulsewitdh differed from mine.

    he had 130 and 230ms.
    on my side the pulses where 80 and 180ms:

    Cycle: 904 Pulse :185
    Cycle: 999 Pulse :88
    Cycle: 1002 Pulse :84
    Cycle: 992 Pulse :191
    Cycle: 1008 Pulse :87
    Cycle: 1007 Pulse :178
    

    After i changed the DCFSplitTime from 180 to 120 in his dcf77.h...

    _#define DCFSplitTime 120
    

    ... i got a vaild time.

    Waiting for DCF77 time ... 
    It will take at least 2 minutes until a first update can be processed.
    0:00:01 1 1 1970
    0:00:02 1 1 1970
    0:00:03 1 1 1970
    0:00:04 1 1 1970
    0:00:05 1 1 1970
    0:00:06 1 1 1970
    ...
    0:02:09 1 1 1970
    0:02:10 1 1 1970
    0:02:11 1 1 1970
    0:02:12 1 1 1970
    0:02:13 1 1 1970
    Time is updated
    22:26:01 15 11 2015
    22:26:02 15 11 2015
    22:26:03 15 11 2015
    22:26:04 15 11 2015
    22:26:05 15 11 2015
    ...
    22:26:58 15 11 2015
    22:26:59 15 11 2015
    22:27:00 15 11 2015
    Time is updated
    22:27:01 15 11 2015
    22:27:02 15 11 2015
    22:27:03 15 11 2015
    ...
    

    How can i achieve this with the espruino?

    PS: i also used a 10k resistor to pull up the Data Pin.

  • i guess the problem could be the setWatch function.
    i'm using the wifi adapter and an i2c oled display on the same espruino.

    so i tried a emtpy one with only the DCF77 sample.
    but i still don't get a valid time:

     _____                 _
    |   __|___ ___ ___ _ _|_|___ ___
    |   __|_ -| . |  _| | | |   | . |
    |_____|___|  _|_| |___|_|_|_|___|
              |_| http://espruino.com
     1v80 Copyright 2015 G.Williams
    >echo(0);
    =undefined
    Erasing Flash.....
    Writing..........
    Compressed 81600 bytes to 7529
    Checking...
    Done!
    10
    20
    30
    40
    50
    60
    Invalid time received: Bad date
    70
    80
    90
    100
    110
    120
    Invalid time received: Bad minutes
    130
    140
    150
    160
    170
    180
    190
    200
    210
    220
    230
    Invalid time received: Bad minutes
    240
    250
    260
    270
    280
    290
    Invalid time received: Bad date
    300
    310
    320
    330
    340
    Invalid time received: Bad minutes
    350
    360
    370
    380
    390
    400
    Fri Apr 25 2036 21:13:00 GMT+0000 { "CEST": true, "CET": true }
    
  • i updated the firmware and switched to pin B6.

    now i get a valid time :)

     _____                 _
    |   __|___ ___ ___ _ _|_|___ ___
    |   __|_ -| . |  _| | | |   | . |
    |_____|___|  _|_| |___|_|_|_|___|
              |_| http://espruino.com
     1v81 Copyright 2015 G.Williams
    >echo(0);
    =undefined
    Erasing Flash.....
    Writing.......
    Compressed 81600 bytes to 4745
    Checking...
    Done!
    pulsewidth: 0.16911602020
    Invalid time received: Bad minutes
    pulsewidth: 0.09433269500
    pulsewidth: 0.09107685089
    pulsewidth: 0.08496856689
    pulsewidth: 0.08354568481
    pulsewidth: 0.08212578296
    pulsewidth: 0.09331417083
    pulsewidth: 0.17105960845
    pulsewidth: 0.17469120025
    pulsewidth: 0.07842826843
    pulsewidth: 0.17037200927
    pulsewidth: 0.07430648803
    pulsewidth: 0.17654514312
    pulsewidth: 0.07944297790
    pulsewidth: 0.08435630798
    pulsewidth: 0.17442226409
    pulsewidth: 0.08007431030
    pulsewidth: 0.08116531372
    pulsewidth: 0.08446788787
    pulsewidth: 0.17115879058
    pulsewidth: 0.17611885070
    pulsewidth: 0.07796955108
    pulsewidth: 0.17144978046
    pulsewidth: 0.08308219909
    pulsewidth: 0.17642116546
    pulsewidth: 0.08210074901
    pulsewidth: 0.08147811889
    pulsewidth: 0.08908569812
    pulsewidth: 0.17521846294
    pulsewidth: 0.08436203002
    Invalid time received: Bad minutes
    pulsewidth: 0.08617496490
    pulsewidth: 0.17422580718
    pulsewidth: 0.17254734039
    pulsewidth: 0.08129310607
    pulsewidth: 0.08000469207
    pulsewidth: 0.07793712615
    pulsewidth: 0.08469009399
    pulsewidth: 0.17134666442
    pulsewidth: 0.07955360412
    pulsewidth: 0.08358573913
    pulsewidth: 0.17171764373
    pulsewidth: 0.16930770874
    pulsewidth: 0.16940021514
    pulsewidth: 0.17125415802
    pulsewidth: 0.07798385620
    pulsewidth: 0.07773876190
    pulsewidth: 0.08058834075
    pulsewidth: 0.16745769977
    pulsewidth: 0.08179664611
    pulsewidth: 0.17019271850
    pulsewidth: 0.08226764202
    pulsewidth: 0.16649723052
    pulsewidth: 0.07572460174
    pulsewidth: 0.07742798328
    pulsewidth: 0.17198657989
    pulsewidth: 0.07287502288
    pulsewidth: 0.08357334136
    pulsewidth: 0.08595657348
    pulsewidth: 0.07994079589
    pulsewidth: 0.07726287841
    pulsewidth: 0.08137893676
    pulsewidth: 0.08567523956
    pulsewidth: 0.08365154266
    pulsewidth: 0.08036613464
    pulsewidth: 0.07942295074
    pulsewidth: 0.08462131023
    pulsewidth: 0.17574119567
    pulsewidth: 0.17203903198
    pulsewidth: 0.06586933135
    pulsewidth: 0.17288875579
    pulsewidth: 0.08118247985
    pulsewidth: 0.17242527008
    pulsewidth: 0.07482910156
    pulsewidth: 0.08241367340
    pulsewidth: 0.17064762115
    pulsewidth: 0.07899856567
    pulsewidth: 0.08000206947
    pulsewidth: 0.08048152923
    pulsewidth: 0.17255878448
    pulsewidth: 0.17012310028
    pulsewidth: 0.07951927185
    pulsewidth: 0.17014408111
    pulsewidth: 0.08236026763
    pulsewidth: 0.16853904724
    pulsewidth: 0.08116734027
    pulsewidth: 0.08185195922
    pulsewidth: 0.08379852771
    pulsewidth: 0.17349720001
    pulsewidth: 0.08322811126
    Mon Nov 16 2015 00:12:00 GMT+0000 { "CEST": true, "CET": true }
    
  • Ahh, great! Glad you got it sorted!

    I guess it could have been a firmware issue then? I'm surprised if the pin that was used made any difference.

  • Hi,

    i'm, sorry but that was only one lucky shot. i didn't get a valid time after that try.
    the pulswidth varies very much. sometimes its about 600ms long. some times the pulses a triggert much faster than one per second.

    i will try to record that tonight.

  • That seems very strange... The Pico's RTC can sometimes be as much as 10% out because it's using the internal oscillator (that will be fixed soon hopefully), but it shouldn't be out by any more than that.

    Maybe you could connect an LED across the signal wire from the radio so you can see what it's really doing?

    It's possible that it's just receiving a lot of noise alongside the signal. The signal itself is very weak. To get to reliable I had to put mine right near the window.

  • i live less then 100km away from frankfurt. i will try again without the wireless shield and the oled display.

  • I suppose it's possible that the wireless (ESP8266?) is causing quite a lot of noise on the power supply lines, and that could be upsetting your receiver?

  • I tried a 10µF capacitor between + and - of the DCF77 shield. But didn't see any difference.

  • not working:
    https://youtu.be/LK4eFga2P7E

    working:

    I think i will buy a new DCF receiver. i can get it to work but its not stable. not on the arduino nor on the espruino.

    Code:

    function wlan() {
    
      digitalWrite(B9,1); // enable on Pico Shim V2
      Serial2.setup(115200, { rx: A3, tx : A2 });
      var wifi = require("ESP8266WiFi_0v25").connect(Serial2, function(err) {
        if (err) throw err;
    
        wifi.reset(function(err) {
          if (err) throw err;
          console.log("Connecting to WiFi");
          wifi.getAPs(function(err, aps) {
            console.log("Scanning Aps");
            //console.log(aps);
          });
    
          wifi.connect("XXX","XXX", function(err) {
            if (err){
              return console.log("NO Wifi");
            }
    
            var myip;
    
            wifi.getIP(function(err, ip) {
              myip = ip;
              console.log("Connected to:",myip);
              //printscreen(myip,13);
            });
    
    
            var http = require("http");
            http.createServer(function (req, res) {
    
                var a = url.parse(req.url, true);
    
                text1 = (a.query && a.query.t1 || "");
                text2 = (a.query && a.query.t2 || "");
                
                if (a.query && (a.query.t1 || a.query.t2)){
                  printscreen(text1,10);
                  setTimeout(function(){printscreen(myip,13);},2500);
                }
    
                res.writeHead(200, {'Content-Type': 'text/html'});
                res.write('<html><head><meta name="viewport" content="width=320"/></head><body>');
                res.write('LCD Display:<input id="t1" value="'+text1+'" type="text"><br>');
                res.write('LCD Display:<input id="t2" value="'+text2+'"type="text"><br>');
                res.write('<input type="button" onclick="get()" value="senden"><br>');
                res.write('<script> function get(){var e;window.XMLHttpRequest&&(e=new XMLHttpRequest),e.onreadystatechange=function(){4==e.readyState&&200==e.status&&console.log("done")};var t=document.getElementById("t1").value,n=document.getElementById("t2").value;e.open("GET","lcd/?t1="+t+"&t2="+n,!0),e.send()} </script>');
                res.end('</body></html>');
    
            }).listen(80);
    
    
          });
        });
      });
    
    }
    
    
    function onInit(){
      console.log("start");
      wlan();
      oled();
    }
    
    
    var img2 = {
      width : 32, height : 32, bpp : 1,
      transparent : 0,
      buffer : E.toArrayBuffer(atob("AAAAAAAAeAAAf/4AH/+bAH/ABQBoAAeAaAADwHQAAf5aAfCfS/8/sUXP//1G//AdQ/wAHUAAAB1AAAAdQAAAHWAAABNgAAAeYAAAEOAAABDgAAAQ8AAAGPgAABz4AAAcfAAAnD4AP/A+B//AHz//wA///4AAP+AAABgAAAAAAAA="))
    };
    
    function image(){
      //g.clear();
      g.drawImage(img2, 96, 0);
      //g.flip();
    }
    
    
    
    var g;
    function printscreen(line1,line2,size){
    
      if (init !== 0){
        clearTimeout(init);
        init=0;
      }
      g.clear();
      // write some text
      g.setFontVector(size);
      g.drawString(line1,2,2);
      g.drawString(line2,2,20);
      // write to the screen
      g.flip();
    
    }
    
    var init=0;
    var step=1;
    function clear(){
    
      var p;
      var p1=".  ";
      var p2=" . ";
      var p3="  .";
     
      if (step==1){
        p=p1;
      }
      if (step==2){
        p=p2;
      }
      if (step==3){
        p=p3;
      }
      step++;
      if (step == 4) step=1;
      
      g.clear();
      g.setFontVector(14);
      g.drawString("waiting "+p,2,4);
      
      image();
      g.flip();
      
      init=setTimeout(clear,300);
      
    }
    
    function oled(){
    
      digitalWrite(B4, LOW);
      digitalWrite(B4, HIGH);
      I2C2.setup({scl:B10, sda:B3});
      g = require("SSD1306").connect(I2C2, clear,{ height : 32 });
    
    }
    
    
    function DCF77(){
    
      // Decode 4 bits into a number
      function decode4(s) {
        return (0|s[0])*1 + (0|s[1])*2 + (0|s[2])*4 + (0|s[3])*8;
      }
    
      // xor all items in s and return the result (for parity checks)
      function xor(s) {
        var r = 0;
        for (var i=0;i<s.length;i++) r^=s[i];
        return r;
      }
    
      // decode the DCF77 time transmission
      function decode(d, callback) {
        if (xor(d.substr(21,7))!=d[28])
          return callback("Bad minutes");
    
        var minute = decode4(d.substr(21,4)) + decode4(d.substr(25,3))*10;
        if (xor(d.substr(29,6))!=d[35]) 
          return callback("Bad hours");
    
        var hour = decode4(d.substr(29,4)) + decode4(d.substr(33,2))*10;
        if (xor(d.substr(36,22))!=d[58]) 
          return callback("Bad date");
    
        var day = decode4(d.substr(36,4)) + decode4(d.substr(40,2))*10;
        var doy = decode4(d.substr(42,3));
        var month = decode4(d.substr(45,4)) + decode4(d.substr(49,1))*10;
        var year = decode4(d.substr(50,4)) + decode4(d.substr(54,4))*10;
        //console.log(hour+":"+minute+", "+day+"/"+month+"/"+year);
    
        var date = new Date(2000+year, month-1, day, hour, minute, 0, 0);
        return callback(null, date, { CEST:!!d[17], CET:!!d[18] } );
      }
    
    
      this.connect = function(dataPin, callback) {
        var dcf = {
          last : getTime(),
          bits : ""
        };
    
        setWatch(function (e) {  
          // Work out what bit we got
          var d = e.time-e.lastTime;
          console.log("pulsewidth:",(d*1000).toString().substring(0, 3));
          var bit = (d<0.12)?0:1;
          // if we had a 2 sec gap then it's the beginning of a minute
          if (e.time - dcf.last > 1.5) {
            decode(dcf.bits, callback);
            dcf.bits = "";
          }
          dcf.last = e.time;  
          // now add this bit of data
          dcf.bits += bit;
          if (dcf.bits.length>59)
            dcf.bits = dcf.bits.substr(-59);
        }, dataPin, { edge:"falling", repeat:true, debounce:60 });
    
        return dcf;
      };
    }
    
    
    
    var Clock = require("clock").Clock;
    var clk=new Clock(0);
    
    
      
    var dcf = new DCF77();
    dcf.connect(B6, function(err, date, info) {
      if (err){
        console.log("Invalid time received: " + err);
      }else{
        console.log(date.toString(), info);
        clk.setClock(date.getTime());
      }
    
    });
    
    
    var woche = ["So","Mo","Di","Mi","Do","Fr","Sa"];
    
    setInterval(function(){
      
      var date = clk.getDate();
      
      var weekday = date.getDay(); //0-31
      var wochentag = woche[weekday];
      
      var jahr = date.getFullYear();
      var monat = date.getMonth()+1; monat=(monat<10)?"0"+monat:monat;
      var tag = date.getDate();        tag=(tag<10)?"0"+tag:tag;
      
      var stunden = date.getHours(); stunden=(stunden<10)?"0"+stunden:stunden;
      var minute = date.getMinutes();minute=(minute<10)?"0"+minute:minute;
      var sek = date.getSeconds();   sek=(sek<10)?"0"+sek:sek;
      
      
      var s1 = wochentag+". "+tag+"."+monat+"."+jahr;
      var s2 = stunden+":"+minute+":"+sek;
      
      printscreen(s1,s2,10);
      
    },1000);
    
    
    
    save();
    
    
    
    
    

    @Gordon thank you for help.

  • No problem - sorry you didn't have any luck with it.

    Radio stuff is tricky. It's always hard to know if it's software, the hardware, or just interference that's at fault :)

  • Hello Gordon,
    I know it is an old entry, but I could not find any other entry regarding DCF77.
    I try to continuously rund the DCF77 module, but only get the attached result. After it runs for a minute or so it comes back with the attached message 'running out of memory'.
    I am using the Conrad module with it, tried different ports and the normal as well as the inverted output.
    Do you possibly have a hint what I do wrong there?
    I am completely new with JavaScript though.
    Joe


    1 Attachment

    • DCF77.jpg
  • problem is the module starts a watch by itself, so your setInterval starts a watch every 2s, which might lead to the memory problem

    but you can create a custom module locally (put it into modules folder and call it myDCF77 or something)

    and use it this way

    setInterval(function(){
        var watchId;
        var result = require('myDCF77').connect(B1, function(err, date, info) {
          if (err) {
            console.log("Invalid time received: " + err);
          }
          else {
            console.log('Date from DCF77 ' + date.toString());
            if(watchId) {
              clearWatch(watchId);
              watchId = undefined;
            }
          }
        });
        watchId = result.watchId;
      }, 1200000);
    

    ... the above needs a change inside the module as

    exports.connect = function(dataPin, callback) {
      var dcf = {
        last : getTime(),
        bits : '',
        watchId: undefined
      };
      dcf.watchId = setWatch(function (e) {
        // Work out what bit we got
        var d = e.time-e.lastTime;
        var bit = (d<0.15)?0:1;
        // if we had a 2 sec gap then it's the beginning of a minute
        if (e.time - dcf.last > 1.5) {
          decode(dcf.bits, callback);
          dcf.bits = "";
        }
        dcf.last = e.time;
        // now add this bit of data
        dcf.bits += bit;
        if (dcf.bits.length>59)
          dcf.bits = dcf.bits.substr(-59);
      }, dataPin, { edge:"falling", repeat:true, debounce:75 });
      return dcf;
    };
    

    keep all other functions as is

  • err if you dont mind the power use you can use the module as is, just use it without setInterval

  • That interval was exactly the problem. The language is quite different from like Pascal or even Assembler. I see I have to think completely different with JavaScript and its syntax.
    Thank you very much for your quick profound help.
    Joe

  • if you are interested in some background

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

DCF77 Radio Time Signal

Posted by Avatar for Gordon @Gordon

Actions