ATtiny85 Weather Station with SD1331 OLED Display

1,782 views

Introduction

Weather matters to all of us; good weather changes our moods, mind, and feelings. However, sometimes unexpected weather ruins our plan. It’s either a trip with our friends or a party with relatives if the weather is not better; it can spoil everything. And, therefore it is necessary to predict the weather, but for this, we need weather stations. So, here in this article, we will discuss How to make Weather Station using Attiny85 & SSD1306 OLED.

The weather station is basically the device used for weather observation and data collection. First and foremost, it gathers the most crucial information: temperature, atmospheric pressure, humidity, and so on. Here we are making a basic weather station with a Tiny sensor BME280.

Overview of BME280

The BME280 is a digital sensor that measures pressure, humidity, and temperature. This module is made up of metal-lid LGA packages that are exceedingly small. It’s built for low current consumption (3.6 A), long-term stability, and EMC robustness. Its humidity sensor component has a quick response time and good accuracy over a wide temperature range, making it ideal to use
It delivers great performance in applications such as humidity and pressure measurement, and it is used in emerging applications because of its specifications.

Specifications

Dimensions40mm x20mm x15mm
WeightG.W 10g
Supply Voltage5V or 3.3V
Current Consumption0.4 mA
Humidity Accuracy±3%
Temperature Range-40 to 85
Temperature Accuracy±1
Air pressure Range300 – 1100 hPa
Air Pressure Accuracy±1.0 hPa

Hardware Required

S. NoComponentsValueQty
1.ModuleAttiny851
2.OLED Display SSD13061
3.Atmospheric Pressure Sensor BME2801
4.Ceramic Capacitors 0.1uF2
5.Resistors 33K2
6.Power Supply 3-5V1
7.Jumper wires

Circuit Diagram

Connection Table

BME280OLED DisplayATtiny85
VINVccVcc
GNDGNDGND
SCLPB2
SDAPB0
SCLPB4
SDAPB1
RESVcc
DCGND
CSPB3

Code

#include <Wire.h>
#include <TinyBME280.h>

// ATtiny85 Pins
int const sda = 0;
int const scl = 2;

int const data = 1;
int const cs = 3;
int const clk = 4;

// Character set for text - stored in program memory
const uint8_t CharMap[107][6] PROGMEM = {
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 
{ 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00 }, 
{ 0x00, 0x07, 0x00, 0x07, 0x00, 0x00 }, 
{ 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00 }, 
{ 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00 }, 
{ 0x23, 0x13, 0x08, 0x64, 0x62, 0x00 }, 
{ 0x36, 0x49, 0x56, 0x20, 0x50, 0x00 }, 
{ 0x00, 0x08, 0x07, 0x03, 0x00, 0x00 }, 
{ 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00 }, 
{ 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00 }, 
{ 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x00 }, 
{ 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00 }, 
{ 0x00, 0x80, 0x70, 0x30, 0x00, 0x00 }, 
{ 0x08, 0x08, 0x08, 0x08, 0x08, 0x00 }, 
{ 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 }, 
{ 0x20, 0x10, 0x08, 0x04, 0x02, 0x00 }, 
{ 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00 }, 
{ 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00 }, 
{ 0x72, 0x49, 0x49, 0x49, 0x46, 0x00 }, 
{ 0x21, 0x41, 0x49, 0x4D, 0x33, 0x00 }, 
{ 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00 }, 
{ 0x27, 0x45, 0x45, 0x45, 0x39, 0x00 }, 
{ 0x3C, 0x4A, 0x49, 0x49, 0x31, 0x00 }, 
{ 0x41, 0x21, 0x11, 0x09, 0x07, 0x00 }, 
{ 0x36, 0x49, 0x49, 0x49, 0x36, 0x00 }, 
{ 0x46, 0x49, 0x49, 0x29, 0x1E, 0x00 }, 
{ 0x00, 0x00, 0x14, 0x00, 0x00, 0x00 }, 
{ 0x00, 0x40, 0x34, 0x00, 0x00, 0x00 }, 
{ 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 }, 
{ 0x14, 0x14, 0x14, 0x14, 0x14, 0x00 }, 
{ 0x00, 0x41, 0x22, 0x14, 0x08, 0x00 }, 
{ 0x02, 0x01, 0x59, 0x09, 0x06, 0x00 }, 
{ 0x3E, 0x41, 0x5D, 0x59, 0x4E, 0x00 }, 
{ 0x7C, 0x12, 0x11, 0x12, 0x7C, 0x00 }, 
{ 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00 }, 
{ 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00 }, 
{ 0x7F, 0x41, 0x41, 0x41, 0x3E, 0x00 }, 
{ 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00 }, 
{ 0x7F, 0x09, 0x09, 0x09, 0x01, 0x00 }, 
{ 0x3E, 0x41, 0x41, 0x51, 0x73, 0x00 }, 
{ 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00 }, 
{ 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00 }, 
{ 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00 }, 
{ 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00 }, 
{ 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00 }, 
{ 0x7F, 0x02, 0x1C, 0x02, 0x7F, 0x00 }, 
{ 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00 }, 
{ 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00 }, 
{ 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00 }, 
{ 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00 }, 
{ 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00 }, 
{ 0x26, 0x49, 0x49, 0x49, 0x32, 0x00 }, 
{ 0x03, 0x01, 0x7F, 0x01, 0x03, 0x00 }, 
{ 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00 }, 
{ 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00 }, 
{ 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00 }, 
{ 0x63, 0x14, 0x08, 0x14, 0x63, 0x00 }, 
{ 0x03, 0x04, 0x78, 0x04, 0x03, 0x00 }, 
{ 0x61, 0x59, 0x49, 0x4D, 0x43, 0x00 }, 
{ 0x00, 0x7F, 0x41, 0x41, 0x41, 0x00 }, 
{ 0x02, 0x04, 0x08, 0x10, 0x20, 0x00 }, 
{ 0x00, 0x41, 0x41, 0x41, 0x7F, 0x00 }, 
{ 0x04, 0x02, 0x01, 0x02, 0x04, 0x00 }, 
{ 0x40, 0x40, 0x40, 0x40, 0x40, 0x00 }, 
{ 0x00, 0x03, 0x07, 0x08, 0x00, 0x00 }, 
{ 0x20, 0x54, 0x54, 0x78, 0x40, 0x00 }, 
{ 0x7F, 0x28, 0x44, 0x44, 0x38, 0x00 }, 
{ 0x38, 0x44, 0x44, 0x44, 0x28, 0x00 }, 
{ 0x38, 0x44, 0x44, 0x28, 0x7F, 0x00 }, 
{ 0x38, 0x54, 0x54, 0x54, 0x18, 0x00 }, 
{ 0x00, 0x08, 0x7E, 0x09, 0x02, 0x00 }, 
{ 0x18, 0xA4, 0xA4, 0x9C, 0x78, 0x00 }, 
{ 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00 }, 
{ 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00 }, 
{ 0x20, 0x40, 0x40, 0x3D, 0x00, 0x00 }, 
{ 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00 }, 
{ 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00 }, 
{ 0x7C, 0x04, 0x78, 0x04, 0x78, 0x00 }, 
{ 0x7C, 0x08, 0x04, 0x04, 0x78, 0x00 }, 
{ 0x38, 0x44, 0x44, 0x44, 0x38, 0x00 }, 
{ 0xFC, 0x18, 0x24, 0x24, 0x18, 0x00 }, 
{ 0x18, 0x24, 0x24, 0x18, 0xFC, 0x00 }, 
{ 0x7C, 0x08, 0x04, 0x04, 0x08, 0x00 }, 
{ 0x48, 0x54, 0x54, 0x54, 0x24, 0x00 }, 
{ 0x04, 0x04, 0x3F, 0x44, 0x24, 0x00 }, 
{ 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00 }, 
{ 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00 }, 
{ 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00 }, 
{ 0x44, 0x28, 0x10, 0x28, 0x44, 0x00 }, 
{ 0x4C, 0x90, 0x90, 0x90, 0x7C, 0x00 }, 
{ 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00 }, 
{ 0x00, 0x08, 0x36, 0x41, 0x00, 0x00 }, 
{ 0x00, 0x00, 0x77, 0x00, 0x00, 0x00 }, 
{ 0x00, 0x41, 0x36, 0x08, 0x00, 0x00 }, 
{ 0x02, 0x01, 0x02, 0x04, 0x02, 0x00 }, 
{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 },
{ 0x38, 0x44, 0x44, 0x38, 0x00, 0x00 }, // Tiny digits 4x5
{ 0x00, 0x08, 0x7C, 0x00, 0x00, 0x00 },
{ 0x48, 0x64, 0x54, 0x48, 0x00, 0x00 },
{ 0x28, 0x44, 0x54, 0x28, 0x00, 0x00 },
{ 0x1C, 0x10, 0x78, 0x10, 0x00, 0x00 },
{ 0x5C, 0x54, 0x54, 0x24, 0x00, 0x00 },
{ 0x38, 0x54, 0x54, 0x20, 0x00, 0x00 },
{ 0x44, 0x24, 0x14, 0x0C, 0x00, 0x00 },
{ 0x28, 0x54, 0x54, 0x28, 0x00, 0x00 },
{ 0x08, 0x14, 0x14, 0x78, 0x00, 0x00 },
{ 0x00, 0x06, 0x09, 0x06, 0x00, 0x00 } // degree symbol
};

const int Degree = 138;
const int Tiny = 128;

// OLED 96x64 colour display **********************************************

// Initialisation sequence for OLED module
int const InitLen = 6;
const unsigned char Init[InitLen] PROGMEM = {
  0xA0,       // Driver remap and colour depth
  0x22,       // COM split, flip horizontal
  0xA8, 0x3F, // Multiplex
  0xAD, 0x8E, // External supply
};

// Send a byte to the display
void Send (uint8_t d) {  
  for (uint8_t bit = 0x80; bit; bit >>= 1) {
    PINB = 1<<clk;                        // clk low
    if (d & bit) PORTB = PORTB | (1<<data); else PORTB = PORTB & ~(1<<data);
    PINB = 1<<clk;                        // clk high
  }
}

void InitDisplay () {
  PINB = 1<<cs;                           // cs low
  for (uint8_t c=0; c<InitLen; c++) Send(pgm_read_byte(&Init[c]));
  PINB = 1<<cs;                           // cs high
}

// Display off = 0, on = 1
void DisplayOn (uint8_t on) {
  PINB = 1<<cs;                           // cs low
  Send(0xAE + on);
  PINB = 1<<cs;                           // cs high
}

// Graphics **********************************************

// Global plot parameters
uint8_t x0 = 0;
uint8_t y0 = 0;
uint8_t ForeR = 0x3F, ForeG = 0x3F, ForeB = 0x3F;
uint8_t BackR = 0x00, BackG = 0x00, BackB = 0x00;
uint8_t Scale = 1; // Text size - 2 for big characters

// Clear display
void ClearDisplay () {
  PINB = 1<<cs;                           // cs low
  Send(0x25);                             // Clear Window
  Send(0); Send(0); Send(95); Send(63);
  PINB = 1<<cs;                           // cs high
  delay(1);
}

void MoveTo (uint8_t x, uint8_t y) {
  x0 = x; y0 = y;
}

// Draw line to (x,y) in foreground colour
void DrawTo (uint8_t x, uint8_t y) {
  PINB = 1<<cs;                           // cs low
  Send(0x21);                             // Draw Line
  Send(x0); Send(y0); Send(x); Send(y);
  Send(ForeR); Send(ForeG); Send(ForeB);
  PINB = 1<<cs;                           // cs high
  x0 = x; y0 = x;
}

// Plot a point at (x,y)
void PlotPoint (uint8_t x, uint8_t y) {
  MoveTo(x, y);
  DrawTo(x, y);
}

// Draw a rectangle in foreground colour optionally filled with background colour
void DrawRect (boolean filled, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) {
  PINB = 1<<cs;                           // cs low
  Send(0x26); Send(filled);               // Enable fill
  Send(0x22);                             // Draw rectangle
  Send(x1); Send(y1); Send(x2); Send(y2);
  Send(ForeR); Send(ForeG); Send(ForeB);
  Send(BackR); Send(BackG); Send(BackB);
  PINB = 1<<cs;                           // cs high
  delay(1);
}

// Plot character in foreground colour
void PlotChar (uint8_t ch) {
  PINB = 1<<cs;                           // cs low
  for (uint8_t c = 0 ; c < 6; c++) {      // Column range
    uint8_t bits = pgm_read_byte(&CharMap[ch-32][c]);
    uint8_t r = 0;
    while (bits) {
      while ((bits & 1) == 0) {r++; bits = bits>>1; }
      uint8_t on = (7-r)*Scale;
      while ((bits & 1) != 0) {r++; bits = bits>>1; }
      uint8_t off = (7-r)*Scale+1;
      for (int i=0; i<Scale; i++) {
        uint8_t h = x0+c*Scale+i;
        Send(0x21);                         // Draw line
        Send(h); Send(y0+on); Send(h); Send(y0+off);
        Send(ForeR); Send(ForeG); Send(ForeB);
      }
    }
  }
  PINB = 1<<cs;                             // cs high
  x0 = x0+6*Scale;
}

// Plot text from program memory
void PlotText(PGM_P p) {
  while (1) {
    char c = pgm_read_byte(p++);
    if (c == 0) return;
    PlotChar(c);
  }
}

// Widgets **********************************************

int Widget;
enum TextColour { BLACK,RED,GREEN,YELLOW,BLUE,MAGENTA,CYAN,WHITE,OLIVE,ORANGE };

void SetFore (int colour) {
  ForeR=0x3F*(colour&1); ForeG=0x3F*(colour>>1&1)+0x1F*(colour>>3&1); ForeB=0x3F*(colour>>2&1);
}

// Draws a titled frame and sets x and y to frame origin
void Frame (PGM_P title, uint8_t *x, uint8_t *y) {
  *x = (Widget%2) * 48;
  *y = 32 - (Widget/2) * 32;
  uint8_t shade;
  if (((Widget+1)/2)%2) shade = 0x04; else shade = 0x0C;
  BackR=BackG=BackB=ForeR=ForeG=ForeB=shade;
  DrawRect(1, *x, *y, (*x)+47, (*y)+31);
  ForeR=ForeG=ForeB=0x3F;
  Scale=1; MoveTo((*x)+1, (*y)+23); PlotText(title);
  Widget=(Widget+1) & 3;
}

// Plot integer with suffix
void PlotInteger (int number, PGM_P suffix) {
  uint8_t len = strnlen_P(suffix, 4);
  boolean dig = false;
  unsigned int j=1000;
  for (int i=0; i<len; i++) j=j/10;
  if (number<0) { PlotChar('-'); j=j/10; }
  do {
    char c = (abs(number)/j) % 10 + '0';
    if (c == '0' && !dig && j != 1) c = ' '; else dig = true;
    PlotChar(c); j=j/10;
  } while (j);
  PlotText(suffix);
}

// Displays an integer with an optional suffix
void IntegerWidget (PGM_P title, int value, PGM_P suffix, int colour) {
  uint8_t x0, y0;
  Frame(title, &x0, &y0);
  Scale=2;
  SetFore(colour);
  MoveTo(x0+1, y0+1);
  PlotInteger(value, suffix);
}

// Displays a number with one decimal place. Range is -99 to 999 displayed "-9.9" to "99.9"
void NumberWidget (PGM_P title, int value, int colour) {
  uint8_t x0, y0;
  int sign=1;
  Frame(title, &x0, &y0);
  Scale=2;
  SetFore(colour);
  if (value < 0) { sign = -1; value = -value; }
  uint8_t tens = value/100;
  MoveTo(x0+1,y0+1);
  if (sign == -1) PlotChar('-'); else if (tens == 0) PlotChar(' '); else PlotChar(tens+'0');
  PlotChar(value/10%10+'0');
  PlotChar('.'); PlotChar(value%10+'0');
}
  
// Displays bar; range is 0 to 100 displayed "0" to "5"
void BarWidget (PGM_P title, int value) {
  uint8_t x0, y0;
  Frame(title, &x0, &y0);
  // Bar background
  BackG=ForeG=0x3F; BackR=BackB=ForeR=ForeB=0x08;
  uint8_t bar = value*4/10;
  DrawRect(1, x0+bar+4, y0+4, x0+44, y0+10);
  // Bar value
  BackR=ForeR=0x3F; BackG=BackB=ForeG=ForeB=0x08;
  DrawRect(1, x0+4, y0+4, x0+bar+4, y0+10);
  // Numbers
  ForeR=ForeG=0x3F; ForeB=0x00;
  for (int i=0; i<=5; i++) {
    MoveTo(x0+i*8+2,12+y0); PlotChar('\200'+i);
  }
}

// Displays dial; range is 0 to 100 displayed "0" to "5"
void AnalogueWidget (PGM_P title, int value) {
  uint8_t x0, y0;
  Frame(title, &x0, &y0);
  ForeR=0x3F; ForeG=0x3F; ForeB=0x00;  // Yellow
  const int Delta2 = 16;
  int x = -(15<<9), y = 0;
  for (int i = 0; i<=100; i++) {
    MoveTo((x>>9)+24+x0, (y>>9)+1+y0);
    x = x + ((y>>9) * Delta2);
    y = y - ((x>>9) * Delta2);
    DrawTo((x>>9)+24+x0, (y>>9)+1+y0);
    if (i == value) {
      MoveTo((x>>9)+24+x0, (y>>9)+1+y0);
      DrawTo(24+x0, 1+y0);
    }
    if (i%20 == 0) {
      MoveTo((x>>9)+24+x0, (y>>9)+1+y0);
      DrawTo((x>>9)-(x>>11)+24+x0, (y>>9)-(y>>11)+1+y0);
    }
  }
  // Plot tiny digits 0 to 5
  MoveTo(3+x0,0+y0); PlotChar(Tiny+0);
  MoveTo(5+x0,9+y0); PlotChar(Tiny+1);
  MoveTo(14+x0,16+y0); PlotChar(Tiny+2);
  MoveTo(30+x0,16+y0); PlotChar(Tiny+3);
  MoveTo(39+x0,9+y0); PlotChar(Tiny+4);
  MoveTo(42+x0,0+y0); PlotChar(Tiny+5);
}

// Setup **********************************************

void setup() {
  // Define pins
  DDRB = 1<<clk | 1<<cs | 1<<data;  // All outputs
  PORTB = 1<<clk | 1<<cs;           // clk and cs high
  InitDisplay();
  ClearDisplay();
  DisplayOn(1);
  // Set up BME280
  Wire.begin();
  BME280setI2Caddress(0x76);
  BME280setup();
}

void loop () {
  int battery = min(22528/analogRead(12),100);
  NumberWidget(PSTR("Temp.\212C"), BME280temperature()/10, ORANGE);
  IntegerWidget(PSTR("Pressure"), BME280pressure()/100, PSTR(""), MAGENTA);
  IntegerWidget(PSTR("Humidity"), BME280humidity()/100, PSTR("%"), CYAN);
  AnalogueWidget(PSTR("Voltage"), battery);
  delay(10000);
}

Working Explanation

This Weather Station is built on a Bosch BME280 sensor, which is ideal for a home weather station because it can measure temperature, pressure, and humidity all in one device. The Weather Station employs a low-cost 96×64 OLED display with 64K colors and an SPI interface to display the readings. Connect the circuit according to the connection table.

Since the graphics library is solely dependent on commands rather than data, therefore, the DC pin is tied low and does not need to be attached to an ATtiny85 pin. This frees up two I/O pins on the ATtiny85 for use by the BME280 sensor’s I2C interface. The chip select is held high by the resistor from the display’s CS pin to prevent the display from being influenced by the ISP signals while programming the ATtiny85. On power-up, the other 33k resistor and the 0.1F capacitor guarantee that the display is properly reset.

So, on power-up, the sensor takes the readings from the environment, a process that in the controller according to the code and displays them on an OLED display

Applications and Uses

Agriculture

Farms, and indeed agriculture on any scale, rely on weather data to direct many parts of their operations, such as the Irrigation of crops To help with planting decisions, keep an eye on the growing circumstances. Thus, this can help in agriculture.

Outdoor Sports

Many sports and leisure activities rely on the weather. Modern weather stations, with their capacity to automatically upload live weather data to a display, allow players to examine actual weather conditions before deciding to start.

Weather Enthusiast

Information for anyone interested in keeping a close eye on the weather, whether