/* File: main.c * Author: Jeremy Port and Thomas Clagett * Target PIC: PIC32MX250F128B * Program Description: * This code is the software portion of a 2017 ECE 414 project * The function of this project is to control a pet feeding system which * provides a household pet with food and water with additional smart * elements to relieve pet owner responsibility and properly provide * for the needs of the pet. This program includes the * implementation several different elements such as a * GUI/LCD Touch Screen, a RealTimeClock, and various * external peripheral devices including a Servo Motor, * Force Sensors, and a Solenoid Valve. */ #include #include #include "config.h" #include "pt_cornell_1_2.h" #include "plib.h" #include "xc.h" #include "adc_intf.h" #include "TouchScreen.h" #include "tft_master.h" #include "tft_gfx.h" #include #include #include "math.h" #define SYS_FREQ 40000000 //#define PBCLK 40000000 #define XM AN0 #define YP AN1 float lbPerServ = 0; float servLeftoverThres = 0; int enableFeed = 1; float maxADCin = 1010; float maxVOLin = 3.2; //1010/3.2 = 315.625 float vratio = 315.625; int Rm = 10000; float Vp = 3.3; float Rfsrf = 0; float Rfsrw = 0; float Voutf = 0.01; float Voutw = 0.01; float Force = 0.01; float Fweight = 0; float Wweight = 0; float WeightThres; char lbs[3] = "lbs"; float resTable[25] = {35, 26.17, 12.44, 5.61, 5.43, 3.74, 2.45, 1.72, 1.43, 1.27, 0.96, 0.84, 0.71, 0.66, 0.63, 0.52, 0.51, 0.47, 0.44, 0.42, 0.38, 0.37, 0.35, 0.33, 0.30}; float diffTablef[25] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; float diffTablew[25] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; float lbTable[25] = {0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1, 1.125, 1.25, 1.375, 1.5, 1.625, 1.75, 1.875, 2, 2.125, 2.25, 2.375, 2.5, 2.625, 2.75, 2.875, 3}; const short PR = 50000;//10ms between each interrupt//PR = (InterruptTime*SYSFREQ)/Prescalar const short PR_2 = 15625;//100ms float wv = 0.01; float fv = 0.01; int waterTimer = 0; int waterFire = 0; int waterFireTrig = 0; int waterInitEn = 0; int check = 0; int rotate = 0; int waterVal = 0; int waterEn = 0; int foodVal = 0; int foodEn = 0; float Servo; float detResVf = 0; float detResVw = 0; float detResVPrevf = 100; float detResVPrevw = 100; int resIndf; int resIndw; uint16_t i, j, k, e, s, d; char buffer[60]; volatile uint8_t hour = 0; volatile uint8_t zero = 0; volatile uint8_t minutes = 0; volatile signed long realHour = 0; volatile signed long realMinutes = 0; volatile uint8_t numMeals = 0; float weight = 0; volatile uint8_t drawRealTime = 0; volatile uint8_t drawMain = 0; volatile uint8_t drawSetTime = 0; volatile uint8_t drawScheduleOne = 0; volatile uint8_t drawScheduleTwo = 0; volatile uint8_t drawSetMeals = 0; volatile uint8_t drawSetWeight = 0; volatile uint8_t drawFeedSched = 0; volatile unsigned long hourArr[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; volatile unsigned long minutesArr[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; volatile uint8_t timeCnt = 0; int draw = 0; int arrCnt = 0; int waterFlag = 0; int foodFlag = 0; int h; int dispense = 0; int foodCntrl = 1; rtccTime tmReal; rtccTime tmCurr; rtccTime tmCurrAlarm; rtccTime tmAlarmArr; // === thread structures ============================================ // thread control structs static struct pt pt_settime, pt_mainscreen, pt_scheduleone, pt_setmeals, pt_setweight, pt_realtime, pt_feedingschedule, pt_weightcontrol; /* @param void * @return no return * This function is run once at the beginning of the program * and is responsible for initializing and configuring * two of our three system timers. * Timer 2: Backbone of the Servo Motor and Solenoid Valve * Control Logic * Timer 3: Backbone of the Force Sensor Control Logic */ void initTimers(void){ //vratio = maxADCin/maxVOLin; OpenTimer2(T2_ON | T2_SOURCE_INT | T2_PS_1_8, PR); ConfigIntTimer2(T2_INT_ON | T2_INT_PRIOR_2); OpenOC1(OC_ON | OC_TIMER2_SRC | OC_PWM_FAULT_PIN_DISABLE | OC_TIMER_MODE16, 0, 0); OpenTimer3(T3_ON | T3_SOURCE_INT | T3_PS_1_256, PR_2); ConfigIntTimer3(T3_INT_ON | T3_INT_PRIOR_3); //OpenCapture1(IC_ON | IC_INT_1CAPTURE | IC_EVERY_EDGE | IC_TIMER3_SRC | IC_CAP_16BIT | IC_IDLE_CON); } /* @param void * @return no return * This function is responsible for setting the * RealTimeClock of the PIC32. After this method * is run the drawMain is set to 1 so the main * screen appears on the screen. This function is * ran on startup or when the user wants to reset * the time of the PIC. */ void setRealTime(void) { while(drawMain == 0) { tft_setCursor(0, 0); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Please Set Current Time"); tft_writeString(buffer); //Draws the '+' and '-' buttons on the screen //that control setting the time tft_fillRoundRect(20, 70, 50, 50, 0, ILI9341_WHITE); tft_fillRoundRect(40, 75, 10, 40, 0, ILI9341_BLACK); tft_fillRoundRect(25, 90, 40, 10, 0, ILI9341_BLACK); tft_fillRoundRect(20, 170, 50, 50, 0, ILI9341_WHITE); tft_fillRoundRect(25, 190, 40, 10, 0, ILI9341_BLACK); tft_fillRoundRect(90, 70, 50, 50, 0, ILI9341_WHITE); tft_fillRoundRect(110, 75, 10, 40, 0, ILI9341_BLACK); tft_fillRoundRect(95, 90, 40, 10, 0, ILI9341_BLACK); tft_fillRoundRect(90, 170, 50, 50, 0, ILI9341_WHITE); tft_fillRoundRect(95, 190, 40, 10, 0, ILI9341_BLACK); tft_setCursor(60, 120); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(7); sprintf(buffer, ":"); tft_writeString(buffer); tft_drawRect(175, 120, 105, 50, ILI9341_WHITE); tft_setCursor(180, 135); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Set Time"); tft_writeString(buffer); //Touch Screen structure struct TSPoint p; p.x = 0; p.y = 0; p.z = 0; getPoint(&p); //Increases the hour if((p.x > 20) && (p.x < 70) && (p.y > 70) && (p.y < 120)) { ++realHour; //Sets the hour back to 0 if it is greater than 23 //due to 24-hour clock used by the RTCC if(realHour > 23) { realHour = 0; } //Erases previous hour tft_fillRoundRect(20, 130, 50, 40, 0, ILI9341_BLACK); tft_setCursor(20, 130); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(4); //Prints the hours on the screen //If hours is less than 10 draws a zero //then the hour if(realHour < 10) { sprintf(buffer, "%d", zero); tft_writeString(buffer); tft_setCursor(45, 130); sprintf(buffer, "%d", realHour); tft_writeString(buffer); } else { sprintf(buffer, "%d", realHour); tft_writeString(buffer); } //Decreases the hour } else if((p.x > 20) && (p.x < 70) && (p.y > 170) && (p.y < 220)) { --realHour; //Sets hour to 23 if it would have gone below 0 //Cannot have negative time if(realHour < 0) { realHour = 23; } //Erases previous hour tft_fillRoundRect(20, 130, 70, 40, 0, ILI9341_BLACK); tft_setCursor(20, 130); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(4); //Prints the hours on the screen //If hours is less than 10 draws a zero //then the hour if(realHour < 10) { sprintf(buffer, "%d", zero); tft_writeString(buffer); tft_setCursor(45, 130); sprintf(buffer, "%d", realHour); tft_writeString(buffer); } else { sprintf(buffer, "%d", realHour); tft_writeString(buffer); } } //Increases the minutes if((p.x > 90) && (p.x < 140) && (p.y > 70) && (p.y < 120)) { ++realMinutes; //Sets minutes back to 0 if it were to //be greater than 59 if(realMinutes > 59) { realMinutes = 0; } //Erases previous minutes tft_fillRoundRect(90, 130, 90, 40, 0, ILI9341_BLACK); tft_setCursor(90, 130); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(4); ////Prints the minutes on the screen //If minutes is less than 10 draws a zero //then the minutes if(realMinutes < 10) { sprintf(buffer, "%d", zero); tft_writeString(buffer); tft_setCursor(115, 130); sprintf(buffer, "%d", realMinutes); tft_writeString(buffer); } else { sprintf(buffer, "%d", realMinutes); tft_writeString(buffer); } //Decreases the minutes } else if((p.x > 90) && (p.x < 140) && (p.y > 170) && (p.y < 220)) { --realMinutes; //Sets minutes back to 0 if it were //to be less than 0 if(realMinutes < 0) { realMinutes = 59; } //Erases previous minutes tft_fillRoundRect(90, 130, 90, 40, 0, ILI9341_BLACK); tft_setCursor(90, 130); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(4); //Prints the minutes on the screen //If minutes is less than 10 draws a zero //then the minutes if(realMinutes < 10) { sprintf(buffer, "%d", zero); tft_writeString(buffer); tft_setCursor(115, 130); sprintf(buffer, "%d", realMinutes); tft_writeString(buffer); } else { sprintf(buffer, "%d", realMinutes); tft_writeString(buffer); } } //Sets the RTCC as the time set by the user. Sets drawMain as //1 so the main screen will be drawn on the screen. Sets //waterFlag, foodFlag, and waterInitEn to 1 so weight monitoring //process can begin if((p.x > 175) && (p.x < 280) && (p.y > 120) && (p.y < 170)) { //Draws the set time on the screen if(realMinutes < 10) { tft_fillRoundRect(0, 29, 350, 20, 0, ILI9341_BLACK); tft_setCursor(0, 30); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Time Set as %d:%d%d", realHour, zero, realMinutes); tft_writeString(buffer); } else { tft_fillRoundRect(0, 29, 350, 20, 0, ILI9341_BLACK); tft_setCursor(0, 30); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Time Set as %d:%d", realHour, realMinutes); tft_writeString(buffer); } //Initializes rtcc time structure to set time //Secinds hard-coded to 0 tmReal.sec = 0x00; tmReal.min = realMinutes; tmReal.hour = realHour; //Enables and initializes RTCC and sets time RtccEnable(); RtccInit(); RtccSetTime(tmReal.l); RtccWrEnable(1); RtccEnable(); //Fills screen black and sets variables to 1 tft_fillScreen(ILI9341_BLACK); waterFlag = 1; foodFlag = 1; drawMain = 1; waterInitEn = 1; } } } /* @param void * @return no return * This interrupt contains the logic for controlling * both the operation of our Servo Motor by setting and * resetting the OC1RS register our Output Compare 1, as it provides * a 10 ms period for the PWM signal, and the operation * of our Solenoid Valve by toggling the input and output of * a digital pin signal */ void __ISR(_TIMER_2_VECTOR, ipl2) T2Int(void){ //Servo Motor Control Logic if (dispense == 1 && foodFlag == 1) { if((Fweight >= weight) && foodCntrl == 1) { dispense = 0; } else { foodCntrl = 0; ++rotate; if (rotate == 100) { OC1RS = 6000; } else if (rotate == 200) { OC1RS = 5750; } else if (rotate == 300) { OC1RS = 5375; } else if (rotate == 500) { OC1RS = 5750; } else if (rotate == 600) { OC1RS = 6000; } else if (rotate == 700) { OC1RS = 6250; rotate = 0; foodCntrl = 1; } } } //Solenoid Valve Control Logic if (waterFireTrig >= 500 || waterInitEn == 1) { waterFire = 0; if (waterFire == 0) { if (Wweight <= 0.750 && waterFlag == 1) { ++waterTimer; if (waterTimer >= 500) { mPORTBSetPinsDigitalOut(BIT_5); waterTimer = 0; waterEn = 1; } ++waterTimer; } if (Wweight > 0.750 && waterFlag == 1) { waterTimer = 0; } if (waterEn == 1) { ++waterVal; } if (waterVal >= 50) { waterFireTrig = 0; waterInitEn = 0; waterFire = 1; mPORTBSetPinsDigitalIn(BIT_5); waterVal = 0; waterEn = 0; } } } if (waterFire == 1) { ++waterFireTrig; } mT2ClearIntFlag(); } /* @param void * @return no return * This interrupt is responsible for monitoring the * input from our weight sensors through the internal * ADC, then calculating the closest possibly weight * based of of our resistance measurement array */ void __ISR(_TIMER_3_VECTOR, ipl3) T3Int(void){ //Approximate Food Weight Calculation fv = (float)analogRead(5); Voutf = fv/vratio; Rfsrf = (((Vp/Voutf)-1)*Rm)*(1e-3); for (i = 0; i < 25; i++) { diffTablef[i] = Rfsrf - resTable[i]; } for (j = 0; j < 25; j++) { diffTablef[j] = Rfsrf - resTable[j]; } for (k = 0; k < 25; k++) { detResVf = diffTablef[k]; if (detResVf < 0) { detResVf = -1*detResVf; } if (detResVf < detResVPrevf) { resIndf = k; Fweight = lbTable[resIndf];//This weight value is NOT EXACT, simply the closest value in the lbTable: MinVal = 0 lbs, MaxVal = 3 lbs } detResVPrevf = detResVf; } //Approximate Water Weight Calculation wv = (float)analogRead(11); Voutw = wv/vratio; Rfsrw = (((Vp/Voutw)-1)*Rm)*(1e-3); for (e = 0; e < 25; e++) { diffTablew[e] = Rfsrw - resTable[e]; } for (s = 0; s < 25; s++) { diffTablew[s] = Rfsrw - resTable[s]; } for (d = 0; d < 25; d++) { detResVw = diffTablew[d]; if (detResVw < 0) { detResVw = -1*detResVw; } if (detResVw < detResVPrevw) { resIndw = d; Wweight = lbTable[resIndw];//This weight value is NOT EXACT, simply the closest value in the lbTable: MinVal = 0 lbs, MaxVal = 3 lbs } detResVPrevw = detResVw; } INTClearFlag(INT_T3); } /* @param struct pt * @return no return * This protothread was used strictly for testing * as it simply displays the voltage, resistance, * weight, and weight array index elements of our * force sensor */ static PT_THREAD (protothread_weightcontrol(struct pt *pt)) { PT_BEGIN(pt); while (1) { tft_fillRoundRect(0, 0, 320, 240, 0, ILI9341_BLACK); tft_setCursor(20, 50); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(3); sprintf(buffer, "%f", Voutf); tft_writeString(buffer); tft_setCursor(20, 100); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(3); sprintf(buffer, "%3.2f", Rfsrf); tft_writeString(buffer); tft_setCursor(20, 150); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(3); sprintf(buffer, "%1.3f %s", Fweight, lbs); tft_writeString(buffer); tft_setCursor(20, 200); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(4); sprintf(buffer, "%d", resIndf); tft_writeString(buffer); PT_YIELD_TIME_msec(10); } PT_END(pt); } /* @param struct pt * @return no return * This thread is responsible for setting the times the user * wishes for meals to occur. The thread allows for the user * to set as many meals as they chose on the previous screen. * After each meal time is set, the hours and minutes are * put into arrays for later use. After the meals are set, * drawMain is set to 1 so the system goes back to the * main screen. */ static PT_THREAD (protothread_settime(struct pt *pt)) { PT_BEGIN(pt); while(1) { if(drawSetTime == 1) { //Used to allow the user to set as many meals as numMeals. //timeCnt is incremented after each meal is set. if(timeCnt == numMeals) { drawSetTime = 0; drawMain = 1; tft_fillScreen(ILI9341_BLACK); timeCnt = 0; PT_YIELD_TIME_msec(50); } else { tft_setCursor(0, 0); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Schedule"); tft_writeString(buffer); //Draws '+' and '-' to set the time tft_fillRoundRect(20, 70, 50, 50, 0, ILI9341_WHITE); tft_fillRoundRect(40, 75, 10, 40, 0, ILI9341_BLACK); tft_fillRoundRect(25, 90, 40, 10, 0, ILI9341_BLACK); tft_fillRoundRect(20, 170, 50, 50, 0, ILI9341_WHITE); tft_fillRoundRect(25, 190, 40, 10, 0, ILI9341_BLACK); tft_fillRoundRect(90, 70, 50, 50, 0, ILI9341_WHITE); tft_fillRoundRect(110, 75, 10, 40, 0, ILI9341_BLACK); tft_fillRoundRect(95, 90, 40, 10, 0, ILI9341_BLACK); tft_fillRoundRect(90, 170, 50, 50, 0, ILI9341_WHITE); tft_fillRoundRect(95, 190, 40, 10, 0, ILI9341_BLACK); tft_setCursor(60, 120); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(7); sprintf(buffer, ":"); tft_writeString(buffer); tft_drawRect(175, 120, 105, 50, ILI9341_WHITE); tft_setCursor(180, 135); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Set Time"); tft_writeString(buffer); //Touch Screen structure struct TSPoint p; p.x = 0; p.y = 0; p.z = 0; getPoint(&p); //Increases the hour if((p.x > 20) && (p.x < 70) && (p.y > 70) && (p.y < 120)) { ++hour; //Sets hour back to 0 if it were to go above 23 if(hour > 23) { hour = 0; } //Erases previous hour tft_fillRoundRect(20, 130, 50, 40, 0, ILI9341_BLACK); tft_setCursor(20, 130); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(4); ////Prints the hours on the screen //If hours is less than 10 draws a zero //then the hour if(hour < 10) { sprintf(buffer, "%d", zero); tft_writeString(buffer); tft_setCursor(45, 130); sprintf(buffer, "%d", hour); tft_writeString(buffer); } else { sprintf(buffer, "%d", hour); tft_writeString(buffer); } PT_YIELD_TIME_msec(50); //Decreases the hour } else if((p.x > 20) && (p.x < 70) && (p.y > 170) && (p.y < 220)) { --hour; //Sets the hour to 23 if it were to be under 0 //0 represented by 255 if(hour == 255) { hour = 23; } //Erase previous hour tft_fillRoundRect(20, 130, 70, 40, 0, ILI9341_BLACK); tft_setCursor(20, 130); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(4); //Prints the hours on the screen //If hours is less than 10 draws a zero //then the hour if(hour < 10) { sprintf(buffer, "%d", zero); tft_writeString(buffer); tft_setCursor(45, 130); sprintf(buffer, "%d", hour); tft_writeString(buffer); } else { sprintf(buffer, "%d", hour); tft_writeString(buffer); } PT_YIELD_TIME_msec(50); } //Increases minutes if((p.x > 90) && (p.x < 140) && (p.y > 70) && (p.y < 120)) { ++minutes; //Sets minutes to 0 if it were to go above 59 if(minutes > 59) { minutes = 0; } //Erases previous minutes tft_fillRoundRect(90, 130, 90, 40, 0, ILI9341_BLACK); tft_setCursor(90, 130); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(4); //Prints the minutes on the screen //If minutes is less than 10 draws a zero //then the minutes if(minutes < 10) { sprintf(buffer, "%d", zero); tft_writeString(buffer); tft_setCursor(115, 130); sprintf(buffer, "%d", minutes); tft_writeString(buffer); } else { sprintf(buffer, "%d", minutes); tft_writeString(buffer); } PT_YIELD_TIME_msec(50); //Decreases minutes } else if((p.x > 90) && (p.x < 140) && (p.y > 170) && (p.y < 220)) { --minutes; //Sets minutes to 59 if it were to go below 0 if(minutes == 255) { minutes = 59; } //Erases previous minutes tft_fillRoundRect(90, 130, 90, 40, 0, ILI9341_BLACK); tft_setCursor(90, 130); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(4); //Prints the minutes on the screen //If minutes is less than 10 draws a zero //then the minutes if(minutes < 10) { sprintf(buffer, "%d", zero); tft_writeString(buffer); tft_setCursor(115, 130); sprintf(buffer, "%d", minutes); tft_writeString(buffer); } else { sprintf(buffer, "%d", minutes); tft_writeString(buffer); } PT_YIELD_TIME_msec(50); } //Sets the meal feeding time. Displays the time that was set and //which number meal it was. Adds hour and minutes to respective //arrays for future use. Increments timeCnt to check how many //meals have been set. if((p.x > 175) && (p.x < 280) && (p.y > 120) && (p.y < 170)) { if(hour != 0) { hourArr[timeCnt] = hour; minutesArr[timeCnt] = minutes; if(minutesArr[timeCnt] < 10) { tft_fillRoundRect(0, 29, 350, 20, 0, ILI9341_BLACK); tft_setCursor(0, 30); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Meal %d set for %d:%d%d", timeCnt + 1, hourArr[timeCnt], zero, minutesArr[timeCnt]); tft_writeString(buffer); } else { tft_fillRoundRect(0, 29, 350, 20, 0, ILI9341_BLACK); tft_setCursor(0, 30); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Meal %d set for %d:%d", timeCnt + 1, hourArr[timeCnt], minutesArr[timeCnt]); tft_writeString(buffer); } ++timeCnt; PT_YIELD_TIME_msec(50); } } PT_YIELD_TIME_msec(50); } } PT_YIELD_TIME_msec(50); } PT_END(pt); } /* @param struct pt * @return no return * This thread acts as an interrupt for the real time * clock. An alarm is set using the values from the * minutes and hour arrays. The current time of the * system is checked against the alarm. If they are * the same than dispense is set to 1 so the feeding * process may begin. arrCnt, originally set to 0, * is used as the index for the arrays and is incremented * after every alarm occurs. */ static PT_THREAD (protothread_realtime(struct pt *pt)) { PT_BEGIN(pt); while(1) { tmCurr.l = RtccGetTime(); if(numMeals > 0) { tmAlarmArr.sec = 0x00; tmAlarmArr.min = minutesArr[arrCnt]; tmAlarmArr.hour = hourArr[arrCnt]; RtccSetAlarmTime(tmAlarmArr.l); tmCurrAlarm.l = RtccGetAlarmTime(); RtccAlarmEnable(); if(tmCurr.l == tmCurrAlarm.l) { ++arrCnt; dispense = 1; } } PT_YIELD_TIME_msec(50); } PT_END(pt); } /* @param struct pt * @return no return * This thread is responsible for displaying the main screen * of the system. It includes three buttons: setting up the * feeding schedule, checking the current feeding schedule, * and resetting the real time clock. */ static PT_THREAD (protothread_mainscreen(struct pt *pt)) { PT_BEGIN(pt); while(1) { if(drawMain == 1) { //Draws title tft_setCursor(25, 10); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(3); sprintf(buffer, "Smart Pet Feeder"); tft_writeString(buffer); //Draws three buttons on screen tft_drawRect(25, 55, 270, 50, ILI9341_WHITE); tft_drawRect(25, 120, 270, 50, ILI9341_WHITE); tft_drawRect(25, 185, 270, 50, ILI9341_WHITE); tft_setCursor(30, 70); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Setup Feeding Schedule"); tft_writeString(buffer); tft_setCursor(80, 130); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Check Feeding"); tft_writeString(buffer); tft_setCursor(100, 150); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Schedule"); tft_writeString(buffer); tft_setCursor(50, 200); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Reset Current Time"); tft_writeString(buffer); //Touch Screen structure struct TSPoint p; p.x = 0; p.y = 0; p.z = 0; getPoint(&p); //Checks if button for setting schedule has been pressed if((p.x > 25) && (p.x < 280) && (p.y > 55) && (p.y < 105)) { drawMain = 0; drawScheduleOne = 1; tft_fillScreen(ILI9341_BLACK); PT_YIELD_TIME_msec(50); } //Checks if button for checking schedule has been pressed else if((p.x > 25) && (p.x < 280) && (p.y > 120) && (p.y < 170)) { drawMain = 0; drawFeedSched = 1; tft_fillScreen(ILI9341_BLACK); PT_YIELD_TIME_msec(50); } //Checks if button for resetting real time has been pressed else if((p.x > 25) && (p.x < 280) && (p.y > 185) && (p.y < 235)) { drawMain = 0; tft_fillScreen(ILI9341_BLACK); setRealTime(); PT_YIELD_TIME_msec(50); } PT_YIELD_TIME_msec(50); } PT_YIELD_TIME_msec(50); } PT_END(pt); } /* @param struct pt * @return no return * This thread is responsible for setting the number of meals * for each day and the weight of each meal. Buttons exist * for going back to the main screen or going to the next menu * after number of meals and weight have been set. */ static PT_THREAD (protothread_scheduleone(struct pt *pt)) { PT_BEGIN(pt); while(1) { if(drawScheduleOne == 1) { //Touch Screen structure struct TSPoint p; p.x = 0; p.y = 0; p.z = 0; tft_setCursor(0, 0); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Schedule"); tft_writeString(buffer); tft_setCursor(10, 50); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Number of Meals Per Day:"); tft_writeString(buffer); tft_setCursor(10, 100); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Weight of Each Meal (lbs):"); tft_writeString(buffer); tft_drawRect(90, 70, 100, 25, ILI9341_WHITE); tft_drawRect(90, 120, 100, 25, ILI9341_WHITE); tft_drawRect(0, 215, 55, 25, ILI9341_WHITE); tft_drawRect(260, 215, 55, 25, ILI9341_WHITE); tft_setCursor(135, 75); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "%d", numMeals); tft_writeString(buffer); tft_setCursor(110, 125); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "%1.3f", weight); tft_writeString(buffer); tft_setCursor(5, 220); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Home"); tft_writeString(buffer); tft_setCursor(265, 220); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Next"); tft_writeString(buffer); getPoint(&p); //Checks if button for going back to main screen //has been pressed if((p.x > 0) && (p.x < 60) && (p.y > 215) && (p.y < 240)) { drawScheduleOne = 0; drawSetMeals = 0; drawSetWeight = 0; drawMain = 1; tft_fillScreen(ILI9341_BLACK); } //Checks if button for going to next screen //has been pressed if((p.x > 260) && (p.x < 315) && (p.y > 215) && (p.y < 240)) { drawScheduleOne = 0; drawSetWeight = 0; drawSetMeals = 0; drawSetTime = 1; tft_fillScreen(ILI9341_BLACK); } //If number of meals has been pressed, drawSetMeals is set to //1 so number of meals can be set if((p.x > 90) && (p.x < 190) && (p.y > 70) && (p.y < 95)) { drawSetMeals = 1; } //If weight of meals has been pressed, drawSetWeight is set to //1 so weight of meals can be set if((p.x > 90) && (p.x < 190) && (p.y > 120) && (p.y < 145)) { drawSetWeight = 1; } } PT_YIELD_TIME_msec(50); } PT_END(pt); } /* @param struct pt * @return no return * This thread is responsible for setting the number of meals per day. * The thread draws a '+' and '-' button on the screen to set the * number of meals. */ static PT_THREAD (protothread_setmeals(struct pt *pt)) { PT_BEGIN(pt); while(1) { if(drawSetMeals == 1) { //Draws '+' and '-' buttons tft_fillRoundRect(145, 155, 50, 50, 0, ILI9341_WHITE); tft_fillRoundRect(165, 160, 10, 40, 0, ILI9341_BLACK); tft_fillRoundRect(150, 175, 40, 10, 0, ILI9341_BLACK); tft_fillRoundRect(85, 155, 50, 50, 0, ILI9341_WHITE); tft_fillRoundRect(90, 175, 40, 10, 0, ILI9341_BLACK); //Draws 'Done' button tft_drawRect(115, 215, 55, 25, ILI9341_WHITE); tft_setCursor(120, 220); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Done"); tft_writeString(buffer); //Touch Screen structure struct TSPoint p; p.x = 0; p.y = 0; p.z = 0; getPoint(&p); //Increases and draws the number of meals if((p.x > 145) && (p.x < 195) && (p.y > 155) && (p.y < 205)) { ++numMeals; tft_fillRoundRect(95, 75, 90, 15, 0, ILI9341_BLACK); tft_setCursor(135, 75); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "%d", numMeals); tft_writeString(buffer); PT_YIELD_TIME_msec(50); } //Decreases and draws the number of meals if((p.x > 85) && (p.x < 135) && (p.y > 155) && (p.y < 205)) { --numMeals; //If meals would go below 0 sets to if(numMeals == 255) { numMeals = 0; } tft_fillRoundRect(95, 75, 90, 15, 0, ILI9341_BLACK); tft_setCursor(135, 75); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "%d", numMeals); tft_writeString(buffer); PT_YIELD_TIME_msec(50); } //Ends thread when 'done' button is pressed if((p.x > 115) && (p.x < 170) && (p.y > 215) && (p.y < 270)) { tft_fillRoundRect(145, 155, 50, 50, 0, ILI9341_BLACK); tft_fillRoundRect(85, 155, 50, 50, 0, ILI9341_BLACK); tft_fillRoundRect(115, 215, 55, 25, 0, ILI9341_BLACK); drawSetMeals = 0; drawSetWeight = 0; PT_YIELD_TIME_msec(50); } PT_YIELD_TIME_msec(50); } PT_YIELD_TIME_msec(50); PT_END(pt); } } /* @param struct pt * @return no return * This thread is responsible for setting the weight of meals per day. * The thread draws a '+' and '-' button on the screen to set the * weight of meals. */ static PT_THREAD (protothread_setweight(struct pt *pt)) { PT_BEGIN(pt); while(1) { if(drawSetWeight == 1) { //Draws '+' and '-' buttons tft_fillRoundRect(145, 155, 50, 50, 0, ILI9341_WHITE); //145 tft_fillRoundRect(165, 160, 10, 40, 0, ILI9341_BLACK); //165 tft_fillRoundRect(150, 175, 40, 10, 0, ILI9341_BLACK); //150 tft_fillRoundRect(85, 155, 50, 50, 0, ILI9341_WHITE); //85 tft_fillRoundRect(90, 175, 40, 10, 0, ILI9341_BLACK); //90 //Draws 'Done' button tft_drawRect(115, 215, 55, 25, ILI9341_WHITE); tft_setCursor(120, 220); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Done"); tft_writeString(buffer); //Touch Screen structure struct TSPoint p; p.x = 0; p.y = 0; p.z = 0; getPoint(&p); //Increases and draws the weight of meals by 0.125 if((p.x > 145) && (p.x < 195) && (p.y > 155) && (p.y < 205)) { weight = weight + 0.125; //Sets max weight at one pound if(weight > 1) { weight = 1; } tft_fillRoundRect(95, 125, 90, 15, 0, ILI9341_BLACK); tft_setCursor(110, 125); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "%1.3f", weight); tft_writeString(buffer); PT_YIELD_TIME_msec(50); } //Decreases and draws the weight of meals by 0.125 if((p.x > 85) && (p.x < 135) && (p.y > 155) && (p.y < 205)) { weight = weight - 0.125; //Sets minimum weight as 0 if(weight < 0) { weight = 0; } tft_fillRoundRect(95, 125, 90, 15, 0, ILI9341_BLACK); tft_setCursor(110, 125); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "%1.3f", weight); tft_writeString(buffer); PT_YIELD_TIME_msec(50); } //Ends thread when 'done' button is pressed if((p.x > 115) && (p.x < 170) && (p.y > 215) && (p.y < 270)) { tft_fillRoundRect(145, 155, 50, 50, 0, ILI9341_BLACK); tft_fillRoundRect(85, 155, 50, 50, 0, ILI9341_BLACK); tft_fillRoundRect(115, 215, 55, 25, 0, ILI9341_BLACK); drawSetWeight = 0; drawSetMeals = 0; PT_YIELD_TIME_msec(50); } PT_YIELD_TIME_msec(50); } PT_YIELD_TIME_msec(50); PT_END(pt); } } /* @param struct pt * @return no return * This thread is responsible for displaying the current feeding schedule. * Displays how many meals, how much weight per meal, and at what times * the feeding will occur. */ static PT_THREAD (protothread_feedingschedule(struct pt *pt)) { PT_BEGIN(pt); int i; while(1) { if(drawFeedSched == 1) { //Draws meals and weight on screen. tft_setCursor(0, 0); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Number of Meals: %d", numMeals); tft_writeString(buffer); tft_setCursor(0, 30); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Meal Weight: %1.3f lbs", weight); tft_writeString(buffer); //Draws 'home' button tft_drawRect(115, 215, 55, 25, ILI9341_WHITE); tft_setCursor(120, 220); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Home"); tft_writeString(buffer); //Touch Screen structure struct TSPoint p; p.x = 0; p.y = 0; p.z = 0; getPoint(&p); //Draws the meal times on the screen by iterating through //meal hour and minutes array for(i = 0; i < numMeals; i++) { //If minutes is below 10, draws a 0 and then the minutes if(minutesArr[i] < 10) { tft_setCursor(0, (30*i) + 60); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Meal %d set for %d:%d%d", i + 1, hourArr[i], zero, minutesArr[i]); tft_writeString(buffer); } else { tft_setCursor(0, (30*i) + 60); tft_setTextColor(ILI9341_WHITE); tft_setTextSize(2); sprintf(buffer, "Meal %d set for %d:%d", i + 1, hourArr[i], minutesArr[i]); tft_writeString(buffer); } } //Goes back to main screen if 'home' button is pressed if((p.x > 115) && (p.x < 170) && (p.y > 215) && (p.y < 270)) { tft_fillRoundRect(145, 155, 50, 50, 0, ILI9341_BLACK); tft_fillRoundRect(85, 155, 50, 50, 0, ILI9341_BLACK); tft_fillRoundRect(115, 215, 55, 25, 0, ILI9341_BLACK); tft_fillScreen(ILI9341_BLACK); drawFeedSched = 0; drawMain = 1; PT_YIELD_TIME_msec(50); } PT_YIELD_TIME_msec(50); } PT_YIELD_TIME_msec(50); } PT_END(pt); } // === Main ====================================================== int main(void) { draw = 0; SYSTEMConfigPerformance(PBCLK); PT_setup(); configureADC(); //Clears interrupt flags mT3ClearIntFlag(); mT2ClearIntFlag(); //Initializes protothreads PT_INIT(&pt_settime); PT_INIT(&pt_mainscreen); PT_INIT(&pt_scheduleone); PT_INIT(&pt_setmeals); PT_INIT(&pt_setweight); PT_INIT(&pt_feedingschedule); PT_INIT(&pt_realtime); PT_INIT(&pt_weightcontrol); //Initializes screen tft_init_hw(); tft_begin(); tft_fillScreen(ILI9341_BLACK); tft_setRotation(3); // Use tft_setRotation(3) for 320x240 w/ top opposite' //Sets PPS output PPSOutput(1, RPB15, OC1); //Sets pin 5 as input mPORTBSetPinsDigitalIn(BIT_5); //Sets output capture for servo use OC1RS = 6500; CM1CON = 0; CM2CON = 0; ANSELA = 0; ANSELB = 0; TRISACLR = 1; //Initializes timers initTimers(); //Calls function for setting real time setRealTime(); //Enables multiple vectored interrupts INTEnableSystemMultiVectoredInt(); //Protothread schedule while (1){ PT_SCHEDULE(protothread_mainscreen(&pt_mainscreen)); PT_SCHEDULE(protothread_settime(&pt_settime)); PT_SCHEDULE(protothread_scheduleone(&pt_scheduleone)); PT_SCHEDULE(protothread_setmeals(&pt_setmeals)); PT_SCHEDULE(protothread_setweight(&pt_setweight)); PT_SCHEDULE(protothread_feedingschedule(&pt_feedingschedule)); PT_SCHEDULE(protothread_realtime(&pt_realtime)); } } // main // === end ======================================================