Automated Perpetual Calendar

by tomatoskins in Workshop > Woodworking

94871 Views, 1228 Favorites, 0 Comments

Automated Perpetual Calendar

square1.jpg
DSC_8066.jpg

Years ago I came across a wooden perpetual calendar and I loved the idea. After thinking about it for months I decided I wanted to make one myself; however, there was a long list of things I wanted to do to improve on the idea. The most important change I wanted to make, is to automate the rings. I know me, and based on current wall calendars not showing the correct month for some time, if a daily change is necessary it would likely only be accurate a small percentage of the year.

Combining microcontrollers, 3D printing, woodworking, and laser jet image transfer, this project will have something for everyone to learn.

Tools and Materials

20200527_173254.jpg
20200529_152716.jpg
20200318_181122.jpg
20200318_181049.jpg
20200318_181001.jpg
20200318_180922.jpg
20200316_152148.jpg
20200316_125716.jpg

Materials:

Tools:

Cut Segments for Seven Segment "Week Day" Ring

20200527_175551.jpg
20200527_180110.jpg
20200527_180116.jpg
20200528_150828.jpg

Using the same method and calculations shown in my Segmented Bowl instructable, cut 21 segments that are 3/16 in. thick.

Glue together seven segments at a time resulting in three rings that are 3/16 in. thick each. This ring should be able to fit a 10 in. OD and 7 in. ID ring inside.

Combine RIngs

20200529_154351.jpg
20200529_154807.jpg
20200529_160906.jpg

I knocked off the corners of the rings, but this is not necessary. Sand the faces of each ring until they are flat. I used 80 grit sandpaper glued to my table.

Glue together the three rings so the seams are alternating. This gives the rings high strength from gluing the side grain of the wood.

Cut and Assemble 12 Segment "Month" Ring

20200601_163714.jpg
20200602_150549.jpg

Follow the same math included in my Segmented Bowl instructable to cut 36 segments for three 12 segment rings 3/16 in. thick.

Glue this ring and the next ring together in smaller sections. Sand each face as before and combine the three rings by alternating the seams to increase ring strength.

This ring should be able to fit a 16 in. OD and 13 in. ID ring inside.

Cut and Assemble 31 Segment "Number of Day" Ring

20200529_171037.jpg
20200529_171926(0).jpg
20200529_171932.jpg
20200529_172352.jpg
20200529_172723(0).jpg
20200601_190258.jpg

Follow the same math included in my Segmented Bowl instructable to cut 93 segments for three 31 segment rings 3/16 in. thick.

Glue this ring and the next ring together in smaller sections. Sand each face as before and combine the three rings by alternating the seams to increase ring strength.

This ring should be able to fit a 22 in. OD and 19 in. ID ring inside.

Apply Ring Template

20200602_165743.jpg
20200601_160900.jpg
20200601_162120(0).jpg

Print out and tape together the 1:1 template and cut out the individual rings. This will aid in getting the rings the exact size necessary. There are blue tick marks to help with aligning the template to each segment.

Downloads

Trim Each Ring

20200603_124757.jpg
20200602_154551.jpg
20200602_170455.jpg
20200603_120013.jpg
20200603_124116.jpg

Using a disc sander and spindle sander, remove the wood to the outside circle of each ring.

Print Ring Gears

20200529_152819.jpg
20200529_153314.jpg
20200529_153655.jpg
20200601_162539.jpg

Download and print the five files on this step. The names of each file include a set of numbers. The second number in the file name indicate how many of that file to print.

Assemble the rings and press three magnets into the flat hole (not countersunk screw holes) with the south pole facing the top. Magnets need to only be included on one segment of each ring.

Route Ring Gear Groove

20200603_152315.jpg
20200603_152410.jpg
20200603_160602.jpg

Using a 1/4 in. rabbeting bit, cut a rabbet on the back of each ring. If the ring gear doesn't quite fit, take light passes off the inside of the ring on the spindle sander then re cut the rabbet on the router until it fits. Use a 1/4 in. roundover bit to smooth the not rabbet edges.

Attach Ring Gears

20200610_132904.jpg
20200610_134302.jpg
20200610_135547.jpg

Using the depth stop on the drill press, drill pilot holes through the ring gear into the wooden ring. Secure with screws.

Print Bracket and Spur Gears

20200605_122536.jpg

Print off brackets, three spur gears, Arduino mount, and 12 standoffs for holding electronics (not shown here) on this step.

Ring Supports

20200605_123208.jpg
20200605_123725.jpg

Cut ring supports from a piece of hardwood that's 3/4 in. thick. Use disc and spindle sanders to refine shape.

Downloads

Attach Stepper and Bracket to Support

20200605_125842.jpg
20200605_132855.jpg
20200605_134534.jpg
20200605_134621.jpg
20200605_135104.jpg

Shave the top open part of the bracket down to allow room for the machine screws holding the stepper motor in place. Drill pilot holes through the bracket and secure with screws.

Add Spline

20200605_145437.jpg
20200605_145500.jpg
20200605_150821.jpg

The brackets needed to be shaved down in a next step. To keep everything aligned, I added a spline across the back using the table saw.

Cut Front Face

20200605_123154.jpg
20200605_152939(0).jpg
20200605_160818.jpg
20200609_154112.jpg

On a 1/4 in. board that measures 13 x 6 in. apply the included template. I used a jig saw to roughly cut the sections out then smoothed them out with a sanding stick.

Finally use an 1/8 in. roundover bit on the front of the board.

Downloads

Attach Supports

20200609_153524.jpg
20200609_154523.jpg

Glue supports in place. Position them using the completed rings.

Attach Top and Bottom Standoffs

20200611_162808.jpg
20200611_184332.jpg
20200611_184346.jpg

Attach wooden standoffs that measure 6 x 1 1/2 x 1 3/4 in. at the top and bottom. This will hold the rings away from the wall and give enough clearance for the electronics.

My pieces were just a bit larger, but I trimmed them to fit after the glue dried.

Sand and roundover edges.

Attach Lables

20200711_193823.jpg
20200711_194235(0).jpg
20200711_194451.jpg
20200711_195043.jpg
20200712_130107.jpg
20200712_130140.jpg
20200712_130156.jpg
20200712_130320.jpg

For the transfer of ink I used the polycrylic method shown here. It's a simple but time consuming process.

Print off a few copies of the attached PDF so when you mess up you can try again right away. I was impatient with a few of the labels but I like the rustic look it provided. You can also touch up the labels with a sharpie or black paint.

Apply 3-4 coats of polycrylic to all rings and face.

Downloads

Insert Power Plug

20200715_143011.jpg
20200715_143727.jpg

Drill a hole the size of the threads on your plug. Then carefully drill a larger hole the outside diameter of the plug until the threads just stick out on the inside of the standoff.

Assemble Electronics and Upload Code

20200713_190158(0).jpg
20200715_171022.jpg
20200702_143455.jpg
schemeit-project (1).png

Following the attached schematic, wire everything up and upload the code.

#include 
#include "RTClib.h"
RTC_DS1307 rtc;

// Call for steppers
#define oneRotation 2038 // the number of steps in one revolution of 28BYJ-48 stepper motor
Stepper numberOfDay(oneRotation, 2, 4, 3, 5);
Stepper nameOfMonth(oneRotation, 6, 8, 7, 9);
Stepper nameOfDay(oneRotation, 10, 12, 11, 13);

// Location of the hall effect sensors used to find the home position of the rings.
#define NUMBER_OF_DAY_SENSOR A0
#define NAME_OF_MONTH_SENSOR A1
#define NAME_OF_DAY_SENSOR A2

// Variables used to keep track of the current day.
uint8_t thisNumberDay = 0;
uint8_t thisMonth = 0;
uint8_t thisNameDay = 0;
uint8_t nextNumberDay = 0;
uint8_t nextMonth = 0;
uint8_t nextNameDay = 0;

// Constants used to advance each ring one day/month.
const int NUMBER_OF_DAY_TO_STEP = -1661;//1108 steps per day
const int NAME_OF_MONTH_TO_STEP = -2946;//1965 steps per month
const int NAME_OF_DAY_TO_STEP = -2745;//1831 steps per day
const int COUNTERCLOCKWISE = -1;

// Update these constants to adust the position of the rings at the reset position.
// Negative numbers will advance the rings forward (counterclockwise) positive will reverse the rings.
const int NUMBER_OF_DAY_RESET_ADJUST = -130;
const int NAME_OF_MONTH_RESET_ADJUST = -60;
const int NAME_OF_DAY_RESET_ADJUST = -35;

// Bit used to test if the ring has been reset to home.
bool NUMBER_OF_DAY_RESET = LOW;
bool NAME_OF_MONTH_RESET = LOW;
bool NAME_OF_DAY_RESET = LOW;

void setup() {
  Serial.begin(9600);
  rtc.begin();

  // Setup real time clock and reset hall effect sensors.
  pinMode(NUMBER_OF_DAY_SENSOR, INPUT_PULLUP);
  pinMode(NAME_OF_MONTH_SENSOR, INPUT_PULLUP);
  pinMode(NAME_OF_DAY_SENSOR, INPUT_PULLUP);


  // Uncomment line below to set time.
  // rtc.adjust(DateTime(2022, 1, 4, 23, 55, 0));
  // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));

  // Get current date
  DateTime now = rtc.now();
  thisNumberDay = now.day();
  thisMonth =  now.month();
  thisNameDay = now.dayOfTheWeek();

  Serial.print("Date: ");
  Serial.print(thisNumberDay);
  Serial.print(" ");
  Serial.print(thisMonth);
  Serial.print(" ");
  Serial.println(thisNameDay);

  // Set top speed of stepper motors.
  numberOfDay.setSpeed(15);
  nameOfMonth.setSpeed(15);
  nameOfDay.setSpeed(15);

  // Loop to move any ring off of the hall effect sensor if it is currently tripped.
  while (digitalRead(NUMBER_OF_DAY_SENSOR) == LOW ||  digitalRead(NAME_OF_MONTH_SENSOR) == LOW || digitalRead(NAME_OF_DAY_SENSOR) == LOW)
  {
    if (digitalRead(NUMBER_OF_DAY_SENSOR) == LOW )
    {
      numberOfDay.step(COUNTERCLOCKWISE);
    }

    else
    {
      delay(1);
    }

    if (digitalRead(NAME_OF_MONTH_SENSOR) == LOW)
    {
      nameOfMonth.step(COUNTERCLOCKWISE);
    }

    else
    {
      delay(1);
    }

    if (digitalRead(NAME_OF_DAY_SENSOR) == LOW)
    {
      nameOfDay.step(COUNTERCLOCKWISE);
    }

    else
    {
      delay(1);
    }
  }

  // Loop until all rings are at the reset position.
  while (digitalRead(NUMBER_OF_DAY_SENSOR) != LOW || digitalRead(NAME_OF_MONTH_SENSOR) != LOW || digitalRead(NAME_OF_DAY_SENSOR) != LOW)
  {
    if (digitalRead(NUMBER_OF_DAY_SENSOR) != LOW )
    {
      numberOfDay.step(COUNTERCLOCKWISE);
    }

    else
    {
      delay(1);
    }

    if (digitalRead(NAME_OF_MONTH_SENSOR) != LOW )
    {
      nameOfMonth.step(COUNTERCLOCKWISE);
    }

    else
    {
      delay(1);
    }

    if (digitalRead(NAME_OF_DAY_SENSOR) != LOW)
    {
      nameOfDay.step(COUNTERCLOCKWISE);
    }

    else
    {
      delay(1);
    }
  }

  //  Use these functions to carefully adjust position of rings after it's been reset.
  numberOfDay.step(NUMBER_OF_DAY_RESET_ADJUST);
  nameOfMonth.step(NAME_OF_MONTH_RESET_ADJUST);
  nameOfDay.step(NAME_OF_DAY_RESET_ADJUST);

  // Move each ring to the correct number, month, and day of week
  for (int i = 1; i < thisNumberDay; i++)
  {
    numberOfDay.step(NUMBER_OF_DAY_TO_STEP);
  }

  for (int i = 1; i < thisMonth; i++)
  {
    nameOfMonth.step(NAME_OF_MONTH_TO_STEP);
  }

  for (int i = 0; i < thisNameDay; i++)
  {
    nameOfDay.step(NAME_OF_DAY_TO_STEP);
  }

}

void loop() {

  // Check the current day number
  DateTime now = rtc.now();
  nextNumberDay = now.day();


  // If the current checked day is different than it was last time it was checked, move the rings to the next date.
  if (thisNumberDay != nextNumberDay)
  {
    // Get the next month and day of week.
    nextMonth =  now.month();
    nextNameDay = now.dayOfTheWeek();

    // If the number of the day is 1 (and it's not currently at 1) reset to 1
    if (nextNumberDay == 1 && thisNumberDay >= 28)
    {
      while (digitalRead(NUMBER_OF_DAY_SENSOR) != LOW)
      {
        numberOfDay.step(COUNTERCLOCKWISE);
        NUMBER_OF_DAY_RESET = HIGH;
      }
      numberOfDay.step(NUMBER_OF_DAY_RESET_ADJUST);
    }

    // If the month is Jan and was Dec, rest to Jan.
    if (nextMonth == 1 && thisMonth == 12)
    {
      while (digitalRead(NAME_OF_MONTH_SENSOR) != LOW)
      {
        nameOfMonth.step(COUNTERCLOCKWISE);
        NAME_OF_MONTH_RESET = HIGH;
      }
      nameOfMonth.step(NAME_OF_MONTH_RESET_ADJUST);
    }

    // If the day is Sun but was Sat, reset to Sun.
    if (nextNameDay == 0 && thisNameDay == 6)
    {
      while (digitalRead(NAME_OF_DAY_SENSOR) != LOW)
      {
        nameOfDay.step(COUNTERCLOCKWISE);
        NAME_OF_DAY_RESET = HIGH;
      }
      nameOfDay.step(NAME_OF_DAY_RESET_ADJUST);
    }

    // If the ring wasn't reset to the beginning and needs to change, step to the next position
    if (NUMBER_OF_DAY_RESET == LOW && thisNumberDay != nextNumberDay)
    {
      numberOfDay.step(NUMBER_OF_DAY_TO_STEP);
    }

    if (NAME_OF_MONTH_RESET == LOW && thisMonth != nextMonth)
    {
      nameOfMonth.step(NAME_OF_MONTH_TO_STEP);
    }

    if (NAME_OF_DAY_RESET == LOW && thisNameDay != nextNameDay)
    {
      nameOfDay.step(NAME_OF_DAY_TO_STEP);
    }

    // reset check values to test each loop
    thisNumberDay = nextNumberDay;
    thisMonth = nextMonth;
    thisNameDay = nextNameDay;
    NUMBER_OF_DAY_RESET = LOW;
    NAME_OF_MONTH_RESET = LOW;
    NAME_OF_DAY_RESET = LOW;
  }

  delay(60000);
}

Enjoy!

square.jpg
DSC_8066.jpg

Now hang up your new calendar and always enjoy knowing what date it is without needing to use any brain power.