372 lines
11 KiB
C++
372 lines
11 KiB
C++
#include <FreeRTOS_SAMD21.h>
|
|
#include <semphr.h>
|
|
|
|
#include <Adafruit_NeoPixel.h>
|
|
#include "SPI.h"
|
|
#include "Adafruit_GFX.h"
|
|
#include "Adafruit_ILI9341.h"
|
|
#include <Fonts/FreeMonoBold9pt7b.h>
|
|
#include <Fonts/FreeMonoBold18pt7b.h>
|
|
#include <Fonts/FreeMono12pt7b.h>
|
|
|
|
// Various sensors used for calibration data reads
|
|
#include <Wire.h>
|
|
#include <I2CSoilMoistureSensor.h>
|
|
#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");
|
|
}
|