top of page

DIY Reader with 128x64 SSD1306 OLED for APAS T1 Soil Moisture Sensor (Part 3: Arduino Code)

Updated: Mar 28

In this article (Part 3), I'm going to provide the required Arduino codes that you can use with the electronic design explained in Part 1. In Part 2 of the article, I provided design files for a 3D printed enclosure and instructions on how to assemble it. In a separate article, I also described how to connect the APAS T1 soil moisture and temperature sensor to Arduino. Almost all of that code is used with reader, so instead of repeating the content of that article I just discuss lines/blocks of codes that are added to it. These codes are going to drive the OLED display.


Please make sure to read those posts first, if this is your first time visiting our blog or haven't read them yet.


Using the codes that are explained here, you'll be able to display both graphics (logo, battery icon, etc) and text on a 128 x 64 SSD1306 OLED display.



Libraries and Declarations

You need to add one library in order for your code to work. These libraries are listed below:


#include <U8g2lib.h>

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
U8G2_SSD1306_128X64_NONAME_F_HW_I2C display(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
  
  const byte Logo_Height = 32;
  const byte Logo_Width  = 32; 
  boolean blnBattBlink = false;
  byte displayCount = 0;

Until last year, I was using the Adafruit Adafruit_SSD1306 library in my projects, but with OLED displays that were not sold by Adafruit. Apparently, not all SSD1306 displays are created equal and due to compatibility issues, I was no longer able to use that library. So after a bit of search I switched to the U8g2lib library. The new library has been working great and I haven't had any issues since.



Byte Arrays for Graphics

To be able to display graphics on the OLED display, they first need to be converted to byte arrays. This is done very easily using a variety of tools that are available online. I created my graphics using Paint and then used this online tool to convert them to byte arrays.


The following code allows you to display the DurUntash Lab logo on the OLED display.

//Byte array of Logo bitmap of 32 x 32 px: 
static const unsigned char PROGMEM logo_bmp [] = {
0x00, 0xe0, 0x0f, 0x00, 0x00, 0x7e, 0x7e, 0x00, 0x00, 0x07, 0xe0, 0x00,
0xc0, 0x03, 0x80, 0x03, 0xe0, 0x00, 0x00, 0x06, 0x70, 0x80, 0x00, 0x0c,
0x38, 0xe0, 0x0e, 0x18, 0x18, 0xf0, 0x0f, 0x30, 0x0c, 0x00, 0x00, 0x20,
0x0e, 0x0c, 0x30, 0x60, 0x06, 0xfc, 0x3f, 0x40, 0x02, 0xfe, 0x7f, 0xc0,
0x02, 0x03, 0xc0, 0x80, 0xe3, 0x03, 0xc0, 0x87, 0xe1, 0xff, 0xff, 0x8f,
0xf1, 0xff, 0xff, 0x8f, 0xf9, 0x80, 0x01, 0x9e, 0xf9, 0x80, 0x01, 0xbe,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xc0, 0x03, 0xe0,
0x06, 0xc0, 0x03, 0xe0, 0xfe, 0x7f, 0xfe, 0x7f, 0x0c, 0x00, 0x00, 0x60,
0x08, 0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x38, 0x30, 0x00, 0x00, 0x08,
0x60, 0x00, 0x00, 0x0e, 0xc0, 0x00, 0x00, 0x03, 0x80, 0x07, 0xc0, 0x03,
0x00, 0x0e, 0xf0, 0x00, 0x00, 0xf8, 0x3f, 0x00   
};

Using the following codes, you can display a battery icon that shows the charge left:

//Byte array of battery indicator bitmap of 16 x 8 px:  
static const unsigned char PROGMEM batt_empty [] = {
0xff, 0x7f, 0x01, 0x40, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0,
0x01, 0x40, 0xff, 0x7f   
};

static const unsigned char PROGMEM batt_3left [] = {
0xff, 0x7f, 0x0f, 0x40, 0x0f, 0xc0, 0x0f, 0xc0, 0x0f, 0xc0, 0x0f, 0xc0,
0x0f, 0x40, 0xff, 0x7f   
};

static const unsigned char PROGMEM batt_6left [] = {
0xff, 0x7f, 0x7f, 0x40, 0x7f, 0xc0, 0x7f, 0xc0, 0x7f, 0xc0, 0x7f, 0xc0,
0x7f, 0x40, 0xff, 0x7f   
};

static const unsigned char PROGMEM batt_9left [] = {
0xff, 0x7f, 0xff, 0x43, 0xff, 0xc3, 0xff, 0xc3, 0xff, 0xc3, 0xff, 0xc3,
0xff, 0x43, 0xff, 0x7f   
};

static const unsigned char PROGMEM batt_12left [] = {
0xff, 0x7f, 0xff, 0x5f, 0xff, 0xdf, 0xff, 0xdf, 0xff, 0xdf, 0xff, 0xdf,
0xff, 0x5f, 0xff, 0x7f   
};

static const unsigned char PROGMEM batt_full [] = {
0xff, 0x7f, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x7f, 0xff, 0x7f   
};

static const unsigned char PROGMEM batt_charging [] = {
0xff, 0x7f, 0x7b, 0x7c, 0x77, 0xf8, 0x6f, 0xf2, 0x4f, 0xf6, 0x1f, 0xee,
0x3f, 0x5e, 0xff, 0x7f 
};

The following array allows you to show an unhappy emoji whenever your sensor readings are above or below certain threshold:

static const unsigned char PROGMEM unhappyImoji [] = {
0x00, 0x7c, 0x00, 0x00, 0x80, 0xff, 0x03, 0x00, 0xe0, 0x01, 0x0f, 0x00,
0x70, 0x00, 0x1c, 0x00, 0x18, 0x00, 0x30, 0x00, 0x1c, 0x00, 0x30, 0x00,
0x0c, 0x01, 0x60, 0x00, 0x86, 0x83, 0xc3, 0x00, 0xc6, 0x83, 0xc7, 0x00,
0x82, 0x83, 0x83, 0x00, 0x03, 0x00, 0x80, 0x01, 0x03, 0x00, 0x80, 0x01,
0x03, 0x00, 0x80, 0x01, 0x03, 0x00, 0x80, 0x01, 0x03, 0x7c, 0x80, 0x01,
0x02, 0xef, 0x81, 0x00, 0x86, 0x83, 0xc1, 0x00, 0x86, 0x01, 0xc3, 0x00,
0x8c, 0x00, 0x62, 0x00, 0x0c, 0x00, 0x70, 0x00, 0x38, 0x00, 0x30, 0x00,
0x70, 0x00, 0x1c, 0x00, 0xc0, 0x01, 0x0f, 0x00, 0x80, 0xff, 0x03, 0x00,
0x00, 0x7a, 0x00, 0x00
}; 


Routines to Display Texts and Graphics

Below, I'm going to provide example routines that you can incorporate into your Arduino code to displays texts (sensor data) and graphics (e.g. battery icon). Feel free to modify to suit your application.


void displayLogo()
{ 
  display.clearBuffer();   // Clear the buffer 
  display.setFont(u8g2_font_luBS12_tf);
  display.setCursor(26, 15);  
  display.println(F("AVAN CT"));
  
  // Show bitmap logo
  display.drawXBM(0, 20, Logo_Width, Logo_Height, logo_bmp);   
  display.setFont(u8g2_font_luBS08_tf);     
  display.setCursor(33, 33);     
  display.println(F(" DurUntash Lab"));
  display.setCursor(33, 48);  
  display.println(F(" San Diego, CA"));
 
  display.sendBuffer();
}

The following routines create four different tabs for three different sensor types. Here, these sensors are the APAS T1 soil moisture and temperature sensor, HITA E0 electrical conductivity, TDS and temperature sensor, and INSHU LWS leaf wetness and temperature sensor. The fourth tab is reserved for a new sensor or when it's unknown. You can creates more tabs as you wish.


The sensor values (variables) to be displayed are as the following:

  1. VWC_P: volumetric water content from the APAS T1

  2. EC25: electrical conductivity at 25°C

  3. intTDS: total disolved solids (TDS)

  4. LW_P: leaf wetness

  5. TpF: temperature in °F

  6. TpC: temperature in °C

Please also note that the battery charge level (%) and icon are updated at the same time as the sensor values.

void displaySensorValues(byte bytSensor)
{ 
  display.clearBuffer();   // Clear the buffer 

  if (fltSensorCode[bytSensor] == 100.0) { // APAS T1
    statusDryWet(bytSensor);
    display.setFont(u8g2_font_lubB18_tr);
    displayFunction (0, 30, 1, '\0', '\0', '\0', '\0', VWC_P[bytSensor], 1, true);
    //displayFunction (0, 30, 1, '\0', '\0', '\0', '\0', RAW[bytSensor], 1, true);
    display.setFont(u8g2_font_lubB12_tr); 
    display.print(F(" % "));  // display string
    display.setFont(u8g2_font_lubB08_tr);  
    if (bytEnableCalibWC == 1) {
      display.setCursor(50, 64);
      display.print(F("c")); // Indicates that moisture measurements are calibrated
    }
    if (bytEnableTempComp == 1) {
      display.setCursor(57, 64);
      display.print(F("t")); // Indicates that moisture measurements are temp-compensated
    }
  
  } else if (fltSensorCode[bytSensor] == 101.0) {// HITA E0
    display.setFont(u8g2_font_lubB14_tr);
    displayFunction (0, 17, 1, '\0', '\0', '\0', '\0', EC25[bytSensor], 2, true);
    display.setFont(u8g2_font_lubB12_tr); 
    display.print(F(" "));  
    display.print(F("dS/m"));  // 1 mho/m = 1 mmho/cm = 1 mS/cm = 1 dS/m = 1000 µS/cm 
    display.setFont(u8g2_font_lubB14_tr);
    displayFunction (0, 41, 1, '\0', '\0', '\0', '\0', intTDS[bytSensor], 0, true);   
    display.setFont(u8g2_font_lubB12_tr);  
    display.print(F(" "));  
    display.print(F("ppm"));  // 1 mho/m = 1 mmho/cm = 1 mS/cm = 1 dS/m = 1000 µS/cm 
       
  } else if (fltSensorCode[bytSensor] == 103.0) {// INSHU LWS
    display.setFont(u8g2_font_lubB18_tr);
    displayFunction (0, 30, 1, '\0', '\0', '\0', '\0', LW_P[bytSensor], 1, true);
    display.setFont(u8g2_font_lubB12_tr); 
    display.print(F(" %"));  // display string
    
  } else {// Unknown
    display.setFont(u8g2_font_lubB14_tr);
    displayFunction (0, 30, 1, '\0', '\0', '\0', '\0', RAW[bytSensor], 1, true);
    display.setFont(u8g2_font_lubB12_tr); 
    display.print(F(" R"));  // display string
  }

  display.setFont(u8g2_font_helvR08_tf);
  if (bytFahrenheit == 1) {
    TpF[bytSensor] = ConvertCtoF(TpC[bytSensor]);
    //displayFunction (35, 54, 1, '\0', '\0', '\0', '\0', TpF[bytSensor], 1, true); 
    displayFunction (0, 64, 1, '\0', '\0', '\0', '\0', TpF[bytSensor], 1, true);    
  } else {
    //displayFunction (35, 54, 1, '\0', '\0', '\0', '\0', TpC[bytSensor], 1, true); 
    displayFunction (0, 64, 1, '\0', '\0', '\0', '\0', TpC[bytSensor], 1, true);
  }
  degreeCF(1, 1);  

  BatteryLevelIndicator();
  display.sendBuffer();
}

void displayFunction (byte cursorX, byte cursorY, byte textSize, char Letter1, char Letter2, char Letter3, char Letter4, float fltValue, byte percision, boolean blnValueTrue)
{ 
  if (blnValueTrue == true) display.setCursor(cursorX, cursorY);
  display.setFontMode(textSize);    
  if (Letter1 != '\0') display.print(Letter1);
  if (Letter2 != '\0') display.print(Letter2);
  if (Letter3 != '\0') display.print(Letter3);
  if (Letter4 != '\0') display.print(Letter4);
  if (blnValueTrue == true) display.print(fltValue, percision);
}

void degreeCF(byte textSize1, byte textSize2)
{
  const char DEGREE_SYMBOL[] = { 0xB0, '\0' };
  display.setFontMode(textSize1);  
  display.setFont(u8g2_font_helvR08_tf);
  display.print(F(" "));
  display.print(DEGREE_SYMBOL);  
  if (bytFahrenheit == 1) {
    display.print(F("F"));
  } else { // if (bytFahrenheit == 0)
    display.print(F("C"));    
  }  
}

void statusDryWet(byte bytSensor) 
{   
  if (bytAutoIrriEnable == 1){     
    if (VWC_P[bytSensor] < bytAutoIrriDryThreshold || VWC_P[bytSensor] > bytAutoIrriWetThreshold)
    {  
       display.drawXBM(103, 8, 25, 25, unhappyImoji);      
    } else {
      
    }
  }
}

In addition to displaying the charge level, the battery indicator routine is able to display a charging status when a charger is present. To be able to use this feature, you need to make sure that your code can measure both the battery voltage (intVoltPercent variable) and USB voltage (USBVolt variable).


void BatteryLevelIndicator()
{
    // Display battery charge left
    display.setFontMode(1);      //
    display.setFont(u8g2_font_helvR08_tf);       
    if (intVoltPercent == 100) 
    {
      display.setCursor(79, 64);
    } else {
      display.setCursor(86, 64);
    }      
    display.print(intVoltPercent, 1);
    //display.print(battValue, 1);     
    display.print(F("%"));  // display string
 
    if (USBVolt >= 4.5)
    {
      // Let user know charger is connected
      if (intVoltPercent < 100)
      {
        display.drawXBM(110, 56, 16, 8, batt_charging);
      } else {
        display.drawXBM(110, 56, 16, 8, batt_full);  
      }         
           
    } else {      
      // Battery values
      if (intVoltPercent < 5)
      {
        // Empty battery!
        if (blnBattBlink == true)
        {
          // Show nothing!
          blnBattBlink = false;
        } else {
          // Draw Batt Cover
          display.drawXBM(110, 56, 16, 8, batt_empty);
          blnBattBlink = true;
        }    
      }  
      // Battery values
      if (intVoltPercent >= 5 && intVoltPercent < 35)
      {
        display.drawXBM(110, 56, 16, 8, batt_3left);
      }
      if (intVoltPercent >= 35 && intVoltPercent < 55)
      {
        display.drawXBM(110, 56, 16, 8, batt_6left);
      }
      if (intVoltPercent >= 55 && intVoltPercent < 75)
      {
        display.drawXBM(110, 56, 16, 8, batt_9left);
      }
      if (intVoltPercent >= 75 && intVoltPercent < 95)
      {
        display.drawXBM(110, 56, 16, 8, batt_12left);
      }
      if (intVoltPercent >= 95)
      {
       display.drawXBM(110, 56, 16, 8, batt_full); 
      }
    }   
}

Please feel free to leave comments in the comments section below if you had any questions.




0 comments
bottom of page