/elec/propeller-clock

To get this branch, use:
bzr branch http://bzr.ed.am/elec/propeller-clock
13 by edam
updated propeller-clock code, added GPL text and renamed fan-test
1
/*
2
 * propeller-clock.pde
3
 *
4
 * Copyright (C) 2011 Tim Marston <edam@waxworlds.org>
5
 *
6
 * This file is part of propeller-clock (hereafter referred to as "this
7
 * program"). See http://ed.am/software/arduino/propeller-clock for more
8
 * information.
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU Lesser General Public License as published
12
 * by the Free Software Foundation, either version 3 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU Lesser General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Lesser General Public License
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
 */
23
16 by edam
finished first revision of propeller-clock code (can display clock and test); added Bounce library
24
/******************************************************************************
25
23 by edam
renamed code directories and updated the comments in the code
26
Set up:
27
28
 * a PC fan is wired up to a 12V power supply
29
30
 * the fan's SENSE (tachiometer) pin connected to pin 2 on the
31
   arduino.
32
33
 * the pins 4 to 13 on the arduino should directly drive an LED (the
34
   LED on pin 4 is in the centre of the clock face and the LED on pin
35
   13 is at the outside.
36
37
 * if a longer hand (and a larger clock face) is desired, pin 4 can be
38
   used to indirectly drive (via a MOSFET) multiple LEDs which turn on
39
   and off in unison in the centre of the clock.
40
41
 * a button should be attached to pin 3 that grounds it when pressed.
42
43
Implementation details:
44
45
 * for a schematic, see project/propeller-clock.sch.
46
47
 * the timing of the drawing of the clock face is recalculated with
48
   every rotation of the propeller.
49
    
50
 * a PC fan actually sends 2 tachiometer pulses per revolution, so the
51
   software skips every other one. This means that the clock may
52
   appear upside-down if started with the propeller in the wrong
53
   position. You will need to experiment to dicsover the position that
54
   the propeller must be in when starting the clock.
55
    
56
Usage instructions:
57
58
 * pressing the button cycles between variations of the current
59
   display mode.
60
  
61
 * pressing and holding the button for a second cycles between display
62
   modes (e.g., analogue and digital).
63
64
 * pressing and holding the button for 5 seconds enters "time set"
65
   mode. In this mode, the following applies:
66
    - the field that is being set flashes
67
    - pressing the button increments the field currently being set
68
    - pressing and holding the button for a second cycles through the
69
      fields that can be set
70
    - press and holding the button for 5 seconds to finish
16 by edam
finished first revision of propeller-clock code (can display clock and test); added Bounce library
71
72
******************************************************************************/
73
74
75
#include <Bounce.h>
76
13 by edam
updated propeller-clock code, added GPL text and renamed fan-test
77
//_____________________________________________________________________________
78
//                                                                         data
79
80
81
// when non-zero, the time (in microseconds) of a new fan pulse that
82
// has just occurred, which means that segment drawing needs to be
83
// restarted
11 by Dan
added initial propeller clock code
84
static unsigned long new_pulse_at = 0;
85
13 by edam
updated propeller-clock code, added GPL text and renamed fan-test
86
// the time (in microseconds) when the last fan pulse occurred
87
static unsigned long last_pulse_at = 0;
88
89
// duration (in microseconds) that a segment should be displayed
90
static unsigned long segment_step = 0;
91
92
// remainder after divisor and a tally of the remainders for each segment
93
static unsigned long segment_step_sub_step = 0;
94
static unsigned long segment_step_sub = 0;
95
16 by edam
finished first revision of propeller-clock code (can display clock and test); added Bounce library
96
// flag to indicate that the drawing mode should be cycled to the next one
97
static bool inc_draw_mode = false;
98
99
// a bounce-managed button
100
static Bounce button( 3, 5 );
101
102
// the time
103
static int time_hours = 0;
104
static int time_minutes = 0;
105
static int time_seconds = 0;
106
15 by edam
moved schematic and Makefile to propeller-clock dir and updated Makefile for Arduino Pro Mini w/ Atmel 168 board
107
// number of segments in a full display (rotation) is 60 (one per
108
// second) times the desired number of sub-divisions of a second
16 by edam
finished first revision of propeller-clock code (can display clock and test); added Bounce library
109
#define NUM_SECOND_SEGMENTS 5
110
#define NUM_SEGMENTS ( 60 * NUM_SECOND_SEGMENTS )
13 by edam
updated propeller-clock code, added GPL text and renamed fan-test
111
112
//_____________________________________________________________________________
113
//                                                                         code
114
115
16 by edam
finished first revision of propeller-clock code (can display clock and test); added Bounce library
116
// check for button presses
117
void checkButtons()
118
{
119
	// update buttons
120
	button.update();
121
122
	// notice button presses
123
	if( button.risingEdge() )
124
		inc_draw_mode = true;
125
}
126
127
128
// keep track of time
129
void trackTime()
130
{
131
	// previous time and any carried-over milliseconds
132
	static unsigned long last_time = millis();
133
	static unsigned long carry = 0;
134
135
	// how many milliseonds have elapsed since we last checked?
136
	unsigned long next_time = millis();
137
	unsigned long delta = next_time - last_time + carry;
138
139
	// update the previous time and carried-over milliseconds
140
	last_time = next_time;
141
	carry = delta % 1000;
142
143
	// add the seconds that have passed to the time
144
	time_seconds += delta / 1000;
145
	while( time_seconds >= 60 ) {
146
		time_seconds -= 60;
147
		time_minutes++;
148
		if( time_minutes >= 60 ) {
149
			time_minutes -= 60;
150
			time_hours++;
151
			if( time_hours >= 24 )
152
				time_hours -= 24;
153
		}
13 by edam
updated propeller-clock code, added GPL text and renamed fan-test
154
	}
155
}
156
157
16 by edam
finished first revision of propeller-clock code (can display clock and test); added Bounce library
158
// draw a segment for the test display
159
void drawNextSegment_test( bool reset )
13 by edam
updated propeller-clock code, added GPL text and renamed fan-test
160
{
16 by edam
finished first revision of propeller-clock code (can display clock and test); added Bounce library
161
	// keep track of segment
13 by edam
updated propeller-clock code, added GPL text and renamed fan-test
162
	static unsigned int segment = 0;
163
	if( reset ) segment = 0;
164
	segment++;
165
16 by edam
finished first revision of propeller-clock code (can display clock and test); added Bounce library
166
	// turn on inside and outside LEDs
167
	digitalWrite( 4, HIGH );
168
	digitalWrite( 13, HIGH );
169
170
	// display segment number in binary across in the inside LEDs,
171
	// with the LED on pin 12 showing the least-significant bit
172
	for( int a = 0; a < 8; a++ )
173
		digitalWrite( 12 - a, ( ( segment >> a ) & 1 )? HIGH : LOW );
174
}
175
176
177
// draw a segment for the time display
178
void drawNextSegment_time( bool reset )
179
{
180
	static unsigned int second = 0;
181
	static unsigned int segment = 0;
182
183
	// handle display reset
184
	if( reset ) {
185
		second = 0;
186
		segment = 0;
187
	}
188
189
	// what needs to be drawn?
17 by Dan
fixed display wdth for hands on clock face
190
	bool draw_tick = !segment && second % 5 == 0;
191
	bool draw_second = !segment && second == time_seconds;
192
	bool draw_minute = !segment && second == time_minute;
193
	bool draw_hour = !segment && second == time_hour;
16 by edam
finished first revision of propeller-clock code (can display clock and test); added Bounce library
194
195
	// set the LEDs
196
	digitalWrite( 13, HIGH );
197
	digitalWrite( 12, draw_tick || draw_minute );
198
	for( int a = 10; a <= 11; a++ )
199
		digitalWrite( a, draw_minute || draw_second );
200
	for( int a = 4; a <= 9; a++ )
201
		digitalWrite( 10, draw_minute | draw_second || draw_hour );
202
203
	// inc position
204
	if( ++segment >= NUM_SECOND_SEGMENTS ) {
205
		segment = 0;
206
		second++;
207
	}
208
}
209
210
211
// draw a display segment
212
void drawNextSegment( bool reset )
213
{
214
	static int draw_mode = 0;
215
216
 	// handle mode switch requests
217
	if( reset && inc_draw_mode ) {
218
		inc_draw_mode = false;
219
		draw_mode++;
220
		if( draw_mode >= 2 )
221
			draw_mode = 0;
222
	}
223
224
	// draw the segment
225
	switch( draw_mode ) {
226
	case 0: drawNextSegment_test( reset ); break;
227
	case 1: drawNextSegment_time( reset ); break;
228
	}
13 by edam
updated propeller-clock code, added GPL text and renamed fan-test
229
}
230
231
232
// calculate time constants when a new pulse has occurred
233
void calculateSegmentTimes()
234
{
235
	// check for overflows, and only recalculate times if there isn't
236
	// one (if there is, we'll just go with the last pulse's times)
237
	if( new_pulse_at > last_pulse_at )
238
	{
239
		// new segment stepping times
240
		unsigned long delta = new_pulse_at - last_pulse_at;
241
		segment_step = delta / NUM_SEGMENTS;
242
		segment_step_sub = 0;
243
		segment_step_sub_step = delta % NUM_SEGMENTS;
244
	}
245
246
	// now we have dealt with this pulse, save the pulse time and
247
	// clear new_pulse_at, ready for the next pulse
248
	last_pulse_at = new_pulse_at;
249
	new_pulse_at = 0;
250
}
251
252
253
// wait until it is time to draw the next segment or a new pulse has
254
// occurred
255
void waitTillNextSegment( bool reset )
256
{
257
	static unsigned long end_time = 0;
258
259
	// handle reset
260
	if( reset )
261
		end_time = last_pulse_at;
262
263
	// work out the time that this segment should be displayed until
264
	end_time += segment_step;
16 by edam
finished first revision of propeller-clock code (can display clock and test); added Bounce library
265
	segment_step_sub += segment_step_sub_step;
266
	if( segment_step_sub >= NUM_SEGMENTS ) {
267
		segment_step_sub -= NUM_SEGMENTS;
13 by edam
updated propeller-clock code, added GPL text and renamed fan-test
268
		end_time++;
269
	}
270
271
	// wait
272
	while( micros() < end_time && !new_pulse_at );
273
}
274
275
16 by edam
finished first revision of propeller-clock code (can display clock and test); added Bounce library
276
// ISR to handle the pulses from the fan's tachiometer
277
void fanPulseHandler()
278
{
279
	// the fan actually sends two pulses per revolution. These pulses
280
	// may not be exactly evenly distributed around the rotation, so
281
	// we can't recalculate times on every pulse. Instead, we ignore
282
	// every other pulse so timings are based on a complete rotation.
283
	static bool ignore = true;
284
	ignore = !ignore;
285
	if( !ignore )
286
	{
287
		// set a new pulse time
288
		new_pulse_at = micros();
289
	}
290
}
291
292
13 by edam
updated propeller-clock code, added GPL text and renamed fan-test
293
// main setup
294
void setup()
295
{
296
	// set up an interrupt handler on pin 2 to nitice fan pulses
297
	attachInterrupt( 0, fanPulseHandler, RISING );
298
	digitalWrite( 2, HIGH );
299
  
300
	// set up output pins (4 to 13) for the led array
301
	for( int a = 4; a < 14; a++ )
302
		pinMode( a, OUTPUT );
303
16 by edam
finished first revision of propeller-clock code (can display clock and test); added Bounce library
304
	// set up mode-switch button on pin 3
305
	pinMode( 3, INPUT );
306
13 by edam
updated propeller-clock code, added GPL text and renamed fan-test
307
	// serial comms
308
	Serial.begin( 9600 );
309
}
310
311
312
// main loop
11 by Dan
added initial propeller clock code
313
void loop()
314
{
13 by edam
updated propeller-clock code, added GPL text and renamed fan-test
315
	// if there has been a new pulse, we'll be resetting the display
316
	bool reset = new_pulse_at? true : false;
317
16 by edam
finished first revision of propeller-clock code (can display clock and test); added Bounce library
318
	// only do this stuff at the start of a display cycle, to ensure
319
	// that no state changes mid-display
320
	if( reset )
321
	{
322
		// check buttons
323
		checkButtons();
324
325
		// keep track of time
326
		trackTime();
327
	}
328
13 by edam
updated propeller-clock code, added GPL text and renamed fan-test
329
	// draw this segment
330
	drawNextSegment( reset );
331
332
	// do we need to recalculate segment times?
333
	if( reset )
334
		calculateSegmentTimes();
335
336
	// wait till it's time to draw the next segment
337
	waitTillNextSegment( reset );
11 by Dan
added initial propeller clock code
338
}