├── Models ├── Body.stl ├── Gear.stl ├── Plate1.stl ├── Plate2.stl ├── Bracket2.stl ├── FullClock.blend ├── plateHolder.stl ├── PlateConnector.stl ├── connectorLarge.stl └── connectorSmall.stl ├── README.md └── ArduinoCode └── ArduinoCode.ino /Models/Body.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuukEsselbrugge/StepperClock/HEAD/Models/Body.stl -------------------------------------------------------------------------------- /Models/Gear.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuukEsselbrugge/StepperClock/HEAD/Models/Gear.stl -------------------------------------------------------------------------------- /Models/Plate1.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuukEsselbrugge/StepperClock/HEAD/Models/Plate1.stl -------------------------------------------------------------------------------- /Models/Plate2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuukEsselbrugge/StepperClock/HEAD/Models/Plate2.stl -------------------------------------------------------------------------------- /Models/Bracket2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuukEsselbrugge/StepperClock/HEAD/Models/Bracket2.stl -------------------------------------------------------------------------------- /Models/FullClock.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuukEsselbrugge/StepperClock/HEAD/Models/FullClock.blend -------------------------------------------------------------------------------- /Models/plateHolder.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuukEsselbrugge/StepperClock/HEAD/Models/plateHolder.stl -------------------------------------------------------------------------------- /Models/PlateConnector.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuukEsselbrugge/StepperClock/HEAD/Models/PlateConnector.stl -------------------------------------------------------------------------------- /Models/connectorLarge.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuukEsselbrugge/StepperClock/HEAD/Models/connectorLarge.stl -------------------------------------------------------------------------------- /Models/connectorSmall.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuukEsselbrugge/StepperClock/HEAD/Models/connectorSmall.stl -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StepperClock 2 | An Arduino based 3D printed stepper motor clock 3 | 4 | ## Components 5 | * 4X 74HC595 6 | * 4X WS2812B LED's 7 | * 4X ULN2003 stepper driver (comes with the default board) 8 | * Arduino Nano 9 | * NEO 6M GPS Module 10 | * 4X 28BYJ-48 Stepper motor 11 | 12 | ## 3D printed parts 13 | * 4X Case 14 | * 4X Plate 1 15 | * 4X Plate 2 16 | * 4X Plate connector 17 | * 4X Stepper Motor Gear 18 | * 8X Plater holder 19 | * 4X Small Case Connector 20 | * 2X Large Case Connector 21 | 22 | ## Diagram 23 | Adding soon 24 | -------------------------------------------------------------------------------- /ArduinoCode/ArduinoCode.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int SER_Pin = 12; //pin 14 on the 75HC595 7 | int RCLK_Pin = 11; //pin 12 on the 75HC595 8 | int SRCLK_Pin = 10; //pin 11 on the 75HC595 9 | 10 | int RX_GPS = 5; //RX pin on GPS module 11 | SoftwareSerial SoftSerial(RX_GPS, 0); 12 | 13 | #define LED_TYPE WS2812 14 | #define NUM_LEDS 4 15 | #define DATA_PIN 3 16 | CRGB leds[NUM_LEDS]; 17 | 18 | OneWire oneWire(6); 19 | DallasTemperature sensors(&oneWire); 20 | 21 | #define shiftRegCount 4 22 | #define RegisterPins shiftRegCount * 8 23 | boolean registers[RegisterPins+8]; 24 | 25 | // Limitswitch Pin, Stepper motor pins shiftregister offset, Calibration offset, CurrentDigit, Amount of Movement left 26 | int MOTORS[4][5] = { 27 | {A0, 0, 20, 0, 0}, 28 | {A3, 8, 30, 0, 0}, 29 | {2, 16, 30, 0, 0}, 30 | {8, 24, 10, 0, 0} 31 | }; 32 | 33 | //Switch 1 and 2 pins used for changing color and modes 34 | int SW1 = A5; 35 | int SW2 = A2; 36 | 37 | int DayColor[] = {255, 255, 255}; 38 | int NightColor[] = {48, 255, 0}; 39 | 40 | //Clear entire shift register 41 | void clearRegisters(){ 42 | for(int i = RegisterPins - 1; i >= 0; i--){ 43 | registers[i] = LOW; 44 | } 45 | } 46 | //Update register 47 | void writeRegisters(){ 48 | digitalWrite(RCLK_Pin, LOW); 49 | for(int i = RegisterPins - 1; i >= 0; i--){ 50 | digitalWrite(SRCLK_Pin, LOW); 51 | int val = registers[i]; 52 | digitalWrite(SER_Pin, val); 53 | digitalWrite(SRCLK_Pin, HIGH); 54 | } 55 | digitalWrite(RCLK_Pin, HIGH); 56 | } 57 | 58 | //Motor step delay lower then 500 has issues 59 | int motorSpeed = 400; 60 | 61 | char currentDate[] = "000000"; 62 | char currentTime[] = "000000.00"; 63 | //Default offset is CET 64 | int timezoneOffset = 1; 65 | 66 | int currentTemp = 0; 67 | int TimeFix = 0; 68 | 69 | int ended = 0; 70 | int started = 1; 71 | 72 | 73 | void setup() { 74 | FastLED.addLeds(leds, NUM_LEDS); 75 | sensors.begin(); 76 | 77 | Serial.begin(9600); 78 | 79 | pinMode(SER_Pin, OUTPUT); 80 | pinMode(RCLK_Pin, OUTPUT); 81 | pinMode(SRCLK_Pin, OUTPUT); 82 | 83 | for(int x = 0; x < 4; x++){ 84 | pinMode(MOTORS[x][0], INPUT); 85 | } 86 | 87 | pinMode(SW1, INPUT); 88 | pinMode(SW2, INPUT); 89 | 90 | //If SW2 pressed on boot change time to UTC +2 (CEST) 91 | if(digitalRead(SW2)){ 92 | timezoneOffset = 2; 93 | } 94 | 95 | for(int x = 0; x < 4; x++){ 96 | calibrate(MOTORS[x]); 97 | } 98 | clearRegisters(); 99 | writeRegisters(); 100 | Background(1, NightColor); 101 | SoftSerial.begin(9600); 102 | } 103 | 104 | int mode = 1; 105 | int colorMode = 0; 106 | long lastBlink = 0; 107 | int blinkBright = 0; 108 | 109 | void loop(){ 110 | if(digitalRead(SW1)){ 111 | mode = !mode; 112 | } 113 | 114 | if(mode){ 115 | updateDateTime(); 116 | showTime(); 117 | }else{ 118 | updateTemp(); 119 | showTemp(); 120 | BackgroundTemp(); 121 | } 122 | //Serial.println(currentTime); 123 | } 124 | 125 | //Update time when it changes 126 | void showTime(){ 127 | //Stop the serial connection with GPS when driving steppers 128 | //Because it causes lag 129 | if(MOTORS[0][4] != 0 || MOTORS[1][4] != 0 || MOTORS[2][4] != 0 || MOTORS[3][4] != 0){ 130 | if(!ended){ 131 | SoftSerial.end(); 132 | ended = 1; 133 | started = 0; 134 | } 135 | }else{ 136 | if(!started){ 137 | SoftSerial.begin(9600); 138 | started = 1; 139 | ended = 0; 140 | } 141 | } 142 | 143 | //Doing this is ugly but the fast way by substracting -48 from the ASCCI value sometimes caused strange behavior 144 | //Converting to string first seems to work best 145 | char tmp[2]; 146 | tmp[1] = 0; 147 | for(int x = 0; x < 4; x++){ 148 | tmp[0] = currentTime[x]; 149 | updateDigit2(MOTORS[x], atoi(tmp)); 150 | ///updateDigit(MOTORS[x], atoi(tmp)); 151 | } 152 | } 153 | 154 | void showTemp(){ 155 | updateDigit(MOTORS[1], currentTemp / 1000); 156 | updateDigit(MOTORS[2], currentTemp / 100 % 10); 157 | } 158 | 159 | void updateTemp(){ 160 | sensors.requestTemperatures(); 161 | currentTemp = sensors.getTempCByIndex(0) * 100; 162 | } 163 | 164 | //Update digit pos type 1; is blocking not used currently 165 | void updateDigit(int motor[], int digit){ 166 | if(motor[3] != digit && digit >= 0 && digit <= 9){ 167 | SoftSerial.end(); 168 | if(digit > motor[3]){ 169 | int x = (digit-motor[3])* (280); 170 | while(x > 0){ 171 | clockwise(motor[1], motorSpeed); 172 | x--; 173 | } 174 | } else{ 175 | int x = (motor[3]-digit)*(280); 176 | while(x > 0){ 177 | counterclockwise(motor[1], motorSpeed); 178 | x--; 179 | } 180 | } 181 | SoftSerial.begin(9600); 182 | motor[3] = digit; 183 | clearstepper(motor[1]); 184 | } 185 | 186 | } 187 | 188 | //Update digit type 2; is non blocking 189 | void updateDigit2(int motor[], int digit){ 190 | if(motor[3] != digit && digit >= 0 && digit <= 9){ 191 | if(digit > motor[3]){ 192 | if(motor[4] == 0){ 193 | motor[4] = (digit-motor[3])* (280); 194 | } 195 | clockwise(motor[1], motorSpeed); 196 | motor[4]--; 197 | } 198 | else{ 199 | if(motor[4] == 0){ 200 | motor[4] = (motor[3]-digit)*(280); 201 | } 202 | counterclockwise(motor[1], motorSpeed); 203 | motor[4]--; 204 | } 205 | 206 | if(motor[4] == 0){ 207 | motor[3] = digit; 208 | clearstepper(motor[1]); 209 | } 210 | } 211 | } 212 | 213 | //Move motor back to zero 214 | void calibrate(int motor[]){ 215 | while(!digitalRead(motor[0])){ 216 | counterclockwise(motor[1], 500); 217 | } 218 | //calibration offset 219 | for(int x = 0; x <= motor[2]; x++){ 220 | clockwise(motor[1], 500); 221 | } 222 | motor[3] = 0; 223 | clearRegisters(); 224 | writeRegisters(); 225 | } 226 | 227 | //Very nice and cool compact code for reading GPS time 228 | char tmpTime[] = "000000.00"; 229 | //GPS time header 230 | char header[] = "$GPRMC"; 231 | int charCount = 0; 232 | int match = 0; 233 | int item = 0; 234 | int itemCount = 0; 235 | int timeReady = 0; 236 | void updateDateTime(){ 237 | while (SoftSerial.available() > 0) { 238 | char c = SoftSerial.read(); 239 | Serial.write(c); 240 | if(match){ 241 | if(c >= 48 && c <= 57 || c == 46){ 242 | tmpTime[itemCount] = c; 243 | itemCount++; 244 | } 245 | if(c == ','){ 246 | if(currentTime != tmpTime && itemCount == 9){ 247 | //Add timezone offset 248 | memcpy(currentTime, tmpTime, 8); 249 | int A = int(currentTime[0])-48; 250 | int B = int(currentTime[1])-48; 251 | 252 | int hour = A*10+(B+timezoneOffset); 253 | if(hour > 23){ 254 | hour -= 24; 255 | } 256 | currentTime[0] = 48 + hour / 10; 257 | currentTime[1] = 48 + hour % 10; 258 | Serial.println(currentTime); 259 | } 260 | itemCount = 0; 261 | match = 0; 262 | item = 0; 263 | } 264 | }else{ 265 | if(charCount >= 6){ 266 | charCount = 0; 267 | match = 1; 268 | } 269 | if (header[charCount] == c){ 270 | charCount++; 271 | }else{ 272 | charCount = 0; 273 | } 274 | } 275 | } 276 | } 277 | 278 | void Background(int br, int color[]){ 279 | leds[0] = CRGB(color[0], color[1], color[2]); 280 | leds[1] = CRGB(color[0], color[1], color[2]); 281 | leds[2] = CRGB(color[0], color[1], color[2]); 282 | leds[3] = CRGB(color[0], color[1], color[2]); 283 | if(br){ 284 | FastLED.setBrightness(255); 285 | }else{ 286 | FastLED.setBrightness(200); 287 | } 288 | 289 | FastLED.show(); 290 | FastLED.show(); 291 | } 292 | 293 | void BackgroundTemp(){ 294 | if(currentTemp < 15){ 295 | leds[1] = CRGB(0, 0, 255); 296 | leds[2] = CRGB(0, 0, 255); 297 | } 298 | if(currentTemp > 20 && currentTemp < 25){ 299 | leds[1] = CRGB(0, 255, 0); 300 | leds[2] = CRGB(0, 255, 0); 301 | } 302 | if(currentTemp > 25){ 303 | leds[1] = CRGB(255, 0, 0); 304 | leds[2] = CRGB(255, 0, 0); 305 | } 306 | 307 | leds[0] = CRGB(0, 0, 0); 308 | leds[3] = CRGB(0, 0, 0); 309 | FastLED.show(); 310 | } 311 | 312 | void setRegisterPin(int index, int value){ 313 | registers[index] = value; 314 | } 315 | 316 | void clearstepper(int offset){ 317 | setRegisterPin(1+offset, LOW); 318 | setRegisterPin(2+offset, LOW); 319 | setRegisterPin(3+offset, LOW); 320 | setRegisterPin(4+offset, LOW); 321 | writeRegisters(); 322 | } 323 | 324 | void counterclockwise (int offset, int motorSpeed){ 325 | // 1 326 | setRegisterPin(1+offset, HIGH); 327 | setRegisterPin(2+offset, LOW); 328 | setRegisterPin(3+offset, LOW); 329 | setRegisterPin(4+offset, LOW); 330 | writeRegisters(); 331 | delayMicroseconds(motorSpeed); 332 | // 2 333 | setRegisterPin(1+offset, HIGH); 334 | setRegisterPin(2+offset, HIGH); 335 | setRegisterPin(3+offset, LOW); 336 | setRegisterPin(4+offset, LOW); 337 | writeRegisters(); 338 | delayMicroseconds (motorSpeed); 339 | // 3 340 | setRegisterPin(1+offset, LOW); 341 | setRegisterPin(2+offset, HIGH); 342 | setRegisterPin(3+offset, LOW); 343 | setRegisterPin(4+offset, LOW); 344 | writeRegisters(); 345 | delayMicroseconds(motorSpeed); 346 | // 4 347 | setRegisterPin(1+offset, LOW); 348 | setRegisterPin(2+offset, HIGH); 349 | setRegisterPin(3+offset, HIGH); 350 | setRegisterPin(4+offset, LOW); 351 | writeRegisters(); 352 | delayMicroseconds(motorSpeed); 353 | // 5 354 | setRegisterPin(1+offset, LOW); 355 | setRegisterPin(2+offset, LOW); 356 | setRegisterPin(3+offset, HIGH); 357 | setRegisterPin(4+offset, LOW); 358 | writeRegisters(); 359 | delayMicroseconds(motorSpeed); 360 | // 6 361 | setRegisterPin(1+offset, LOW); 362 | setRegisterPin(2+offset, LOW); 363 | setRegisterPin(3+offset, HIGH); 364 | setRegisterPin(4+offset, HIGH); 365 | writeRegisters(); 366 | delayMicroseconds (motorSpeed); 367 | // 7 368 | setRegisterPin(1+offset, LOW); 369 | setRegisterPin(2+offset, LOW); 370 | setRegisterPin(3+offset, LOW); 371 | setRegisterPin(4+offset, HIGH); 372 | writeRegisters(); 373 | delayMicroseconds(motorSpeed); 374 | // 8 375 | setRegisterPin(1+offset, HIGH); 376 | setRegisterPin(2+offset, LOW); 377 | setRegisterPin(3+offset, LOW); 378 | setRegisterPin(4+offset, HIGH); 379 | writeRegisters(); 380 | delayMicroseconds(motorSpeed); 381 | } 382 | 383 | void clockwise(int offset, int motorSpeed){ 384 | // 1 385 | setRegisterPin(4+offset, HIGH); 386 | setRegisterPin(3+offset, LOW); 387 | setRegisterPin(2+offset, LOW); 388 | setRegisterPin(1+offset, LOW); 389 | writeRegisters(); 390 | delayMicroseconds(motorSpeed); 391 | // 2 392 | setRegisterPin(4+offset, HIGH); 393 | setRegisterPin(3+offset, HIGH); 394 | setRegisterPin(2+offset, LOW); 395 | setRegisterPin(1+offset, LOW); 396 | writeRegisters(); 397 | delayMicroseconds (motorSpeed); 398 | // 3 399 | setRegisterPin(4+offset, LOW); 400 | setRegisterPin(3+offset, HIGH); 401 | setRegisterPin(2+offset, LOW); 402 | setRegisterPin(1+offset, LOW); 403 | writeRegisters(); 404 | delayMicroseconds(motorSpeed); 405 | // 4 406 | setRegisterPin(4+offset, LOW); 407 | setRegisterPin(3+offset, HIGH); 408 | setRegisterPin(2+offset, HIGH); 409 | setRegisterPin(1+offset, LOW); 410 | writeRegisters(); 411 | delayMicroseconds(motorSpeed); 412 | // 5 413 | setRegisterPin(4+offset, LOW); 414 | setRegisterPin(3+offset, LOW); 415 | setRegisterPin(2+offset, HIGH); 416 | setRegisterPin(1+offset, LOW); 417 | writeRegisters(); 418 | delayMicroseconds(motorSpeed); 419 | // 6 420 | setRegisterPin(4+offset, LOW); 421 | setRegisterPin(3+offset, LOW); 422 | setRegisterPin(2+offset, HIGH); 423 | setRegisterPin(1+offset, HIGH); 424 | writeRegisters(); 425 | delayMicroseconds (motorSpeed); 426 | // 7 427 | setRegisterPin(4+offset, LOW); 428 | setRegisterPin(3+offset, LOW); 429 | setRegisterPin(2+offset, LOW); 430 | setRegisterPin(1+offset, HIGH); 431 | writeRegisters(); 432 | delayMicroseconds(motorSpeed); 433 | // 8 434 | setRegisterPin(4+offset, HIGH); 435 | setRegisterPin(3+offset, LOW); 436 | setRegisterPin(2+offset, LOW); 437 | setRegisterPin(1+offset, HIGH); 438 | writeRegisters(); 439 | delayMicroseconds(motorSpeed); 440 | } 441 | --------------------------------------------------------------------------------