// // ^ Vcc MSP430G2452 GND ^ Vcc ^ Vcc // | /-------------\ | | | // +--- -| Vcc U GND |- ---+ | | 47 k === 100 nF // 0 -| P1.0 XTAL |- ------+ Crystal | | | // 1 -| P1.1 XTAL |- --|O|-+ 32768 Hz | --- GND // 2 -| P1.2 TEST |- -------------------|--o TEST +--------+ // 3 -| P1.3 ~RST |- -------------------+--o ~RST | | // 4 -| P1.4 P1.7 |- BUTTON_1 connected to Vcc | === 10 uF // 5 -| P1.5 P1.6 |---------------------------------+ | // 6 -| P2.0 P2.5 |- BUTTON_2 connected to Vcc | LED - /| Coil- // 7 -| P2.1 P2.4 |- SR_RCK (latch clock) / \|/ | | | Speaker // SR_SI -| P2.2 P2.3 |- SR_SCK (shift clock) v/ --- - \| (16 R) // \-------------/ v | | // 0..7 LED row select --- GND --- GND // // // Vcc Vcc +----------------+ Vcc // ^ SI RCK ^ | Vcc ^ | RCK ^ // | Al | GND | SCK | | | Ar | GND | SCK | // | | | | | | | | | | | | | | | | // /------------------------\ /------------------------\ // |Vcc QA SI OE RST H | |Vcc QA SI OE RST H | // |> MC74HC595 | |> MC74HC595 | // |QB QC QD QE QF QG QH GND| |QB QC QD QE QF QG QH GND| // \------------------------/ \------------------------/ // | | | | | | | | | | | | | | | | // Bl Cl Dl El Fl Gl Hl GND Br Cr Dr Er Fr Gr Hr GND // (left 8 LED columns) (right 8 LED columns) // // // A B C D E F G H // 0 o o o o o o o o * A C * // 1 o o o o o o o o 4 * F * // 2 o o o o o o o o 2 * * 3 the label is on // 3 o o o o o o o o * D H * the right side: // 4 o o o o o o o o 7 * * 5 (LD788BS-SS22) // 5 o o o o o o o o * B * 6 // 6 o o o o o o o o 1 * G * // 7 o o o o o o o o 0 * E * // // 0..7 (-) cathode pin assignment as // A..H (+) anode seen from the top #include #include #include "glyphs.h" #include "tune.h" #define SR_SI BIT2 // Serial Data Input #define SR_SCK BIT3 // Clock Input #define SR_RCK BIT4 // Storage Register Clock Input #define BUZZER BIT6 #define BUTTON_1 BIT7 #define BUTTON_2 BIT5 #define DISPLAYING_LIMIT 3 // Seconds #define SHINING_LIMIT 60 // Seconds #define SHINING_WARN_TICKS 4 static uint8_t fb[ 16 ]; // LED frame buffer static volatile uint8_t ticks = 0, seconds = 0, minutes = 0, hours = 0; static volatile uint8_t alarm_hour = 0, alarm_minute = 0; static volatile uint8_t ticks_at_button_press = 0; static volatile uint8_t displaying_watchdog = 0; static volatile uint8_t shining_watchdog = 0; static volatile bool alarm_is_active = false; static volatile bool go_to_sleep = false; static volatile bool tick_happened = false; static volatile uint16_t tune_index = 0; static volatile uint8_t tune_count = 0; enum TState { SLEEPING, SHINING, TIME_DISPLAYING, TIME_HOUR_EDITING, TIME_MINUTE_EDITING, ALARM_DISPLAYING, ALARM_HOUR_EDITING, ALARM_MINUTE_EDITING, ALARM_IS_ACTIVE_DISPLAYING, ALARM_IS_ACTIVE_EDITING, BLINKING, PLAYING }; static volatile TState state = TIME_DISPLAYING; static void config_registers() { WDTCTL = WDTPW | WDTHOLD; DCOCTL = CALDCO_16MHZ; BCSCTL1 = CALBC1_16MHZ; BCSCTL3 |= LFXT1S_0 | XCAP_3; // Use Crystal as ACLK + 12.5 pF caps WDTCTL = WDT_ADLY_16; // Interval timer mode using ACLK clock source IE1 |= WDTIE; P1OUT = 0x00; P2OUT = 0x00; P1DIR = 0xFF & ~BUTTON_1 & ~BUZZER; // All output except BUTTON_1 and BUZZER P2DIR = 0xFF & ~BUTTON_2; // All output except BUTTON_2 P1OUT &= ~BUTTON_1; // select resistor to be pull-down P2OUT &= ~BUTTON_2; P1REN |= BUTTON_1; // enable resistor P2REN |= BUTTON_2; //P1IES &= ~BUTTON_1; // select the positive edge (low -> high transition) to cause an interrupt //P2IES &= ~BUTTON_2; //P1IFG &= ~BUTTON_1; // Clear any pending interrupt flags //P2IFG &= ~BUTTON_2; //P1IE |= BUTTON_1; // enable button interrupt //P2IE |= BUTTON_2; P1SEL |= BIT6; // enable Timer0_A Out1 function TACCTL1 = OUTMOD_7; // Reset/set |~~|_____ TACTL = TASSEL_2 | MC_1 | TACLR; // SMCLK + Up mode + Clear timer TACCR0 = 1; // period TACCR1 = 0; // 2 is permanent on, 0 is off when TACCR0 is 1. } static uint8_t shift_row( int8_t y ) { uint8_t on_led_count = 0; P2OUT &= ~SR_RCK; for ( uint8_t x = 0; x < 16; ++x ) { P2OUT &= ~SR_SCK; if ( fb[ 15 - x ] & ( 0x01 << y ) ) { P2OUT |= SR_SI; ++on_led_count; } else { P2OUT &= ~SR_SI; } P2OUT |= SR_SCK; } return on_led_count; } static void select_row( int8_t y ) { // Turn all rows off: P1OUT |= 0x3F; P2OUT |= 0x03; // Strobe columns: P2OUT |= SR_RCK; // Select respective row: if ( y >= 0 ) { if ( y < 6 ) { P1OUT &= ~( 0x01 << y ); } else { P2OUT &= ~( 0x01 << ( y - 6 ) ); } } } static void sleep() { // Set columns low: P2OUT &= ~SR_RCK; for ( uint8_t x = 0; x < 16; ++x ) { P2OUT &= ~SR_SCK; P2OUT &= ~SR_SI; P2OUT |= SR_SCK; } // Strobe columns: P2OUT |= SR_RCK; // Set rows low: P1OUT &= ~0x3F; P2OUT &= ~0x03; // Set the rest low: P2OUT &= ~( SR_SI | SR_SCK | SR_RCK ); TACCR0 = 1; TACCR1 = 0; // Discharge the 10 uF capacitor, otherwise the __delay_cycles( 16000 ); // device consumes over 40 uA in sleep mode. // Change Buzzer to input: P1DIR &= ~BUZZER; // Go to sleep: LPM3; } static void clear_fb() { for ( uint8_t i = 0; i < sizeof( fb ); ++i ) { fb[ i ] = 0; } } static void on_fb() { fb[ 0 ] = 0x00; fb[ 1 ] = 0x3C; fb[ 2 ] = 0x24; fb[ 3 ] = 0x3C; fb[ 4 ] = 0x42; fb[ 5 ] = 0x81; fb[ 6 ] = 0xFF; fb[ 7 ] = 0x00; fb[ 8 ] = 0x00; fb[ 9 ] = 0x24; fb[ 10 ] = 0x18; fb[ 11 ] = 0x42; fb[ 12 ] = 0x3C; fb[ 13 ] = 0x81; fb[ 14 ] = 0x7E; fb[ 15 ] = 0x00; } static void off_fb() { fb[ 0 ] = 0x00; fb[ 1 ] = 0x3C; fb[ 2 ] = 0x24; fb[ 3 ] = 0x3C; fb[ 4 ] = 0x42; fb[ 5 ] = 0x81; fb[ 6 ] = 0xFF; fb[ 7 ] = 0x00; fb[ 8 ] = 0x00; fb[ 9 ] = 0x42; fb[ 10 ] = 0x24; fb[ 11 ] = 0x18; fb[ 12 ] = 0x18; fb[ 13 ] = 0x24; fb[ 14 ] = 0x42; fb[ 15 ] = 0x00; } static void render_fb() { const bool is_off_interval = ( ticks - ticks_at_button_press + 64 ) % 64 > 32; if ( ( state == PLAYING && ticks > 32 ) || ( state == ALARM_IS_ACTIVE_EDITING && is_off_interval ) ) { clear_fb(); return; } uint8_t hour, minute; bool display_blinking_point = false; switch ( state ) { case TIME_DISPLAYING: case TIME_HOUR_EDITING: case TIME_MINUTE_EDITING: case BLINKING: case PLAYING: hour = hours; minute = minutes; display_blinking_point = true; break; case ALARM_DISPLAYING: case ALARM_HOUR_EDITING: case ALARM_MINUTE_EDITING: hour = alarm_hour; minute = alarm_minute; break; } if ( state == ALARM_IS_ACTIVE_DISPLAYING || state == ALARM_IS_ACTIVE_EDITING ) { if ( alarm_is_active ) { on_fb(); } else { off_fb(); } } else { uint8_t digit_1 = hour / 10; uint8_t digit_2 = hour % 10; uint8_t digit_3 = minute / 10; uint8_t digit_4 = minute % 10; if ( ( state == TIME_HOUR_EDITING || state == ALARM_HOUR_EDITING ) && is_off_interval ) { digit_1 = digit_2 = 10; // none } else if ( digit_1 == 0 ) { digit_1 = 10; // none } if ( ( state == TIME_MINUTE_EDITING || state == ALARM_MINUTE_EDITING ) && is_off_interval ) { digit_3 = digit_4 = 10; // none } fb[ 0 ] = glyph[ digit_1 ][ 0 ]; fb[ 1 ] = glyph[ digit_1 ][ 1 ]; fb[ 2 ] = glyph[ digit_1 ][ 2 ]; fb[ 3 ] = 0; fb[ 4 ] = glyph[ digit_2 ][ 0 ]; fb[ 5 ] = glyph[ digit_2 ][ 1 ]; fb[ 6 ] = glyph[ digit_2 ][ 2 ]; fb[ 7 ] = 0; fb[ 8 ] = 0; fb[ 9 ] = glyph[ digit_3 ][ 0 ]; fb[ 10 ] = glyph[ digit_3 ][ 1 ]; fb[ 11 ] = glyph[ digit_3 ][ 2 ]; fb[ 12 ] = 0; fb[ 13 ] = glyph[ digit_4 ][ 0 ]; fb[ 14 ] = glyph[ digit_4 ][ 1 ]; fb[ 15 ] = glyph[ digit_4 ][ 2 ]; if ( display_blinking_point && ticks < 32 ) { fb[ seconds >> 2 ] |= 192; } if ( alarm_is_active ) { fb[ 15 ] |= 192; } } } static void display_fb() { for ( int8_t y = 7; y >= 0; --y ) { uint8_t on_led_count = shift_row( y ) + 2; // Without adding some value, single dots are to dark. const uint8_t max_led_count = 14; if ( on_led_count > max_led_count ) { on_led_count = max_led_count; } select_row( y ); const uint16_t delay = 357; // 16E6 / ( 300 * 14(=max_led_count) ) / 8 rows = 400.16 Hz for ( uint8_t i = 0; i < on_led_count; ++i ) { __delay_cycles( delay ); } select_row( -1 ); for ( uint8_t i = 0; i < max_led_count - on_led_count; ++i ) { __delay_cycles( delay ); } } } int main( void ) { config_registers(); clear_fb(); _enable_interrupts(); while ( true ) { if ( go_to_sleep ) { go_to_sleep = false; state = SLEEPING; sleep(); } if ( tick_happened ) { tick_happened = false; switch ( state ) { case SHINING: if ( shining_watchdog + SHINING_WARN_TICKS > SHINING_LIMIT ) { TACCR1 = ticks == 0 ? 0 : 2; } break; case BLINKING: if ( seconds < 30 ) { TACCR1 = seconds % 2 ? 0 : 2; } else { TACCR1 = ( ticks & 0x0F ) == 0 ? 2 : 0; } break; case PLAYING: if ( ( ticks & 0x01 ) == 0 ) { const uint16_t period = scale[ tune[ tune_index ] ]; TACCR0 = period; TACCR1 = period >> 1; ++tune_index; if ( tune_index == sizeof( tune ) ) { tune_index = 0; // be kind, rewind :) ++tune_count; if ( tune_count == 2 ) { tune_count = 0 ; go_to_sleep = true; // All attempts were in vain. } } } break; } render_fb(); } if ( state != SHINING ) { display_fb(); // Does busy waiting. } } } #pragma vector = WDT_VECTOR __interrupt void WDT_ISR( void ) { // called 64 times per second. Consumes about 2.6 uA @ 3.3 V. Device works down to 2.4 V. tick_happened = true; ++ticks; if ( ticks == 64 ) { ticks = 0; ++seconds; if ( seconds == 60 ) { seconds = 0; ++minutes; if ( minutes == 60 ) { minutes = 0; ++hours; if ( hours == 24 ) { hours = 0; //seconds = 1; // correction for too slow quarz __delay_cycles( 16000000 ); // correction for too fast quarz } } if ( alarm_is_active && hours == alarm_hour && minutes == alarm_minute ) { state = BLINKING; P1DIR |= BUZZER; TACCR0 = 1; TACCR1 = 0; // 2 is permanent on, 0 is off when TACCR0 is 1. LPM3_EXIT; } else if ( state == BLINKING ) { state = PLAYING; // Play a delightful tune. tune_index = 0; } } switch ( state ) { case TIME_DISPLAYING: case ALARM_IS_ACTIVE_DISPLAYING: case ALARM_DISPLAYING: ++displaying_watchdog; if ( displaying_watchdog > DISPLAYING_LIMIT ) { go_to_sleep = true; } break; case SHINING: ++shining_watchdog; if ( shining_watchdog > SHINING_LIMIT ) { go_to_sleep = true; } break; } } enum TButtonState { RELEASED, PRESSED, HELD }; static volatile TButtonState button1_state = RELEASED; if ( ( P1IN & BUTTON_1 ) == BUTTON_1 ) { if ( button1_state == RELEASED ) { button1_state = PRESSED; switch ( state ) { case SLEEPING: state = TIME_DISPLAYING; LPM3_EXIT; break; case SHINING: if ( shining_watchdog + SHINING_WARN_TICKS > SHINING_LIMIT ) { shining_watchdog = 0; } else { go_to_sleep = true; } break; case TIME_DISPLAYING: state = alarm_is_active ? ALARM_DISPLAYING : ALARM_IS_ACTIVE_DISPLAYING; break; case ALARM_IS_ACTIVE_DISPLAYING: case ALARM_DISPLAYING: go_to_sleep = true; break; case TIME_HOUR_EDITING: hours = ++hours; if ( hours == 24 ) { hours = 0; } break; case TIME_MINUTE_EDITING: minutes = ++minutes; if ( minutes == 60 ) { minutes = 0; } seconds = 0; ticks = 0; break; case ALARM_IS_ACTIVE_EDITING: alarm_is_active ^= 1; break; case ALARM_HOUR_EDITING: alarm_hour = ++alarm_hour; if ( alarm_hour == 24 ) { alarm_hour = 0; } break; case ALARM_MINUTE_EDITING: alarm_minute = alarm_minute + 5; if ( alarm_minute == 60 ) { alarm_minute = 0; } break; case BLINKING: case PLAYING: go_to_sleep = true; break; } displaying_watchdog = 0; ticks_at_button_press = ticks; } else if ( button1_state == HELD && ( ( ticks & 0x07 ) == 0 ) ) { switch ( state ) { case TIME_HOUR_EDITING: hours = ++hours; if ( hours == 24 ) { hours = 0; } break; case TIME_MINUTE_EDITING: minutes = ++minutes; if ( minutes == 60 ) { minutes = 0; } seconds = 0; ticks = 0; break; case ALARM_HOUR_EDITING: alarm_hour = ++alarm_hour; if ( alarm_hour == 24 ) { alarm_hour = 0; } break; case ALARM_MINUTE_EDITING: alarm_minute = alarm_minute + 5; if ( alarm_minute == 60 ) { alarm_minute = 0; } break; } displaying_watchdog = 0; ticks_at_button_press = ticks; } else if ( ( ticks - ticks_at_button_press + 64 ) % 64 > 24 ) { button1_state = HELD; } } else { button1_state = RELEASED; } static volatile TButtonState button2_state = RELEASED; if ( ( P2IN & BUTTON_2 ) == BUTTON_2 ) { if ( button2_state == RELEASED ) { button2_state = PRESSED; bool start_blinking_with_OFF_period = true; switch ( state ) { case SLEEPING: state = SHINING; // Consumes about 20 mA in SHINING state. P1DIR |= BUZZER; TACCR0 = 1; TACCR1 = 2; // 2 is permanent on, 0 is off when TACCR0 is 1. shining_watchdog = 0; LPM3_EXIT; break; case SHINING: if ( shining_watchdog + SHINING_WARN_TICKS > SHINING_LIMIT ) { shining_watchdog = 0; } else { go_to_sleep = true; } break; case TIME_DISPLAYING: state = TIME_HOUR_EDITING; break; case TIME_HOUR_EDITING: state = TIME_MINUTE_EDITING; break; case TIME_MINUTE_EDITING: state = TIME_DISPLAYING; break; case ALARM_DISPLAYING: start_blinking_with_OFF_period = false; // Fall through intended. case ALARM_IS_ACTIVE_DISPLAYING: state = ALARM_IS_ACTIVE_EDITING; break; case ALARM_IS_ACTIVE_EDITING: state = alarm_is_active ? ALARM_HOUR_EDITING : ALARM_IS_ACTIVE_DISPLAYING; start_blinking_with_OFF_period = false; break; case ALARM_HOUR_EDITING: state = ALARM_MINUTE_EDITING; break; case ALARM_MINUTE_EDITING: //state = ALARM_IS_ACTIVE_DISPLAYING; state = ALARM_DISPLAYING; break; case BLINKING: case PLAYING: go_to_sleep = true; break; } displaying_watchdog = 0; if ( start_blinking_with_OFF_period ) { //ticks_at_button_press = ( ticks + 31 ) % 64; // immediately off for 1/2 second ____|~~~~|____|~~ ticks_at_button_press = ( ticks + 16 ) % 64; // first off time is 1/4 second __|~~~~|____|~~~~ } else { //ticks_at_button_press = ticks; // immediately on for 1/2 second ~~~~|____|~~~~|__ ticks_at_button_press = ( ticks + 48 ) % 64; // 1/4 second delay before blinking starts ~~|____|~~~~|____ } } } else { button2_state = RELEASED; } // The interrupt flag is cleared automatically. } #pragma vector = PORT1_VECTOR __interrupt void PORT1_ISR( void ) { //P1IFG &= ~BUTTON_1; // clear the interrupt flag } #pragma vector = PORT2_VECTOR __interrupt void PORT2_ISR( void ) { //P2IFG &= ~BUTTON_2; // clear the interrupt flag } #pragma vector = USI_VECTOR __interrupt void USI_ISR( void ) {} #pragma vector = ADC10_VECTOR __interrupt void ADC10_ISR( void ) {} #pragma vector = TIMER0_A1_VECTOR __interrupt void TIMER0_A1_ISR( void ) {} #pragma vector = TIMER0_A0_VECTOR __interrupt void TIMER0_A0_ISR( void ) {} #pragma vector = COMPARATORA_VECTOR __interrupt void COMPARATORA_ISR( void ) {} #pragma vector = NMI_VECTOR __interrupt void NMI_ISR( void ) {}