#include #include #include "time.h" #define LCDwidth 16 //characters /* Use an 2x16 alphanumeric LCD connected to PORTC as follows: [LCD] [Mega32 pin] 1 GND - GND 2 +5V - VCC 3 VLC - GND 4 RS - PC0 5 RD - PC1 6 EN - PC2 11 D4 - PC4 12 D5 - PC5 13 D6 - PC6 14 D7 - PC7 */ // LCD on PORTC #asm .equ __lcd_port=0x15 #endasm #include // LCD driver routines char lcd_buffer[17]; // LCD display buffer // LCD Day Strings char SUNDAY[] = "SUN"; char MONDAY[] = "MON"; char TUESDAY[] = "TUE"; char WEDNESDAY[] = "WED"; char THURSDAY[] = "THU"; char FRIDAY[] = "FRI"; char SATURDAY[] = "SAT"; // Clock Modes typedef enum { CAL_TIME = 0, CAL_ALARM, ALARM0, ALARM1, ALARM2, ALARM3, ALARM4, ALARM5, ALARM6 } mode_t; mode_t mode = CAL_TIME; // Button Sequences #define SNOOZE 0b10000000 #define MODE_CHANGE 0b01000000 #define RESET 0b00100000 #define HOUR_INC 0b00010000 #define HOUR_DEC 0b00001000 #define MIN_INC 0b00000010 #define MIN_DEC 0b00000001 // Snooze Time #define SNOOZE_MIN 15 // Alarm Wheel Margin of Error #define WHEEL_MARGIN 30 // STEPPER #define NUM_STEPS 8 // Bit 5 of steps for solenoid output // Port output step sequences unsigned char steps[] = {0b10111, 0b10011, 0b11011, 0b11001, 0b11101, 0b11100, 0b11110, 0b10110} ; unsigned int steps_remain = 0; // Number of steps remaining in requested move // Task time values (ms) #define t_lcd 300 #define t_button 25 #define t_step 10 #define t_check_alarm 1000 #define t_led 500 // Task subroutine declarations void task_lcd(void); void task_button(void); void task_step(void); void task_check_alarm(void); void task_led(void); // MCU Initialization void initialize(void); // Task time counters unsigned int time_lcd = 0, time_button = 0, time_step = 0, time_check_alarm = 0, time_led = 0; unsigned long time_snooze = 0; // Disable stepping while snooze activated // Time/Alarm datastructure typedef eeprom struct{ unsigned char day; unsigned char hour; // hour == 24 indicates disabled alarm unsigned char min; unsigned char sec; unsigned char tick; } time_t; #define TICKS_PER_SEC 60 // Steps to turn wheel by 12 hours #define REV_STEPS 923 #define TICK_PER_STEP ((TICKS_PER_SEC*60*60*12) / REV_STEPS) eeprom time_t avr_time = {0,0,0,0,0}; // Current Time // Daily alarms eeprom time_t avr_alarms[7] = {{0,24,0,0,0}, {1,24,0,0,0}, {2,24,0,0,0}, {3,24,0,0,0}, {4,24,0,0,0}, {5,24,0,0,0}, {6,24,0,0,0}}; // Current time shown on alarm wheel eeprom time_t avr_wheel = {0,0,0,0,0}; // Check for tick within error range of MCU cock unsigned int time_tick = 0; // Increment current time by 1 tick void tick(void){ if(time_tick > 5){ // Tick occurs too fast (discard as noise) return; } else { // Reset tick timer time_tick = (1000 / TICKS_PER_SEC); } if( ++avr_time.tick == TICKS_PER_SEC){ avr_time.tick = 0; if( ++avr_time.sec == 60){ avr_time.sec = 0; if( ++avr_time.min == 60){ avr_time.min = 0; if( ++avr_time.hour == 24){ avr_time.hour = 0; if( ++avr_time.day == 7){ avr_time.day = 0; } } } } } } // Adjust wheel time by number of steps void dec_wheel(unsigned long num_steps){ unsigned long dec_ticks; signed long wheel_ticks; // Converts steps to decrment into time in ticks dec_ticks = TICK_PER_STEP * num_steps; // Convert wheel time to ticks wheel_ticks = ((unsigned long)avr_wheel.hour)*TICKS_PER_SEC*60*60 + ((unsigned long)avr_wheel.min)*TICKS_PER_SEC*60 + ((unsigned long)avr_wheel.sec)*TICKS_PER_SEC + ((unsigned long)avr_wheel.tick); // Calculate new wheel time in ticks wheel_ticks -= dec_ticks; if(wheel_ticks < 0){ // Adjust for roll over by 1 revolution wheel_ticks += (12*60*60*TICKS_PER_SEC); } // Convert time in ticks back to time struct and reset wheel time avr_wheel.hour = (unsigned char)(wheel_ticks / (60 * 60 *TICKS_PER_SEC)); wheel_ticks -= (((unsigned long)avr_wheel.hour) *60*60*TICKS_PER_SEC); avr_wheel.min = (unsigned char)(wheel_ticks / (60 * TICKS_PER_SEC)); wheel_ticks -= (((unsigned long)avr_wheel.min) *60*TICKS_PER_SEC); avr_wheel.sec = (unsigned char)(wheel_ticks / (TICKS_PER_SEC)); wheel_ticks -= (((unsigned long)avr_wheel.sec) *TICKS_PER_SEC); avr_wheel.tick = (unsigned char)(wheel_ticks); } // Convert number of minutes to decrement into how many steps required unsigned long convert_steps(unsigned long min){ return (((unsigned long)REV_STEPS * min) / (12 * 60)); } //********************************************************** // timer 0 overflow ISR // executes once per ms interrupt [TIM0_COMP] void timer0_compare(void){ //Decrement the time if not already zero if (time_lcd>0) --time_lcd; if (time_button>0) --time_button; if (time_check_alarm>0) --time_check_alarm; if (time_step>0) --time_step; if (time_led>0) --time_led; if (time_tick>0) --time_tick; if (time_snooze>0) --time_snooze; } //********************************************************* // External Interrupt 0 // Connected to time base signal (60Hz) interrupt [EXT_INT0] void external_int0(void){ // Increment ticks in time counter tick(); } //********************************************************** //Entry point and task scheduler loop void main(void){ initialize(); //main task scheduler loop while(1) { if (time_lcd==0) task_lcd(); if (time_button==0) task_button(); if (time_step==0) task_step(); if (time_check_alarm==0) task_check_alarm(); if (time_led==0) task_led(); } } //********************************************************* // Make request to begin stepping the alarm wheel // Turns wheel to decrease alarm wheel time by min minutes unsigned char start_step(unsigned long min){ if(steps_remain){ // Currently processing another step request return 1; // Error code } else { // Assert solenoid (bit 5), to prevent alarm from // sounding while turning wheel DDRA = 0xff; // Set PORTA (Stepper and Solenoid) to Output // Convert minutes into integer number of steps steps_remain = convert_steps(min); // Convert the integer steps into exact time change // and update the avr_wheel variable dec_wheel(steps_remain); return 0; } } //******************************************************** // Turns the motor 1 more step and updates state variables void task_step(void){ // steps index, stepping state variable static unsigned char i = 0, stepping = 0; time_step=t_step; //reset the task timer if(steps_remain > 0){ stepping = 1; --steps_remain; PORTA = steps[i]; if(--i == 0xff) i = NUM_STEPS - 1; } else if(stepping){ // finished last step (steps_remain == 0) stepping = 0; // reset stepping state // Stop stepping and solenoid // Set PORTA to Tri-State Input // High-Z sources no current DDRA = 0x00; PORTA = 0x00; } } //********************************************************* // Check to see if alarm is soon (within WHEEL_MARGIN) // and if so move alarm wheel to correct position void task_check_alarm(void){ static unsigned char moved = 0; // Only move once per alarm, stop checking after 1st move signed long alarm_min, time_min, delta; if(!time_snooze){ // Disable checking while snooze activated // Today's alarm time in minutes alarm_min = (((signed long)avr_alarms[avr_time.day].hour) * 60 + ((signed long)avr_alarms[avr_time.day].min)); // Current time of day in minutes time_min = ((signed long)avr_time.hour) * 60 + ((signed long)avr_time.min); // Time till alarm delta = alarm_min - time_min; // Check if alarm is valid (hour < 24), and if the delta is within WHEEL_MARGIN if(avr_alarms[avr_time.day].hour < 24 && delta >= -WHEEL_MARGIN && delta < WHEEL_MARGIN){ if(!moved){ // Convert 24-hour alarm time into 12-hour wheel time if(avr_alarms[avr_time.day].hour >= 12){ alarm_min -= (12*60); } // Calculate minutes to adjust alarm wheel delta = (((signed long)avr_wheel.hour) * 60 + ((signed long)avr_wheel.min)) - alarm_min; if(delta < 0){ // In the case of a negative delta, we must add a full revolution (12*60) minutes delta += (12*60); } // Begin adjusting alarm start_step((unsigned long)delta); moved = 1; // Assert moved to disable further adjusting } } else{ // Check for AM/PM overlap to avoid unwanted alarm activation if(time_min >= (12*60)) time_min -= (12*60); // Convert 24->12 hour delta = (((signed long)avr_wheel.hour) * 60 + ((signed long)avr_wheel.min)) - time_min; if(delta < 0){ // In the case of a negative delta, we must add a full revolution (12*60) minutes delta += (12*60); } if(delta >= 0 && delta < WHEEL_MARGIN){ if(!moved){ // Advance wheel to before current time // So that alarm will not activate // Adjust to 3*margin to give some room for step/min conversion precision delta += (3*WHEEL_MARGIN); start_step((unsigned long)delta); moved = 1; } } else { moved = 0; // Reset moved to prepare for next alarm } } } } //********************************************************** //Timed task subroutine //Task 1 void task_lcd(void){ time_lcd=t_lcd; //reset the task timer lcd_clear(); // Clear current content lcd_gotoxy(0,0); //position to upper left on display // Write First Line switch(mode){ case CAL_TIME: lcd_putsf("Calibrate Time"); break; case CAL_ALARM: lcd_putsf("Calibrate Alarm"); break; case ALARM0: lcd_putsf("Sunday Alarm"); break; case ALARM1: lcd_putsf("Monday Alarm"); break; case ALARM2: lcd_putsf("Tuesday Alarm"); break; case ALARM3: lcd_putsf("Wednesday Alarm"); break; case ALARM4: lcd_putsf("Thursday Alarm"); break; case ALARM5: lcd_putsf("Friday Alarm"); break; case ALARM6: lcd_putsf("Saturday Alarm"); break; default: lcd_putsf(""); } lcd_gotoxy(0,1); //position to second line // Write Second Line switch(mode){ case CAL_TIME: switch(avr_time.day){ case 0: sprintf(lcd_buffer,"%s %02d:%02d:%02d",SUNDAY,avr_time.hour,avr_time.min,avr_time.sec); break; case 1: sprintf(lcd_buffer,"%s %02d:%02d:%02d",MONDAY,avr_time.hour,avr_time.min,avr_time.sec); break; case 2: sprintf(lcd_buffer,"%s %02d:%02d:%02d",TUESDAY,avr_time.hour,avr_time.min,avr_time.sec); break; case 3: sprintf(lcd_buffer,"%s %02d:%02d:%02d",WEDNESDAY,avr_time.hour,avr_time.min,avr_time.sec); break; case 4: sprintf(lcd_buffer,"%s %02d:%02d:%02d",THURSDAY,avr_time.hour,avr_time.min,avr_time.sec); break; case 5: sprintf(lcd_buffer,"%s %02d:%02d:%02d",FRIDAY,avr_time.hour,avr_time.min,avr_time.sec); break; case 6: sprintf(lcd_buffer,"%s %02d:%02d:%02d",SATURDAY,avr_time.hour,avr_time.min,avr_time.sec); break; default: sprintf(lcd_buffer,""); break; } break; case CAL_ALARM: sprintf(lcd_buffer,"%02d:%02d",avr_wheel.hour,avr_wheel.min); break; case ALARM0: if(avr_alarms[0].hour < 24){ sprintf(lcd_buffer,"%02d:%02d",avr_alarms[0].hour,avr_alarms[0].min); } else { sprintf(lcd_buffer,"DISABLED"); } break; case ALARM1: if(avr_alarms[1].hour < 24){ sprintf(lcd_buffer,"%02d:%02d",avr_alarms[1].hour,avr_alarms[1].min); } else { sprintf(lcd_buffer,"DISABLED"); } break; case ALARM2: if(avr_alarms[2].hour < 24){ sprintf(lcd_buffer,"%02d:%02d",avr_alarms[2].hour,avr_alarms[2].min); } else { sprintf(lcd_buffer,"DISABLED"); } break; case ALARM3: if(avr_alarms[3].hour < 24){ sprintf(lcd_buffer,"%02d:%02d",avr_alarms[3].hour,avr_alarms[3].min); } else { sprintf(lcd_buffer,"DISABLED"); } break; case ALARM4: if(avr_alarms[4].hour < 24){ sprintf(lcd_buffer,"%02d:%02d",avr_alarms[4].hour,avr_alarms[4].min); } else { sprintf(lcd_buffer,"DISABLED"); } break; case ALARM5: if(avr_alarms[5].hour < 24){ sprintf(lcd_buffer,"%02d:%02d",avr_alarms[5].hour,avr_alarms[5].min); } else { sprintf(lcd_buffer,"DISABLED"); } break; case ALARM6: if(avr_alarms[6].hour < 24){ sprintf(lcd_buffer,"%02d:%02d",avr_alarms[6].hour,avr_alarms[6].min); } else { sprintf(lcd_buffer,"DISABLED"); } break; default: sprintf(lcd_buffer,"ERROR"); } lcd_puts(lcd_buffer); } void change_hour(unsigned char inc){ switch(mode){ case CAL_TIME: if(inc){ if(++avr_time.hour == 24){ // Rollover hour to increment day avr_time.hour = 0; if(++avr_time.day == 7){ // Rollover day avr_time.day = 0; } } } else { // Rollunder if(avr_time.hour-- == 0){ avr_time.hour = 23; if(avr_time.day-- == 0){ avr_time.day = 6; } } } break; case CAL_ALARM: if(inc){ // Rollover at 12 hours if(++avr_wheel.hour == 12){ avr_wheel.hour = 0; } } else { if(avr_wheel.hour-- == 0){ avr_wheel.hour = 11; } } break; case ALARM0: if(inc){ // Allow 24 hour (hour > 23) => disabled // Rollover past 24 if(++avr_alarms[0].hour > 24){ avr_alarms[0].hour = 0; } } else { if(avr_alarms[0].hour-- == 0){ avr_alarms[0].hour = 24; } } break; case ALARM1: if(inc){ if(++avr_alarms[1].hour > 24){ avr_alarms[1].hour = 0; } } else { if(avr_alarms[1].hour-- == 0){ avr_alarms[1].hour = 24; } } break; case ALARM2: if(inc){ if(++avr_alarms[2].hour > 24){ avr_alarms[2].hour = 0; } } else { if(avr_alarms[2].hour-- == 0){ avr_alarms[2].hour = 24; } } break; case ALARM3: if(inc){ if(++avr_alarms[3].hour > 24){ avr_alarms[3].hour = 0; } } else { if(avr_alarms[3].hour-- == 0){ avr_alarms[3].hour = 24; } } break; case ALARM4: if(inc){ if(++avr_alarms[4].hour > 24){ avr_alarms[4].hour = 0; } } else { if(avr_alarms[4].hour-- == 0){ avr_alarms[4].hour = 24; } } break; case ALARM5: if(inc){ if(++avr_alarms[5].hour > 24){ avr_alarms[5].hour = 0; } } else { if(avr_alarms[5].hour-- == 0){ avr_alarms[5].hour = 24; } } break; case ALARM6: if(inc){ if(++avr_alarms[6].hour > 24){ avr_alarms[6].hour = 0; } } else { if(avr_alarms[6].hour-- == 0){ avr_alarms[6].hour = 24; } } break; } } void change_min(unsigned char inc){ switch(mode){ case CAL_TIME: if(inc){ // Rollover at 60 minutes if(++avr_time.min == 60){ avr_time.min = 0; } } else { if(avr_time.min-- == 0){ avr_time.min = 59; } } break; case CAL_ALARM: if(inc){ if(++avr_wheel.min == 60){ avr_wheel.min = 0; } } else { if(avr_wheel.min-- == 0){ avr_wheel.min = 59; } } break; case ALARM0: if(inc){ if(++avr_alarms[0].min == 60){ avr_alarms[0].min = 0; } } else { if(avr_alarms[0].min-- == 0){ avr_alarms[0].min = 59; } } break; case ALARM1: if(inc){ if(++avr_alarms[1].min == 60){ avr_alarms[1].min = 0; } } else { if(avr_alarms[1].min-- == 0){ avr_alarms[1].min = 59; } } break; case ALARM2: if(inc){ if(++avr_alarms[2].min == 60){ avr_alarms[2].min = 0; } } else { if(avr_alarms[2].min-- == 0){ avr_alarms[2].min = 59; } } break; case ALARM3: if(inc){ if(++avr_alarms[3].min == 60){ avr_alarms[3].min = 0; } } else { if(avr_alarms[3].min-- == 0){ avr_alarms[3].min = 59; } } break; case ALARM4: if(inc){ if(++avr_alarms[4].min == 60){ avr_alarms[4].min = 0; } } else { if(avr_alarms[4].min-- == 0){ avr_alarms[4].min = 59; } } break; case ALARM5: if(inc){ if(++avr_alarms[5].min == 60){ avr_alarms[5].min = 0; } } else { if(avr_alarms[5].min-- == 0){ avr_alarms[5].min = 59; } } break; case ALARM6: if(inc){ if(++avr_alarms[6].min == 60){ avr_alarms[6].min = 0; } } else { if(avr_alarms[6].min-- == 0){ avr_alarms[6].min = 59; } } break; } } void snooze(){ // Advance alarm wheel by -SNOOZE_MIN time_snooze = SNOOZE_MIN * 60 * 1000; // Disable alarm checking for SNOOZE_MIN minutes // Incrementing time corresponds to decrementing by 12*60 - t start_step((unsigned long) ((12*60) - SNOOZE_MIN)); } void alarm_reset(){ // Advance alarm wheel by 3*WHEEL_MARGIN time_snooze = WHEEL_MARGIN * 2 * 60 * 1000; // Disable alarm checking for 2 WHEEL_MARGIN start_step((unsigned long)(3 * WHEEL_MARGIN)); } void task_led(void){ time_led = t_led; if(avr_time.hour >= 12){ // PM -> turn on LED PORTB = 0Xff; } else { // AM -> turn off LED PORTB = 0X00; } } void task_button(void){ static char count = 0, hold = 0; static enum BUTT_STATE { released, maybe_pushed, pushed, maybe_released } button_state = released; static unsigned char last_buttons = 0, buttons = 0, my_buttons = 0; time_button = t_button; // reset task timer buttons = (~PIND) & 0b11111011; // No button connected to D.2 EXT_INT switch(button_state){ case released: count = 0; hold = 0; if(buttons) button_state = maybe_pushed; break; case maybe_pushed: if(buttons==last_buttons){ button_state = pushed; my_buttons = buttons; } else if(!buttons) button_state = released; break; case pushed: // Activate functionality while holding // Wait 20 * 25 ms before activating if (++count == 20){ // Activate every 10 * 25 ms while holding count = 10; hold = 1; switch(my_buttons){ // Call function based on button held case HOUR_INC: change_hour(1); break; case HOUR_DEC: change_hour(0); break; case MIN_INC: change_min(1); break; case MIN_DEC: change_min(0); break; } } if (!buttons) button_state = maybe_released; else if(buttons!=last_buttons) button_state = maybe_pushed; break; case maybe_released: if (!buttons){ button_state = released; // Call functions for buttons // Which only activate on release switch(my_buttons){ case SNOOZE: snooze(); break; case RESET: alarm_reset(); break; case MODE_CHANGE: // Change mode, rollover past ALARM6 if(++mode > ALARM6){ mode = 0; } break; default: // Act while hold buttons // which were not held down long enough if(!hold){ switch(my_buttons){ case HOUR_INC: change_hour(1); break; case HOUR_DEC: change_hour(0); break; case MIN_INC: change_min(1); break; case MIN_DEC: change_min(0); break; } } } count = 0; } else button_state = maybe_pushed; break; } last_buttons = buttons; } //********************************************************** //Set it all up void initialize(void){ //set up timer 0 for 1 mSec timebase TIMSK=2; //turn on timer 0 cmp match ISR OCR0 = 250; //set the compare re to 250 time ticks //prescalar to 64 and turn on clear-on-match TCCR0=0b00001011; // Set INT0 rising edge MCUCR |= 0x02; GICR |= 0b01000000; DDRA = 0x00; DDRB = 0xff; // LED on B.1 // B.0 is shorted PORTB = 0x00; // INT0 input is D.2 // All other PORTD are button inputs DDRD = 0x00 ; // Disable PULLUP on INT0 // Enable for buttons PORTD = 0b11111011; //init the task timer time_lcd=t_lcd; time_button=t_button; time_step=t_step; time_led=t_led; lcd_init(LCDwidth); //initialize the display lcd_clear(); //crank up the ISRs #asm("sei"); }