Designing and building a clock from scratch
Arduino Programming
Need to integrate the following parts:
- Nema 17 style stepper motor, 200 steps per rotation
- Adafruit motor shield
- DS3231 RTC
After I figured out that I could execute a function every certain number of milliseconds using the arduino-timer.h library, I got the stepper making one revolution every minute using the stepper’s interleave stepping and making one step every 150ms. Now, I have read that the built-in timer on the Arduino is not very accurate, so I got an RTC (Real Time Clock) to keep accurate time. I went down a few dead-ends trying to figure out how to adjust the number of steps based on the actual time from the RTC. Initially I was reading the time from the clock and checking the number of steps every minute/hour/day.
I started looking into using the SQW (square wave output) pin from the RTC. The example code I found for using the square wave function of the RTC used that signal to step the system time on the Arduino and keep it synced with the RTC. But, me being me, I ignored the obvious solution and insisted on trying to keep track of seconds myself using a counter. The DS3231 can only output a 1Hz, 1.024 kHz, 4.096 kHz, or 8.192 kHz signal. The only one that made sense was the 1 Hz signal, and make 20 steps every three seconds for a nice integer number of steps per rotation, which is a weird way for a clock to move.
Then I realized, duh, if the Arduino’s time-keeping was being kept in sync with the RTC, then I could go back to using the arduino-timer library to step the motor every 150ms, and it should be accurate because the Arduino’s internal time was being kept accurate by the RTC. Done!
Eventually, I’ll add some routines to automatically set the clock to the exact time by quickly rotating the stepper motor by some calculated number of steps based on the time set on the RTC.
Here is my code, and the test output for 53 minutes.
//Set Board to Arduino Uno
//Set Port to SLAB_USB to UART
#include <Wire.h>
#include <Adafruit_MotorShield.h>
#include <arduino-timer.h>
#include "RTClib.h"
#include <time.h>
RTC_DS3231 rtc;
auto timer = timer_create_default();
// Pin receiving the one-pulse-per-second signal from the RTC.
// This should be an interrupt-capable pin.
const uint8_t pin1pps = 2;
// Create the motor shield object with the default I2C address
// Connect a stepper motor with 200 steps per revolution (1.8 degree)
// to motor port #2 (M3 and M4)
Adafruit_MotorShield AFMS = Adafruit_MotorShield();
Adafruit_StepperMotor *myMotor = AFMS.getStepper(200, 2);
unsigned long target_minute;
long target_minute_steps;
long total_steps = 0L;
long start_steps;
int interval = 150;
void time_check(int expected_steps) {
int x;
int steps;
Serial.println("time check!");
Serial.print("expected ");
Serial.println(expected_steps);
Serial.print("steps ");
Serial.println(total_steps);
}
bool one_step(void *) {
myMotor->onestep(FORWARD, INTERLEAVE);
total_steps++;
// From here on, we only use the standard C timing functions.
// time() returns the current time as a single number of type time_t,
// this is the number of seconds elapsed since a reference "epoch".
time_t now = time(nullptr);
// Serial.print("now ");
// Serial.println(now);
// Serial.print("target_minute ");
// Serial.println(target_minute);
// Serial.print("target_minute_steps ");
// Serial.println(target_minute_steps);
// Serial.print("total_steps ");
// Serial.println(total_steps);
if (now == target_minute) {
target_minute = now + 60ul;
time_check(target_minute_steps);
target_minute_steps = total_steps + (60000/interval);
}
// // gmtime() converts the time to a broken-down form (year, month...)
// // similar to the DateTime class. Unlike localtime(), it doesn't
// // attempt timezone conversions.
// struct tm *broken_down_time = gmtime(&now);
//
// // asctime() returns a textual representation of the date and time as
// // a C string (pointer to a character array). The format is similar to
// // the DateTime::toString() format "DDD MMM DD hh:mm:ss YYYY".
// Serial.println(asctime(broken_down_time));
return true; // to repeat the action - false to stop
}
void setup() {
Serial.begin(9600); // set up Serial library at 9600 bps
Wire.begin();
rtc.begin();
AFMS.begin(); // create with the default frequency 1.6KHz
// following line sets the RTC to the date & time this sketch was compiled
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// Initialize the system time from the RTC time. Both avr-libc and
// DateTime::secondstime() use the start of year 2000 as their
// reference "epoch".
set_system_time(rtc.now().secondstime());
// Keep the time in sync using the one-pulse-per-second output of the
// RTC as an interrupt source and calling system_tick() from the
// interrupt service routine.
pinMode(pin1pps, INPUT_PULLUP);
rtc.writeSqwPinMode(0x00);
attachInterrupt(digitalPinToInterrupt(pin1pps), system_tick, FALLING);
time_t now = time(nullptr);
target_minute = now + 60ul;
target_minute_steps = total_steps + (60000/interval);
// call the one_step function every n millis
timer.every(150, one_step);
}
void loop() {
timer.tick();
}
time check!
expected 400
steps 401
time check!
expected 801
steps 801
time check!
expected 1201
steps 1201
time check!
expected 1601
steps 1602
time check!
expected 2002
steps 2002
time check!
expected 2402
steps 2403
time check!
expected 2803
steps 2803
time check!
expected 3203
steps 3203
time check!
expected 3603
steps 3604
time check!
expected 4004
steps 4004
time check!
expected 4404
steps 4404
time check!
expected 4804
steps 4805
time check!
expected 5205
steps 5205
time check!
expected 5605
steps 5605
time check!
expected 6005
steps 6006
time check!
expected 6406
steps 6406
time check!
expected 6806
steps 6806
time check!
expected 7206
steps 7207
time check!
expected 7607
steps 7607
time check!
expected 8007
steps 8007
time check!
expected 8407
steps 8408
time check!
expected 8808
steps 8808
time check!
expected 9208
steps 9208
time check!
expected 9608
steps 9609
time check!
expected 10009
steps 10009
time check!
expected 10409
steps 10409
time check!
expected 10809
steps 10810
time check!
expected 11210
steps 11210
time check!
expected 11610
steps 11610
time check!
expected 12010
steps 12011
time check!
expected 12411
steps 12411
time check!
expected 12811
steps 12811
time check!
expected 13211
steps 13212
time check!
expected 13612
steps 13612
time check!
expected 14012
steps 14012
time check!
expected 14412
steps 14413
time check!
expected 14813
steps 14813
time check!
expected 15213
steps 15213
time check!
expected 15613
steps 15614
time check!
expected 16014
steps 16014
time check!
expected 16414
steps 16414
time check!
expected 16814
steps 16815
time check!
expected 17215
steps 17215
time check!
expected 17615
steps 17615
time check!
expected 18015
steps 18016
time check!
expected 18416
steps 18416
time check!
expected 18816
steps 18816
time check!
expected 19216
steps 19217
time check!
expected 19617
steps 19617
time check!
expected 20017
steps 20017
time check!
expected 20417
steps 20418
time check!
expected 20818
steps 20818
time check!
expected 21218
steps 21218