/* https://www.youtube.com/watch?v=sTYPuDMPva8
Set the appropriate screen size in the Adafruit_SSD1306.h by uncommenting
one of the two following lines.
//#define SSD1306_128_64 ///< DEPRECTAED: old way to specify 128x64 screen
//#define SSD1306_128_32 ///< DEPRECATED: old way to specify 128x32 screen
*/
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_DRIVER_ADAFRUIT true
#define OLED_DRIVER_U8G2LIB !OLED_DRIVER_ADAFRUIT
#define OLED_ADAFRUIT_USE_FONTS false
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#define OLED_SCROLL_SIZE 128
const char ScrollFields[] = "Temp:%.1fC, Humd:%.1f%%, Pres:%dmmHg, eCO2:%dppm, TVOC:%dppb, Rate:%.1f%%";
uint32_t lcdOled_fps; // delay for each individual animation frame (same for all the frames inside one animation)
uint32_t lcdOled_tick;
uint32_t lcdOled_FPSms;
uint32_t lcdOled_flipCounter;
bool lcdOled_flip;
int x, minX;
enum Error_t {
DHT_S1 = 0,
DHT_S2,
// leave this last entry
DHT_Sn};
typedef struct DHT_t
{
float TemperC; // deg C
float TemperF; // deg F
float Humidity; // %
float HeatIndexC; // deg C
float HeatIndexF; // deg F
float DewPointC; // deg C
} SENSOR_DHT_t;
typedef struct CCS_t
{
float eCO2; // ppm
float eTVOC; // ppb
float calcTemp; // deg C
} SENSOR_CCS_t;
typedef struct BMP_t
{
float TemperC; // deg C
float PressurehPa; // hPa
float PressuremmHg; // mmHg
float Altitude; // m
float SeaPress; // Pa
float SeaAltit; // m
} SENSOR_BMP_t;
typedef struct MPU_t
{
float tsrate; // %
float rssi; // dbm
float looptime; // ms
} SENSOR_MPU_t;
typedef struct RTC_t
{
uint8_t tm_hour;
uint8_t tm_min;
uint8_t tm_sec;
uint32_t tm_ticks;
} CLOCK_RTC_t;
typedef struct SENSORS_t
{
SENSOR_MPU_t mpux;
SENSOR_DHT_t dthx[DHT_Sn];
SENSOR_CCS_t ccsx;
SENSOR_BMP_t bmpx;
} SENSORS_DB_t;
SENSORS_DB_t SensorsDB;
CLOCK_RTC_t TimeClock;
enum TEXT_ALIGN_t {
NO_JST = 0,
LEFT_JST = 1,
RIGHT_JST = 2,
CENTER_JST = 3,
// leave this last entry
TEXT_N };
typedef uint8_t mGFX_t;
static bool OLED_I2C_DEBUG_SPRINT(Print &out, TEXT_ALIGN_t _strJ, mGFX_t _strX, mGFX_t _strY, const char* _fmt, ... );
static bool OLED_I2C_DEBUG_SPRINT_NUMERIC(Print &out, const char* _fmt, void* _val);
#define OLED_I2C_PrintMsg(just,posX,posY,str, ...) OLED_I2C_DEBUG_SPRINT(oled, just, posX, posY, str, ##__VA_ARGS__ )
#define OLED_I2C_PrintVal(str, val) OLED_I2C_DEBUG_SPRINT_NUMERIC(oled, str, val)
/* ======================================= */
void setup(){
Serial.begin(115200);
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if(!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
// Clear the buffer
oled.clearDisplay();
oled.setTextSize(2);
oled.setTextColor(WHITE);
oled.setTextWrap(false);
lcdOled_fps = 10;
lcdOled_tick = lcdOled_fps;
lcdOled_flipCounter = 0;
lcdOled_flip = !lcdOled_flip;
x = oled.width();
LcdOled_InitClock(&TimeClock);
}
/* ======================================= */
void loop() {
Sensor_update(&SensorsDB);
LcdOled_Animation(&SensorsDB);
}
/* ======================================= */
void LcdOled_ShowDataScroll(SENSORS_DB_t* snx, bool flip)
{
//char message[OLED_SCROLL_SIZE] = {"test\0"};
char message[64] = {'\0'};
//char dummy[sizeof(ScrollFields)+1] {'\0'};
//strcpy_P(dummy, ScrollFields); // copy fields format from PSTR to dummy location
/*
snprintf(message, OLED_SCROLL_SIZE, dummy,
snx->dthx[DHT_S1].TemperC,
snx->dthx[DHT_S1].Humidity,
snx->bmpx.PressuremmHg,
snx->ccsx.eCO2,
snx->ccsx.eTVOC,
snx->mpux.tsrate);
*/
strcat_P(message, PSTR("@0123456"));
//strcat_P(message, PSTR("0123456789abcdef0123456789ABCDEF"));
//Serial.print(message);
//Serial.println(strlen(message), DEC);
oled.setTextSize(2);
oled.setFont();
//oled.setFont(&PxPlus_IBM_VGA8_12pt7b);
oled.setTextColor(WHITE);
oled.setTextWrap(false);
//x = oled.width();
//minX = -12 * strlen(message); // 12 = 6 pixels/character * text size 2
minX = -12 * strlen(message); // 12 = 6 pixels/character * text size 2
oled.setCursor(x,16);
oled.print(message);
//OLED_I2C_PrintMsg(NO_JST, x-32, 16, "%s", message);
x=x-4; // scroll speed, make more positive to slow down the scroll
//if(--x < minX) x = oled.width();
if(x < minX) x = oled.width();
Serial.print(millis(), DEC);
Serial.print("ms : ");
Serial.println(x, DEC);
}
/* ======================================= */
void Sensor_update(SENSORS_DB_t* snx)
{
snx->dthx[DHT_S1].TemperC = 23.1;
snx->dthx[DHT_S1].Humidity = 49.8;
snx->bmpx.PressuremmHg = 720.1;
snx->ccsx.eCO2 = 410.1;
snx->ccsx.eTVOC = 7.2;
snx->mpux.tsrate =98.5;
}
/* ======================================= */
void LcdOled_Animation(SENSORS_DB_t* snx)
{
if ((millis()-lcdOled_FPSms) > (1000 / lcdOled_fps))
{
lcdOled_FPSms = millis();
OLED_I2C_Clear();
//oled.setFont();
//OLED_I2C_ShowANI((SCREEN_WIDTH - 48)/2, SCREEN_HEIGHT-1 , 48, 48, ArraySize(wave_f), SCAN_BMP, wave_f);
LcdOled_ShowClock(&TimeClock);
if(0 == (++lcdOled_flipCounter % 50)) {
lcdOled_flip = !lcdOled_flip;
}
//LcdOled_ShowData(snx, lcdOled_flip);
LcdOled_ShowDataScroll(snx, lcdOled_flip);
OLED_I2C_Refresh(true);
}
}
/* ======================================= */
static void LcdOled_ShowData(SENSORS_DB_t* snx, bool flip)
{
oled.setTextSize(1);
oled.setFont();
if(!flip) {
OLED_I2C_PrintMsg(NO_JST, 38, 9, "Temp: %1f", snx->dthx[DHT_S1].TemperC);
OLED_I2C_PrintMsg(NO_JST, 38, 20, "Humd: %1f", snx->dthx[DHT_S1].Humidity);
OLED_I2C_PrintMsg(NO_JST, 38, 31, "Pres: %1f", snx->bmpx.PressuremmHg);
}
else {
OLED_I2C_PrintMsg(NO_JST, 38, 9, "eCO2: %.1f", snx->ccsx.eCO2);
OLED_I2C_PrintMsg(NO_JST, 38, 20, "TVOC: %.1f", snx->ccsx.eTVOC);
OLED_I2C_PrintMsg(NO_JST, 38, 31, "Rate: %1f", snx->mpux.tsrate);
}
}
/* ================================================================
= =
================================================================ */
static void LcdOled_InitClock(CLOCK_RTC_t* rtc)
{
rtc->tm_hour = 3;
rtc->tm_min = 30;
rtc->tm_sec = 45;
rtc->tm_ticks = millis();
//rawtime = time(nullptr);
//timeinfo = localtime(&rawtime);
//configTime(0, 0, "de.pool.ntp.org", "time.nist.gov"); //sync at boot time
//setenv("TZ", "EET-2EEST,M3.5.0/3,M10.5.0/4", 1); // Bucharest TZ & DST.
//tzset();
}
/* ================================================================
= =
================================================================ */
static void LcdOled_ShowClock(CLOCK_RTC_t* rtc)
{
float angle;
if (millis() - rtc->tm_ticks > 1000) {
rtc->tm_ticks = millis();
if (++rtc->tm_sec > 59) {
rtc->tm_sec = 0;
if (++rtc->tm_min > 59) {
rtc->tm_min = 0;
if (++rtc->tm_hour > 11) {
rtc->tm_hour = 0;
}
}
}
}
// draw clock ticks
for(int z=0; z<360;z=z+30)
{
angle = (float)z / 57.3; //Convert degrees to radians
int x1=(16+(sin(angle)*15));
int y1=(15-(cos(angle)*15));
int x2=(16+(sin(angle)*(12)));
int y2=(15-(cos(angle)*(12)));
oled.drawLine(x1,y1,x2,y2,WHITE);
}
// draw clock hour
angle=((float)rtc->tm_hour * 30 + (float)rtc->tm_min / 2) / 57.3 ; //Convert degrees to radians
int x2=(16+(sin(angle)*(9)));
int y2=(15-(cos(angle)*(9)));
oled.drawLine(16,15,x2,y2,WHITE);
// draw clock minute
angle=((float)rtc->tm_min * 6 / 57.3) ; //Convert degrees to radians
x2=(16+(sin(angle)*(11)));
y2=(15-(cos(angle)*(11)));
oled.drawLine(16,15,x2,y2,WHITE);
// draw clock second
angle=((float)rtc->tm_sec * 6 / 57.3) ; //Convert degrees to radians
x2=(16+(sin(angle)*(13)));
y2=(15-(cos(angle)*(13)));
oled.drawLine(16,15,x2,y2,WHITE);
/*
if (tm_min == 59 && tm_sec == 0) //sync every hour at 59'0"
{
configTime(0, 0, "de.pool.ntp.org", "time.nist.gov");
setenv("TZ", "EET-2EEST,M3.5.0/3,M10.5.0/4", 1); // Bucharest TZ & DST.
tzset();
//delay(900); //prevent multiple NTP requests
}
*/
}
/**
* =================================================================
* = D I S P L A Y - O L E D - R A M - B U F F E R - T O - O L E D =
* =================================================================
*/
static void OLED_I2C_Refresh(bool refresh)
{
if (!refresh) return;
#if (OLED_DRIVER_ADAFRUIT)
oled.display();
#endif
#if (OLED_DRIVER_U8G2LIB)
oled.sendBuffer();
#endif
}
/**
* =================================================================
* = D I S P L A Y - O L E D - C L E A R - R A M - B U F F E R =
* =================================================================
*/
static void OLED_I2C_Clear(void)
{
#if (OLED_DRIVER_ADAFRUIT)
oled.clearDisplay();
#endif
#if (OLED_DRIVER_U8G2LIB)
oled.clearBuffer();
#endif
}
/**
* =================================================================
* = D I S P L A Y - O L E D - G E T - G F X - S T R I N G - L E N=
* =================================================================
*/
static mGFX_t OLED_I2C_GetGfxStrWidth(/* const String &buf */ const char* buf)
{
#if (OLED_DRIVER_ADAFRUIT)
int16_t x1, y1;
uint16_t w, h;
oled.getTextBounds("A", (SCREEN_WIDTH/2), (SCREEN_HEIGHT/2), &x1, &y1, &w, &h); //calc width of char
#if(OLED_ADAFRUIT_USE_FONTS)
w += 1; // add additional 1 pixel vertical spacing
#endif
w *= strlen(buf); // calculate number of chars by font pixel width on first char
if (w > SCREEN_WIDTH) w = SCREEN_WIDTH;
return (mGFX_t)w;
#endif
#if (OLED_DRIVER_U8G2LIB)
return (mGFX_t)oled.getStrWidth(buf);
#endif
}
/**
* =================================================================
* = D I S P L A Y - O L E D - G E T - G F X - F O N T - H E I G H T =
* =================================================================
*/
static mGFX_t OLED_I2C_GetGfxFontHeight(void)
{
#if (OLED_DRIVER_ADAFRUIT)
int16_t x1, y1;
uint16_t w, h;
oled.getTextBounds("A", (SCREEN_WIDTH/2), (SCREEN_HEIGHT/2), &x1, &y1, &w, &h); //calc width of char
return (mGFX_t)h;
#endif
#if (OLED_DRIVER_U8G2LIB)
return (mGFX_t)oled.getMaxCharHeight() - 5; // "A"
#endif
}
/**
* =================================================================
* = D I S P L A Y - O L E D - D R A W - A T - C E N T E R =
* =================================================================
*/
static void OLED_I2C_DrawCentreString(/* const String &buf */ const char* buf , mGFX_t x, mGFX_t y)
{
#if (OLED_DRIVER_ADAFRUIT)
int16_t x1, y1;
uint16_t w, h;
oled.getTextBounds(buf, x, y, &x1, &y1, &w, &h); //calc width of new string
oled.setCursor(x - w / 2, y);
oled.print(buf);
#endif
}
/**
* =================================================================
* = D I S P L A Y - O L E D - S E T - T E X T - C U R S O R =
* =================================================================
*/
static void OLED_I2C_SetCursor(TEXT_ALIGN_t _strJ, mGFX_t _strX, mGFX_t _strY, const char* _fmt)
{
#if((OLED_DRIVER_ADAFRUIT) && (!OLED_ADAFRUIT_USE_FONTS))
_strY -= OLED_I2C_GetGfxFontHeight(); // Y offset correction for Adfruit standard fonts
#endif
switch (_strJ) {
case LEFT_JST: _strX = 0; break;
case CENTER_JST: _strX = (SCREEN_WIDTH - OLED_I2C_GetGfxStrWidth(_fmt)) >> 1; break;
case RIGHT_JST: _strX = (SCREEN_WIDTH - OLED_I2C_GetGfxStrWidth(_fmt)); break;
case NO_JST:
default: break;
}
oled.setCursor(_strX, _strY);
}
/**
* =================================================================
* = D I S P L A Y - O L E D - P R I N T - F O R M A T E D - S T R =
* =================================================================
*/
static bool OLED_I2C_DEBUG_SPRINT(Print &out, TEXT_ALIGN_t _strJ, mGFX_t _strX, mGFX_t _strY, const char* _fmt, ... )
{
bool retBit = true;
OLED_I2C_SetCursor(_strJ, _strX, _strY, _fmt);
va_list argv;
va_start(argv, _fmt);
for (int i = 0; _fmt[i] != '\0'; i++) {
if (_fmt[i] == '%') {
// Look for specification of number of decimal places
int places = 2;
if (_fmt[i+1] == '.') i++; //allows %.4f precision like in stdio printf (%4f will still work).
if (_fmt[i+1] >= '0' && _fmt[i+1] <= '9') {
places = _fmt[i+1] - '0';
i++;
}
switch (_fmt[++i]) {
case 'B': out.print(F("0b")); // Fall through intended
case 'b': out.print(va_arg(argv, int), BIN); break;
case 'c': out.print((char) va_arg(argv, int)); break;
case 'd': case 'i': out.print(va_arg(argv, int), DEC); break;
case 'f': out.print(va_arg(argv, double), places); break;
case 'l': out.print(va_arg(argv, long), DEC); break;
case 'o': out.print(va_arg(argv, int) == 0 ? F("Off") : F("On")); break;
case 's': out.print(va_arg(argv, const char*)); break;
case 'S': out.print(va_arg(argv, const String)); break;
case 'X': out.print(F("0x")); // Fall through intended
case 'x': out.print(va_arg(argv, int), HEX); break;
case '%': out.print(_fmt[i]); break;
case 'p':
case 'q': out.print(F("[#]"));
case '0':
case 0 : retBit = !retBit; break;
default: out.print(F("?")); break;
}
} else out.print(_fmt[i]);
}
va_end(argv);
return retBit;
}