Aleph Clock

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

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.