This is the full code. The transferBMPtoTFT works perfectly, and when I try to structure the drawRaindrop() like it it causes my image to have that weird distortion.
#include <Adafruit_GFX.h>
#include <Adafruit_GC9A01A.h>
#include <SPI.h>
#include <SD.h>
#include <Fonts/FreeSansBold9pt7b.h>
#include <Fonts/FreeSans9pt7b.h>
#define SD_CS_PIN 6
#define TFT_CS_PIN 3
#define TFT_DC_PIN 4
#define TFT_RESET 7
#define TFT_BLACK 0x0000
#define CHUNK_SIZE 2048 // Adjust this value to optimize performance
// Define user-defined variables for desired width and height
uint32_t desiredWidth = 240; // Default width
uint32_t desiredHeight = 120; // Default height
#define AOUT_PINA A6
#define AOUT_PINB A5
#define LightAOUT_PIN A4
#define BUTTON_PIN 2 // Change this to an interrupt-capable pin (e.g., 2 or 3 on most Arduino boards)
Adafruit_GC9A01A tft(TFT_CS_PIN, TFT_DC_PIN, TFT_RESET);
const char* imageFilenames[] = { "image1.bmp", "image2.bmp", "image3.bmp", "image4.bmp", "image5.bmp", "image6.bmp", "image7.bmp", "image8.bmp", "image9.bmp", "image10.bmp", "raindrop.bmp" };
const int numImages = sizeof(imageFilenames) / sizeof(imageFilenames[0]);
int currentImageIndex = 0; // Variable to keep track of the current image index
unsigned long previousMillis = 0;
const unsigned long interval = 5000; // Change the interval to 10 seconds
float averageMoisture = 0.0;
int lightValue = 0;
const char* plantTypes[] = { "Succulent", "Grasses", "Cactus", "Fern", "Flowers", "Bonsai", "Herbs", "Vines", "Broad Leaf", "Moss" };
int plantTypeIndex = 0;
const int moistureRanges[][2] = {
{ 550, 570 }, // Succulent
{ 460, 490 }, // Native Grass
{ 550, 570 }, // Cactus
{ 340, 370 }, // Fern
{ 400, 430 }, // Orchid
{ 490, 520 }, // Bonsai
{ 460, 490 }, // Herb
{ 400, 430 }, // Vine
{ 400, 430 }, // Broad Leaf
{ 310, 340 } // Moss
};
unsigned long lastWateredTime = 0; // Variable to store the time when the plant was last watered
unsigned long elapsedHours = 0; // Variable to store elapsed hours since last watering
unsigned long elapsedMinutes = 0; // Variable to store elapsed minutes since last watering
int currentMoistureValue = 0; // Global variable to store the current moisture value
float previousAverageMoisture = 0.0;
enum DisplayMode {
LAST_WATERED,
LIGHT_LEVEL,
CUSTOM_PROMPT
};
DisplayMode currentMode = LAST_WATERED;
unsigned long animationTimer = 0;
const unsigned long animationInterval = 5000; // 5 seconds
bool shouldDrawRaindrop = false; // Flag to track if raindrop needs to be drawn
int readBMPHeader(File& bmpFile, uint32_t& bmpDataOffset, uint32_t& actualWidth, uint32_t& actualHeight) {
static uint16_t bmpSignature;
static uint32_t bmpFileSize;
// Read BMP file signature
bmpFile.read((uint8_t*)&bmpSignature, sizeof(bmpSignature));
if (bmpSignature != 0x4D42) {
return 1; // Invalid BMP file signature
}
// Read BMP file size
bmpFile.read((uint8_t*)&bmpFileSize, sizeof(bmpFileSize));
// Skip over the reserved fields
bmpFile.seek(10);
// Read BMP data offset
bmpFile.read((uint8_t*)&bmpDataOffset, sizeof(bmpDataOffset));
// Read actual image width and height
bmpFile.seek(18);
bmpFile.read((uint8_t*)&actualWidth, sizeof(actualWidth));
bmpFile.read((uint8_t*)&actualHeight, sizeof(actualHeight));
// Skip past the remaining header data
bmpFile.seek(bmpDataOffset);
return 0;
}
void transferBMPToTFT(File& bmpFile) {
uint32_t bmpDataOffset, actualWidth, actualHeight;
if (readBMPHeader(bmpFile, bmpDataOffset, actualWidth, actualHeight) != 0) {
Serial.println("Error reading BMP header!");
bmpFile.close();
return;
}
// Check if the user-defined width and height exceed the actual image dimensions
if (desiredWidth > actualWidth || desiredHeight > actualHeight) {
Serial.println("User-defined dimensions exceed the actual image dimensions!");
bmpFile.close();
return;
}
uint32_t rowSize = (desiredWidth * 2 + 3) & ~3; // Calculate the row size with padding
uint16_t* buffer = new uint16_t[desiredWidth]; // Dynamically allocate buffer based on desired width
bmpFile.seek(bmpDataOffset);
for (int y = 0; y < desiredHeight; y++) { // Loop through all rows
bmpFile.read((uint8_t*)buffer, desiredWidth * sizeof(uint16_t)); // Read the desired width of pixels
// Swap the byte order of RGB565 pixel data
for (int x = 0; x < desiredWidth; x++) {
buffer[x] = ((buffer[x] << 8) & 0xFF00) | ((buffer[x] >> 8) & 0x00FF);
}
tft.startWrite();
tft.writePixels(buffer, desiredWidth, 0, y + (120 - desiredHeight) / 2); // Center the image vertically
tft.endWrite();
// Skip over any padding bytes at the end of the row
bmpFile.seek(bmpFile.position() + rowSize - desiredWidth * 2);
}
// Free the dynamically allocated buffer
delete[] buffer;
}
void drawRaindrop() {
File bmpFile = SD.open("raindrop.bmp", FILE_READ);
if (!bmpFile) {
Serial.println("Could not open raindrop.bmp");
return;
}
uint32_t bmpDataOffset, imageWidth, imageHeight;
if (readBMPHeader(bmpFile, bmpDataOffset, imageWidth, imageHeight) != 0) {
Serial.println("Error reading BMP header!");
bmpFile.close();
return;
}
// Adjust startX and startY positions
int startX = 97;
int startY = 190;
tft.startWrite();
uint16_t* imageDataBuffer = new uint16_t[imageWidth * imageHeight]; // Buffer to hold entire image data
int bytesPerPixel = 2; // For 16-bit images
int rowSizeWithPadding = (imageWidth * bytesPerPixel + 3) & (~3); // Calculate total bytes per row, including padding
int padding = rowSizeWithPadding - imageWidth * bytesPerPixel; // Calculate padding size
bmpFile.seek(bmpDataOffset);
// Read entire image data into buffer
for (int y = 0; y < imageHeight; y++) {
bmpFile.read((uint8_t*)(imageDataBuffer + y * imageWidth), imageWidth * bytesPerPixel);
// Skip padding bytes
bmpFile.seek(bmpFile.position() + padding);
}
// Write entire image data to TFT screen
for (int y = 0; y < imageHeight; y++) {
for (int x = 0; x < imageWidth; x++) {
tft.writePixel(x + startX, y + startY, imageDataBuffer[y * imageWidth + x]);
}
}
tft.endWrite();
bmpFile.close();
// Free the dynamically allocated buffer
delete[] imageDataBuffer;
}
void changeImageAndPlantType() {
// Find the next image filename that is not "raindrop.bmp"
do {
currentImageIndex = (currentImageIndex + 1) % (numImages - 1); // Exclude the last index (raindrop.bmp)
} while (strcmp(imageFilenames[currentImageIndex], "raindrop.bmp") == 0);
plantTypeIndex = (plantTypeIndex + 1) % (sizeof(plantTypes) / sizeof(plantTypes[0]));
currentMoistureValue = (moistureRanges[plantTypeIndex][0] + moistureRanges[plantTypeIndex][1]) / 2;
// Clear the screen before displaying the new image and text
//tft.fillScreen(TFT_BLACK);
tft.fillRect(0, 0, 240, 160, 0x0000);
// Open and display the new image
File bmpFile = SD.open(imageFilenames[currentImageIndex], FILE_READ);
if (!bmpFile) {
Serial.print("Could not open ");
Serial.println(imageFilenames[currentImageIndex]);
return;
}
transferBMPToTFT(bmpFile);
bmpFile.close();
// Draw the raindrop image after displaying the background image
drawRaindrop();
updateDisplay();
}
void buttonPressed() {
static unsigned long lastInterruptTime = 0;
unsigned long interruptTime = millis();
// Debounce the button press
if (interruptTime - lastInterruptTime > 200) {
changeImageAndPlantType();
lastInterruptTime = interruptTime;
}
}
void setup() {
Serial.begin(9600);
pinMode(BUTTON_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonPressed, FALLING);
readSoilMoisture(); // Calculate the initial averageMoisture value
previousAverageMoisture = averageMoisture; // Store the initial value in previousAverageMoisture
SPI.begin();
if (!SD.begin(SD_CS_PIN)) {
Serial.println("SD card initialization failed!");
return;
}
tft.begin();
tft.fillScreen(TFT_BLACK); // Clear the screen initially
Serial.println("GC9A01A Test!");
tft.setRotation(2); // Set rotation to 0 before displaying text
tft.fillRoundRect(75, 50, 90, 2, .25, 0xFFFF);
// Read soil moisture and calculate currentMoistureValue
readSoilMoisture();
currentMoistureValue = (moistureRanges[plantTypeIndex][0] + moistureRanges[plantTypeIndex][1]) / 2;
tft.setTextSize(1);
tft.setFont(&FreeSansBold9pt7b);
tft.setTextColor(0xffff);
// Calculate the position to center "Soil Moisture:" at (120, 60)
const char* text = "Soil Moisture";
int16_t x1, y1;
uint16_t width, height;
tft.getTextBounds(text, 0, 0, &x1, &y1, &width, &height);
int16_t x = 120 - width / 2;
int16_t y = 75;
// Display "Soil Moisture:" centered at (120, 50)
tft.setCursor(x, y);
tft.println(text);
tft.setFont(&FreeSans9pt7b);
tft.setCursor(60, 120);
tft.println(averageMoisture);
tft.setTextColor(0xf800);
tft.setCursor(120, 120);
tft.print(currentMoistureValue);
tft.setTextColor(0xffff);
tft.setRotation(0);
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
readSoilMoisture();
readLightLevel();
tft.setRotation(2);
tft.setTextSize(1);
tft.setTextColor(0x0000);
tft.setFont(&FreeSans9pt7b);
tft.setCursor(60, 120);
tft.println(previousAverageMoisture);
tft.setRotation(0);
noInterrupts(); // Disable interrupts temporarily
previousAverageMoisture = averageMoisture; // Store the current averageMoisture value
updateDisplay();
updateTimeSinceWatering(currentMillis);
interrupts();
// Re-enable interrupts
previousMillis = currentMillis;
}
if (currentMillis - animationTimer >= animationInterval) {
animationTimer = currentMillis;
noInterrupts();
updateAnimationMode();
interrupts();
}
}
void readSoilMoisture() {
int valueA = analogRead(AOUT_PINA);
int valueB = analogRead(AOUT_PINB);
int sum = valueA + valueB;
averageMoisture = sum / 2.0;
//Serial.print("Moisture: ");
//Serial.println(averageMoisture);
if (averageMoisture < 310) {
updateLastWateredTime();
elapsedHours = 0; // Reset elapsed hours
elapsedMinutes = 0; // Reset elapsed minutes
}
}
void updateLastWateredTime() {
lastWateredTime = millis();
}
void readLightLevel() {
lightValue = analogRead(LightAOUT_PIN);
}
void updateDisplay() {
tft.setRotation(2);
tft.setTextSize(1);
tft.setTextColor(0xFFFF);
tft.setFont(&FreeSansBold9pt7b);
// Calculate the position to center "Soil Moisture:" at (120, 60)
const char* text = "Soil Moisture";
int16_t x1, y1;
uint16_t width, height;
tft.getTextBounds(text, 0, 0, &x1, &y1, &width, &height);
int16_t x = 120 - width / 2;
int16_t y = 75;
// Display "Soil Moisture:" centered at (120, 50)
tft.setCursor(x, y);
tft.println(text);
tft.setFont(&FreeSans9pt7b);
tft.setCursor(60, 120);
tft.println(averageMoisture);
tft.setTextColor(0xf800);
tft.setCursor(120, 120);
tft.print(currentMoistureValue);
tft.setRotation(0);
}
void updateTimeSinceWatering(unsigned long currentTime) {
unsigned long elapsedTime = currentTime - lastWateredTime;
unsigned long elapsedSeconds = elapsedTime / 1000;
elapsedMinutes = elapsedSeconds / 60;
elapsedHours = elapsedMinutes / 60;
elapsedMinutes %= 60;
}
void updateAnimationMode() {
/* tft.setTextColor(0xFFFF);
void updateAnimationMode() {
/* tft.setTextColor(0xFFFF);
tft.setCursor(60, 165);
tft.setTextSize(1);
tft.setRotation(2);
switch (currentMode) {
case LAST_WATERED:
tft.setCursor(60, 145);
tft.print("Last Watered: ");
tft.setCursor(60, 170);
tft.print(elapsedHours);
tft.print("h ");
tft.print(elapsedMinutes);
tft.print("m ");
currentMode = LIGHT_LEVEL;
break;
case LIGHT_LEVEL:
tft.setCursor(60, 145);
tft.print("Light Level: ");
tft.setCursor(60, 170);
tft.print(lightValue);
currentMode = CUSTOM_PROMPT;
break;
case CUSTOM_PROMPT:
tft.setCursor(60, 145);
tft.print("Smart Tip: ");
tft.setCursor(60, 170);
// Check if the plant needs to be watered
int targetMoistureValue = (moistureRanges[plantTypeIndex][0] + moistureRanges[plantTypeIndex][1]) / 2;
unsigned long timeSinceLastWatered = (millis() - lastWateredTime) / 60000; // Time in minutes
if (averageMoisture > moistureRanges[plantTypeIndex][0] + 75 && timeSinceLastWatered >= 5) {
tft.print("Needs Water");
} else if (averageMoisture < moistureRanges[plantTypeIndex][1] - 75) {
tft.print("Too Wet");
} else {
tft.print("No Action");
}
currentMode = LAST_WATERED;
break;
}
tft.setRotation(0); */
}