Making a Cuckoo-Clock with Teensy

cebersp

Well-known member
Making a Cockoo-Clock with teensy.

Hi, perhaps this is an idea for a project of some other people. At least I had some fun with it…

A cockoo clock is a clock hanging at a wall. It has a small window which opens every full hour. A little bird comes out of the window and calls "Kuckuck". These clocks have a great tradition in the Blackforest in South Germany. I live some 150km from this region. When I saw such clocks at a tourist shop there some weeks ago, I thought this would be fun.
Normally everything is done by mechanics which would be a very big task. But with microcontrollers everything is much simpler at least when you can reuse a lot of the work of others - many thanks to all!

So the goal was to make a clock (shows time), which mechanicly opens a small window every full hour, shows a bird and lets the bird sing. And closes the window shutter again.

I was very happy, when I discovered that you can buy rings with 12 multicolour Neopixels from adafruit. These give a nice display. I think this has more style than a digital text display and it is much simpler than a mechanical clock face.

The bird normally moves with a linear movement on a kind of sled. This was simplified to circular movement. A small platform has the shutter of the window fixed to it and carries the bird. It is directly fixed to a rc-servo axle. So the swing of the servo opens the shutter and brings the bird forward at the same time.

Of course the bird must sing at full hours, so you need a possibility to output sound, which is done using *.wav files together with the DAC of the teensy and with the sound library.

Function Hardware Library Comment (The following lines should be a table)

Time: Built-In Timer TimeLib

Set time accurately: Receiver for time broadcast DCF77 (Pollin) DCF77 Library hat to be modified slightly because of different interrupts. Because of automatic time setting, no user buttons are needed.

LED (13) for indication of reception
LED blinks 3 times, if the last correct reception was within last hour
2 times if it was within one day and
1 time per signal (signal is copied to LED) if the reception was not yet correct or if it was longer ago than one day.


Open/Close Shutter: show bird Rc-servo, high quality for low noise PWMservo
The servo brings noise into the audio output. To minimise this, the pwm signal is shut down after movement (make pin input). In addition a RC filter of 47 Ohms and 470µF is used at the 5V supply of the LM386.

Clock face: Adafruit Neopixel Ring, WS2812Serial Small addition to deal with 4 LEDs per pixel. See function "setPixel".
The LEDs are very bright, so they are used only with 1/255 (White) and 10/255 (others) of their power.
Red indicates hours.
Green indicates minutes in steps of 5.
Blue shows at 5 seconds intervals.
White shows the 12 positions of the face constantly.

Let bird sing: SD-card to hold wav- Files, See example WAV player There are nice WAV files on the internet. To convert them to 16bit, 44.1kHz you can use Audacity. So the bird is not allways a cockoo…. https://www.vogelstimmen.info/Vogelstimmen_Hoerproben_GRATIS.html
DAC 12-bit
Small amp with LM386 and small loud speaker The amp is as included in the datasheet of LM386.

Multitasking TeensyThreads Simplifies programming a lot and is easy to use! There are 4 tasks:
1. Check DCF77 reception and indicate its status
2. Set the clock if new broadcast available
3. Display the time
4. Normal loop: Show bird and start to play WAV

I took some time to consider, which controller would be best to use. (Original Arduino, Arduino with BASCOM, Arduino M0, Raspi, Parallax Propeller, Teensy 3.2, 3.5)
I choose Teensy 3.5 mainly because of the audio library, the DAC and the SD-card, DCF77-library for Arduino. Fallback would have been the audio shield. With a raspi I would have used internet time with wlan instead of DCF77. Of course Teensy 3.5 needs only about 7% of its memory and you could solder a SD-card to 3.2….
 

Attachments

  • 20180722_154243.jpg
    20180722_154243.jpg
    60.7 KB · Views: 246
  • Kuckuck_F.ino
    9.5 KB · Views: 172
Hi,
Thanks to pjrc and the guys who have made all the libraries. I just want to say, that the clock works completely stable for some months now 7/24.
😊 Christof
 
I would like to feature this project on our blog. Do you have any more pictures of it?
 
I was inspired by this project to make something to appeal to my grandson. Thanks for all the hard work that made my job easy!

https://www.youtube.com/watch?v=fK1i3N29YwI

Code:
#include <TimeLib.h>

#define BROWNTOUCH  0         //not yet
#define BLUETOUCH   33       //touchpads -- color wire attached to pad/pin #
#define PURPLETOUCH 25      //hours
#define GRAYTOUCH   30      //???attached or miscounted???
#define WHITETOUCH  32
#define YELLOWTOUCH 17
#define GREENTOUCH  16      //minutes
#define ORANGETOUCH 15       //not yet

#include <PWMServo.h>  
  PWMServo myservo;
  int pos;
#define SERVO_PIN  21

#include <WS2812Serial.h>
const int numled = 60;
const int led_pin = 10;       //Pin to connect LED ring data in
byte drawingMemory[numled*3];         //  3 bytes per LED
DMAMEM byte displayMemory[numled*12]; // 12 bytes per LED

WS2812Serial ring(numled, displayMemory, drawingMemory, led_pin, WS2812_RGB);



#define  HR_COLOR  0x000018       //RGB::Blue
#define  MIN_COLOR 0x180000      //RGB::Green
#define  SEC_COLOR 0x001800       //RGB::Red
#define  BLACK     0x000000
//Adafruit_NeoPixel ring(LED_COUNT, LED_PIN, NEO_RGB + NEO_KHZ800);

#include <SPI.h>
#include <SD.h>
#include <Audio.h>
#include <SerialFlash.h>
// https://github.com/FrankBoesing/Arduino-Teensy-Codec-lib
#include <play_sd_aac.h>


// GUItool: begin automatically generated code
//AudioPlaySdWav           playSdWav1;     //xy=154,422
AudioPlaySdAac           playAac1; //xy=154,422
AudioMixer4              mixer1;         //xy=327,432
AudioOutputAnalog        dac1;           //xy=502,412
AudioConnection          patchCord1(playAac1, 0, mixer1, 0);
AudioConnection          patchCord2(playAac1, 1, mixer1, 1);
AudioConnection          patchCord3(mixer1, dac1);
// GUItool: end automatically generated code


#define PROP_AMP_ENABLE    5
#define FLASH_CHIP_SELECT  6

  int h = 0;
  int m = 1;
  int s = 2;
  int hADD = 0; 
  int mADD = 0;
  int state = 0;
  
void setup()  {
  // set the Time library to use Teensy 3.0's RTC to keep time
  setSyncProvider(getTeensy3Time);
  ring.begin();
  ring.show(); // Initialize all pixels to 'off'
  //ring.setBrightness(32);
  AudioMemory(8); //4
  delay(2000);

  // Start SerialFlash
  if (!SerialFlash.begin(FLASH_CHIP_SELECT)) {
    while (1) {
      Serial.println ("Cannot access SPI Flash chip");
      delay (1000);
    }
  }
  //Set Volume
  mixer1.gain(0, 0.1);     //volume very low for testing
  mixer1.gain(1, 0.1);

  //Start Amplifier
  pinMode(PROP_AMP_ENABLE, OUTPUT);
  digitalWrite(PROP_AMP_ENABLE, HIGH);    // Enable Amplifier

  myservo.write(90);
  pos = 90;
  myservo.attach(SERVO_PIN);  // attaches the servo on pin 35 to the servo object 
  delay(1000);
  
  
 Serial.begin(9600);
  //while (!Serial);  // Wait for Arduino Serial Monitor to open

#ifdef TeensyView
  delay(100);
  oled.begin();    // Initialize the OLED
  oled.clear(ALL); // Clear the display's internal memory
 //oled.display();  // Display what's in the buffer (splashscreen)
 //delay(1000);     // Delay 1000 ms
  oled.clear(PAGE); // Clear the buffer.
  oled.setFontType(1);  // Set font to type 1
  oled.setCursor(5, 0); // move cursor

  
  if (timeStatus()!= timeSet) {
    oled.print("Unable to sync with the RTC");
  } else {
    oled.print("RTC has set the system time");
  }

  oled.display(); 
  delay(1000);

  oled.clear(PAGE);
#endif //TeensyView

 //Teensy3_setSystemClock(Teensy3Clock.get());   //teensy4
}    //end setup()

void loop() {
 
  if (Serial.available()) {
    time_t t = processSyncMessage();
    if (t != 0) {
      Teensy3Clock.set(t); // set the RTC
      setTime(t);
    }
  }
 Serial.print( touchRead(PURPLETOUCH) ); Serial.print("  ");  Serial.print( touchRead(BLUETOUCH) ); Serial.print("  "); Serial.print( touchRead(GRAYTOUCH) ); Serial.print("  ");Serial.print("  "); 
   Serial.print( touchRead(YELLOWTOUCH) ); Serial.print("  ");   Serial.print( touchRead(ORANGETOUCH) ); Serial.print("  ");Serial.print( touchRead(WHITETOUCH) ); Serial.println("  ");


  if (touchRead(PURPLETOUCH) >3000) { 
    hADD++; Serial.println("hour button"); delay(200);} 
     
  if (touchRead(GREENTOUCH) >3000){
    mADD++; Serial.println("minute button"); delay(200);}
    
  if (touchRead(BLUETOUCH)  > 3000) { state = 1; Serial.println("Touched Blue"); }
  if (touchRead(YELLOWTOUCH)  > 3000) { state = 3; Serial.println("Touched Yellow"); }
  if (touchRead(WHITETOUCH)  > 3000) { state = 2; Serial.println("Touched White");}
  
  if (touchRead(ORANGETOUCH)  > 3000) { state = 4; Serial.println("Touched Orange");}
  
  if (touchRead(GRAYTOUCH)  > 3000) { state = 5;Serial.println("Touched Gray"); }
  if (touchRead(BROWNTOUCH)  > 3000) { state = 6;Serial.println("Touched Brown"); }
  

  if (state == 0) {
      digitalClockDisplay();
    delay(200);
  }
  if (state == 1) {
      rainbowSpin1();
  }
  if (state == 2) {
      rainbowSpin2();
    }
    if (state == 3) {
      rainbowSpin3();
    }
    if (state == 4) {
      elvisRockClock();     //ORANGE
    }
    if (state == 5) {
      elvisCutTheCake();   //gray
    }
    if (state == 6) {
      elvisList();
    }
  
}

long getPixelColor(int pix){      //read the value of a neopixel. This is included in Adafruit_Neopixel. 
  int k=pix*3;                    //I suspect the code is slightly different if you have GRB, BRG, RGB, RBG, BFG. RBG is the best!
  return((drawingMemory[k]) | (drawingMemory[k+2]<<16) | (drawingMemory[k+1]<<8) );
}

void digitalClockDisplay() {       //  digital clock display of the time
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.print(" ");
  Serial.print(day());
  Serial.print(" ");
  Serial.print(month());
  Serial.print(" ");
  Serial.print(year()); 
  Serial.println(); 
  
  

  ring.setPixel(h, BLACK);  //blank old time
  ring.setPixel(m, BLACK);  //blank old time
  ring.setPixel(s, BLACK);  //blank old time

  h =30+ 5*(hADD+hour());       //30+ because pixel 0 is at 6 o'clock on ring.
  while (h>59) {h -= 60;}
  m = 30+ minute() + mADD;
  while (m > 59) {m -= 60;}
  s = 30+ second();
  while (s > 59) {s -= 60;}
  ring.setPixel(h, HR_COLOR);
  uint32_t color = getPixelColor(m);
  ring.setPixel(m, color|MIN_COLOR);
  color = getPixelColor(s);
  ring.setPixel(s, color|SEC_COLOR);
  ring.show();
}

time_t getTeensy3Time() {
  return Teensy3Clock.get();
}


/*void Teensy3_setSystemClock(unsigned long t)               //teensy 4 teensyduino beta 4/5/2020 doesn't set the system clock
{
   // stop the RTC
   SNVS_HPCR &= ~(SNVS_HPCR_RTC_EN | SNVS_HPCR_HP_TS);
   while (SNVS_HPCR & SNVS_HPCR_RTC_EN); // wait
   // stop the SRTC
   SNVS_LPCR &= ~SNVS_LPCR_SRTC_ENV;
   while (SNVS_LPCR & SNVS_LPCR_SRTC_ENV); // wait
   // set the SRTC
   SNVS_LPSRTCLR = t << 15;
   SNVS_LPSRTCMR = t >> 17;
   // start the SRTC
   SNVS_LPCR |= SNVS_LPCR_SRTC_ENV;
   while (!(SNVS_LPCR & SNVS_LPCR_SRTC_ENV)); // wait
   // start the RTC and sync it to the SRTC
   SNVS_HPCR |= SNVS_HPCR_RTC_EN | SNVS_HPCR_HP_TS;
}
*/

/*  code to process time sync messages from the serial port   */
#define TIME_HEADER  "T"   // Header tag for serial time sync message

unsigned long processSyncMessage() {
  unsigned long pctime = 0L;
  const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013 

  if(Serial.find(TIME_HEADER)) {
     pctime = Serial.parseInt();
     return pctime;
     if( pctime < DEFAULT_TIME) { // check the value is a valid time (greater than Jan 1 2013)
       pctime = 0L; // return 0 to indicate that the time is not valid
     }
  }
  return pctime;
}

void printDigits(int digits){
  // utility function for digital clock display: prints preceding colon and leading 0
  Serial.print(":");
  if(digits < 10)
    Serial.print('0');
  Serial.print(digits);
}


static const uint8_t PROGMEM _NeoPixelGammaTable[256] = {
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,  1,  1,  1,  1,
    1,  1,  1,  1,  2,  2,  2,  2,  2,  2,  2,  2,  3,  3,  3,  3,
    3,  3,  4,  4,  4,  4,  5,  5,  5,  5,  5,  6,  6,  6,  6,  7,
    7,  7,  8,  8,  8,  9,  9,  9, 10, 10, 10, 11, 11, 11, 12, 12,
   13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20,
   20, 21, 21, 22, 22, 23, 24, 24, 25, 25, 26, 27, 27, 28, 29, 29,
   30, 31, 31, 32, 33, 34, 34, 35, 36, 37, 38, 38, 39, 40, 41, 42,
   42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
   58, 59, 60, 61, 62, 63, 64, 65, 66, 68, 69, 70, 71, 72, 73, 75,
   76, 77, 78, 80, 81, 82, 84, 85, 86, 88, 89, 90, 92, 93, 94, 96,
   97, 99,100,102,103,105,106,108,109,111,112,114,115,117,119,120,
  122,124,125,127,129,130,132,134,136,137,139,141,143,145,146,148,
  150,152,154,156,158,160,162,164,166,168,170,172,174,176,178,180,
  182,184,186,188,191,193,195,197,199,202,204,206,209,211,213,215,
  218,220,223,225,227,230,232,235,237,240,242,245,247,250,252,255};
  
static uint8_t    gamma8(uint8_t x) {
    return pgm_read_byte(&_NeoPixelGammaTable[x]); // 0-255 in, 0-255 out
  }
// A 24-bit variant of gamma8() that applies the same function
// to all components of a packed RBG.
uint32_t gamma32(uint32_t x) {
  uint8_t *y = (uint8_t *)&x;
  for(uint8_t i=0; i<3; i++) y[i] = gamma8(y[i]);
  return x; // Packed 32-bit return
}

uint32_t colorHSV(uint16_t hue) {

  uint8_t r, g, b, sat=255, val=100;  //if val is too high, power draw makes it hang.

  // Remap 0-65535 to 0-1529. Pure red is CENTERED on the 64K rollover;
  // 0 is not the start of pure red, but the midpoint...a few values above
  // zero and a few below 65536 all yield pure red (similarly, 32768 is the
  // midpoint, not start, of pure cyan). The 8-bit RGB hexcone (256 values
  // each for red, green, blue) really only allows for 1530 distinct hues
  // (not 1536, more on that below), but the full unsigned 16-bit type was
  // chosen for hue so that one's code can easily handle a contiguous color
  // wheel by allowing hue to roll over in either direction.
  hue = (hue * 1530L + 32768) / 65536;
  // Because red is centered on the rollover point (the +32768 above,
  // essentially a fixed-point +0.5), the above actually yields 0 to 1530,
  // where 0 and 1530 would yield the same thing. Rather than apply a
  // costly modulo operator, 1530 is handled as a special case below.

  // So you'd think that the color "hexcone" (the thing that ramps from
  // pure red, to pure yellow, to pure green and so forth back to red,
  // yielding six slices), and with each color component having 256
  // possible values (0-255), might have 1536 possible items (6*256),
  // but in reality there's 1530. This is because the last element in
  // each 256-element slice is equal to the first element of the next
  // slice, and keeping those in there this would create small
  // discontinuities in the color wheel. So the last element of each
  // slice is dropped...we regard only elements 0-254, with item 255
  // being picked up as element 0 of the next slice. Like this:
  // Red to not-quite-pure-yellow is:        255,   0, 0 to 255, 254,   0
  // Pure yellow to not-quite-pure-green is: 255, 255, 0 to   1, 255,   0
  // Pure green to not-quite-pure-cyan is:     0, 255, 0 to   0, 255, 254
  // and so forth. Hence, 1530 distinct hues (0 to 1529), and hence why
  // the constants below are not the multiples of 256 you might expect.

  // Convert hue to R,G,B (nested ifs faster than divide+mod+switch):
  if(hue < 510) {         // Red to Green-1
    b = 0;
    if(hue < 255) {       //   Red to Yellow-1
      r = 255;
      g = hue;            //     g = 0 to 254
    } else {              //   Yellow to Green-1
      r = 510 - hue;      //     r = 255 to 1
      g = 255;
    }
  } else if(hue < 1020) { // Green to Blue-1
    r = 0;
    if(hue <  765) {      //   Green to Cyan-1
      g = 255;
      b = hue - 510;      //     b = 0 to 254
    } else {              //   Cyan to Blue-1
      g = 1020 - hue;     //     g = 255 to 1
      b = 255;
    }
  } else if(hue < 1530) { // Blue to Red-1
    g = 0;
    if(hue < 1275) {      //   Blue to Magenta-1
      r = hue - 1020;     //     r = 0 to 254
      b = 255;
    } else {              //   Magenta to Red-1
      r = 255;
      b = 1530 - hue;     //     b = 255 to 1
    }
  } else {                // Last 0.5 Red (quicker than % operator)
    r = 255;
    g = b = 0;
  }

  // Apply saturation and value to R,G,B, pack into 32-bit result:
  uint32_t v1 =   1 + val; // 1 to 256; allows >>8 instead of /255
  uint16_t s1 =   1 + sat; // 1 to 256; same reason
  uint8_t  s2 = 255 - sat; // 255 to 0
  return ((((((r * s1) >> 8) + s2) * v1) & 0xff00) << 8) |
          (((((g * s1) >> 8) + s2) * v1) & 0xff00)       |
         ( ((((b * s1) >> 8) + s2) * v1)           >> 8);
}

void rainbowSpin1() {
// Rainbow cycle along whole strip. Pass delay time (in ms) between frames.

  // Hue of first pixel runs 5 complete loops through the color wheel.
  // Color wheel has a range of 65536 but it's OK if we roll over, so
  // just count from 0 to 5*65536. Adding 256 to firstPixelHue each time
  // means we'll make 5*65536/256 = 1280 passes through this outer loop:
  for(uint32_t firstPixelHue = 0; firstPixelHue < 1*65536; firstPixelHue += 256) {
    Serial.print("firstPixelHue = "); Serial.println(firstPixelHue);
    for(int i=0; i<numled; i++) { // For each pixel in strip...
      // Offset pixel hue by an amount to make one full revolution of the
      // color wheel (range of 65536) along the length of the strip
      // (strip.numPixels() steps):
      long pixelHue = firstPixelHue + (i * 65536L / numled);
      // Here we're using just the single-argument hue variant. The result
      // is passed through strip.gamma32() to provide 'truer' colors
      // before assigning to each pixel:
     // Serial.print(i); Serial.print("  "); Serial.print(pixelHue); Serial.print("  ");Serial.print(colorHSV(pixelHue)); Serial.println();
      ring.setPixelColor(i, gamma32(colorHSV(pixelHue)  ));
      
    }
    ring.show(); // Update strip with new contents
    delay(10);  // Pause for a moment
  }
  state = 0;     //back to clock
  ring.clear();
}


// Rainbow-enhanced theater marquee. Pass delay time (in ms) between frames.
void rainbowSpin2() {
  int firstPixelHue = 0;     // First pixel starts at red (hue 0)
  for(int a=0; a<10; a++) {  // Repeat 30 times...
    for(int b=0; b<3; b++) { //  'b' counts from 0 to 2...
      ring.clear();         //   Set all pixels in RAM to 0 (off)
      // 'c' counts up from 'b' to end of strip in increments of 3...
      for(int c=b; c<numled; c += 3) {
        // hue of pixel 'c' is offset by an amount to make one full
        // revolution of the color wheel (range 65536) along the length
        // of the strip (strip.numPixels() steps):
        long      hue   = firstPixelHue + c * 65536L / numled;
        uint32_t color = gamma32(colorHSV(hue)); // hue -> RGB
        ring.setPixelColor(c, color ); // Set pixel 'c' to value 'color'
      }
      ring.show();                // Update strip with new contents
      delay(70);                 // Pause for a moment
      firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames
    }
  }
  state = 0;     //back to clock
  ring.clear();
}


void rainbowSpin3() {
  uint32_t color = random(65535);  
  for(int a=0; a<9; a++) {  // Repeat 10 times...
    for(int b=0; b<3; b++) { //  'b' counts from 0 to 2...
      ring.clear();         //   Set all pixels in RAM to 0 (off)
      // 'c' counts up from 'b' to end of strip in steps of 3...
      for(int c=b; c<numled; c += 3) {
        ring.setPixelColor(c,gamma32(colorHSV(color)) ); // Set pixel 'c' to value 'color'
      }
      ring.show(); // Update strip with new contents
      delay(100);  // Pause for a moment
    }
  }
  state = 0;     //back to clock
  ring.clear();
}

void playFile(const char *filename) {
 // Serial.println(); Serial.println("in playFile");
  SerialFlashFile ff = SerialFlash.open(filename);
  Serial.print("Playing file: ");
  Serial.println(filename);

  uint32_t sz = ff.size();
  uint32_t pos = ff.getFlashAddress();

  // Start playing the file.  This sketch continues to
  // run while the file plays.
  playAac1.play(pos,sz);

  // Simply wait for the file to finish playing.
  while (playAac1.isPlaying()) {
    yield();
  }
}

void openDoor() {
  int startPos = myservo.read();
  if(startPos == 255) {startPos = 90;}     //255 means undefined
  Serial.print(" OpenDoor startPosition = "); Serial.println(startPos);
  for(pos = startPos; pos <= startPos+88; pos++) {   //may need to adjust range
   // Serial.print(" pos="); Serial.print(pos);
    myservo.write(pos);
    delay(12);
  }
}

void closeDoor() {
  int startPos = myservo.read();
  if(startPos == 255) {startPos = 90;}     //255 means undefined
  Serial.print(" closeDoor startPosition = "); Serial.println(startPos);
  for (pos = startPos; pos >= startPos-88; pos--) {
    //Serial.print("pos = "); Serial.print(pos);
    myservo.write(pos);
    delay(12);
  }
}
void elvisRockClock() {
  openDoor();
  playFile("ROCKCLK.m4a");
  closeDoor();  
  state = 0;     //back to clock
  ring.clear();
}

void elvisCutTheCake() {
  openDoor();
  playFile("CUTCAKE.m4a");
  closeDoor(); 
  state = 0;     //back to clock
  ring.clear(); 
}


char fileList[6][13] = {"ENGLE.m4a","MARVTOY.m4a","HOTFROGS.m4a","GOZOO.m4a","FRED.m4a","DAYBORN.m4a"};
int filePointer = 0;

void elvisList() {
  openDoor();
  playFile(fileList[filePointer]);
  filePointer++;
  if(filePointer >= 6) {filePointer = 0;}
  closeDoor();
  state=0;
  ring.clear();
}
 
Huh. I can still see it and my son watched it. I'm new to youtube but I thought it was hard to find but public.
 
Dear sir! Bravo! It is a very nice project! I find it while was googling to make a clock like that for my kids,for education and use in home. It is perfect what i need. I do not see instructions on how to make it, the motor , the code, things like that. Is it possible to help me make it?
 
Hi Axenias,
in the first post there is some description (sorry, no full step by step tutorial) and the code is attached there too. There is some other code from jrraines in post #9. He does set the clock with buttons, while I use a long wave time broadcast radio receiver.
The motor to open the door is a servo motor for radio controlled models. It provides directly all the bearing for the circular sled and the door, which are directly coupled to its axle. It still works without problems after nearly 5 years now.
The clock face works without moving parts, just "Neopixel" LEDs with resolution 5 minutes.
Something like a bird in my version, Elvis in jrraines version. Feel free to make a quite different design....
Christof
 
Back
Top