Aleph Clock

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.