/* Main Program LCD - commands to access & display, bitmaps in a .h file SPI - commands to read & write RTC SERIAL - commands to download & store med schedule */ #include #include "ks0108\lcd_ks0108.h" #include "serial_lib.h" #include "spi_rtc.h" #include #include //================ PORT DEFINITIONS ======================= #define BUTTON1 PINB.0 // Pushbutton #1 #define BUTTON2 PINA.7 // Pushbutton #2 #define LED1 PORTB.1 // Bin 1 light #define LED2 PORTB.2 // Bin 2 light #define LED3 PORTD.2 // Bin 3 light #define LED4 PORTD.3 // Bin 4 light #define SENSOR1 PIND.4 // Bin 1 open #define SENSOR2 PIND.5 // Bin 2 open #define SENSOR3 PIND.6 // Bin 3 open #define SENSOR4 PIND.7 // Bin 4 open #define SPEAKER PORTB.3 // Speaker #define BPUSHED 0 // Buttons Pushed #define SOPEN 0 // Bin Open Sensor #define LEDON 1 // LED lit #define SPEAKER_PERIOD 2 // in ms => 1000 Hz #define BEEP_DUR 250 #define BEEP_CNT 8 #define SCREEN_WIDTH 12 // ===== MAIN DATA STRUCTURES eeprom eeprom_Schedule schedule; //=============== INTERRUPT SERVICE ROUTINES ============= // prototype used after loading a new schedule void init_bin_states(void); // serial interrupt for new downloads interrupt [USART_RXC] void uart_rec(void) { // Turn off serial interrupts UCSRB.7 = 0; // Display a message to the user for downloading ks0108_clear(); string2LCDf("Downloading.",0,0); // Call the serial library get_schedule(&schedule); // Turn on serial interrupts and initialize bin states UCSRB.7 = 1; ks0108_clear(); init_bin_states(); } // Timer2 interrupt for Alarms enum {OFF, ON} alarm_state; int ms_count = BEEP_DUR; int beep_count = BEEP_CNT; unsigned char beeping = 1; interrupt [TIM2_COMP] ms_timer(void) { if (alarm_state == ON) { // Produce square wave when beeping if (beeping) { SPEAKER = !SPEAKER; } ms_count--; // If beep has lasted more than BEEP_DUR, toggle beeping if (ms_count <= 0) { ms_count = BEEP_DUR; beeping = !beeping; beep_count--; // If the number of beeps has reached BEEP_COUNT turn off if (beep_count <= 0) { alarm_state = OFF; beeping = 1; } } } else { SPEAKER = 0; beep_count = BEEP_CNT; } } //=============== CLOCK FUNCTIONS ======================== unsigned char hours, minutes, seconds; unsigned char temp; // Set the RAM hours, minutes, seconds variables void get_time(void){ temp = spi_read_reg(0x01); //read seconds seconds=10*((temp & 0x70)>>4) + (temp & 0x0F); temp = spi_read_reg(0x02); //read minutes minutes=10*((temp & 0x70)>>4) + (temp & 0x0F); temp = spi_read_reg(0x03); //read hours hours = 10*((temp & 0x30)>>4) + (temp & 0x0F); } // Sets the RTC hours, minutes, seconds registers void set_time (unsigned char h, unsigned char m, unsigned char s){ spi_write_reg(0x01,(((s/10)<<4) | (s%10))); spi_write_reg(0x02,(((m/10)<<4) | (m%10))); spi_write_reg(0x03,(((h/10)<<4) | (h%10))); hours=h; minutes=m; seconds=s; } //================ INITIALIZER FUNCTION ===================== void initialize(void){ //initialize inputs & outputs //LCD DDRA=0x7F; //control PORTA.7 = 1; DDRC=0xFF; //data //SPI, Pushbutton, speaker, 2 LED DDRB=0b10111110; PORTB = 0x01; //activate pullup resistor on pushbutton //Serial, 2 LEDs, 4 bin switches DDRD=0b00001110; PORTD = 0xF0; //activate pullup resistors on switches ks0108_init(); string2LCDf("Init",0,0); spi_init(); serial_init(); //Timer0 for audio generation //Timer2 for ms interrupt TCCR2=0x0C; //CTC mode, prescaler = timer/64 OCR2=249; TCNT2=0; TIMSK = 0x80; // Turn ON interrupts #asm sei #endasm } //==================== LED FUNCTIONS ================================ // Lights a specific LED void light_led(int num) { switch(num) { case 0: LED1 = LEDON; break; case 1: LED2 = LEDON; break; case 2: LED3 = LEDON; break; case 3: LED4 = LEDON; break; } } // Lights ALl LEDs void light_leds() { LED1 = LEDON; LED2 = LEDON; LED3 = LEDON; LED4 = LEDON; } // Turns off All LEDs void leds_off() { // turn off all LED's LED1 = !LEDON; LED2 = !LEDON; LED3 = !LEDON; LED4 = !LEDON; } //================ AUDIO ALARM FUNCTIONS ============================== // Allows timer interrupt to sound alarm void sound_alarm() { alarm_state = ON; } // Prevents any fluctuation on speaker pin void silence_alarm() { alarm_state = OFF; } //================= LCD MESSAGING FUNCTIONS ========================== // Used to automatically turn off the screen after some time int last_disp_hour, last_disp_min; // Warning for Bins opened inappropriately void display_warning(void) { ks0108_clear(); string2LCDf("*Warning*", 0, 20); string2LCDf("Please take", 2, 0); string2LCDf("medication", 4, 0); string2LCDf("as directed.", 6, 0); get_time(); last_disp_hour = hours; last_disp_min = minutes; } // Used to clear the screen and display info after bins are closed void display_disclaimer(void) { ks0108_clear(); get_time(); last_disp_hour = hours; last_disp_min = minutes; } // Updates the screen with the current MCU time void display_time(void) { char buffer[16]; sprintf(buffer,"%02i:%02i:%02i",hours, minutes, seconds); string2LCD(buffer, 4,20); get_time(); last_disp_hour = hours; last_disp_min = minutes; } // Notifies user that snooze was activated void display_snooze_disclaimer(void) { ks0108_clear(); string2LCDf(" *Notice* ", 0, 0); string2LCDf(" Alarm will ", 2, 0); string2LCDf("sound again ", 4, 0); string2LCDf(" in 5 mins. ", 6, 0); delay_ms(3000); // Display message for 3 seconds ks0108_clear(); get_time(); last_disp_hour = hours; last_disp_min = minutes; } // Display a positive confirmation message after an event void display_thank_you() { ks0108_clear(); string2LCDf(" Thank You! ", 2, 0); delay_ms(3000); // Display for 3 seconds ks0108_clear(); get_time(); last_disp_hour = hours; last_disp_min = minutes; } // Finds the name number in the names buffer and displays it void display_med_name(int name_num) { int i, curr_name = 0; int screen_pos = 0; char buffer[2]; // Iterate over the entire schedule.names buffer for (i = 0; i < 450; i++) { // 0s indicate spaces between names if (schedule.names[i] == 0) { curr_name++; } else if (curr_name == name_num) { // Display each character until name end or screen width exceeded sprintf(buffer, "%c", schedule.names[i]); string2LCD(buffer,0,screen_pos); screen_pos += 10; if (schedule.names[i+1] == 0 || screen_pos > 127) { break; } } } } // Displays special message corresponding to the special number void showSpecial(int special, int row) { switch (special) { case 0: break; case 1: string2LCDf("with food", row, 0); break; case 2: string2LCDf("before meal", row, 0); break; case 3: string2LCDf("after meal", row , 0); break; case 4: string2LCDf("with water", row, 0); break; case 5: string2LCDf("empty stomak", row, 0); break; case 6: string2LCDf("before bed", row, 0); break; } } // Displays all information pertaining to a dose // - i determines which part of description to show void display_dose(int bin_num, int dose_num, int i) { eeprom_Medication eeprom* m; eeprom_Dose eeprom* d; char buffer[12]; d = &schedule.bins[bin_num].doses[dose_num]; m = &schedule.meds[d->med_num]; ks0108_clear(); // for i == 0 display only the name and amount if(i == 0) { display_med_name(m->name_num); sprintf(buffer, "dosage: %i.%i", (d->amount)/10, (d->amount)%10); string2LCD(buffer, 2, 0); string2LCDf("info) (next", 6, 0); // for i == 1 display the medication details } else { // Color switch (m->color) { case 0: string2LCDf("---", 0,0); break; case 1: string2LCDf("RED", 0,0); break; case 2: string2LCDf("ORANGE", 0,0); break; case 3: string2LCDf("YELLOW", 0,0); break; case 4: string2LCDf("GREEN", 0,0); break; case 5: string2LCDf("BLUE", 0,0); break; case 6: string2LCDf("PINK", 0,0); break; case 7: string2LCDf("PURPLE", 0,0); break; } // Shape switch(m->shape) { case 1: drawShape(1, 0, 64); break; // round case 2: drawShape(2, 0, 64); break;// capsule case 3: drawShape(3, 0, 64); break;// ellipsoid case 4: drawShape(0, 0, 64); break;// tablet } // Specials showSpecial(m->special1, 4); showSpecial(m->special2, 6); } } //===================== PROMPT FUNCTION ============================= // Shows a yes/no question to the user and allows them to answer int launch_med_prompt(void) { unsigned char last_button1_state = !BPUSHED; unsigned char last_button2_state = !BPUSHED; // Show the question message ks0108_clear(); string2LCDf("Did you", 0,0); string2LCDf("take the", 2, 0); string2LCDf("medication?", 4, 0); string2LCDf("(yes) (no)", 6, 0); // Wait for a yes or no response and debounce while (1) { if (BUTTON1 == BPUSHED && BUTTON1 != last_button1_state) { return 1; } else if ( BUTTON2 == BPUSHED && BUTTON2 != last_button2_state) { return 0; } last_button1_state = BUTTON1; last_button2_state = BUTTON2; } } //==================== BIN/SCHEDULE FUNCTIONS ======================= // Snooze times track any snooze activity int bin_snooze_time[4]; // Check if bin time has expired int check_bin(int bin_num) { int check_min, check_hour, hours_diff; check_min = schedule.bins[bin_num].min + bin_snooze_time[bin_num]; check_hour = (schedule.bins[bin_num].hour + check_min/60) % 24; hours_diff = hours - check_hour; check_min = check_min % 60; // Check if the hours are greater than the current time but no greater than three // if hours are equal, compare the minutes return (hours_diff > 0 && hours_diff < 3) || (check_hour == hours && minutes >= check_min); } // Bin states - determines whether a bin is empty or full enum {FULL,EMPTY} bin_states[4]; void init_bin_states(void) { int bin_num; char buffer[12]; get_time(); // Initialize each depend on the current time - set snooze to zero for (bin_num = 0; bin_num < 4; bin_num++) { bin_snooze_time[bin_num] = 0; if (check_bin(bin_num)) { bin_states[bin_num] = EMPTY; } else { bin_states[bin_num] = FULL; } } } // Bin_ready called when a bin is ready to be opened // returns true if the pills were taken, false if not // can also be used for refills if refill is 1 int bin_ready(int bin_num, int refill) { unsigned char disp_dose_num = 0; unsigned char dose_num_changed = 1; unsigned char last_button1_state = !BPUSHED; unsigned char last_button2_state = !BPUSHED; unsigned char last_bin_state = !SOPEN; unsigned char bin_opened = 0; // If not refilling light the LED and sound the alarm if (!refill) { // Light LED light_led(bin_num); // Sound alarm sound_alarm(); } // Cycle Med info & wait for bin opening and closing while(!bin_opened || (SENSOR1 == SOPEN || SENSOR2 == SOPEN || SENSOR3 == SOPEN || SENSOR4 == SOPEN)) { delay_ms(100); // Display medication information if (dose_num_changed) { display_dose(bin_num, (disp_dose_num/2) % schedule.bins[bin_num].num_doses, disp_dose_num%2); dose_num_changed = 0; } // if button push, display next med on screen if (BUTTON1 == BPUSHED && last_button1_state != BUTTON1) { if (BUTTON2 == BPUSHED && last_button2_state != BUTTON2) { // snooze push both leds_off(); silence_alarm(); return 0; } else { // button1 - cycle med displayed disp_dose_num++; dose_num_changed = 1; } } if (BUTTON2 == BPUSHED && last_button1_state != BUTTON2) { disp_dose_num += (!(disp_dose_num % 2)) + 1; dose_num_changed = 1; } // Update button states last_button1_state = BUTTON1; last_button2_state = BUTTON2; // check bin open/close state // once open, turn off Led, silence alarm switch (bin_num) { case 0: if (SENSOR1 == SOPEN && last_bin_state == SOPEN) { silence_alarm(); leds_off(); bin_opened = 1; } last_bin_state = SENSOR1; break; case 1: if (SENSOR2 == SOPEN && last_bin_state == SOPEN) { silence_alarm(); leds_off(); bin_opened = 1; } last_bin_state = SENSOR2; break; case 2: if (SENSOR3 == SOPEN && last_bin_state == SOPEN) { silence_alarm(); leds_off(); bin_opened = 1; } last_bin_state = SENSOR3; break; case 3: if (SENSOR4 == SOPEN && last_bin_state == SOPEN) { silence_alarm(); leds_off(); bin_opened = 1; } last_bin_state = SENSOR4; break; } } // once closed again prompt the user if not on a refill if (!refill) { return launch_med_prompt(); } else { return 0; } } // Snoozes the bin five minutes by adding to the snooze time void bin_snooze(int bin_num) { bin_snooze_time[bin_num] += 5; } //===================== PILL SCHEDULER MAIN ====================== void main(void){ int bin_num; int last_sec; char buffer[16]; // Initialize all aspects as necessary initialize(); init_bin_states(); leds_off(); ks0108_clear(); // Show the current time get_time(); display_time(); last_disp_hour = hours; last_disp_min = minutes; while(1){ //read time, compare to bin alarm times //if match, light appropriate LED, enable alarm, display med info //if bin opened when not appropriate, flash LEDs, display warning // Update the time every pass through last_sec = seconds; get_time(); // Check each bin for (bin_num = 0; bin_num < 4; bin_num++) { // If a bin is full and ready according to the time, call bin_ready if(bin_states[bin_num] == FULL && check_bin(bin_num) && schedule.bins[bin_num].num_doses > 0) { // Turn on the display in case it was off ks0108_send_inst_left(DISPLAY_ON); ks0108_send_inst_right(DISPLAY_ON); // If the bin_ready returns true, bin is now empty if (bin_ready(bin_num, 0)) { display_thank_you(); bin_states[bin_num] = EMPTY; // Otherwise bin is now full and should be snoozed } else { bin_snooze(bin_num); display_snooze_disclaimer(); bin_states[bin_num] = FULL; } } } // Check bin open/close contact - sound alarm if inappropriate opening if (SENSOR1 == SOPEN | SENSOR2 == SOPEN | SENSOR3 == SOPEN | SENSOR4 == SOPEN) { ks0108_send_inst_left(DISPLAY_ON); ks0108_send_inst_right(DISPLAY_ON); // Light the LED of the open bin and display refill info if empty if (SENSOR1 == SOPEN) { light_led(0); if (bin_states[0] == EMPTY) { if (schedule.bins[0].num_doses > 0) { bin_ready(0, 1); ks0108_clear(); } leds_off(); continue; } } else if (SENSOR2 == SOPEN) { light_led(1); if (bin_states[1] == EMPTY) { if (schedule.bins[1].num_doses > 0) { bin_ready(1,1); ks0108_clear(); } leds_off(); continue; } } else if (SENSOR3 == SOPEN) { light_led(2); if (bin_states[2] == EMPTY) { if (schedule.bins[2].num_doses > 0) { bin_ready(2,1); ks0108_clear(); } leds_off(); continue; } } else if (SENSOR4 == SOPEN) { light_led(3); if (bin_states[3] == EMPTY) { if (schedule.bins[3].num_doses > 0) { bin_ready(3,1); ks0108_clear(); } leds_off(); continue; } } // If Bin is not empty sound the alarm and display a warning until closed again if (SENSOR1 == SOPEN | SENSOR2 == SOPEN | SENSOR3 == SOPEN | SENSOR4 == SOPEN) { sound_alarm(); display_warning(); while (SENSOR1 == SOPEN | SENSOR2 == SOPEN | SENSOR3 == SOPEN | SENSOR4 == SOPEN); silence_alarm(); leds_off(); display_disclaimer(); } } // Any button push should reactivate the screen if (BUTTON1 == BPUSHED || BUTTON2 == BPUSHED) { ks0108_send_inst_left(DISPLAY_ON); ks0108_send_inst_right(DISPLAY_ON); } // Update the time for each new second if (last_sec != seconds) { display_time(); } // if 1 minute has passed since the last LCD activity turn off the LCD to save power if ((minutes - last_disp_min)%60 >= 1) { ks0108_send_inst_left(DISPLAY_OFF); ks0108_send_inst_right(DISPLAY_OFF); } } }