Designing and building a clock from scratch
Arduino Programming
Today, I was intending to work on learning joints in Fusion 360, so I can try to use them to position my gears, instead I started looking at the Arduino program, probably because I felt I already had some momentum there.
I discovered two errors, one minor (I was using the wrong data type [int instead of a long in a number of places]), the other major. The major error was that I was moving the goalpost, and that the accuracy test was invalid.
When I fixed the goalpost error, I discovered that indeed the motor was drifting out of sync with the time. So, I had to go back to inserting corrections so that the number of steps tracked closer to the time. I guess the timer function I’m using is not synced to the Arduino clock. Here’s the new code, and the beginning output. I’ll let this run for a day as a test.
//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;
bool one_step(void *) {
long x = 0;
long ts;
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);
if (now == target_minute) {
Serial.println("time check!");
Serial.print("expected ");
Serial.println(target_minute_steps);
Serial.print("steps ");
Serial.println(total_steps);
target_minute = now + 60ul;
if (total_steps < target_minute_steps) {
ts = total_steps;
for (x = ts; x < target_minute_steps; x++) {
myMotor->onestep(FORWARD, INTERLEAVE);
total_steps++;
}
}
if (total_steps > target_minute_steps) {
ts = total_steps;
for (x = ts; x > target_minute_steps; x--) {
myMotor->onestep(BACKWARD, INTERLEAVE);
total_steps--;
}
}
// 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));
target_minute_steps = target_minute_steps + (60000/interval);
}
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
Fri Dec 11 14:22:20 2020
time check!
expected 800
steps 800
Fri Dec 11 14:23:20 2020
time check!
expected 1200
steps 1200
Fri Dec 11 14:24:20 2020
time check!
expected 1600
steps 1601
Fri Dec 11 14:25:20 2020
time check!
expected 2000
steps 2000
Fri Dec 11 14:26:20 2020
time check!
expected 2400
steps 2400
Fri Dec 11 14:27:20 2020
time check!
expected 2800
steps 2801
Fri Dec 11 14:28:20 2020
time check!
expected 3200
steps 3200
Fri Dec 11 14:29:20 2020
time check!
expected 3600
steps 3600
Fri Dec 11 14:30:20 2020
time check!
expected 4000
steps 4001
Fri Dec 11 14:31:20 2020
time check!
expected 4400
steps 4400
Fri Dec 11 14:32:20 2020
x04time check!
epected 480
steps 4800
Fri Dec 11 1:33:20 2020
time check!
expected 5200
steps 5201
Fri Dec 11 14:34:20 2020
time check!
expected 5600
steps 5600
Fri Dec 11 14:35:20 2020
time check!
expected 6000
steps 6000
Fri Dec 11 14:36:20 2020
time check!
expected 6400
steps 6401
Fri Dec 11 14:37:20 2020
time check!
expected 6800
steps 6800
Fri Dec 11 14:38:20 2020
time check!
expected 7200
steps 7200
Fri Dec 11 14:39:20 2020
time check!
expected 7600
steps 7601
Fri Dec 11 14:40:20 2020
c time check!
expected 8000
steps 8000
Fri Dec 11 14:41:20 2020
time check!
expected 8400
steps 8400
Fri Dec 11 14:42:20 2020
time check!
expected 8800
steps 8801
Fri Dec 11 14:43:20 2020
One more issue is that the motor is getting hot. What I read is that this is not that big of a concern in the short run, but I’ll add a heatsink and fan in the final build to extend the life of the stepper motor.