Files
AQS-FW/lcd.cpp

400 lines
10 KiB
C++

#include "lcd.h"
#define NEEDLE_LENGTH 35 // Visible length
#define NEEDLE_WIDTH 5 // Width of needle - make it an odd number
#define NEEDLE_RADIUS 90 // Radius at tip
#define NEEDLE_COLOR1 TFT_MAROON // Needle periphery colour
#define NEEDLE_COLOR2 TFT_RED // Needle centre colour
#define DIAL_CENTRE_X 120
#define DIAL_CENTRE_Y 120
#define MAXGUAGE 240
#define MINGUAGE 0
#define DISPLAY_ROTATE 15 //sec
#define BACKLIGHTTIMEOUT 60000
#define BACKLIGHTONBRGT 84
#define BACKLIGHTSTEP 1
#define AA_FONT_LARGE NotoSansBold36
enum DISPLAY_STATE
{
DISPLAY_PM1P0,
DISPLAY_PM2P5,
DISPLAY_PM10P0,
DISPLAY_TEMP,
DISPLAY_HUM,
DISPLAY_CO2,
DISPLAY_LAST
};
// sprite elements
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite needle = TFT_eSprite(&tft); // Sprite object for needle
TFT_eSprite spr = TFT_eSprite(&tft); // Sprite for meter reading
TFT_eSprite nameSpr = TFT_eSprite(&tft);
TFT_eSprite unitSpr = TFT_eSprite(&tft);
TFT_eSprite ProgressBar = TFT_eSprite(&tft);
uint16_t *tft_buffer;
bool buffer_loaded = false;
uint16_t spr_width = 0;
uint16_t name_spr_width = 0;
bool progessbarActive = false;
DISPLAY_STATE displaystate = DISPLAY_PM1P0;
uint32_t display_last_update = 0;
uint32_t backlightTimeout = 0;
uint8_t backlightBrightness = BACKLIGHTONBRGT;
// =======================================================================================
// Declarations
// =======================================================================================
void createNeedle(void);
void plotNeedle(int16_t angle, uint16_t ms_delay);
void setBacklight(uint8_t value);
bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap);
void backlightRefresh(void);
// =======================================================================================
// implementation
// =======================================================================================
bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap)
{
// Stop further decoding as image is running off bottom of screen
if (y >= tft.height())
return 0;
// This function will clip the image block rendering automatically at the TFT boundaries
tft.pushImage(x, y, w, h, bitmap);
// Return 1 to decode next block
return 1;
}
void setBacklight(uint8_t value)
{
ledcWrite(TFT_BL_PWMCHANNEL, value);
}
void ProgressbarVisible(bool visible)
{
progessbarActive = visible;
if (!visible)
{
ProgressBar.fillSprite(TFT_BLACK);
ProgressBar.pushSprite(10, 270);
}
}
void setOTAProgress(uint8_t value)
{
uint16_t progress = map(value, 0, 100, 0, 212);
ProgressBar.drawRoundRect(0, 2, 220, 22, 5, TFT_BLUE);
ProgressBar.fillRoundRect(4, 5, progress, 15, 3, TFT_BLUE);
if (progessbarActive)
{
ProgressBar.pushSprite(10, 270);
}
}
void backlightRefresh(void)
{
backlightTimeout = millis();
}
void plotNeedle(int16_t angle, uint16_t ms_delay, uint32_t value)
{
static int16_t old_angle = -120; // Starts at -120 degrees
// Bounding box parameters
static int16_t min_x;
static int16_t min_y;
static int16_t max_x;
static int16_t max_y;
if (angle < 0)
angle = 0; // Limit angle to emulate needle end stops
if (angle > 240)
angle = 240;
angle -= 120; // Starts at -120 degrees
// Move the needle until new angle reached
while (angle != old_angle || !buffer_loaded)
{
if (old_angle < angle)
old_angle++;
else
old_angle--;
// Only plot needle at even values to improve plotting performance
if ((old_angle & 1) == 0)
{
if (buffer_loaded)
{
// Paste back the original needle free image area
tft.pushRect(min_x, min_y, 1 + max_x - min_x, 1 + max_y - min_y, tft_buffer);
}
if (needle.getRotatedBounds(old_angle, &min_x, &min_y, &max_x, &max_y))
{
// Grab a copy of the area before needle is drawn
tft.readRect(min_x, min_y, 1 + max_x - min_x, 1 + max_y - min_y, tft_buffer);
buffer_loaded = true;
}
// Draw the needle in the new postion, black in needle image is transparent
needle.pushRotated(old_angle, TFT_BLACK);
// Wait before next update
delay(ms_delay);
}
// Update the number at the centre of the dial
spr.drawNumber(value, spr_width / 2, spr.fontHeight() / 2);
spr.pushSprite(120 - spr_width / 2, 120 - spr.fontHeight() / 2);
// Slow needle down slightly as it approaches the new position
if (abs(old_angle - angle) < 10)
ms_delay += ms_delay / 5;
}
}
// =======================================================================================
// Setup
// =======================================================================================
void createProgressBar(void)
{
ProgressBar.createSprite(220, 30);
ProgressBar.fillSprite(TFT_BLACK);
ProgressBar.drawRoundRect(0, 2, 220, 22, 5, TFT_BLUE);
ProgressBar.pushSprite(10, 270);
}
void createNameSprite(void)
{
nameSpr.setTextFont(2);
name_spr_width = nameSpr.textWidth("---Sensor Name---");
nameSpr.createSprite(name_spr_width, nameSpr.fontHeight() * 2 + 2);
uint16_t bg_color = tft.readPixel(120, 120); // Get colour from dial centre
nameSpr.fillSprite(bg_color);
nameSpr.setTextColor(TFT_WHITE, bg_color);
nameSpr.setTextDatum(MC_DATUM);
nameSpr.setTextPadding(name_spr_width);
nameSpr.drawString("Sensor Name", name_spr_width / 2, nameSpr.fontHeight() / 2);
nameSpr.drawString("Unit", name_spr_width / 2, nameSpr.fontHeight() / 2 * 3 + 2);
nameSpr.pushSprite(DIAL_CENTRE_X - name_spr_width / 2, DIAL_CENTRE_Y + 40, 2);
}
void createValueSprinte(void)
{
spr.loadFont(AA_FONT_LARGE);
spr_width = spr.textWidth("277");
spr.createSprite(spr_width, spr.fontHeight());
uint16_t bg_color = tft.readPixel(120, 120); // Get colour from dial centre
spr.fillSprite(bg_color);
spr.setTextColor(TFT_WHITE, bg_color);
spr.setTextDatum(MC_DATUM);
spr.setTextPadding(spr_width);
spr.drawNumber(0, spr_width / 2, spr.fontHeight() / 2);
spr.pushSprite(DIAL_CENTRE_X - spr_width / 2, DIAL_CENTRE_Y - spr.fontHeight() / 2);
}
void createNeedle(void)
{
needle.setColorDepth(16);
needle.createSprite(NEEDLE_WIDTH, NEEDLE_LENGTH); // create the needle Sprite
needle.fillSprite(TFT_BLACK); // Fill with black
// Define needle pivot point relative to top left corner of Sprite
uint16_t piv_x = NEEDLE_WIDTH / 2; // pivot x in Sprite (middle)
uint16_t piv_y = NEEDLE_RADIUS; // pivot y in Sprite
needle.setPivot(piv_x, piv_y); // Set pivot point in this Sprite
// Draw the red needle in the Sprite
needle.fillRect(0, 0, NEEDLE_WIDTH, NEEDLE_LENGTH, TFT_MAROON);
needle.fillRect(1, 1, NEEDLE_WIDTH - 2, NEEDLE_LENGTH - 2, TFT_RED);
// Bounding box parameters to be populated
int16_t min_x;
int16_t min_y;
int16_t max_x;
int16_t max_y;
// Work out the worst case area that must be grabbed from the TFT,
// this is at a 45 degree rotation
needle.getRotatedBounds(45, &min_x, &min_y, &max_x, &max_y);
// Calculate the size and allocate the buffer for the grabbed TFT area
tft_buffer = (uint16_t *)malloc(((max_x - min_x) + 2) * ((max_y - min_y) + 2) * 2);
}
void createDail(void)
{
TJpgDec.setSwapBytes(true);
// The jpeg decoder must be given the exact name of the rendering function above
TJpgDec.setCallback(tft_output);
// Draw the dial
TJpgDec.drawJpg(0, 0, dial, sizeof(dial));
tft.drawCircle(DIAL_CENTRE_X, DIAL_CENTRE_Y, NEEDLE_RADIUS - NEEDLE_LENGTH, TFT_DARKGREY);
// Define where the needle pivot point is on the TFT before
// creating the needle so boundary calculation is correct
tft.setPivot(DIAL_CENTRE_X, DIAL_CENTRE_Y);
}
void initLCD()
{
Serial.print("InitLCD:");
// The byte order can be swapped (set true for TFT_eSPI)
tft.begin();
tft.setRotation(0);
tft.fillScreen(TFT_BLACK);
// Create the Sprites
createProgressBar();
createDail();
createValueSprinte();
createNeedle();
createNameSprite();
ledcSetup(TFT_BL_PWMCHANNEL, TFT_BL_FREQ, TFT_BL_BITS);
ledcAttachPin(TFT_BL, TFT_BL_PWMCHANNEL);
ledcWrite(TFT_BL_PWMCHANNEL, BACKLIGHTONBRGT);
// Reset needle position to 0
plotNeedle(0, 0, 0);
Serial.println("OK");
}
// =======================================================================================
// Loop
// =======================================================================================
void handleBacklight(void)
{
uint32_t timeNow = millis();
if (!backlightTimeout)
{
backlightRefresh();
}
if (timeNow - backlightTimeout > BACKLIGHTTIMEOUT)
{
if (backlightBrightness > BACKLIGHTSTEP)
{
backlightBrightness -= BACKLIGHTSTEP;
}
else
{
backlightBrightness = 0;
}
}
else
{
if (backlightBrightness < BACKLIGHTONBRGT)
{
backlightBrightness += BACKLIGHTSTEP;
}
}
ledcWrite(TFT_BL_PWMCHANNEL, backlightBrightness);
}
sensor_e displaySensor[] =
{
AE_1P0,
AE_2P5,
AE_10P0,
SCD30_temp,
SCD30_hum,
SCD30_co2,
SGP30_TVOC,
};
uint8_t displaySensorSize = 7;
uint8_t displaySensorIndex = 0;
void nextSensor(void)
{
if (displaySensorIndex < displaySensorSize - 1)
{
displaySensorIndex++;
}
else
{
displaySensorIndex = 0;
}
display_last_update = millis();
}
void previousSensor(void)
{
if (displaySensorIndex > 0)
{
displaySensorIndex--;
}
else
{
displaySensorIndex = displaySensorSize - 1;
}
display_last_update = millis();
}
void handleplotSensor(void)
{
if(!backlightBrightness)
{
return;
}
AQSSensor *sensor = getSensor(displaySensor[displaySensorIndex]);
if (sensor == NULL)
{
Serial.println("LCD: getSensor=NULL!");
nextSensor();
return;
}
uint16_t angle = map(sensor->value(), sensor->getMin(), sensor->getMax(), MINGUAGE, MAXGUAGE);
plotNeedle(angle, 15, sensor->value());
nameSpr.drawString(sensor->getName().c_str(), name_spr_width / 2, nameSpr.fontHeight() / 2);
nameSpr.drawString(sensor->getUnit().c_str(), name_spr_width / 2, nameSpr.fontHeight() / 2 * 3 + 2);
nameSpr.pushSprite(DIAL_CENTRE_X - name_spr_width / 2, DIAL_CENTRE_Y + 40, 2);
}
void handleDisplayRotate(void)
{
//rotate display
if(!backlightBrightness)
{
return;
}
uint32_t timenow = millis();
if (timenow - display_last_update > (DISPLAY_ROTATE * 1000))
{
Serial.println("LCD next State");
nextSensor();
}
}
void handleLCD()
{
handleDisplayRotate();
handleplotSensor();
handleBacklight();
}
// =======================================================================================