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.
3 Answers 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– Gerben2016年11月16日 10:31:00 +00:00Commented Nov 16, 2016 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– Chris Stratton2016年11月16日 16:19:54 +00:00Commented Nov 16, 2016 at 16:19
-
1@ChrisStratton: You are mistaken. Since
micros()
rolls over cleanly modulo 2^32, the rollover is not an issue.Edgar Bonet– Edgar Bonet2016年11月16日 16:33:34 +00:00Commented Nov 16, 2016 at 16:33
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();
}
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.
millis()
or, for better accuracy, use a proper watch crystal.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 firstif
from being called multiple times.millis()
interrupt. There is no way this can be more accurate thanmillis()
itself, thus the whole technique serves no purpose at all. Ultimately, the accuracy of bothmillis()
and the PWM is limited by the ceramic resonator clocking the Arduino, which is quite poor.