4

I am trying to run a clock off of an arduino following this site: http://www.cibomahto.com/2008/03/controlling-a-clock-with-an-arduino/comment-page-1/ and I tried to combine it with this to get it to run accurately. https://contractorwolf.com/arduino-pwm-interrupt-clock/

For the project that I want to do I need to slow down the minute hand by 23.9345 (So that it will run on sidereal time)

Now the bare code of the first link with some mods works perfectly, but the delay does not seem to be accurate:

    const int a1  =  8;     //use digital I/O pin 8
    const int a2  =  9;
    const int tick1 = 1000;
    const int tick2 = 50;  

    void setup()
    {
    pinMode(a1,OUTPUT);   //set pin 8 to be an output output
    pinMode(a2,OUTPUT);   //set pin 9 to be an output output
    }

    void loop()
    {
       delay(tick1);          //delay 1000 milliseconds 
       digitalWrite(a1,HIGH);   //set pin 8 HIGH, turning on LED
       delay(tick2);          //delay 1000 milliseconds 
       digitalWrite(a1,LOW);    //set pin 8 LOW, turning off LED
       delay(tick1);          //delay 1000 milliseconds 
       digitalWrite(a2,HIGH);   //set pin 9 HIGH, turning on LED
       delay(tick2);          //delay 1000 milliseconds 
       digitalWrite(a2,LOW);    //set pin 9 LOW, turning off LED
    }

For this reason I have tried to merge it with the 1 wire clock project and got

const int a1  =  8;     //use digital I/O pin 8
const int a2  =  9;
const int tick1 = 1000;
const int tick2 = 50;  


int cyclesPerSecond = 974;

//pins
int clockInterrupt = 0; //interrupt 0 is pin 2 on UNO
int pwmOut = 6; //pin 6

//timekeeping
int seconds = 0;
int masterClock = 0;//number of square waves

void setup()
{
attachInterrupt(clockInterrupt, clockCounter, RISING);
pinMode(a1,OUTPUT);   //set pin 8 to be an output output
pinMode(a2,OUTPUT);   //set pin 8 to be an output output

Serial.println(pwmOut);
analogWrite(pwmOut, 127); // this starts our PWM 'clock' with a 50% duty cycle//digital pin 10 for analogWrite pwm out
}

void clockCounter() // called by interrupt 0 (pin 2 on the UNO) receiving a rising clock edge PWM
{
masterClock ++; // with each clock rise add 1 to masterclock count
if(masterClock >= cyclesPerSecond*23.9345) // 974hz on pin 6, may be 490Hz if you use pin 9 or 10
{
seconds ++; // after one cycle add 1 second
masterClock = 0; //reset clock counter
}
}

void loop()
{
  if (seconds == 1){
   digitalWrite(a1,HIGH);   //set pin 8 HIGH, turning on LED
   delay(tick1);          //delay 1000 milliseconds 
   digitalWrite(a1,LOW);    //set pin 8 LOW, turning off LED
   else if(seconds == 2)
   seconds = 0;
   digitalWrite(a2,HIGH);   //set pin 9 HIGH, turning on LED
   delay(tick1);          //delay 1000 milliseconds 
   digitalWrite(a2,LOW);    //set pin 9 LOW, turning off LED
  }
}

However this does not seem to move the second hand properly, so the clock does tick but not strong enough to actually work. Is this a hardware thing or a processor thing? I have no idea why this would not work while the other code does.

  • Tear out the resonator, put a 32kHz clock crystal in place, and tune the async timer to sidereal time. – Ignacio Vazquez-Abrams Nov 16 '16 at 7:15
  • The idea of using the Arduino's own PWM as a clock is completely misguided. The author of that instructable does not have a clue what he is talking about. Forget about that and just calibrate your millis() or, for better accuracy, use a proper watch crystal. – Edgar Bonet Nov 16 '16 at 9:21
  • tick1 has a way to large value (1000), if it has to wait one second, and the do some other stuff, but it has to do this every second, it will fail. I also miss a way to prevent the first if from being called multiple times. – Gerben Nov 16 '16 at 10:26
  • @EdgarBonet - actually it is you who are mistaken. The idea is not really to use PWM, but to use the hardware timer behind the PWM, which is a very good idea indeed. – Chris Stratton Nov 16 '16 at 16:17
  • 1
    @ChrisStratton: Are we talking about the same instructable? This guy is using a PWM signal from pin 6, which is driven by the same hardware timer as the millis() interrupt. There is no way this can be more accurate than millis() itself, thus the whole technique serves no purpose at all. Ultimately, the accuracy of both millis() and the PWM is limited by the ceramic resonator clocking the Arduino, which is quite poor. – Edgar Bonet Nov 16 '16 at 16:44
3

As I said in my comment, the idea of using the Arduino's own PWM as a clock source is completely misguided. It will be no more accurate than using millis() or micros(): both these functions and the PWM rely on the same primary clock source, namely the ceramic resonator clocking the chip.

Also, you should avoid using delay() for timekeeping. The time it takes to run around the loop() is the sum of all your delays plus the time needed to execute the actual instructions, which you do not know accurately. It is far better to use either millis() or micros().

Here is a tentative sidereal clock code, not tested:

// Pins driving the clock.
const int a1 = 8;
const int a2 = 9;

// Length of one sidereal second in microseconds.
const unsigned long SIDEREAL_SECOND = 997270;

void setup()
{
    pinMode(a1, OUTPUT);
    pinMode(a2, OUTPUT);
}

void tick()
{
    static int activePin = a1;

    // Pulse the pin.
    digitalWrite(activePin, HIGH);
    delay(50);
    digitalWrite(activePin, LOW);

    // Next time use the other pin.
    if (activePin == a1) activePin = a2;
    else activePin = a1;
}

void loop()
{
    static unsigned long last_tick;
    if (micros() - last_tick >= SIDEREAL_SECOND) {
        tick();
        last_tick += SIDEREAL_SECOND;
    }
}

You can tune the speed of the clock by changing the constant SIDEREAL_SECOND. I set the constant to the actual length of a sidereal second, in microseconds, but you may find that the clock does not run exactly at the correct speed. This is because the ceramic resonator inside the Arduino is not very accurate. You can remove most of the inaccuracy by calibration: measure how fast or slow it runs and change the value of SIDEREAL_SECOND to compensate. You will still face some residual inaccuracy owing to the fact that the resonator's frequency is not very stable, and is highly temperature-dependent.

If you want to build a clock that has decent accuracy, your best bet is to use a 32768 Hz quartz crystal, as suggested by Ignacio Vazquez-Abrams in his comment. I did that on a bare ATmega chip (a “barebones Arduino”) to make a 24-hour analog wall clock. You should be able to easily adapt the code I published in that page to fit your project.

  • Thank you for sharing your project. I made a Vetinari Clock using AS2, but mine is running a bit too fast. So I'll give your calibration method a try. – Gerben Nov 16 '16 at 10:31
  • Bad idea, this will fail or at least momentarily misoperate in a little over an hour. Unless you are going to use very large integers, you cannot use a mechanism which figures time from zero, but must do so incrementally. – Chris Stratton Nov 16 '16 at 16:19
  • 1
    @ChrisStratton: You are mistaken. Since micros() rolls over cleanly modulo 2^32, the rollover is not an issue. – Edgar Bonet Nov 16 '16 at 16:33
2

Writing another answer because I just had a completely different idea.

A standard option for keeping reasonably accurate time with an Arduino is to use an RTC module. If possible, choose one based on the DS3231 rather than the DS1307, as the former has builtin temperature compensation, which makes it significantly more accurate.

The RTC is typically used to provide the time over I2C in a broken-down form: year, month, day, hour, minute and second. This is not really convenient for a sidereal clock, as it would require complex conversions to derive the sidereal time. However, most RTC modules have the option to output a 32,768 Hz square wave. If you can feed this signal to the digital pin 5 of your Arduino Uno, then you can use the Timer/Counter 1 to count the cycles and raise an interrupt every sidereal second, i.e. every 32,678.53 cycles of the RTC signal.

There is small issue with the above number not being an integer. One could just round it to the nearest integer, which would make the clock slow by about 14 ppm, or 1.2 s/day. This may not seem like a large error, but the DS3231 is capable of much greater accuracy: ±2 ppm from 0°C to +40°C.

An elegant solution is to count the cycles internally using a 32-bit fixed-point number, with 16 bits for the integer part and 16 bits for the fractional part. On each timer interrupt, we compute what value the timer should have on the next interrupt (variable next_count below), simply by adding 32,678.53 to the previous value. Then we use the integer part of the result (16 most significant bits) to program the next interrupt. This way the interrupts will be spaced by either 32,678 or 32,679 signal periods, with the average being exactly the value we need. This provides very high granularity, as our time unit is now effectively 2−31 s ≈ 0.466 ns.

In the code below, the first three functions are left empty, as “an exercise for the reader”. Or rather because they are dependent on the specifics of the hardware setup. I am only showing the logic of using the Timer 1 for getting an interrupt every sidereal second.

/*
 * RTC-based sidereal clock.
 *
 * Configure the RTC to output a 32,768 Hz square wave.
 * Feed this square wave to pin T1 = PD5 = digital 5.
 */

#include <avr/sleep.h>

static void setup_rtc()
{
    // Configure the RTC to output a 32,768 Hz square wave.
}

static void setup_clock_pins()
{
    // Configure the pins for driving the clock.
}

static void tick()
{
    // Advance the clock by one second.
}

/*
 * For better granularity, time is counted in units of 2^-16 cycles of
 * the 32768 Hz square wave (~ 0.47 ns), as a 32-bit unsigned integer.
 * Timer 1 holds the 16 most significant bits.
 */
const uint32_t SI_SECOND       = 32768UL << 16;
const uint32_t SIDEREAL_SECOND = 0.99726958 * SI_SECOND;

// Interrupt fired every sidereal second.
ISR(TIMER1_COMPA_vect)
{
    static uint32_t next_count;
    next_count += SIDEREAL_SECOND;
    OCR1A = next_count >> 16;  // use the 16 most significant bits
    tick();
}

void setup()
{
    // Configure Timer 1.
    TCCR1A = 0;            // normal counting mode
    TCCR1B = _BV(CS10)     // count rising edges of T1
           | _BV(CS11)     // ditto
           | _BV(CS12);    // ditto
    TIMSK1 = _BV(OCIE1A);  // enable COMPA interrupt

    // Enable pullup on T1 = PD5.
    PORTD |= _BV(PD5);

    setup_rtc();
    setup_clock_pins();
}

void loop()
{
    sleep_mode();
}
1

I'm not 100% certain of what you are trying to do but I noticed a few bits in the code which don't look right to me. For instance this function:

void clockCounter() // called by interrupt 0 (pin 2 on the UNO) receiving a rising clock edge PWM
{
  masterClock ++; // with each clock rise add 1 to masterclock count
  if(masterClock >= cyclesPerSecond*23.9345) // 974hz on pin 6, may be 490Hz if you use pin 9 or 10
  {
    seconds ++; // after one cycle add 1 second
    masterClock = 0; //reset clock counter
  }
}

This is causing a part of your inaccuracy. You are incrementing the integer masterClock variable and then seeing if it is >= to a floating point value, the equals is not necessary and will almost never be true. If the condition occurs then you increment seconds before setting masterClock to zero, losing any part of a second you had left over.

Looking at what others have said I would be inclined to take a different approach to this project rather than try and fix this code.

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.