/* Speaker Timer by Philip A. Polstra, Sr. March 2012 Distributed under Creative Commons 2.5 -- Attib & Share Alike */ #include // this contains all the IO port definitions #include // definitions for interrupts #include // definitions for power-down modes #include // definitions or keeping constants in program memory #include #include #include /* This program is for a speaker timer. This speaker timer utilizes a large 7-segment style display which is created using a set of LEDs. The LEDs are Charlieplexed which allows them to be controlled by 8 lines. The LEDs could be controlled by 7 lines in theory, but using 8 lines makes the wiring simplier. Each of the LED segments is mapped to a pair of GPIO lines with a direction. After the timer is set and started it will count down the display. There are a series of outputs that will go high as time elapses. This allows fun warning devices (nerf rockets, etc.) to be activated close to quitting time. */ /* * This device uses an ATMega328 chip, although a 20-pin AVR MCU would * work just as well. I just have some ATMega328 chips laying around so * I decided to use them. Port B is used to drive the 8 wires for the * display. Port C is used for setting the timer. Port D is used * for outputs. Note that 1/2 normal value for resistors should be used since there * is resistance on both high and low lines. About 150 ohms for red. * * Here is the pinout: ____________ * Pin 1 - NC normally RESET pin ----| |---- Pin 28 - NC * Pin 2 - PD0 - 10 minute warning terminal ----| |---- Pin 27 - NC * Pin 3 - PD1 - 5 minute warning terminal ----| |---- Pin 26 - PC3 - Restart button * Pin 4 - PD2 - 1 minute warning terminal ----| |---- Pin 25 - PC2 - Start/Stop button * Pin 5 - PD3 - overtime warning terminal ----| |---- Pin 24 - PC1 - Minute minus button * Pin 6 - NC ----| |---- Pin 23 - PC0 - Minute plus button * Pin 7 - Vcc - plus 5V ----| |---- Pin 22 - Ground * Pin 8 - Ground ----| |---- Pin 21 - AVref - plus 5V * Pin 9 - PB6 - charlieplex line 6 ----| |---- Pin 20 - AVcc - plus 5V * Pin 10 -PB7 - charlieplex line 7 ----| |---- Pin 19 - PB5 - charlieplex line 5 * Pin 11 - NC ----| |---- Pin 18 - PB4 - charlieplex line 4 * Pin 12 - NC ----| |---- Pin 17 - PB3 - charlieplex line 3 * Pin 13 - NC ----| |---- Pin 16 - PB2 - charlieplex line 2 * Pin 14 - PB0 - charlieplex line 0 ----| |---- Pin 15 - PB1 - charlieplex line 1 * -------------- */ /* Each 2 LED segment is activated by applying +5V to one line, grounding another and setting everything else * to input (high impedence or an open circuit). * The following diagram shows a REAR VIEW of my board so be warned the leftmost * digit is the least significant. * * Key * ^^ LED with circuit symbol pointing up (+ on bottom - on top) * << LED with circuit symbol pointing left (- on left + on right) * >> LED with circuit symbol pointing right (+ on left - on right) * ## LED with circuit symbol pointing down (+ on top - on bottom) * numbers refer to the charlieplexing line (note 0 runs around the the perimeter) * * 00000000000000000000000000000000000000000000000000000000000000000000 * 0 | | | | | | | | 0 * 0 ^^ ^^ ^^ ^^ ## ## ## ## 0 *0 -->>--1--<<-- 3 --<<--4-->>-- 2 1 -->>--4--<<-- 3 --<<--6-->>-- 0 *0 -->>-/ \-<<-- 3 --<<-/ \->>-- 2 -->>-- 1 -->>-/ \-<<-- 3 --<<-/ \->>-- 0 *0 ## ## 3 ## ## 2 1 ^^ ^^ 3 ^^ ^^ 0 *0 --<<--2-->>-- 3 -->>--5--<<-- 2 -->>-- 1 --<<--5-->>-- 3 -->>--7--<<-- 0 *0 --<<-/ \->>-- 3 -->>-/ \-<<-- 2 1 --<<-/ \->>-- 3 -->>-/ \-<<-- 0 0 ^^ ^^ 3 ^^ ^^ 2 1 ## ## 3 ## ## 0 0 | | | | | | | | 0 000000000000000000000000000000000000000000000000000000000000000000000 */ /* Each segment is mapped using 2 bytes. The first byte indicates the high * pin for the output. The second byte indicates the bitmask for the * DDRB register. For this register 1=output 0=input. * */ const unsigned char segs0[] = { (1<<1), ((1<<1)|(1<<0)), //A0 (top) (1<<0), ((1<<0)|(1<<1)), //B0 (upper right) (1<<2), ((1<<2)|(1<<0)), //C0 (lower right) (1<<0), ((1<<0)|(1<<2)), //D0 (bottom) (1<<2), ((1<<2)|(1<<3)), //E0 (lower left) (1<<3), ((1<<3)|(1<<1)), //F0 (upper left) (1<<1), ((1<<1)|(1<<2)) //G0 (center) }; const unsigned char segs1[] = { (1<<4), ((1<<4)|(1<<0)), //A1 (top) (1<<4), ((1<<4)|(1<<3)), //B1 (upper right) (1<<3), ((1<<3)|(1<<5)), //C1 (lower right) (1<<0), ((1<<0)|(1<<5)), //D1 (bottom) (1<<2), ((1<<2)|(1<<5)), //E1 (lower left) (1<<4), ((1<<4)|(1<<2)), //F1 (upper left) (1<<4), ((1<<4)|(1<<5)) //G1 (center) }; const unsigned char segs2[] = { (1<<0), ((1<<0)|(1<<4)), //A2 (top) (1<<1), ((1<<1)|(1<<4)), //B2 (upper right) (1<<5), ((1<<5)|(1<<1)), //C2 (lower right) (1<<5), ((1<<5)|(1<<0)), //D2 (bottom) (1<<5), ((1<<5)|(1<<3)), //E2 (lower left) (1<<3), ((1<<3)|(1<<4)), //F2 (upper left) (1<<5), ((1<<5)|(1<<4)) //G2 (center) }; const unsigned char segs3[] = { (1<<0), ((1<<0)|(1<<6)), //A3 (top) (1<<6), ((1<<6)|(1<<3)), //B3 (upper right) (1<<3), ((1<<3)|(1<<7)), //C3 (lower right) (1<<7), ((1<<7)|(1<<0)), //D3 (bottom) (1<<0), ((1<<0)|(1<<7)), //E3 (lower left) (1<<6), ((1<<6)|(1<<0)), //F3 (upper left) (1<<7), ((1<<7)|(1<<6)) //G3 (center) }; /* The following defines a bitmap for the digits to * be displayed on our timer. We include the * hex digits to allow for times above 99 minutes. * If the timer is set up 99 then it will change * from decimal to hexadecimal for the minutes only. */ #define _s_A 0 #define _s_B 1 #define _s_C 2 #define _s_D 3 #define _s_E 4 #define _s_F 5 #define _s_G 6 const unsigned char segs[] = { _BV(_s_A) | _BV(_s_B) | _BV(_s_C) | _BV(_s_D) | _BV(_s_E) | _BV(_s_F), //0 _BV(_s_B) | _BV(_s_C), //1 _BV(_s_A) | _BV(_s_B) | _BV(_s_D) | _BV(_s_E) | _BV(_s_G), //2 _BV(_s_A) | _BV(_s_B) | _BV(_s_C) | _BV(_s_D) | _BV(_s_G), //3 _BV(_s_B) | _BV(_s_C) | _BV(_s_F) | _BV(_s_G), //4 _BV(_s_A) | _BV(_s_C) | _BV(_s_D) | _BV(_s_F) | _BV(_s_G), //5 _BV(_s_A) | _BV(_s_C) | _BV(_s_D) | _BV(_s_E) | _BV(_s_F) | _BV(_s_G), //6 _BV(_s_A) | _BV(_s_B) | _BV(_s_C), //7 _BV(_s_A) | _BV(_s_B) | _BV(_s_C) | _BV(_s_D) | _BV(_s_E) | _BV(_s_F) | _BV(_s_G),//8 _BV(_s_A) | _BV(_s_B) | _BV(_s_C) | _BV(_s_F) | _BV(_s_G),//9 _BV(_s_A) | _BV(_s_B) | _BV(_s_C) | _BV(_s_E) | _BV(_s_F) | _BV(_s_G), //A _BV(_s_C) | _BV(_s_D) | _BV(_s_E) | _BV(_s_F) | _BV(_s_G), //B _BV(_s_A) | _BV(_s_D) | _BV(_s_E) | _BV(_s_F), //C _BV(_s_B) | _BV(_s_C) | _BV(_s_D) | _BV(_s_E) | _BV(_s_G), //D _BV(_s_A) | _BV(_s_D) | _BV(_s_E) | _BV(_s_F) | _BV(_s_G), //E _BV(_s_A) | _BV(_s_E) | _BV(_s_F) | _BV(_s_G) //F }; unsigned long allottedTime = 1800; // default to 30 minutes unsigned long remainingTime = 1800; // how much time does the speaker have left? volatile unsigned short count = 0; // this is a counter for our interrupt handler volatile unsigned char displayedSegs[4]; // store segment masks for currently displayed digits volatile uint8_t seg = 0; // which segment in the digit is being displayed volatile uint8_t digit = 0; // which digit is currently displayed uint8_t timerStatus = 0; // 0=stopped 1=started volatile unsigned char *p; // pointer to current segX array volatile unsigned char currSeg; // current segment mask // Setup timing constants #define CPU_FREQ 8000000 //use the internal 8MHz #define UPDATE_FREQ 50 // how often do we refresh the display? #define MAX_SEGMENTS 29 // max number of segments that can be lighted at once #define IR_FREQ 1450 // interrupt occurs at 1450 Hz #define WARN1 600 // 1st warning #define WARN2 300 // 2nd warning #define WARN3 60 // last warning void delay_ms(unsigned int i) { volatile unsigned int b, c, a; a=i*8; for(b=0;b!= a; b++)for(c=0;c!= 50;c++); return; } void illuminateSegment(unsigned char pinb, unsigned char ddrb) { DDRB = 0xff; // set all to output PORTB = 0x00; // ground it all DDRB = ddrb; // first set everything to input that should be PORTB = pinb; // now set the one pin high and the other low } void illuminateColon() { DDRB = 0xff; PORTB = 0x00; DDRB = ((1<<2)|(1<<1)); PORTB = (1<<2); } //Helper function to update the segment variables void updateDisplayedSegs() { unsigned short secs, mins; secs = remainingTime%60; mins = remainingTime/60; displayedSegs[0]=segs[secs%10]; displayedSegs[1]=segs[secs/10]; if(mins < 100) { displayedSegs[2]=segs[mins%10]; displayedSegs[3]=segs[mins/10]; } else { displayedSegs[2]=segs[mins%16]; displayedSegs[3]=segs[mins/16]; } } /* This interrupt handler runs at 1450 Hz. * This allows each of the 29 segments to be updated at * 50 Hz. Every 1450 passes update the remaining time * counter. */ ISR(TIMER1_COMPA_vect) { seg = count%29; // segment varies from 0-28 digit = seg/7; //is this segment illuminated or not? if (digit == 4) { illuminateColon(); } else { // illuminate a segment or dim all currSeg = displayedSegs[digit]; if (digit == 0) p = &segs0[0]; else if (digit == 1) p = &segs1[0]; else if (digit == 2) p = &segs2[0]; else p = &segs3[0]; if (currSeg & (1<<(seg%7))) { // current segment is illuminated DDRB = 0xFF; // set all to output PORTB = 0x00; // set all to zero DDRB = p[2*(seg%7)+1]; // set the output mask PORTB = p[2*(seg%7)]; // set the pins } else { // current segment is dim so turn off everything DDRB = 0xFF; PORTB = 0x00; DDRB = 0x00; } } count++; // increment counter // has a second gone by? if (count == IR_FREQ) { count = 0; if (timerStatus && (remainingTime >0)) remainingTime--; //update the digits updateDisplayedSegs(); if (remainingTime < WARN1) PORTD |= (1<<0); if (remainingTime < WARN2) PORTD |= (1<<1); if (remainingTime < WARN3) PORTD |= (1<<2); if (remainingTime == 0) PORTD |= (1<<3); } return; } #define B_RESET() (bit_is_clear(PINC,3)) #define B_START_STOP() (bit_is_clear(PINC,2)) #define B_MINUS() (bit_is_clear(PINC,1)) #define B_PLUS() (bit_is_clear(PINC,0)) #define B_WAIT 300 #define B_DEBOUNCE 25 #define nop() asm volatile ("nop;") // Power-on-Self-Test cycles through LED segments void post() { uint8_t i, j; unsigned char *p=NULL; for (i=0; i < 4; i++) { if (i==0) p=&segs0[0]; else if (i==1) p=&segs1[0]; else if (i==2) p=&segs2[0]; else if (i==3) p=&segs3[0]; else return; for (j=0; j < 7; j++) { illuminateSegment(p[j*2], p[j*2+1]); delay_ms(300); } } illuminateColon(); delay_ms(1000); } int main(void) { TIMSK1 = (1< 60) { remainingTime -= 60; } else { remainingTime = 0; } } updateDisplayedSegs(); delay_ms(B_WAIT); } if(B_PLUS() && !timerStatus) { delay_ms(B_DEBOUNCE); if (B_PLUS()) { if (remainingTime < 15300) // max of 0xff minutes remainingTime += 60; else remainingTime = 15300; } updateDisplayedSegs(); delay_ms(B_WAIT); } } }