Skip to content

Commit aa3ac2c

Browse files
committed
Sports clocks and counters
1 parent afed41b commit aa3ac2c

File tree

4 files changed

+611
-0
lines changed

4 files changed

+611
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//UNDB v8 modified to v9 spec (put Sel/Alt on A7/A6, Up/Down on A0/A1, relay on A3, led on 9, and cathode B4 on A2), relay disabled, Sel/Alt buttons reversed, with 6-digit display.
2+
3+
// What input is associated with each control?
4+
const byte mainSel = A6;
5+
const byte mainAdjUp = A0;
6+
const byte mainAdjDn = A1;
7+
const byte altSel = A7; //if not equipped, set to 0
8+
9+
// What type of adj controls are equipped?
10+
// 1 = momentary buttons. 2 = quadrature rotary encoder (not currently supported).
11+
const byte mainAdjType = 1;
12+
13+
//What are the signal pin(s) connected to?
14+
const char piezoPin = 10;
15+
16+
// How long (in ms) are the button hold durations?
17+
const word btnShortHold = 1000; //for setting the displayed feataure
18+
const word btnLongHold = 3000; //for for entering options menu
19+
20+
const char ledPin = 9;
21+
22+
const char beepTone = 75;
23+
24+
//This clock is 2x3 multiplexed: two tubes powered at a time.
25+
//The anode channel determines which two tubes are powered,
26+
//and the two SN74141 cathode driver chips determine which digits are lit.
27+
//4 pins out to each SN74141, representing a binary number with values [1,2,4,8]
28+
const char outA1 = 2;
29+
const char outA2 = 3;
30+
const char outA3 = 4;
31+
const char outA4 = 5;
32+
const char outB1 = 6;
33+
const char outB2 = 7;
34+
const char outB3 = 8;
35+
const char outB4 = 16; //A2 - was 9 before PWM fix pt2
36+
//3 pins out to anode channel switches
37+
const char anode1 = 11;
38+
const char anode2 = 12;
39+
const char anode3 = 13;
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
//For sports exhibit: how many times can you press Sel in N seconds?
2+
//For 6-tube display
3+
4+
#include "configs/v8c-6tube-relayswitch-pwm.h"
5+
6+
// Hardware inputs and value setting
7+
byte btnCur = 0; //Momentary button currently in use - only one allowed at a time
8+
byte btnCurHeld = 0; //Button hold thresholds: 0=none, 1=unused, 2=short, 3=long, 4=set by btnStop()
9+
unsigned long inputLast = 0; //When a button was last pressed
10+
unsigned long inputLast2 = 0; //Second-to-last of above
11+
//TODO the math between these two may fail very rarely due to millis() rolling over while setting. Need to find a fix. I think it only applies to the rotary encoder though.
12+
13+
word timerStart = 5;
14+
byte isRunning = 0; //1 if running, 2 if finished(?)
15+
unsigned long runStart = 0;
16+
word btnCount = 0;
17+
word timerCur = 5;
18+
word timerLast = 5;
19+
20+
////////// Main code control //////////
21+
22+
void setup(){
23+
Serial.begin(9600);
24+
initInputs();
25+
delay(100);
26+
initOutputs(); //depends on some EEPROM settings
27+
updateDisplay();
28+
}
29+
30+
void loop(){
31+
//TODO does not account for millis rollover - should be restarted at least once every 49 days
32+
//while (millis() < start + ms) ; // BUGGY version
33+
//while (millis() - start < ms) ; // CORRECT version
34+
//https://arduino.stackexchange.com/a/12588
35+
if(isRunning==1) {
36+
unsigned long now = millis();
37+
if(now-runStart >= timerStart*1000) { //If timer has finished, stop it
38+
Serial.println(F("Stopping."));
39+
timerCur = 0;
40+
timerLast = 0;
41+
isRunning = 0; //or 2
42+
tone(piezoPin,getHz(beepTone),500);
43+
updateDisplay();
44+
} else {
45+
timerCur = timerStart - ((now - runStart)/1000); //Update timercur value
46+
if(timerLast>timerCur) {
47+
Serial.print(timerCur,DEC);
48+
Serial.println(" seconds left");
49+
updateDisplay();
50+
//tone(piezoPin,getHz(beepTone),50);
51+
timerLast = timerCur;
52+
}
53+
}
54+
}
55+
checkInputs(); //if inputs have changed, this will do things + updateDisplay as needed
56+
cycleDisplay(); //keeps the display hardware multiplexing cycle going
57+
}
58+
59+
60+
////////// Control inputs //////////
61+
62+
void initInputs(){
63+
//TODO are there no "loose" pins left floating after this? per https://electronics.stackexchange.com/q/37696/151805
64+
pinMode(mainSel, INPUT_PULLUP);
65+
pinMode(mainAdjUp, INPUT_PULLUP);
66+
pinMode(mainAdjDn, INPUT_PULLUP);
67+
pinMode(altSel, INPUT_PULLUP);
68+
//rotary encoder init
69+
//TODO encoder support
70+
}
71+
72+
void checkInputs(){
73+
//TODO can all this if/else business be defined at load instead of evaluated every sample? OR is it compiled that way?
74+
//TODO potential issue: if user only means to rotate or push encoder but does both?
75+
//check button states
76+
checkBtn(mainSel); //main select
77+
if(mainAdjType==1) { checkBtn(mainAdjUp); checkBtn(mainAdjDn); } //if mainAdj is buttons
78+
if(altSel!=0) checkBtn(altSel); //alt select (if equipped)
79+
}
80+
81+
bool readInput(byte pin){
82+
if(pin==A6 || pin==A7) return analogRead(pin)<100?0:1; //analog-only pins
83+
else return digitalRead(pin);
84+
}
85+
void checkBtn(byte btn){
86+
//Changes in momentary buttons, LOW = pressed.
87+
//When a button event has occurred, will call ctrlEvt
88+
bool bnow = readInput(btn);
89+
unsigned long now = millis();
90+
//If the button has just been pressed, and no other buttons are in use...
91+
if(btnCur==0 && bnow==LOW) {
92+
btnCur = btn; btnCurHeld = 0; inputLast2 = inputLast; inputLast = now;
93+
ctrlEvt(btn,1); //hey, the button has been pressed
94+
}
95+
//If the button is being held...
96+
if(btnCur==btn && bnow==LOW) {
97+
if((unsigned long)(now-inputLast)>=btnLongHold && btnCurHeld < 3) { //account for rollover
98+
btnCurHeld = 3;
99+
ctrlEvt(btn,3); //hey, the button has been long-held
100+
}
101+
else if((unsigned long)(now-inputLast)>=btnShortHold && btnCurHeld < 2) {
102+
btnCurHeld = 2;
103+
ctrlEvt(btn,2); //hey, the button has been short-held
104+
}
105+
}
106+
//If the button has just been released...
107+
if(btnCur==btn && bnow==HIGH) {
108+
btnCur = 0;
109+
if(btnCurHeld < 4) ctrlEvt(btn,0); //hey, the button was released
110+
btnCurHeld = 0;
111+
}
112+
}
113+
void btnStop(){
114+
//In some cases, when handling btn evt 1/2/3, we may call this so following events 2/3/0 won't cause unintended behavior (e.g. after a fn change, or going in or out of set)
115+
btnCurHeld = 4;
116+
}
117+
118+
119+
////////// Input handling and value setting //////////
120+
121+
void ctrlEvt(byte ctrl, byte evt){
122+
// Serial.print(F("Button "));
123+
// Serial.print(ctrl,DEC);
124+
// Serial.println(F(" pressed"));
125+
//Handle control events (from checkBtn or checkRot), based on current fn and set state.
126+
//evt: 1=press, 2=short hold, 3=long hold, 0=release.
127+
if(evt==1) { //only event we care about
128+
if(isRunning==0) {
129+
if(ctrl==mainSel && (millis()-runStart >= (timerStart+2)*1000)) {
130+
//start!
131+
Serial.println(F("Starting! 1 press"));
132+
timerCur = timerStart;
133+
timerLast = timerStart;
134+
isRunning = 1;
135+
btnCount = 1;
136+
runStart = millis();
137+
tone(piezoPin, getHz(beepTone), 50);
138+
updateDisplay();
139+
}
140+
//else if up, change the count time
141+
else if(ctrl==mainAdjUp) {
142+
switch(timerStart) {
143+
case 3: timerStart=5; break;
144+
case 5: timerStart=10; break;
145+
case 10: timerStart=15; break;
146+
case 15: timerStart=3; break;
147+
default: break;
148+
}
149+
timerCur = timerStart;
150+
updateDisplay();
151+
}
152+
else if(ctrl==mainAdjDn) {
153+
switch(timerStart) {
154+
case 3: timerStart=15; break;
155+
case 5: timerStart=3; break;
156+
case 10: timerStart=5; break;
157+
case 15: timerStart=10; break;
158+
default: break;
159+
}
160+
timerCur = timerStart;
161+
timerLast = timerStart;
162+
updateDisplay();
163+
}
164+
}
165+
else if(isRunning==1) {
166+
if(ctrl==mainSel) {
167+
//increment button count
168+
btnCount++;
169+
Serial.print(btnCount,DEC);
170+
Serial.println(F(" presses"));
171+
//tone(piezoPin,getHz(beepTone),50);
172+
updateDisplay();
173+
}
174+
}
175+
else { //isRunning=2
176+
if(ctrl==mainSel && (millis()-runStart >= (timerStart+2)*1000)) {
177+
//reset - but wait 2 seconds
178+
Serial.println(F("Resetting"));
179+
isRunning = 0;
180+
btnCount = 0;
181+
timerCur = timerStart;
182+
timerLast = timerStart;
183+
tone(piezoPin, getHz(beepTone), 50);
184+
updateDisplay();
185+
}
186+
}
187+
} //end if evt==1
188+
} //end ctrlEvt
189+
190+
191+
////////// Display data formatting //////////
192+
193+
byte displayNext[6] = {15,15,15,15,15,15}; //Internal representation of display. Blank to start. Change this to change tubes.
194+
byte displayLast[6] = {11,11,11,11,11,11}; //for noticing changes to displayNext and fading the display to it
195+
byte scrollDisplay[6] = {15,15,15,15,15,15}; //For animating a value into displayNext from right, and out to left
196+
197+
void updateDisplay(){
198+
editDisplay(btnCount, 0, 3, 0, 0); //button count
199+
editDisplay(timerCur, 4, 5, 0, 0); //countdown
200+
} //end updateDisplay()
201+
202+
void editDisplay(word n, byte posStart, byte posEnd, bool leadingZeros, bool fade){
203+
//Splits n into digits, sets them into displayNext in places posSt-posEnd (inclusive), with or without leading zeros
204+
//If there are blank places (on the left of a non-leading-zero number), uses value 15 to blank tube
205+
//If number has more places than posEnd-posStart, the higher places are truncated off (e.g. 10015 on 4 tubes --> 0015)
206+
word place;
207+
for(byte i=0; i<=posEnd-posStart; i++){
208+
switch(i){ //because int(pow(10,1))==10 but int(pow(10,2))==99...
209+
case 0: place=1; break;
210+
case 1: place=10; break;
211+
case 2: place=100; break;
212+
case 3: place=1000; break;
213+
default: break;
214+
}
215+
displayNext[posEnd-i] = (i==0&&n==0 ? 0 : (n>=place ? (n/place)%10 : (leadingZeros?0:15)));
216+
if(!fade) displayLast[posEnd-i] = displayNext[posEnd-i]; //cycleDisplay will be none the wiser
217+
}
218+
} //end editDisplay()
219+
220+
221+
////////// Hardware outputs //////////
222+
223+
//This clock is 2x3 multiplexed: two tubes powered at a time.
224+
//The anode channel determines which two tubes are powered,
225+
//and the two SN74141 cathode driver chips determine which digits are lit.
226+
//4 pins out to each SN74141, representing a binary number with values [1,2,4,8]
227+
byte binOutA[4] = {outA1,outA2,outA3,outA4};
228+
byte binOutB[4] = {outB1,outB2,outB3,outB4};
229+
//3 pins out to anode channel switches
230+
byte anodes[3] = {anode1,anode2,anode3};
231+
232+
const int fadeDur = 5; //ms - each multiplexed pair of digits appears for this amount of time per cycle
233+
const int dimDur = 4; //ms - portion of fadeDur that is left dark during dim times
234+
int fadeNextDur = 0; //ms - during fade, incoming digit's portion of fadeDur
235+
int fadeLastDur = fadeDur; //ms - during fade, outgoing digit's portion of fadeDur
236+
unsigned long fadeStartLast = 0; //millis - when the last digit fade was started
237+
unsigned long setStartLast = 0; //to control flashing during start
238+
239+
void initOutputs() {
240+
for(byte i=0; i<4; i++) { pinMode(binOutA[i],OUTPUT); pinMode(binOutB[i],OUTPUT); }
241+
for(byte i=0; i<3; i++) { pinMode(anodes[i],OUTPUT); }
242+
if(piezoPin>=0) pinMode(piezoPin, OUTPUT);
243+
if(ledPin>=0) pinMode(ledPin, OUTPUT); analogWrite(ledPin,0); //don't want it
244+
}
245+
246+
void cycleDisplay(){
247+
//Anode channel 0: tubes #2 (min x10) and #5 (sec x1)
248+
setCathodes(displayLast[2],displayLast[5]); //Via d2b decoder chip, set cathodes to old digits
249+
digitalWrite(anodes[0], HIGH); //Turn on tubes
250+
delay(fadeLastDur);//-(dim?dimDur:0)); //Display for fade-out cycles
251+
setCathodes(displayNext[2],displayNext[5]); //Switch cathodes to new digits
252+
delay(fadeNextDur);//-(dim?dimDur:0)); //Display for fade-in cycles
253+
digitalWrite(anodes[0], LOW); //Turn off tubes
254+
255+
//Anode channel 1: tubes #4 (sec x10) and #1 (hour x1)
256+
setCathodes(displayLast[4],displayLast[1]);
257+
digitalWrite(anodes[1], HIGH);
258+
delay(fadeLastDur);
259+
setCathodes(displayNext[4],displayNext[1]);
260+
delay(fadeNextDur);
261+
digitalWrite(anodes[1], LOW);
262+
263+
//Anode channel 2: tubes #0 (hour x10) and #3 (min x1)
264+
setCathodes(displayLast[0],displayLast[3]);
265+
digitalWrite(anodes[2], HIGH);
266+
delay(fadeLastDur);
267+
setCathodes(displayNext[0],displayNext[3]);
268+
delay(fadeNextDur);
269+
digitalWrite(anodes[2], LOW);
270+
} //end cycleDisplay()
271+
272+
void setCathodes(byte decValA, byte decValB){
273+
bool binVal[4]; //4-bit binary number with values [1,2,4,8]
274+
decToBin(binVal,decValA); //have binary value of decVal set into binVal
275+
for(byte i=0; i<4; i++) digitalWrite(binOutA[i],binVal[i]); //set bin inputs of SN74141
276+
decToBin(binVal,decValB);
277+
for(byte i=0; i<4; i++) digitalWrite(binOutB[i],binVal[i]); //set bin inputs of SN74141
278+
} //end setCathodes()
279+
280+
void decToBin(bool binVal[], byte i){
281+
//binVal is a reference (modify in place) of a binary number bool[4] with values [1,2,4,8]
282+
if(i<0 || i>15) i=15; //default value, turns tubes off
283+
binVal[3] = int(i/8)%2;
284+
binVal[2] = int(i/4)%2;
285+
binVal[1] = int(i/2)%2;
286+
binVal[0] = i%2;
287+
} //end decToBin()
288+
289+
word getHz(byte note){
290+
//Given a piano key note, return frequency
291+
char relnote = note-49; //signed, relative to concert A
292+
float reloct = relnote/12.0; //signed
293+
word mult = 440*pow(2,reloct);
294+
return mult;
295+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//UNDB v8 modified to v9 spec (put Sel/Alt on A7/A6, Up/Down on A0/A1, relay on A3, led on 9, and cathode B4 on A2), relay disabled, Sel/Alt buttons reversed, with 6-digit display.
2+
3+
// What input is associated with each control?
4+
const byte mainSel = A1;
5+
const byte mainAdjUp = A2;
6+
const byte mainAdjDn = A3;
7+
const byte altSel = A0; //if not equipped, set to 0
8+
9+
// What type of adj controls are equipped?
10+
// 1 = momentary buttons. 2 = quadrature rotary encoder (not currently supported).
11+
const byte mainAdjType = 1;
12+
13+
//What are the signal pin(s) connected to?
14+
const char piezoPin = 10;
15+
16+
// How long (in ms) are the button hold durations?
17+
const word btnShortHold = 1000; //for setting the displayed feataure
18+
const word btnLongHold = 3000; //for for entering options menu
19+
20+
const char ledPin = 9;
21+
22+
const char beepTone = 75;
23+
24+
//This clock is 2x3 multiplexed: two tubes powered at a time.
25+
//The anode channel determines which two tubes are powered,
26+
//and the two SN74141 cathode driver chips determine which digits are lit.
27+
//4 pins out to each SN74141, representing a binary number with values [1,2,4,8]
28+
const char outA1 = 2;
29+
const char outA2 = 3;
30+
const char outA3 = 4;
31+
const char outA4 = 5;
32+
const char outB1 = 6;
33+
const char outB2 = 7;
34+
const char outB3 = 8;
35+
const char outB4 = 9;
36+
//3 pins out to anode channel switches
37+
const char anode1 = 11;
38+
const char anode2 = 12;
39+
const char anode3 = 13;

0 commit comments

Comments
 (0)