/* * Project: Automated Irrigation System * Author: Adam Ness and Christer Hoeflinger * * Target PIC: PIC32MX250F128B */ #include "plib.h" #include "xc.h" #include "adc_intf.h" #include "TouchScreen.h" // graphics libraries #include "config.h" #include "tft_master.h" #include "tft_gfx.h" // need for rand function #include #include // threading library // config.h sets 40 MHz #define SYS_FREQ 40000000 #include "pt_cornell_1_2.h" /*========================Configuration Parameters============================== * The following parameters are used to alter the configuration of the watering * system. Below are descriptions of values. * * Note: If placed in test setup, RTCC will interrupt every 10sec. * If placed in real-time setup, RTCC will interrupt every 10 minutes. * * Typical values (for each configuration) are listed right of param.'s */ // System Configuration #define SYS_CONFIG 0 // 0 for test setup, 1 for real-time // Real Time Clock Information (in 24Hr format) // Change each of these values to current values when building. #define COMP_HR 0x10 // Compile Hour #define COMP_MIN 0x00 // Compile Minute #define COMP_SEC 0x00 // Compile Second #define COMP_DAY 0x09 // Compile Day of month #define COMP_MTH 0x12 // Compile Month #define COMP_YR 0x16 // Compile Year #define FREQ_UPDTE 1 // Update the temp. every X RTCC interrupts [Test: 1 (10 sec), Real: 6 (1 hr)] #define WAIT_TMOUT 12 // Wait X RTCC interrupts for ideal temp. [Test: 12 (2 min), Real: 108 (18 hrs)] #define WAIT_TIME 6 // Wait X RTCC interrupts for soil to saturate [Test: 6 (1 min), Real: 6 (1 hr)] #define WATER_TGT 100 // Default water target (in ml) [Test: 6 (1 min), Real: 6 (1 hr)] #define MOISTURE_TGT 700 // Default moisture target #define MAX_TEMP 27 // Max. temp. threshold to dispense water (in C) [80.6 F] #define MIN_TEMP 7 // Min. temp. threshold to dispense water [44.6 F] #define FAIL_SFE 6 // Wait X RTCC interrupts for auto shutoff of valve. [Test: 6 (1 min), Real: 6 (1 hr)] /*============================================================================== */ // Time Information rtccTime tme; // Current time rtccTime lastWaterTm; // Last watering time rtccDate dte; // Current date uint32_t tempUpdate = 0; // Counter for determining temp. update freq. uint32_t waitTimeoutCnt = 0; // Counter to determine if wait timeout has occurred uint32_t waitSat; // Counter used to wait for soil saturation check uint32_t oldMinute = 61; // Used to prevent screen flicker uint32_t oldLastMinute = 61; // Used to prevent screen flicker uint32_t oldDay = 32; // Used to prevent screen flicker // Water Dispensing Information uint32_t oldWaterAmt = 100; // Used to prevent screen flicker uint32_t waterAmtArray[5]; // Records watering amounts to alert user to refill tank uint32_t currWaterAmt; // How much water we have currently dispensed (30.2 FP) uint32_t prevWaterAmt; // How much water we dispensed in our previous watering session uint32_t waterAmtTgt = WATER_TGT; // Target water amount (to dispense) uint32_t oldWaterAmtTgt = 0; uint32_t failSafeCnt = FAIL_SFE; // Fail safe for closing valve. // Temperature Information (addresses when ADD0 is tied to GND) #define I2C_PERIF I2C1 #define TMP_RD 0x91 #define TMP_WR 0x90 #define TMP_REG 0x00 uint16_t temperature[6]; // Temperature values for trending temp uint8_t trend; // Number of cooling sample intervals uint8_t cooling; // 1 if trending cool/constant, 0 if increasing in temp uint16_t currTemp; // Current ambient temperature uint16_t oldTemp = 151; // Used to prevent screen flicker #define TESTUPDATE 20000000 // Updates water levels during testing #define UPDATE 3600000000 // Updates water level during usage // String buffers used for printing on display char stateName[60]; char timeBuffer[60]; char lastTimeBuffer[60]; char dayBuffer[60]; char tempBuffer[60]; char waterTgtBuffer[60]; char moistureBuffer[60]; char moistureTgtBuffer[60]; char flowBuffer[60]; char flowTgtBuffer[60]; char lastWaterAmtBuffer[60]; char lastWaterTimeBuffer[60]; char startUpBuffer[60]; char timeOutBuffer[60]; #define moisture 9 // Moisture Sensor Readings from AN9 #define XM AN0 // Variables used in screen configuration #define YP AN1 #define RADIUS 22 // Radius used to determine button press #define OVERLAP RADIUS*RADIUS // Used in overlap detection //Used for debouncing purposes #define NOPUSH 0 #define MAYBEPUSH 1 #define PUSHED 2 #define MAYBENOPUSH 3 int pushState = NOPUSH; int screenPressed; /* * Tested in the lab (Adam 11/19), values listed as decimal uint32_t returns: * Open air: 11 * Submerged in water: 75 * Watered plant on 4th floor: 30 * * Second test: * * Open air: 12 * Submerged: 24 * Watered plant on 4th floor: 20 * * Third test (12/2): * * Open air: 24 * Submerged: 820 * Watered plant on the 4th floor: 600 * * The large variability in the sensor readings from day to day is making the implementation of the algorithm difficult for normal situations. */ #define MOIST_MAX 820 #define MOIST_MIN 24 uint32_t moistureValue = 0; // Value of ADC'd value from moisture sensor uint32_t oldMoistureValue = 1000; // Used to prevent the screen from flicker uint32_t realOldMoistureValue = 0; uint32_t moistureValueTgt = MOISTURE_TGT; // Target value of moisture sensor uint32_t oldMoistureValueTgt = 0; // Used to prevent screen from flicker uint32_t valveState = 0; uint32_t oldValveState = 10; // Contains x,y,z values of debounced finger press struct TSPoint press; // Function used to translate read values to drawn values // Untranslated boundaries: (690,990) Translate to: (0,320) uint32_t returnX(uint32_t orig_x) { uint32_t conv_x = 0; conv_x = (((orig_x - 690) * 0x00111114) >> 20); if (conv_x >= 319) return 319; else if (conv_x <= 0) return 0; else return conv_x; } // Function used to translate read values to drawn values // Untranslated boundaries: (190,850) Translate to: (0,240) uint32_t returnY(uint32_t orig_y) { uint32_t conv_y = 0; conv_y = (((orig_y - 190) * 0x0005D173) >> 20); if (conv_y >= 239) return 0; else if (conv_y <= 0) return 239; else return 239 - conv_y; } //Function used to determine if press overlaps a button uint32_t checkTouchOverlap(uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2) { uint32_t xdiff = x2 - x1; uint32_t ydiff = y2 - y1; uint32_t overlap_calc = (xdiff * xdiff) + (ydiff * ydiff); if (overlap_calc > OVERLAP) { return 0; } else { return 1; } } /*===Debounce button inputs============================================ * This method takes a user keypress and debounces it to ensure the action * only occurs/registers once. * * Source: Craig Lombardo * Modified by: Adam Ness * * Inputs: (x1, y1) - Press location, (x2, y2) - Circle to be checked */ static void checkDebouncedPress(int x1, int y1, int z1, int x2, int y2) { switch (pushState) { case NOPUSH: if (screenPressed) pushState = MAYBEPUSH; else pushState = NOPUSH; break; case MAYBEPUSH: if (screenPressed) { if(checkTouchOverlap(returnX(y1), returnY(x1), x2, y2)) { press.x = returnX(y1); press.y = returnY(x1); press.z = z1; } else { press.x = 0; press.y = 0; press.z = 0; } pushState = PUSHED; } else pushState = NOPUSH; break; case PUSHED: if (screenPressed) pushState = PUSHED; else pushState = MAYBENOPUSH; break; case MAYBENOPUSH: if (screenPressed) pushState = PUSHED; else pushState = NOPUSH; break; } } void initInput(void) { PPSInput(1, INT4, RPB3); mPORTBSetPinsDigitalIn(BIT_7); ConfigINT4(EXT_INT_ENABLE | RISING_EDGE_INT | EXT_INT_PRI_3); } void initTimer(void) { if(SYS_CONFIG == 1) OpenTimer23(T23_ON | T23_PS_1_2 | T23_32BIT_MODE_ON | T23_SOURCE_INT, UPDATE); else if(SYS_CONFIG == 0) OpenTimer23(T23_ON | T23_PS_1_2 | T23_32BIT_MODE_ON | T23_SOURCE_INT, TESTUPDATE); ConfigIntTimer23(T23_INT_ON | T23_INT_PRIOR_4); } void initClock(void) { tme.hour = COMP_HR; tme.min = COMP_MIN; tme.sec = COMP_SEC; dte.mday = COMP_DAY; dte.mon = COMP_MTH; dte.year = COMP_YR; RtccInit(); RtccSetTimeDate(tme.l, dte.l); //RtccSelectPulseOutput(1); // select the seconds clock pulse as the function of the RTCC output pin //RtccOutputEnable(1); // enable the Output pin of the RTCC (PIN 7 on 28 pin PDIP) RtccAlarmEnable(); RtccChimeEnable(); RtccSetAlarmTime(tme.l); if(SYS_CONFIG == 1) RtccSetAlarmRpt(RTCC_RPT_TEN_MIN); else if(SYS_CONFIG == 0) RtccSetAlarmRpt(RTCC_RPT_TEN_SEC); INTEnable(INT_RTCC, INT_ENABLED); INTSetVectorPriority(INT_RTCC_VECTOR, INT_PRIORITY_LEVEL_2); INTSetVectorSubPriority(INT_RTCC_VECTOR, INT_SUB_PRIORITY_LEVEL_0); RtccEnable(); } uint16_t readTemp(void) { uint8_t msb; uint8_t lsb; uint16_t temp; StartI2C1(); IdleI2C1(); MasterWriteI2C1(TMP_WR); // Let sensor know it is being queried IdleI2C1(); MasterWriteI2C1(TMP_REG); // Let sensor know we want temp. info. IdleI2C1(); StopI2C1(); IdleI2C1(); StartI2C1(); IdleI2C1(); MasterWriteI2C1(TMP_RD); // Tell sensor to give us temp. info. IdleI2C1(); msb = MasterReadI2C1(); // Read first byte of temp. IdleI2C1(); AckI2C1(); // We got it (only 1 item on bus) IdleI2C1(); lsb = MasterReadI2C1(); // Read second byte of temp. IdleI2C1(); AckI2C1(); // We got it (same reason as before) IdleI2C1(); StopI2C1(); IdleI2C1(); temp = (msb << 8) | lsb; // Shift and store temp >>= 4; //The TMP102 temperature registers are left justified, correctly right justify them return temp * 0.0625; // Translate to Celsius } void updateTemps(void) { // Shift temperature values, due to new sample trend = 0; temperature[5] = temperature[4]; temperature[4] = temperature[3]; temperature[3] = temperature[2]; temperature[2] = temperature[1]; temperature[1] = temperature[0]; temperature[0] = currTemp; // Calculate number of decreasing temperature intervals if((temperature[4] - temperature[5]) < 0) trend++; if((temperature[3] - temperature[4]) < 0) trend++; if((temperature[2] - temperature[3]) < 0) trend++; if((temperature[1] - temperature[2]) < 0) trend++; if((temperature[0] - temperature[1]) < 0) trend++; // Do we have a majority of cooling intervals? if(trend >= 3) { cooling = 1; }else { cooling = 0; } } void updateWater(void) { // Shift watering amounts, due to new sample waterAmtArray[4] = waterAmtArray[3]; waterAmtArray[3] = waterAmtArray[2]; waterAmtArray[2] = waterAmtArray[1]; waterAmtArray[1] = waterAmtArray[0]; waterAmtArray[0] = currWaterAmt; } void clearWater(void) { // Clear array to make sure no values equal each other waterAmtArray[4] = 50; waterAmtArray[3] = 40; waterAmtArray[2] = 30; waterAmtArray[1] = 20; waterAmtArray[0] = 10; } void __ISR(_RTCC_VECTOR, ipl2soft) RTCCInt(void) { //mPORTBToggleBits(BIT_7); // Uncomment to test accuracy of RTCC clock if(tempUpdate % FREQ_UPDTE == 0) updateTemps(); // Check the temperature every FREQ_UPDTE interrupts if(valveState == 0) { // If in CLOSED state of Valve FSM, check the soil moisture. mPORTBSetBits(BIT_13); moistureValue = analogRead(moisture); mPORTBClearBits(BIT_13); realOldMoistureValue = moistureValue; } else moistureValue = 0; if(valveState == 1) waitTimeoutCnt++; // If in WAIT state of the Valve FSM, increment timeout counter. else waitTimeoutCnt = 0; // Since not in the WAIT state, reset counter. if(valveState == 2) failSafeCnt++; // If in the actively watering state, keep track of how long the valve has been open. else failSafeCnt = 0; if(valveState == 3) waitSat++; // If in the COMPARE state of Valve FSM, increment counter. else waitSat = 0; // Since not in the COMPARE state, reset counter. currTemp = readTemp(); tempUpdate++; mRTCCClearIntFlag(); } void __ISR(_EXTERNAL_4_VECTOR , ipl3soft) INT4_Handler(void) { currWaterAmt += 0x00000009; // Accumulate 2.25 (30.2 FP) mINT4ClearIntFlag(); } void __ISR(_TIMER_23_VECTOR, ipl4soft) T23Int(void) { updateWater(); mT23ClearIntFlag(); } // === thread structures ============================================ // thread control structs // note that UART input and output are threads static struct pt pt_display, pt_watercntl; static PT_THREAD(protothread_display(struct pt *pt)) { PT_BEGIN(pt); static int currentScreen; static int lastScreen = 2; while (1) { PT_YIELD_TIME_msec(1); struct TSPoint p; p.x = 0; p.y = 0; p.z = 0; getPoint(&p); press.x = 0; press.y = 0; press.z = 0; screenPressed = (p.z > 80 && p.z < 500); checkDebouncedPress(p.x, p.y, p.z, 270, 40); if(valveState == 2) currentScreen = 1; else if(valveState == 3) currentScreen = 0; else if(valveState == 4) currentScreen = 0; tft_setTextSize(2); tft_setCursor(0,10); tme.l = RtccGetTime(); dte.l = RtccGetDate(); if(oldMinute != tme.min){ tft_setTextColor(ILI9341_BLACK); tft_writeString(timeBuffer); sprintf(timeBuffer, "Time: %02x:%02x", tme.hour, tme.min); tft_setTextColor(ILI9341_WHITE); tft_setCursor(0,10); tft_writeString(timeBuffer); } oldMinute = tme.min; tft_setCursor(0,30); if(oldDay != dte.mday) { tft_setTextColor(ILI9341_BLACK); tft_writeString(dayBuffer); sprintf(dayBuffer, "Date: %x/%x/%x", dte.mday, dte.mon, dte.year); tft_setTextColor(ILI9341_WHITE); tft_setCursor(0,30); tft_writeString(dayBuffer); } oldDay = dte.mday; tft_setTextSize(1); tft_setCursor(0,60); if(oldTemp != currTemp) { tft_setTextColor(ILI9341_BLACK); tft_writeString(tempBuffer); sprintf(tempBuffer, "Current Ambient Temperature: %d", currTemp); tft_setTextColor(ILI9341_WHITE); tft_setCursor(0,60); tft_writeString(tempBuffer); } oldTemp = currTemp; tft_setCursor(0,70); if(oldMoistureValue != moistureValue) { tft_setTextColor(ILI9341_BLACK); tft_writeString(moistureBuffer); sprintf(moistureBuffer, "Current Moisture Level: %d", realOldMoistureValue); tft_setTextColor(ILI9341_WHITE); tft_setCursor(0,70); tft_writeString(moistureBuffer); } oldMoistureValue = moistureValue; tft_setCursor(0,80); if(oldMoistureValueTgt != moistureValueTgt) { tft_setTextColor(ILI9341_BLACK); tft_writeString(moistureTgtBuffer); sprintf(moistureTgtBuffer, "Target Moisture Level: %d", moistureValueTgt); tft_setTextColor(ILI9341_WHITE); tft_setCursor(0,80); tft_writeString(moistureTgtBuffer); } oldMoistureValueTgt = moistureValueTgt; tft_setCursor(0,100); if(oldValveState != valveState) { tft_setTextColor(ILI9341_BLACK); tft_writeString(stateName); if(valveState == 0){ sprintf(stateName, "Valve is closed. Monitoring soil moisture."); } else if(valveState == 1){ sprintf(stateName, "Searching for optimal watering time."); } else if(valveState == 2){ sprintf(stateName, "Actively watering."); } else if(valveState == 3){ sprintf(stateName, "Recording most recent water session."); } else { sprintf(stateName, "Comparing most recent water session values."); } tft_setTextColor(ILI9341_WHITE); tft_setCursor(0,100); tft_writeString(stateName); } oldValveState = valveState; tft_setCursor(0,120); if(oldWaterAmtTgt != waterAmtTgt) { tft_setTextColor(ILI9341_BLACK); tft_writeString(flowTgtBuffer); sprintf(flowTgtBuffer, "Target Amount of Water to be used (mL): %d", waterAmtTgt); tft_setTextColor(ILI9341_WHITE); tft_setCursor(0,120); tft_writeString(flowTgtBuffer); } oldWaterAmtTgt = waterAmtTgt; tft_setCursor(0,130); if(oldWaterAmt != currWaterAmt) { tft_setTextColor(ILI9341_BLACK); tft_writeString(flowBuffer); sprintf(flowBuffer, "Amount of Water Used (mL): %d", (currWaterAmt >> 2)); tft_setTextColor(ILI9341_WHITE); tft_setCursor(0,130); tft_writeString(flowBuffer); } //oldWaterAmt = currWaterAmt; if(valveState == 2) { oldWaterAmt = currWaterAmt; } else { currWaterAmt = oldWaterAmt; } tft_setCursor(0,140); if(oldLastMinute != lastWaterTm.min) { tft_setTextColor(ILI9341_BLACK); tft_writeString(lastTimeBuffer); sprintf(lastTimeBuffer, "Last Watering Time: %02x:%02x", lastWaterTm.hour, lastWaterTm.min); tft_setTextColor(ILI9341_WHITE); tft_setCursor(0, 140); tft_writeString(lastTimeBuffer); } oldLastMinute = lastWaterTm.min; switch (currentScreen) { case 0: tft_fillCircle(270, 50, RADIUS, ILI9341_WHITE); tft_fillCircle(30, 200, RADIUS, ILI9341_RED); tft_fillCircle(150, 200, RADIUS - 1, ILI9341_BLACK); tft_fillCircle(270, 200, RADIUS - 1, ILI9341_BLACK); lastScreen = currentScreen; if (press.z > 80 && press.z < 500) { // Button was pressed, manually begin watering // Will still only dispense the target amount currWaterAmt = 0; valveState = 2; currentScreen = 1; } else currentScreen = 0; break; case 1: tft_fillCircle(270, 50, RADIUS - 1, ILI9341_BLACK); tft_fillCircle(30, 200, RADIUS - 1, ILI9341_BLACK); if((waterAmtArray[2] == waterAmtArray[1]) && (waterAmtArray[1] == waterAmtArray[0])) { tft_fillCircle(270, 200, RADIUS, ILI9341_BLUE); tft_fillCircle(150, 200, RADIUS - 1, ILI9341_BLACK); } else { tft_fillCircle(150, 200, RADIUS, ILI9341_GREEN); tft_fillCircle(270, 200, RADIUS - 1, ILI9341_BLACK); } if (press.z > 80 && press.z < 500) { // Button was pressed, end manual watering valveState = 3; currentScreen = 0; lastWaterTm = tme; } else currentScreen = 1; break; default: // Start Screen currentScreen = 0; break; } // NEVER exit while } // END WHILE(1) PT_END(pt); } static PT_THREAD(protothread_watercntl(struct pt *pt)) { PT_BEGIN(pt); static float error; while (1) { switch (valveState) { case 0: // CLOSED clearWater(); // Transition to WAIT when upper moisture. threshold reached if((moistureValue < (moistureValueTgt - 100)) && (moistureValue > 0)) { waitTimeoutCnt = 0; valveState = 1; }else { valveState = 0; } break; case 1: // WAIT // Waiting here while looking for optimal temp., // or time limit has been reached. if(SYS_CONFIG == 1 && (moistureValue < (moistureValueTgt - 300)) && (temperature[0] > MIN_TEMP)) valveState = 2; else if((temperature[0] > MIN_TEMP) && // Make sure its above min. temp. or else we could have issues (((temperature[0] < MAX_TEMP) && cooling == 1) || (waitTimeoutCnt > WAIT_TMOUT))) { // Then look for cooling, or timeout currWaterAmt = 0; valveState = 2; }else { valveState = 1; } break; case 2: // OPEN // Actively watering mPORTBSetBits(BIT_5); // Open the valve if(failSafeCnt >= FAIL_SFE) { // Failsafe activated valveState = 3; lastWaterTm = tme; } else if((currWaterAmt >> 2) > waterAmtTgt) { // We have hit target dispense val. valveState = 3; prevWaterAmt = (currWaterAmt >> 2); lastWaterTm = tme; break; }else { valveState = 2; } break; case 3: // RECORD // Stop watering, record the watering statistics clearWater(); mPORTBClearBits(BIT_5); // Close the valve if(waitSat > WAIT_TIME) { // We have waited saturation period valveState = 4; }else { valveState = 3; } break; case 4: // COMPARE mPORTBSetBits(BIT_13); // Check the moisture moistureValue = analogRead(moisture); mPORTBClearBits(BIT_13); realOldMoistureValue = moistureValue; // +/-50 is so that a "window" of moisture levels are acceptable if(moistureValue > moistureValueTgt + 50){ // We put out too much water, cut it back error = moistureValue - moistureValueTgt; // Will be + error /= (MOIST_MAX - MOIST_MIN); // Normalize error error = 1 - error; waterAmtTgt = (uint32_t)(waterAmtTgt * error); if(waterAmtTgt <= 0) waterAmtTgt = 0; // If we calculate a negative value, just make it equal to zero. }else if(moistureValue < moistureValueTgt - 50) { // We need a little more water, increase for next time error = moistureValueTgt - moistureValue; // Will be + error /= (MOIST_MAX - MOIST_MIN); // Normalize error error = 1 + error; waterAmtTgt = (uint32_t)(waterAmtTgt * error); }else { // Do nothing, we watered "perfectly" } moistureValue = 0; // Clear value after making determination valveState = 0; // After updating break; default: // Go back to CLOSED state valveState = 0; break; } PT_YIELD_TIME_msec(1); // NEVER exit while } // END WHILE(1) PT_END(pt); } // === Main ====================================================== void main(void) { SYSTEMConfigPerformance(PBCLK); configureADC(); initInput(); initClock(); initTimer(); mPORTBSetPinsAnalogIn(BIT_3); // C1INA (Comparator +) mPORTBSetPinsDigitalOut(BIT_5 | BIT_13); mPORTBClearBits(BIT_5); mPORTBSetBits(BIT_13); // Check the moisture moistureValue = analogRead(moisture); mPORTBClearBits(BIT_13); I2CSetFrequency(I2C_PERIF, PBCLK, 100000); I2CEnable(I2C_PERIF, TRUE); CNPUB = (BIT_8 | BIT_9); // === config threads ========== // turns OFF UART support and debugger pin PT_setup(); // === setup system wide interrupts ======== INTEnableSystemMultiVectoredInt(); // init the threads PT_INIT(&pt_display); PT_INIT(&pt_watercntl); // init the display tft_init_hw(); tft_begin(); tft_fillScreen(ILI9341_BLACK); //240x320 vertical display tft_setRotation(1); // Use tft_setRotation(1) for 320x240 tft_fillCircle(30, 200, RADIUS, ILI9341_RED); tft_fillCircle(150, 200, RADIUS, ILI9341_GREEN); tft_fillCircle(270, 200, RADIUS, ILI9341_BLUE); tft_setTextSize(1); tft_setTextColor(ILI9341_WHITE); sprintf(startUpBuffer, "Valve Closed"); tft_setCursor(0,165); tft_writeString(startUpBuffer); sprintf(startUpBuffer, "Valve Open"); tft_setCursor(120,165); tft_writeString(startUpBuffer); sprintf(startUpBuffer, "Add Water"); tft_setCursor(245,165); tft_writeString(startUpBuffer); sprintf(startUpBuffer, "Toggle Valve"); tft_setCursor(235,12); tft_writeString(startUpBuffer); // round-robin scheduler for threads while (1){ PT_SCHEDULE(protothread_display(&pt_display)); PT_SCHEDULE(protothread_watercntl(&pt_watercntl)); } } // main // === end ======================================================