DCF77 Scope 2

This experiment establishes the successor of my DCF77 scope. It offers all features of the old DCF77 Scope plus support for the Arduino Due. Thus the documentation of what it is supposed to do and how it behaves is exactly the same as for the old scope.

This is the first stepping stone in the direction of a DCF77 library that will also work on the Arduino Due. However as we will see there are quite some things that I needed to adapt.

First of all I need to find out if the compile target is an AVR or an ARM based Arduino. This is because they require different timer handling. Also enabling / disabling of interrupts is somewhat different. I control the differences by means of some macros. These macros are also required to cater for the different pin mappings. Luckily there are lots of predefined macros to determine the compile target.

If you are still following my blog I assume that you are reasonable advanced to figure out what I do with the macros. Otherwise you may want to read the Macro based Knight Rider Without Flicker Experiment to learn how to analyze code with lots of macros.

Now lets look into the really tricky stuff. First of all there is the “critical section” macro. For AVR it uses the macro provided by AVR Libc. For ARM it copies its implementation. Unfortunately the standard libraries for ARM do not provide a convenient way to restore the state of the interrupt flag. So far I did not find out why this is the case. Thus I had to add some small pieces of assembler to achieve this. The inner workings of this macro are incredible. All the credits go to the AVR Libc guys. It combines to clever pieces. One is the introduction of a helper variable which is attributed with __cleanup__. This makes the compiler process some code when this variable goes out of scope. Or with other words: this is a destructor in a none OO context. The other thing is to wrap this all into a header of a for loop. The point is that the variables in this header will go out of scope as soon as the loop body is left. In particular this will trigger restoration of the interrupt flag even if the loop is left by a return statement. And believe it or not, the compiler will properly optimize this “loop” and only the really necessary instructions will be left in the compiler output.

If you grok this then the timer code will be a piece of cake for you. I just reuse what I have for AVR in the old DCF77 scope and reuse what I got from the Lighthouses 2 experiment.

//
//  www.blinkenlight.net
//
//  Copyright 2015 Udo Klein
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program. If not, see http://www.gnu.org/licenses/

namespace {
    // workaround to convince the Arduino IDE to not mess with the macros
}

#if defined(__AVR__)
// pin settings for AVR based Arduino like Blinkenlighty, Uno or Nano
const uint8_t dcf77_analog_sample_pin = 5;
const uint8_t dcf77_sample_pin = 19;  // A5 == D19 for standard Arduinos
const uint8_t dcf77_inverted_samples = 1;
const uint8_t dcf77_analog_samples = 1;

const uint8_t dcf77_monitor_led = 18; // A4 == D18 for standard Arduinos

const uint8_t lower_output_led = 2;
const uint8_t upper_output_led = 17;

uint8_t ledpin(const int8_t led) {
    return led;
}

#else
// Pin settings for Arduino Due
// No support for "analog samples", the "analog samples".
// is only required for the Blinkenlighty or Arduino Uno + Blinkenlight Shield.
// For the Due any free pin may be used in digital mode.

const uint8_t dcf77_sample_pin = 53;
const uint8_t dcf77_inverted_samples = 0;

const uint8_t dcf77_monitor_led = 18;

const uint8_t lower_output_led = 2;
const uint8_t upper_output_led = 17;

uint8_t ledpin(const int8_t led) {
    return led<14? led: led+(54-14);
}

// Sine the DUE has so many pins it may be convenient to power
// some pins in a way that fits some specific DCF77 module.

// As an example these are pin settings for the "Pollin Module".
#define dedicated_module_support 1
const uint8_t gnd_pin  = 51;
const uint8_t pon_pin  = 51;  // connect pon to ground !!!
const uint8_t data_pin = dcf77_sample_pin;  // 53
const uint8_t vcc_pin  = 49;
#endif

// CRITICAL_SECTION macro definition depending on architecture
#if defined(__AVR__)
    #include <avr/eeprom.h>

    #include <util/atomic.h>
    #define CRITICAL_SECTION ATOMIC_BLOCK(ATOMIC_RESTORESTATE)

#elif defined(__arm__)
    // Workaround as suggested by Stackoverflow user "Notlikethat"
    // http://stackoverflow.com/questions/27998059/atomic-block-for-reading-vs-arm-systicks

    static inline int __int_disable_irq(void) {
        int primask;
        asm volatile("mrs %0, PRIMASK\n" :: "r"(primask));
        asm volatile("cpsid i\n");
        return primask & 1;
    }

    static inline void __int_restore_irq(int *primask) {
        if (!(*primask)) {
            asm volatile ("" ::: "memory");
            asm volatile("cpsie i\n");
        }
    }
    // This critical section macro borrows heavily from
    // avr-libc util/atomic.h
    // --> http://www.nongnu.org/avr-libc/user-manual/atomic_8h_source.html
    #define CRITICAL_SECTION for (int primask_save __attribute__((__cleanup__(__int_restore_irq))) = __int_disable_irq(), __n = 1; __n; __n = 0)

#else
    #error Unsupported controller architecture
#endif

#define sprint(...)   Serial.print(__VA_ARGS__)
#define sprintln(...) Serial.println(__VA_ARGS__)


uint8_t sample_input_pin() {
    const uint8_t sampled_data =
    #if defined(__AVR__)
        dcf77_inverted_samples ^ (dcf77_analog_samples? (analogRead(dcf77_analog_sample_pin) > 200):
                                                        digitalRead(dcf77_sample_pin));
    #else
        dcf77_inverted_samples ^ digitalRead(dcf77_sample_pin);
    #endif

    digitalWrite(ledpin(dcf77_monitor_led), sampled_data);
    return sampled_data;
}

void led_display_signal(const uint8_t sample) {
    static uint8_t ticks_per_cycle = 12;
    static uint8_t rolling_led = lower_output_led;
    static uint8_t counter = 0;

    digitalWrite(ledpin(rolling_led), sample);

    if (counter < ticks_per_cycle) {
        ++counter;
    } else {
        rolling_led = rolling_led < upper_output_led? rolling_led + 1: lower_output_led;
        counter = 1;
        // toggle between 12 and 13 to get 12.5 on average
        ticks_per_cycle = 25-ticks_per_cycle;
    }
}

const uint16_t samples_per_second = 1000;
const uint8_t bins                = 100;
const uint8_t samples_per_bin     = samples_per_second / bins;

volatile uint8_t gbin[bins];
boolean samples_pending = false;

void process_one_sample() {
    static uint8_t sbin[bins];

    const uint8_t sample = sample_input_pin();
    led_display_signal(sample);

    static uint16_t ticks = 999;  // first pass will init the bins
    ++ticks;

    if (ticks == 1000) {
        ticks = 0;
        memcpy((void *)gbin, sbin, bins);
        memset(sbin, 0, bins);
        samples_pending = true;
    }

    sbin[ticks/samples_per_bin] += sample;
}

void setup() {
    Serial.begin(115200);
    sprintln();

    #if defined(dedicated_module_support)
    pinMode(gnd_pin, OUTPUT);
    digitalWrite(gnd_pin, LOW);
    pinMode(pon_pin, OUTPUT);
    digitalWrite(pon_pin, LOW);
    pinMode(vcc_pin, OUTPUT);
    digitalWrite(vcc_pin, HIGH);
    #endif

    pinMode(dcf77_sample_pin, INPUT);
    digitalWrite(dcf77_sample_pin, HIGH);

    pinMode(dcf77_monitor_led, OUTPUT);
    for (uint8_t led = lower_output_led; led <= upper_output_led; ++led) {
        pinMode(ledpin(led), OUTPUT);
        digitalWrite(ledpin(led), LOW);
    }

    setupTimer();
}

void loop() {
    static int64_t count = 0;
    uint8_t lbin[bins];

    if (samples_pending) {
        CRITICAL_SECTION {
            memcpy(lbin, (void *)gbin, bins);
            samples_pending = false;
        }

        ++count;
        // ensure the count values will be aligned to the right
        for (int32_t val=count; val < 100000000; val *= 10) {
            sprint(' ');
        }
        sprint((int32_t)count);
        sprint(", ");
        for (uint8_t bin=0; bin<bins; ++bin) {
            switch (lbin[bin]) {
                case  0: sprint(bin%10? '-': '+'); break;
                case 10: sprint('X'); break;
                default: sprint(lbin[bin]);
            }
        }
        sprintln();
     }
}

// timer handling depending on architecture
#if defined(__AVR_ATmega168__)  || \
    defined(__AVR_ATmega48__)   || \
    defined(__AVR_ATmega88__)   || \
    defined(__AVR_ATmega328P__) || \
    defined(__AVR_ATmega1280__) || \
    defined(__AVR_ATmega2560__) || \
    defined(__AVR_AT90USB646__) || \
    defined(__AVR_AT90USB1286__)
ISR(TIMER2_COMPA_vect) {
    process_one_sample();
}

void stopTimer0() {
    // ensure that the standard timer interrupts will not
    // mess with msTimer2
    TIMSK0 = 0;
}

void initTimer2() {
    // Timer 2 CTC mode, prescaler 64
    TCCR2B = (0<<WGM22) | (1<<CS22);
    TCCR2A = (1<<WGM21) | (0<<WGM20);

    // 249 + 1 == 250 == 250 000 / 1000 =  (16 000 000 / 64) / 1000
    OCR2A = 249;

    // enable Timer 2 interrupts
    TIMSK2 = (1<<OCIE2A);
}

void setupTimer() {
    initTimer2();
    stopTimer0();
}
#endif

#if defined(__SAM3X8E__)
extern "C" {
    // sysTicks will be triggered once per 1 ms
    int sysTickHook(void) {
        process_one_sample();
        return 0;
    }
}

void setupTimer() {}
#endif

One final word. The ARM code contains some special handling for a so called “Pollin” Module. This is a cheap DCF77 module which is available from a well known German distributor. The point is that this has 4 pins and is 3.3V compatible. Since it is very low power I just drive the Due’s pins to provide the power supply. This would not be OK with a standard Arduino as this module is not 5V compatible. The point is that I need no wiring at all to connect this module. Just plug and play. Have a look at the picture below for my new setup. In particual you can see one of the primary reasons for the DUE. Not only lots of memory but also lots of pins. So I can drive a Blinkenlight Shield and still have enough pins for whatever else I want to connect 🙂

DU3_Due_DCF77_compressed_1280

Advertisements

2 Responses to DCF77 Scope 2

  1. Combinatix says:

    Hi Udo,
    did you solve the compiler problem with DUE boards, “DCF77_Scope:286: error: previous declaration of ‘int sysTickHook()’ with ‘C++’ linkage” ? I found an open topic at http://forum.arduino.cc/index.php?topic=380196.0 but there is no sollution yet.

    • Hi Cobinatix,

      so far I have no satisfying solution. As it works with the library I have a sufficient workaround. But this one keeps bugging me as I have no clue why this happens. Do you have any hints or ideas for me?

      Best regards, Udo

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s