/* * Program for a 6-digit, SN74141-based alarm clock; * coding by Robin Birtles and Chris Gerekos ( based on http://arduinix.com/Main/Code/ANX-6Tube-Clock-Crossfade.txt ); * for RLB Designs ( http://www.rlb-designs.com/ ). */ // SN74141 : Truth Table //D C B A # //L,L,L,L 0 //L,L,L,H 1 //L,L,H,L 2 //L,L,H,H 3 //L,H,L,L 4 //L,H,L,H 5 //L,H,H,L 6 //L,H,H,H 7 //H,L,L,L 8 //H,L,L,H 9 // SN74141 (1) int ledPin_0_a = 2; int ledPin_0_b = 3; int ledPin_0_c = 4; int ledPin_0_d = 5; // SN74141 (2) int ledPin_1_a = 6; int ledPin_1_b = 7; int ledPin_1_c = 8; int ledPin_1_d = 9; // anode pins int ledPin_a_1 = 10; int ledPin_a_2 = 11; int ledPin_a_3 = 12; int ledPin_a_4 = 13; void setup() { pinMode(ledPin_0_a, OUTPUT); pinMode(ledPin_0_b, OUTPUT); pinMode(ledPin_0_c, OUTPUT); pinMode(ledPin_0_d, OUTPUT); pinMode(ledPin_1_a, OUTPUT); pinMode(ledPin_1_b, OUTPUT); pinMode(ledPin_1_c, OUTPUT); pinMode(ledPin_1_d, OUTPUT); pinMode(ledPin_a_1, OUTPUT); pinMode(ledPin_a_2, OUTPUT); pinMode(ledPin_a_3, OUTPUT); pinMode(ledPin_a_4, OUTPUT); // NOTE: All analog pins as digital inputs with pull-up resistor activated. pinMode(A0, INPUT_PULLUP); // Set pinMode(A1, INPUT_PULLUP); // Mode swicth pinMode(A2, INPUT_PULLUP); // Alarm arming pinMode(A3, INPUT_PULLUP); // Increase switch pinMode(A4, INPUT_PULLUP); // Decrease switch pinMode(1, OUTPUT); // Alarm tone will be sent there } void SetSN74141Chips( int num2, int num1 ) { int a,b,c,d; // set defaults. a=0;b=0;c=0;d=0; // will display a zero. // Load the a,b,c,d.. to send to the SN74141 IC (1) switch( num1 ) { case 0: a=0;b=0;c=0;d=0;break; case 1: a=1;b=0;c=0;d=0;break; case 2: a=0;b=1;c=0;d=0;break; case 3: a=1;b=1;c=0;d=0;break; case 4: a=0;b=0;c=1;d=0;break; case 5: a=1;b=0;c=1;d=0;break; case 6: a=0;b=1;c=1;d=0;break; case 7: a=1;b=1;c=1;d=0;break; case 8: a=0;b=0;c=0;d=1;break; case 9: a=1;b=0;c=0;d=1;break; default: a=1;b=1;c=1;d=1; break; } // Write to output pins. digitalWrite(ledPin_0_d, d); digitalWrite(ledPin_0_c, c); digitalWrite(ledPin_0_b, b); digitalWrite(ledPin_0_a, a); // Load the a,b,c,d.. to send to the SN74141 IC (2) switch( num2 ) { case 0: a=0;b=0;c=0;d=0;break; case 1: a=1;b=0;c=0;d=0;break; case 2: a=0;b=1;c=0;d=0;break; case 3: a=1;b=1;c=0;d=0;break; case 4: a=0;b=0;c=1;d=0;break; case 5: a=1;b=0;c=1;d=0;break; case 6: a=0;b=1;c=1;d=0;break; case 7: a=1;b=1;c=1;d=0;break; case 8: a=0;b=0;c=0;d=1;break; case 9: a=1;b=0;c=0;d=1;break; default: a=1;b=1;c=1;d=1; break; } // Write to output pins digitalWrite(ledPin_1_d, d); digitalWrite(ledPin_1_c, c); digitalWrite(ledPin_1_b, b); digitalWrite(ledPin_1_a, a); } float fadeMax = 5.0f; float fadeStep = 1.0f; int NumberArray[6]={0,0,0,0,0,0}; int currNumberArray[6]={0,0,0,0,0,0}; float NumberArrayFadeInValue[6]={0.0f,0.0f,0.0f,0.0f,0.0f,0.0f}; float NumberArrayFadeOutValue[6]={8.0f,8.0f,8.0f,8.0f,8.0f,8.0f}; void DisplayFadeNumberString() { // Anode channel 1 - numerals 0,3 SetSN74141Chips(currNumberArray[0],currNumberArray[3]); digitalWrite(ledPin_a_2, HIGH); delay(NumberArrayFadeOutValue[0]); SetSN74141Chips(NumberArray[0],NumberArray[3]); delay(NumberArrayFadeInValue[0]); digitalWrite(ledPin_a_2, LOW); // Anode channel 2 - numerals 1,4 SetSN74141Chips(currNumberArray[1],currNumberArray[4]); digitalWrite(ledPin_a_3, HIGH); delay(NumberArrayFadeOutValue[1]); SetSN74141Chips(NumberArray[1],NumberArray[4]); delay(NumberArrayFadeInValue[1]); digitalWrite(ledPin_a_3, LOW); // Anode channel 3 - numerals 2,5 SetSN74141Chips(currNumberArray[2],currNumberArray[5]); digitalWrite(ledPin_a_4, HIGH); delay(NumberArrayFadeOutValue[2]); SetSN74141Chips(NumberArray[2],NumberArray[5]); delay(NumberArrayFadeInValue[2]); digitalWrite(ledPin_a_4, LOW); // Loop thru and update all the arrays, and fades. for( int i = 0 ; i < 6 ; i ++ ) { if( NumberArray[i] != currNumberArray[i] ) { NumberArrayFadeInValue[i] += fadeStep; NumberArrayFadeOutValue[i] -= fadeStep; if( NumberArrayFadeInValue[i] >= fadeMax ) { NumberArrayFadeInValue[i] = 0.0f; NumberArrayFadeOutValue[i] = fadeMax; currNumberArray[i] = NumberArray[i]; } } } } unsigned long runTime = 0; //Time from when we started. // Default time: the clock will start at 19:40:00, 10/03/14. long ClockHourSet = 19; long ClockMinSet = 40; long ClockSecSet = 00; long ClockDaySet = 10; long ClockMonthSet= 03; long ClockYearSet = 14; int AlmHours = 8; //Those are set directly, no need for time count. int AlmMins = 0; byte Mode = 1; //1: time, 2: date, 3: alarm byte ModeButtonPressed = false; byte Set = 1; //1: left, 2: middle, 3: right long SetTimer = 0; byte SETMODE = false; byte SetButtonPressed = false; byte IncreaseButtonPressed = false; byte DecreaseButtonPressed = false; byte AlmButtonPressed = false; int nixState = HIGH; int toneState = LOW; long previousTimeBlink = 0; long previousTimeTone= 0; byte AlmArmed=false; // Used as flag as well as indicator on the tube themselves (0 or 1). byte AlmPlaying=false; void loop(){ // Get milliseconds. runTime = millis(); // MODE SELECTION /////////////////////////////////// ///////////////////////////////////////////////////// byte ModeInput = digitalRead(A1); if(ModeInput==0) ModeButtonPressed = true; if (Mode==1 && ModeButtonPressed==true && ModeInput==1) { Mode = 2; //Switching from Time to Date mode ModeButtonPressed = false; } if (Mode==2 && ModeButtonPressed==true && ModeInput==1) { Mode = 3; //Switching from Date to Alarm mode ModeButtonPressed = false; } if (Mode==3 && ModeButtonPressed==true && ModeInput==1) { Mode = 1; //Switching from Alarm to Time mode ModeButtonPressed = false; } // SETTING ////////////////////////////////////////// ///////////////////////////////////////////////////// byte SetInput = digitalRead(A0); if(SetInput==0) SetButtonPressed = true; while(SetInput==0){ SetTimer++; delay(10); SetInput = digitalRead(A0); } if(SetTimer>=100){ if(SETMODE==false) SETMODE=true; //A long press will either enter or exit the setting mode option. else SETMODE=false; } SetTimer=0; //Reinitializing. if(SETMODE==true){ if (Set==1 && SetButtonPressed==true && SetInput==1) { Set = 2; //Switching from left to middle pair available for setting. SetButtonPressed = false; } if (Set==2 && SetButtonPressed==true && SetInput==1) { Set = 3; //Switching from middle to right pair available for setting. SetButtonPressed = false; } if (Set==3 && SetButtonPressed==true && SetInput==1) { Set = 1; //Switching from right to left pair available for setting. SetButtonPressed = false; } //Now we set the selected the pair of tube, let's modify its value: int DecreaseInput = digitalRead(A3); int IncreaseInput = digitalRead(A4); if(DecreaseInput==0) DecreaseButtonPressed = true; if(IncreaseInput==0) IncreaseButtonPressed = true; if(DecreaseButtonPressed==true && DecreaseInput==1) { if(Set==1){ if(Mode==1) ClockHourSet--; if(Mode==2) ClockDaySet--; if(Mode==3) AlmHours--; } if(Set==2){ if(Mode==1) ClockMinSet--; if(Mode==2) ClockMonthSet--; if(Mode==3) AlmMins--; } if(Set==3){ if(Mode==1) ClockSecSet--; if(Mode==2) ClockYearSet--; } DecreaseButtonPressed = false; } if(IncreaseButtonPressed==true && IncreaseInput==1) { if(Set==1){ if(Mode==1) ClockHourSet++; if(Mode==2) ClockDaySet++; if(Mode==3) AlmHours++; } if(Set==2){ if(Mode==1) ClockMinSet++; if(Mode==2) ClockMonthSet++; if(Mode==3) AlmMins++; } if(Set==3){ if(Mode==1) ClockSecSet++; if(Mode==2) ClockYearSet++; } IncreaseButtonPressed = false; } } // ALARM ARMING ///////////////////////////////////// ///////////////////////////////////////////////////// if(Mode==3 && AlmPlaying==false){ //The second condition ensures that the alarm will stay armed when the user presses A2 just to turn it off. int AlmInput = digitalRead(A2); if(AlmInput==0) AlmButtonPressed = true; if(AlmButtonPressed==true && AlmInput==1){ if(AlmArmed==true) AlmArmed=false; else AlmArmed=true; AlmButtonPressed = false; } } // TIME CALCULATION ///////////////////////////////// ///////////////////////////////////////////////////// // Get time in seconds. long time = runTime / 1000; time += ClockSecSet + 60*ClockMinSet + 3600*ClockHourSet + 86400*ClockDaySet; //Time in seconds, based on offset. // Convert time to days,hours,mins,seconds long days = time / 86400; time -= days * 86400; long hours = time / 3600; time -= hours * 3600; long minutes = time / 60; time -= minutes * 60; long seconds = time; // Splitting time into actual tube values. byte lowerHours = hours % 10; byte upperHours = hours - lowerHours; byte lowerMins = minutes % 10; byte upperMins = minutes - lowerMins; byte lowerSeconds = seconds % 10; byte upperSeconds = seconds - lowerSeconds; if( upperSeconds >= 10 ) upperSeconds = upperSeconds / 10; if( upperMins >= 10 ) upperMins = upperMins / 10; if( upperHours >= 10 ) upperHours = upperHours / 10; // DATE CALCULATION ///////////////////////////////// ///////////////////////////////////////////////////// int Day = days; //We define new variables to work with without affecting what's above. int Month = ClockMonthSet; int Year = ClockYearSet; //Defining years incrementation. if(Month>=13){ Year++; Month=Month-12; } if(Month<=0){ Year--; Month=Month+12; } //Number of days in a month: byte February; if(Year%4==0) February=29; else February=28; byte MonthArray[13] = {0,31,February,31,30,31,30,31,31,30,31,30,31}; //We'll skip the first entry, so we can use the array's index directly. //Defining Month incrementation. for(byte i=1;i<13;i++){ if(Month==i){ if(Day>MonthArray[i]){ //If "Day" exceeds the number of days in the present month, it is resetted to 1 and we step into the next month. Day -= MonthArray[i]; Month++; } if(Day<1){ //Similarly if "Day" is to become 0. if(i-1==0) i=13; //Before January comes December. Day += MonthArray[i-1]; Month--; } } } // Splitting date. byte lowerYears = Year % 10; byte upperYears = Year - lowerYears; byte lowerMonths = Month % 10; byte upperMonths = Month - lowerMonths; byte lowerDays = Day % 10; byte upperDays = Day - lowerDays; if( upperDays >= 10 ) upperDays = upperDays / 10; if( upperMonths >= 10 ) upperMonths = upperMonths / 10; if( upperYears >= 10 ) upperYears = upperYears / 10; // ALM RULES ///////////////////////////////// ////////////////////////////////////////////// if(AlmHours>23) AlmHours=AlmHours-24; //After 23:00 comes 00:00 if(AlmHours<0) AlmHours=AlmHours+23; //vice-versa if(AlmMins>59) AlmMins=AlmMins-60; //same thing if(AlmMins<0) AlmMins=AlmMins+59; // Splitting. byte lowerAlmHours = AlmHours % 10; byte upperAlmHours = AlmHours - lowerAlmHours; byte lowerAlmMins = AlmMins % 10; byte upperAlmMins = AlmMins - lowerAlmMins; if( upperAlmMins >= 10 ) upperAlmMins = upperAlmMins / 10; if( upperAlmHours >= 10 ) upperAlmHours = upperAlmHours / 10; //Alm actions: if(AlmArmed==true){ if(upperAlmHours==upperHours && lowerAlmHours==lowerHours && upperAlmMins==upperMins && lowerAlmMins==lowerMins && upperSeconds==0 && lowerSeconds==0){ AlmPlaying = true; //Alarm rings when the time matches the pre-entered alarm values. } } if(AlmPlaying==true){ if(runTime-previousTimeTone > 500){ //Play an intermittent (1s period) tune. previousTimeTone = runTime; if(toneState == HIGH){ tone(1, 200); //Tune is 200Hz. toneState=LOW; } else{ noTone(1); toneState=HIGH; } } if(digitalRead(A0)==LOW || digitalRead(A1)==LOW || digitalRead(A2)==LOW || digitalRead(A3)==LOW || digitalRead(A4)==LOW) AlmPlaying=false; //Press anything to stop it. } else noTone(1); // SHOWTIME ///////////////////////////////////////// ///////////////////////////////////////////////////// // Fill in the Number array used to display on the tubes following the Mode. if(Mode==1){ if(SETMODE==true){ if(Set==1){ if(runTime-previousTimeBlink > 500){ //Blinking these digits while in setmode. previousTimeBlink = runTime; if(nixState == LOW){ NumberArray[5] = 11; //Turning this pair off. NumberArray[1] = 11; nixState = HIGH; } else{ NumberArray[5] = upperHours; //Turning this pair on again. NumberArray[1] = lowerHours; nixState = LOW; } } NumberArray[3] = upperMins; //Makes sure no pair stays off even if left in that state in the previous set mode. NumberArray[2] = lowerMins; NumberArray[4] = upperSeconds; NumberArray[0] = lowerSeconds; } if(Set==2){ if(runTime-previousTimeBlink > 500){ //Blinking these digits while in setmode. previousTimeBlink = runTime; if(nixState == LOW){ NumberArray[3] = 11; NumberArray[2] = 11; nixState = HIGH; } else{ NumberArray[3] = upperMins; NumberArray[2] = lowerMins; nixState = LOW; } } NumberArray[5] = upperHours; NumberArray[1] = lowerHours; NumberArray[4] = upperSeconds; NumberArray[0] = lowerSeconds; } if(Set==3){ if(runTime-previousTimeBlink > 500){ //Blinking these digits while in setmode. previousTimeBlink = runTime; if(nixState == LOW){ NumberArray[4] = 11; NumberArray[0] = 11; nixState = HIGH; } else{ NumberArray[4] = upperSeconds; NumberArray[0] = lowerSeconds; nixState = LOW; } } NumberArray[5] = upperHours; NumberArray[1] = lowerHours; NumberArray[3] = upperMins; NumberArray[2] = lowerMins; } } else{ //Fill the digits normally. NumberArray[5] = upperHours; NumberArray[1] = lowerHours; NumberArray[3] = upperMins; NumberArray[2] = lowerMins; NumberArray[4] = upperSeconds; NumberArray[0] = lowerSeconds; } DisplayFadeNumberString(); // Display time. //Lighting all the numbers every 10min to prevent cathode poisining. if((lowerMins==5)&&upperSeconds==0&&lowerSeconds==1) { for(int j=-1; j<11; j++){ //Alternating to make it more beautiful. the -1 and 11 value will turn the digits off to mark a short pause. NumberArray[5] = j; NumberArray[1] = 9-j; NumberArray[3] = j; NumberArray[2] = 9-j; NumberArray[4] = j; NumberArray[0] = 9-j; int k = 0; while(k<10){ DisplayFadeNumberString(); //Clean each nixie digits for 2 seconds. k++; delay(1); } } } //Display date only on every (03,13,23,33) mins. if(lowerMins==3 && upperSeconds==0 && lowerSeconds==1){ NumberArray[5] = upperDays; NumberArray[1] = lowerDays; NumberArray[3] = upperMonths; NumberArray[2] = lowerMonths; NumberArray[4] = upperYears; NumberArray[0] = lowerYears; int k = 0; while(k<550){ // Display date for 10 seconds (50 = 1sec). DisplayFadeNumberString(); // Display date. k++; delay(1); } } } if(Mode==2){ if(SETMODE==true){ if(Set==1){ if(runTime-previousTimeBlink > 500){ //Blinking these digits while in setmode. previousTimeBlink = runTime; if(nixState == LOW){ NumberArray[5] = 11; //Turning this pair off. NumberArray[1] = 11; nixState = HIGH; } else{ NumberArray[5] = upperDays; NumberArray[1] = lowerDays; nixState = LOW; } } NumberArray[3] = upperMonths; NumberArray[2] = lowerMonths; NumberArray[4] = upperYears; NumberArray[0] = lowerYears; } if(Set==2){ if(runTime-previousTimeBlink > 500){ //Blinking these digits while in setmode. previousTimeBlink = runTime; if(nixState == LOW){ NumberArray[3] = 11; NumberArray[2] = 11; nixState = HIGH; } else{ NumberArray[3] = upperMonths; NumberArray[2] = lowerMonths; nixState = LOW; } } NumberArray[5] = upperDays; NumberArray[1] = lowerDays; NumberArray[4] = upperYears; NumberArray[0] = lowerYears; } if(Set==3){ if(runTime-previousTimeBlink > 500){ //Blinking these digits while in setmode. previousTimeBlink = runTime; if(nixState == LOW){ NumberArray[4] = 11; NumberArray[0] = 11; nixState = HIGH; } else{ NumberArray[4] = upperYears; NumberArray[0] = lowerYears; nixState = LOW; } } NumberArray[5] = upperDays; NumberArray[1] = lowerDays; NumberArray[3] = upperMonths; NumberArray[2] = lowerMonths; } } else{ //Fill the digits normally. NumberArray[5] = upperDays; NumberArray[1] = lowerDays; NumberArray[3] = upperMonths; NumberArray[2] = lowerMonths; NumberArray[4] = upperYears; NumberArray[0] = lowerYears; } DisplayFadeNumberString(); // Display date. } if(Mode==3){ if(SETMODE==true){ if(Set==1){ if(runTime-previousTimeBlink > 500){ //Blinking these digits while in setmode. previousTimeBlink = runTime; if(nixState == LOW){ NumberArray[5] = 11; //Turning this pair off. NumberArray[1] = 11; nixState = HIGH; } else{ NumberArray[5] = upperAlmHours; NumberArray[1] = lowerAlmHours; nixState = LOW; } } NumberArray[3] = upperAlmMins; NumberArray[2] = lowerAlmMins; NumberArray[0] = AlmArmed; //1: armed, 0: not armed } if(Set==2){ if(runTime-previousTimeBlink > 500){ //Blinking these digits while in setmode. previousTimeBlink = runTime; if(nixState == LOW){ NumberArray[3] = 11; NumberArray[2] = 11; nixState = HIGH; } else{ NumberArray[3] = upperAlmMins; NumberArray[2] = lowerAlmMins; nixState = LOW; } } NumberArray[5] = upperAlmHours; NumberArray[1] = lowerAlmHours; NumberArray[0] = AlmArmed; } if(Set==3) Set=1; //Dirty fix to convert a 3-cycle into a 2-cycle. } else{ //Fill the digits normally. NumberArray[5] = upperAlmHours; NumberArray[1] = lowerAlmHours; NumberArray[3] = upperAlmMins; NumberArray[2] = lowerAlmMins; NumberArray[0] = AlmArmed; } NumberArray[4] = 11; //Eleven will turn off the tube DisplayFadeNumberString(); // Display alarm. } }