#include #include #include #include "SPI.h" #include "Adafruit_GFX.h" #include "Adafruit_ILI9341.h" #include #include #include // Various sensors used for calibration data reads #include #include #include "Adafruit_seesaw.h" Adafruit_seesaw sensor_soil_adafruit; I2CSoilMoistureSensor sensor_soil_catnip; // Various tunables #define ERROR_LED_PIN 13 #define ERROR_LED_LIGHTUP_STATE HIGH #define PIXEL_PIN 40 // pin of pixel #define PIXEL_MAX_BRIGHTNESS 48 // 255 max #define PIXEL_MIN_BRIGHTNESS 4 // 0 min #define BREATHE_DELAY 5 // milliseconds #define BUTTON_UP 6 // MUST be an interrupt pin #define BUTTON_DOWN 5 // MUST be an interrupt pin #define BUTTON_OK 7 // MUST be an interrupt pin // TFT pins #define TFT_DC 9 #define TFT_CS 10 #define TFT_MOSI 13 #define TFT_CLK 12 #define TFT_RST 8 #define TFT_MISO 11 // Init hardware used in tasks Adafruit_NeoPixel pixel(1, PIXEL_PIN, NEO_GRB + NEO_KHZ800); // Adafruit 2.2 TFT -- 320x240 resolution, up to 18-bit (262,144) color // Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC //Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC); // If using the breakout, change pins as desired Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI, TFT_CLK, TFT_RST, TFT_MISO); // Button Semaphores SemaphoreHandle_t sem_btn_up; SemaphoreHandle_t sem_btn_down; SemaphoreHandle_t sem_btn_ok; // define two tasks for Blink & AnalogRead void TaskBlink( void *pvParameters ); void InterruptHandlerButtonUp(); void InterruptHandlerButtonDown(); void InterruptHandlerButtonOk(); void TaskLCD(void *pvParameters); // UI screens void screenClear(); void screenI2CDIP(bool readValue); void screenSoilStemma(bool readValue); void screenSoilCatnip(bool readValue); void screenWaterLevelETape(bool readValue); void screenWaterLevelETapeI2C(bool readValue); // Color conversion (RGB888 -> RGB565 used by Adafruit GFX) uint16_t RGB565(uint8_t r, uint8_t g, uint8_t b) { return ((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | (b >> 3); } // the setup function runs once when you press reset or power the board void setup() { vNopDelayMS(1000); // prevents usb driver crash on startup, do not omit this // Set the led the rtos will blink when we have a fatal rtos error // RTOS also Needs to know if high/low is the state that turns on the led. // Error Blink Codes: // 3 blinks - Fatal Rtos Error, something bad happened. Think really hard about what you just changed. // 2 blinks - Malloc Failed, Happens when you couldnt create a rtos object. // Probably ran out of heap. // 1 blink - Stack overflow, Task needs more bytes defined for its stack! // Use the taskMonitor thread to help gauge how much more you need vSetErrorLed(ERROR_LED_PIN, ERROR_LED_LIGHTUP_STATE); // initialize serial communication at 9600 bits per second: //Serial.begin(9600); //while (!Serial) { // ; // wait for serial port to connect. Needed for native USB, on LEONARDO, MICRO, YUN, and other 32u4 based boards. //} Wire.begin(); pinMode(A0, INPUT); pixel.begin(); pixel.clear(); pixel.setBrightness(PIXEL_MAX_BRIGHTNESS); pixel.setPixelColor(0, pixel.Color(0, 0, 255)); pixel.show(); tft.begin(); tft.setRotation(3); screenClear(); tft.setFont(&FreeMonoBold18pt7b); tft.setCursor(tft.width() / 2 - 138, tft.height() / 2 + 10); tft.println("Press Up/Down"); pinMode(BUTTON_UP, INPUT_PULLUP); pinMode(BUTTON_DOWN, INPUT_PULLUP); pinMode(BUTTON_OK, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(BUTTON_UP), InterruptHandlerButtonUp, RISING); attachInterrupt(digitalPinToInterrupt(BUTTON_DOWN), InterruptHandlerButtonDown, RISING); attachInterrupt(digitalPinToInterrupt(BUTTON_OK), InterruptHandlerButtonOk, RISING); sem_btn_up = xSemaphoreCreateBinary(); sem_btn_down = xSemaphoreCreateBinary(); sem_btn_ok = xSemaphoreCreateBinary(); // Now set up two tasks to run independently. xTaskCreate( TaskBlink , (const portCHAR *)"Blink" // A name just for humans , 128 // This stack size can be checked & adjusted by reading the Stack Highwater , NULL , 0 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest. , NULL ); xTaskCreate( TaskLCD , (const portCHAR *)"LCD" // A name just for humans , 128 // This stack size can be checked & adjusted by reading the Stack Highwater , NULL , 2 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest. , NULL ); // Now the task scheduler, which takes over control of scheduling individual tasks, is automatically started. vTaskStartScheduler(); } void loop() { // Empty. Things are done in Tasks. } /*--------------------------------------------------*/ /*---------------------- Tasks ---------------------*/ /*--------------------------------------------------*/ void TaskBlink(void *pvParameters) { (void) pvParameters; while (1) { pixel.setPixelColor(0, pixel.Color(0, 255, 0)); // Pulse heartbeat green -- reset in case another task interrupts w/ a different color for (int i = PIXEL_MIN_BRIGHTNESS ; i < PIXEL_MAX_BRIGHTNESS ; i++) { pixel.setBrightness(i); pixel.show(); vTaskDelay( 50 / portTICK_PERIOD_MS ); } for (int i = PIXEL_MAX_BRIGHTNESS ; i > PIXEL_MIN_BRIGHTNESS ; i--) { pixel.setBrightness(i); pixel.show(); vTaskDelay( 50 / portTICK_PERIOD_MS ); } } } void TaskLCD(void *pvParameters) { (void) pvParameters; void (*screens[5])(bool) = {screenI2CDIP, screenSoilStemma, screenSoilCatnip, screenWaterLevelETape, screenWaterLevelETapeI2C}; int current = 0; while (1) { if (xSemaphoreTake(sem_btn_up, 50 / portTICK_PERIOD_MS) == pdPASS) { screenClear(); tft.fillRect(tft.width() - 50, 0, 50, 20, RGB565(0, 0, 0)); tft.setFont(&FreeMonoBold9pt7b); tft.setCursor(tft.width() - 50, 15); tft.print("Up"); current++; if (current >= sizeof(screens) / sizeof(screens[0])) { current = 0; } screens[current](false); } if (xSemaphoreTake(sem_btn_down, 50 / portTICK_PERIOD_MS) == pdPASS) { screenClear(); tft.fillRect(tft.width() - 50, 0, 50, 20, RGB565(0, 0, 0)); tft.setFont(&FreeMonoBold9pt7b); tft.setCursor(tft.width() - 50, 15); tft.print("Down"); current--; if (current < 0) { current = sizeof(screens) / sizeof(screens[0]) - 1; } screens[current](false); } if (xSemaphoreTake(sem_btn_ok, 50 / portTICK_PERIOD_MS) == pdPASS) { tft.fillRect(tft.width() - 50, 0, 50, 20, RGB565(0, 0, 0)); tft.setFont(&FreeMonoBold9pt7b); tft.setCursor(tft.width() - 50, 15); tft.print("OK"); screens[current](true); } } } void InterruptHandlerButtonUp() { xSemaphoreGiveFromISR(sem_btn_up, NULL); } void InterruptHandlerButtonDown() { xSemaphoreGiveFromISR(sem_btn_down, NULL); } void InterruptHandlerButtonOk() { xSemaphoreGiveFromISR(sem_btn_ok, NULL); } void screenClear() { tft.setCursor(0, 0); tft.fillScreen(RGB565(0, 0, 0)); } void pixelDataReadAlert() { pixel.setPixelColor(0, pixel.Color(0, 0, 255)); pixel.setBrightness(PIXEL_MAX_BRIGHTNESS); pixel.show(); } void screenI2CDIP(bool readValue) { byte dip_1 = 0; byte dip_2 = 0; if (readValue) { taskENTER_CRITICAL(); pixelDataReadAlert(); screenClear(); Wire.requestFrom(0x65, 2); // Wait for data to become available -- can be a delayed response due to # of pins to enumerate while (!Wire.available()); dip_1 = Wire.read(); dip_2 = Wire.read(); taskEXIT_CRITICAL(); } tft.setFont(&FreeMonoBold18pt7b); tft.setCursor(0, 25); tft.println("DIP"); tft.setFont(&FreeMono12pt7b); tft.println("i2c DIP switch"); tft.println("16 pin"); tft.println("i2c address: 0x65"); tft.println(""); tft.setFont(&FreeMonoBold9pt7b); tft.println("--Press OK To Read Value--"); tft.println(""); tft.print("DIP 1: "); tft.println(dip_1, BIN); tft.print("DIP 2: "); tft.println(dip_2, BIN); } void screenSoilStemma(bool readValue) { float tempC = 0; uint16_t capread = 0; if (readValue) { taskENTER_CRITICAL(); pixelDataReadAlert(); screenClear(); sensor_soil_adafruit.begin(0x36); tempC = sensor_soil_adafruit.getTemp(); capread = sensor_soil_adafruit.touchRead(0); taskEXIT_CRITICAL(); } tft.setFont(&FreeMonoBold18pt7b); tft.setCursor(0, 25); tft.println("Soil"); tft.setFont(&FreeMono12pt7b); tft.println("Adafruit STEMMA"); tft.println("Soil Sensor"); tft.println("i2c address: 0x36"); tft.println("seesaw address: 0x36"); tft.println(""); tft.setFont(&FreeMonoBold9pt7b); tft.println("--Press OK To Read Value--"); tft.println(""); tft.print("Temperature: "); tft.print(tempC); tft.println("*C"); tft.print("Capacitive: "); tft.println(capread); } void screenSoilCatnip(bool readValue) { unsigned int capacitance = 0; float temp_c = 0; unsigned int light = 0; if (readValue) { taskENTER_CRITICAL(); pixelDataReadAlert(); screenClear(); sensor_soil_catnip.begin(); while (sensor_soil_catnip.isBusy()) delay(50); // available since FW 2.3 capacitance = sensor_soil_catnip.getCapacitance(); temp_c = sensor_soil_catnip.getTemperature() / (float)10; light = sensor_soil_catnip.getLight(true); taskEXIT_CRITICAL(); } tft.setFont(&FreeMonoBold18pt7b); tft.setCursor(0, 25); tft.println("Soil"); tft.setFont(&FreeMono12pt7b); tft.println("Catnip Soil Sensor"); tft.println("i2c address: 0x20"); tft.println(""); tft.setFont(&FreeMonoBold9pt7b); tft.println("--Press OK To Read Value--"); tft.println(""); tft.print("Capacitance: "); tft.println(capacitance); tft.print("Temperature: "); tft.print(temp_c); tft.println("*C"); tft.print("Light: "); tft.println(light); } void screenWaterLevelETape(bool readValue) { float value = 0; if (readValue) { taskENTER_CRITICAL(); pixelDataReadAlert(); screenClear(); value = analogRead(A0); taskEXIT_CRITICAL(); } tft.setFont(&FreeMonoBold18pt7b); tft.setCursor(0, 25); tft.println("Water Level"); tft.setFont(&FreeMono12pt7b); tft.println("Milone eTape"); tft.println("Analog Pin #0"); tft.println(""); tft.setFont(&FreeMonoBold9pt7b); tft.println("--Press OK To Read Value--"); tft.println(""); tft.print("Value: "); tft.print(value); } void screenWaterLevelETapeI2C(bool readValue) { if (readValue) { taskENTER_CRITICAL(); pixelDataReadAlert(); screenClear(); taskEXIT_CRITICAL(); } tft.setFont(&FreeMonoBold18pt7b); tft.setCursor(0, 25); tft.println("Water Level"); tft.setFont(&FreeMono12pt7b); tft.println("Milone eTape"); tft.println("i2c address: 0x5F"); tft.println(""); tft.setFont(&FreeMonoBold9pt7b); tft.println("--Press OK To Read Value--"); tft.println(""); tft.println("Value: unknown"); }