|
| 1 | +/*! |
| 2 | + * @file AS7331_TFT_demo.ino |
| 3 | + * |
| 4 | + * TFT demo for AS7331 UV Spectral Sensor on Feather ESP32-S2 TFT |
| 5 | + * Displays UVA, UVB, UVC readings as bar graphs with temperature |
| 6 | + * |
| 7 | + * Hardware: |
| 8 | + * - Adafruit Feather ESP32-S2 TFT |
| 9 | + * - AS7331 UV Sensor breakout (I2C) |
| 10 | + * |
| 11 | + * Written by Limor Fried/Ladyada for Adafruit Industries. |
| 12 | + * BSD license, all text above must be included in any redistribution |
| 13 | + */ |
| 14 | + |
| 15 | +#include <Adafruit_AS7331.h> |
| 16 | +#include <Adafruit_GFX.h> |
| 17 | +#include <Adafruit_ST7789.h> |
| 18 | +#include <Fonts/FreeSansBold9pt7b.h> |
| 19 | +#include <Fonts/FreeSans9pt7b.h> |
| 20 | +#include <Wire.h> |
| 21 | + |
| 22 | +// Feather ESP32-S2 TFT built-in display |
| 23 | +Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); |
| 24 | + |
| 25 | +// AS7331 UV sensor |
| 26 | +Adafruit_AS7331 as7331; |
| 27 | + |
| 28 | +// Display layout constants (240x135 landscape) |
| 29 | +#define SCREEN_WIDTH 240 |
| 30 | +#define SCREEN_HEIGHT 135 |
| 31 | + |
| 32 | +// Bar graph layout |
| 33 | +#define BAR_X 48 // Left edge of bars (after labels) |
| 34 | +#define BAR_WIDTH 120 // Maximum bar width |
| 35 | +#define BAR_HEIGHT 22 // Height of each bar |
| 36 | +#define BAR_SPACING 6 // Space between bars |
| 37 | + |
| 38 | +// Y positions |
| 39 | +#define TITLE_Y 18 |
| 40 | +#define UVA_BAR_Y 32 |
| 41 | +#define UVB_BAR_Y (UVA_BAR_Y + BAR_HEIGHT + BAR_SPACING) |
| 42 | +#define UVC_BAR_Y (UVB_BAR_Y + BAR_HEIGHT + BAR_SPACING) |
| 43 | +#define TEMP_Y 128 |
| 44 | + |
| 45 | +// Colors |
| 46 | +#define COLOR_UVA 0x780F // Purple/violet |
| 47 | +#define COLOR_UVB 0x339F // Bright blue |
| 48 | +#define COLOR_UVC 0x07FF // Cyan |
| 49 | +#define COLOR_BAR_BG 0x2104 // Dark gray |
| 50 | +#define COLOR_TITLE 0xFFFF // White |
| 51 | + |
| 52 | +// Fixed scale maximums (uW/cm2) |
| 53 | +#define UVA_MAX 500.0 |
| 54 | +#define UVB_MAX 100.0 |
| 55 | +#define UVC_MAX 50.0 |
| 56 | + |
| 57 | +// Previous values for flicker-free updates |
| 58 | +float prevUVA = -1, prevUVB = -1, prevUVC = -1; |
| 59 | +float prevTemp = -999; |
| 60 | + |
| 61 | +void setup() { |
| 62 | + Serial.begin(115200); |
| 63 | + unsigned long start = millis(); |
| 64 | + while (!Serial && (millis() - start < 3000)) { |
| 65 | + delay(10); |
| 66 | + } |
| 67 | + |
| 68 | + Serial.println(F("AS7331 UV Sensor TFT Demo")); |
| 69 | + |
| 70 | + // Enable TFT power |
| 71 | + pinMode(TFT_BACKLITE, OUTPUT); |
| 72 | + digitalWrite(TFT_BACKLITE, HIGH); |
| 73 | + pinMode(TFT_I2C_POWER, OUTPUT); |
| 74 | + digitalWrite(TFT_I2C_POWER, HIGH); |
| 75 | + delay(10); |
| 76 | + |
| 77 | + // Initialize TFT |
| 78 | + tft.init(135, 240); |
| 79 | + tft.setRotation(3); |
| 80 | + tft.fillScreen(ST77XX_BLACK); |
| 81 | + |
| 82 | + // Draw title with nice font |
| 83 | + tft.setFont(&FreeSansBold9pt7b); |
| 84 | + tft.setTextColor(COLOR_TITLE); |
| 85 | + tft.setCursor(0, TITLE_Y); |
| 86 | + tft.print(F("Adafruit AS7331 UV Sensor")); |
| 87 | + |
| 88 | + // Initialize I2C and sensor |
| 89 | + Wire.begin(); |
| 90 | + |
| 91 | + if (!as7331.begin(&Wire)) { |
| 92 | + Serial.println(F("Failed to find AS7331 sensor!")); |
| 93 | + tft.setFont(&FreeSansBold9pt7b); |
| 94 | + tft.setTextColor(ST77XX_RED); |
| 95 | + tft.setCursor(20, 70); |
| 96 | + tft.print(F("Sensor Error!")); |
| 97 | + tft.setFont(&FreeSans9pt7b); |
| 98 | + tft.setCursor(10, 95); |
| 99 | + tft.print(F("Check wiring")); |
| 100 | + while (1) { |
| 101 | + delay(100); |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + Serial.println(F("AS7331 found!")); |
| 106 | + |
| 107 | + // Configure sensor |
| 108 | + as7331.setMeasurementMode(AS7331_MODE_CMD); |
| 109 | + as7331.setGain(AS7331_GAIN_64X); |
| 110 | + as7331.setIntegrationTime(AS7331_TIME_64MS); |
| 111 | + |
| 112 | + drawStaticUI(); |
| 113 | +} |
| 114 | + |
| 115 | +void drawStaticUI() { |
| 116 | + tft.setFont(&FreeSansBold9pt7b); |
| 117 | + |
| 118 | + // Bar labels |
| 119 | + tft.setTextColor(COLOR_UVA); |
| 120 | + tft.setCursor(2, UVA_BAR_Y + 16); |
| 121 | + tft.print(F("UVA")); |
| 122 | + |
| 123 | + tft.setTextColor(COLOR_UVB); |
| 124 | + tft.setCursor(2, UVB_BAR_Y + 16); |
| 125 | + tft.print(F("UVB")); |
| 126 | + |
| 127 | + tft.setTextColor(COLOR_UVC); |
| 128 | + tft.setCursor(2, UVC_BAR_Y + 16); |
| 129 | + tft.print(F("UVC")); |
| 130 | + |
| 131 | + // Bar backgrounds and outlines |
| 132 | + tft.fillRect(BAR_X, UVA_BAR_Y, BAR_WIDTH, BAR_HEIGHT, COLOR_BAR_BG); |
| 133 | + tft.fillRect(BAR_X, UVB_BAR_Y, BAR_WIDTH, BAR_HEIGHT, COLOR_BAR_BG); |
| 134 | + tft.fillRect(BAR_X, UVC_BAR_Y, BAR_WIDTH, BAR_HEIGHT, COLOR_BAR_BG); |
| 135 | + |
| 136 | + tft.drawRect(BAR_X - 1, UVA_BAR_Y - 1, BAR_WIDTH + 2, BAR_HEIGHT + 2, |
| 137 | + COLOR_UVA); |
| 138 | + tft.drawRect(BAR_X - 1, UVB_BAR_Y - 1, BAR_WIDTH + 2, BAR_HEIGHT + 2, |
| 139 | + COLOR_UVB); |
| 140 | + tft.drawRect(BAR_X - 1, UVC_BAR_Y - 1, BAR_WIDTH + 2, BAR_HEIGHT + 2, |
| 141 | + COLOR_UVC); |
| 142 | +} |
| 143 | + |
| 144 | +void updateBar(int y, float value, float maxVal, uint16_t color, |
| 145 | + float *prevValue) { |
| 146 | + int fillWidth = (int)((value / maxVal) * BAR_WIDTH); |
| 147 | + if (fillWidth > BAR_WIDTH) |
| 148 | + fillWidth = BAR_WIDTH; |
| 149 | + if (fillWidth < 0) |
| 150 | + fillWidth = 0; |
| 151 | + |
| 152 | + int prevFillWidth = 0; |
| 153 | + if (*prevValue >= 0) { |
| 154 | + prevFillWidth = (int)((*prevValue / maxVal) * BAR_WIDTH); |
| 155 | + if (prevFillWidth > BAR_WIDTH) |
| 156 | + prevFillWidth = BAR_WIDTH; |
| 157 | + if (prevFillWidth < 0) |
| 158 | + prevFillWidth = 0; |
| 159 | + } |
| 160 | + |
| 161 | + if (fillWidth != prevFillWidth) { |
| 162 | + tft.fillRect(BAR_X, y, BAR_WIDTH, BAR_HEIGHT, COLOR_BAR_BG); |
| 163 | + if (fillWidth > 0) { |
| 164 | + tft.fillRect(BAR_X, y, fillWidth, BAR_HEIGHT, color); |
| 165 | + } |
| 166 | + } |
| 167 | + |
| 168 | + // Numeric value to the right of bar |
| 169 | + tft.fillRect(BAR_X + BAR_WIDTH + 4, y, 68, BAR_HEIGHT, ST77XX_BLACK); |
| 170 | + tft.setFont(&FreeSans9pt7b); |
| 171 | + tft.setTextColor(color); |
| 172 | + tft.setCursor(BAR_X + BAR_WIDTH + 6, y + 16); |
| 173 | + if (value < 10.0) { |
| 174 | + tft.print(value, 2); |
| 175 | + } else if (value < 100.0) { |
| 176 | + tft.print(value, 1); |
| 177 | + } else { |
| 178 | + tft.print(value, 0); |
| 179 | + } |
| 180 | + |
| 181 | + *prevValue = value; |
| 182 | +} |
| 183 | + |
| 184 | +void updateTemperature(float temp) { |
| 185 | + if (abs(temp - prevTemp) > 0.1) { |
| 186 | + tft.fillRect(0, TEMP_Y - 14, SCREEN_WIDTH, 18, ST77XX_BLACK); |
| 187 | + tft.setFont(&FreeSans9pt7b); |
| 188 | + tft.setTextColor(ST77XX_YELLOW); |
| 189 | + tft.setCursor(30, TEMP_Y); |
| 190 | + tft.print(F("Temp: ")); tft.print(temp, 1); |
| 191 | + tft.print(F(" C")); |
| 192 | + prevTemp = temp; |
| 193 | + } |
| 194 | +} |
| 195 | + |
| 196 | +void loop() { |
| 197 | + float uva, uvb, uvc; |
| 198 | + |
| 199 | + if (as7331.oneShot_uWcm2(&uva, &uvb, &uvc)) { |
| 200 | + float temp = as7331.readTemperature(); |
| 201 | + |
| 202 | + Serial.print(F("UVA: ")); |
| 203 | + Serial.print(uva, 2); |
| 204 | + Serial.print(F(" | UVB: ")); |
| 205 | + Serial.print(uvb, 2); |
| 206 | + Serial.print(F(" | UVC: ")); |
| 207 | + Serial.print(uvc, 2); |
| 208 | + Serial.print(F(" | Temp: ")); |
| 209 | + Serial.print(temp, 1); |
| 210 | + Serial.println(F(" C")); |
| 211 | + |
| 212 | + updateBar(UVA_BAR_Y, uva, UVA_MAX, COLOR_UVA, &prevUVA); |
| 213 | + updateBar(UVB_BAR_Y, uvb, UVB_MAX, COLOR_UVB, &prevUVB); |
| 214 | + updateBar(UVC_BAR_Y, uvc, UVC_MAX, COLOR_UVC, &prevUVC); |
| 215 | + updateTemperature(temp); |
| 216 | + } else { |
| 217 | + Serial.println(F("Failed to read UV sensor!")); |
| 218 | + } |
| 219 | + |
| 220 | + delay(200); |
| 221 | +} |
0 commit comments