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