/* FABACADEMY 2025 - Machine Week (12) * FabLab Ísafjörður * * DAYS OF DARKNESS * * A realistic landscape / solar model of Skutulsfjörður * * by Jóhannes Andrésson, Bjartur Leó Hlynsson, Ólöf Hannesdóttir, Högni Friðriksson */ #include #include #include LiquidCrystal_I2C lcd(0x27, 20, 4); // Define Analog Inputs for poteniometer and hall effect sensor const int ai_knob1 = 14; const int ai_knob2 = 15; const int ai_knob3 = 16; const int ai_hall = 18; // Define Analog Output for PWM of light const int ao_pwm = 13; // Define stepper motor constants and variables const int steps = 48; // internal steps in motor const float azratio = 97/10; // spur gear ratio const float elratio = 834/10; // spur gear ratio const float azstepdeg = 360/(steps*azratio); // deg per step const float elstepdeg = 360/(steps*elratio); // deg per step int azstep = 1; // azimuth step counter int elstep = 1; // elevation step counter float azpos; // az absolute position in deg float elpos; // el absolute position in deg const int do_M1P1 = 11; const int do_M1P2 = 10; const int do_M1P3 = 9; const int do_M1P4 = 8; const int do_M2P1 = 7; const int do_M2P2 = 6; const int do_M2P3 = 5; const int do_M2P4 = 4; // Define Digital Inputs (switches) const int di_set = 24; const int di_end = 25; // Define Variables for solar path double lat = 66.07243; double lon = -23.118711; int time_zone = 0; //UTC offset // Define Date and Time int yr = 2025; int dy = 25; int mo = 3; int hr = 15; int mm = 0; // Solar position double az; //azimuth in degrees double el; //elevation in degrees // Define Status Variables bool home_az = false; // True when homing is complete bool home_el = false; // True when homing is complete bool run_stat = false; // True when model is running int spd = 10; // Speed for constant operation in percentage // Define Variables for display refresh int lcddy; int lcdmo; int lcdyr; int lcdaz; int lcdel; // run zenith stepper motor either forward or backward int runel(unsigned long duration, bool dir){ static unsigned long chrono = millis(); if (millis() - chrono < duration) return; // check if desired elevation is reached with accuracy of 1 deg if (abs(elpos - el)< 1) return; chrono = millis(); // make step switch (elstep){ case 1: digitalWrite(do_M1P1, HIGH); digitalWrite(do_M1P2, HIGH); digitalWrite(do_M1P3, LOW); digitalWrite(do_M1P4, LOW); break; case 2: digitalWrite(do_M1P1, LOW); digitalWrite(do_M1P2, HIGH); digitalWrite(do_M1P3, HIGH); digitalWrite(do_M1P4, LOW); break; case 3: digitalWrite(do_M1P1, LOW); digitalWrite(do_M1P2, LOW); digitalWrite(do_M1P3, HIGH); digitalWrite(do_M1P4, HIGH); break; case 4: digitalWrite(do_M1P1, HIGH); digitalWrite(do_M1P2, LOW); digitalWrite(do_M1P3, LOW); digitalWrite(do_M1P4, HIGH); break; } //forward or reverse operation if(dir == true){ elpos = elpos+elstepdeg; if(elpos > 360)elpos=elpos-360; if(elstep < 4){ elstep++; }else{ elstep = 1; } }else{ //run motor cw elpos = elpos-elstepdeg; if(elpos < 0)elpos=elpos+360; if(elstep < 2){ elstep=4; }else{ elstep--; } } } // homing function for elevation stepper motor int homeel(bool dir){ // make step switch (elstep){ case 1: digitalWrite(do_M1P1, HIGH); digitalWrite(do_M1P2, HIGH); digitalWrite(do_M1P3, LOW); digitalWrite(do_M1P4, LOW); break; case 2: digitalWrite(do_M1P1, LOW); digitalWrite(do_M1P2, HIGH); digitalWrite(do_M1P3, HIGH); digitalWrite(do_M1P4, LOW); break; case 3: digitalWrite(do_M1P1, LOW); digitalWrite(do_M1P2, LOW); digitalWrite(do_M1P3, HIGH); digitalWrite(do_M1P4, HIGH); break; case 4: digitalWrite(do_M1P1, HIGH); digitalWrite(do_M1P2, LOW); digitalWrite(do_M1P3, LOW); digitalWrite(do_M1P4, HIGH); break; } //forward or reverse operation if(dir == true){ if(elstep < 4){ elstep++; }else{ elstep = 1; } }else{ //run motor cw if(elstep < 2){ elstep=4; }else{ elstep--; } } } // run azimuth stepper motor either forward or backward int runaz(unsigned long duration, bool dir){ static unsigned long chrono = millis(); if (millis() - chrono < duration) return; // check if desired azimuth is reached with accuracy of 1 deg if (abs(azpos - az)< 1) return; chrono = millis(); // make step switch (azstep){ case 1: digitalWrite(do_M2P1, HIGH); digitalWrite(do_M2P2, HIGH); digitalWrite(do_M2P3, LOW); digitalWrite(do_M2P4, LOW); break; case 2: digitalWrite(do_M2P1, LOW); digitalWrite(do_M2P2, HIGH); digitalWrite(do_M2P3, HIGH); digitalWrite(do_M2P4, LOW); break; case 3: digitalWrite(do_M2P1, LOW); digitalWrite(do_M2P2, LOW); digitalWrite(do_M2P3, HIGH); digitalWrite(do_M2P4, HIGH); break; case 4: digitalWrite(do_M2P1, HIGH); digitalWrite(do_M2P2, LOW); digitalWrite(do_M2P3, LOW); digitalWrite(do_M2P4, HIGH); break; } //forward or reverse operation if(dir == true){ azpos = azpos+azstepdeg; if(azpos > 360)azpos=azpos-360; if(azstep < 4){ azstep++; }else{ azstep = 1; } }else{ //run motor cw azpos = azpos-azstepdeg; if(azpos < 0)azpos=azpos+360; if(azstep < 2){ azstep=4; }else{ azstep--; } } } // homing function for azimuth stepper motor int homeaz(bool dir){ // make step switch (azstep){ case 1: digitalWrite(do_M2P1, HIGH); digitalWrite(do_M2P2, HIGH); digitalWrite(do_M2P3, LOW); digitalWrite(do_M2P4, LOW); break; case 2: digitalWrite(do_M2P1, LOW); digitalWrite(do_M2P2, HIGH); digitalWrite(do_M2P3, HIGH); digitalWrite(do_M2P4, LOW); break; case 3: digitalWrite(do_M2P1, LOW); digitalWrite(do_M2P2, LOW); digitalWrite(do_M2P3, HIGH); digitalWrite(do_M2P4, HIGH); break; case 4: digitalWrite(do_M2P1, HIGH); digitalWrite(do_M2P2, LOW); digitalWrite(do_M2P3, LOW); digitalWrite(do_M2P4, HIGH); break; } //forward or reverse operation if(dir == true){ if(azstep < 4){ azstep++; }else{ azstep = 1; } }else{ //run motor cw if(azstep < 2){ azstep=4; }else{ azstep--; } } } void readai_stop (unsigned long duration) { static unsigned long chrono = millis(); if (millis() - chrono < duration) return; chrono = millis(); dy = int(float(analogRead(ai_knob1)) / 1023 * 30 + 0.5)+1; mo = int(float(analogRead(ai_knob2)) / 1023 * 11 + 0.5)+1; hr = int(float(analogRead(ai_knob3)) / 1023 * 23); mm = int((float(analogRead(ai_knob3)) / 1023 * 23 - hr) * 60 +0.5); } void setbutton(unsigned long duration) { static unsigned long chrono = millis(); if (millis() - chrono < duration) return; chrono = millis(); if(digitalRead(di_set) == LOW){ run_stat = !run_stat; } } void updateoled (unsigned long duration){ static unsigned long chrono = millis(); if (millis() - chrono < duration) return; chrono = millis(); // check if values have changed - if not, return if (lcddy == dy && lcdmo == mo && lcdyr == yr && lcdaz == int(az+0.5) && lcdel == int(el+0.5)) return; // update line 1 lcd.clear(); lcd.setCursor(0,0); if(dy<10)lcd.print("0"); lcd.print(dy); lcd.print("."); if(mo<10)lcd.print("0"); lcd.print(mo); lcd.print(".2025 "); if(hr<10)lcd.print("0"); lcd.print(hr); lcd.print(":"); if(mm<10)lcd.print("0"); lcd.print(mm); //update line 2 if(int(az+0.5)>=100)lcd.setCursor(0,1); if(int(az+0.5)< 100)lcd.setCursor(1,1); if(int(az+0.5)< 10)lcd.setCursor(2,1); lcd.print(int(az+0.5)); lcd.print(char(223)); if(int(el+0.5)>=10)lcd.setCursor(7,1); if(int(el+0.5)< 10)lcd.setCursor(8,1); if(int(el+0.5)< 0)lcd.setCursor(7,1); if(int(el+0.5)< -9)lcd.setCursor(6,1); lcd.print(int(el+0.5)); lcd.print(char(223)); lcddy = dy; lcdmo = mo; lcdyr = yr; lcdaz = az; lcdel = el; } void solarcalc (unsigned long duration){ static unsigned long chrono = millis(); if (millis() - chrono < duration) return; chrono = millis(); //set new time according to poteniometer input setTime(hr,0,0,dy,mo,yr); time_t utc = now(); // Calculate solar position calcHorizontalCoordinates(utc, lat, lon, az, el); } // Adjust brightnes of the sun by elevation starting from -14° void sunpwm (unsigned long duration){ static unsigned long chrono = millis(); if (millis() - chrono < duration) return; chrono = millis(); int bright; bright = int(3.6563*el+71.2); if(bright > 254) bright = 254; if(bright < 0) bright = 0; analogWrite(ao_pwm,bright); } // Turn of motor to save energy void stopaz (unsigned long duration){ static unsigned long chrono = millis(); if (millis() - chrono < duration) return; chrono = millis(); digitalWrite(do_M2P1, LOW); digitalWrite(do_M2P2, LOW); digitalWrite(do_M2P3, LOW); digitalWrite(do_M2P4, LOW); } void setup() { analogWrite(ao_pwm,124); Wire.begin(); lcd.begin(16,2); // Set the LCD size (16 columns and 2 rows) lcd.backlight(); // Turn on backlight lcd.setCursor(0,0); lcd.print(" FABACADEMY 25"); lcd.setCursor(0,1); lcd.print("DAYS OF DARKNESS"); // set switches with internal pullup pinMode(di_set, INPUT_PULLUP); pinMode(di_end, INPUT_PULLUP); delay(2000); analogWrite(ao_pwm,0); } void loop() { // check if homing needs to be done if(home_az == false){ //Homing azimuth axis lcd.clear(); lcd.setCursor(1,0); lcd.print("Homing azimuth"); int hall_last = analogRead(ai_hall); lcd.setCursor(0,1); lcd.print(hall_last); // when close to magnet reverse a bit while(analogRead(ai_hall) < 500){ homeaz(false); lcd.clear(); lcd.setCursor(1,0); lcd.print("Homing azimuth"); lcd.setCursor(0,1); lcd.print(analogRead(ai_hall)); delay(30); // when close to magnet } // approach magnet fast and stop while(analogRead(ai_hall) > 400){ homeaz(true); lcd.clear(); lcd.setCursor(1,0); lcd.print("Homing azimuth"); lcd.setCursor(0,1); lcd.print(analogRead(ai_hall)); delay(30); } // find minimum value for magnet - compare with value from step before // run until minimum has passed do{ hall_last = analogRead(ai_hall); homeaz(true); lcd.clear(); lcd.setCursor(1,0); lcd.print("Homing azimuth"); delay(500); }while(hall_last > analogRead(18)); // back one step homeaz(false); lcd.clear(); lcd.setCursor(1,0); lcd.print("Homing azimuth"); lcd.setCursor(5,1); lcd.print("DONE"); home_az = true; azpos = 180; // Home position is with sun from the south delay(2000); } if(home_el == false){ lcd.clear(); lcd.setCursor(1,0); lcd.print("Homing zenith"); lcd.setCursor(0,1); lcd.print(digitalRead(di_end)); while(digitalRead(di_end)==HIGH){ lcd.clear(); lcd.setCursor(1,0); lcd.print("Homing zenith"); lcd.setCursor(0,1); lcd.print(digitalRead(di_end)); homeel(false); delay(30); } home_el = true; elpos = 0; //End switch is at around 0° elevation lcd.clear(); lcd.setCursor(1,0); lcd.print("Homing zenith"); lcd.setCursor(5,1); lcd.print("DONE"); delay(1000); } // Update solar position solarcalc(100); // Check if set button is pressed setbutton(200); // Check if model is running or stop if(run_stat == true){ if(abs(azpos - az)> 0.8)runaz(30,true); if(abs(elpos - el)> 0.8){ if(elelpos)runel(30,true); } //set brightness sunpwm(1000); // when position is reached, turn off if (abs(azpos - az)< 1 && abs(elpos - el)<1)run_stat = false; }else{ stopaz(400); readai_stop(500); } // Display update updateoled(200); }