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
Dimensions | 40mm x20mm x15mm |
Weight | G.W 10g |
Supply Voltage | 5V or 3.3V |
Current Consumption | 0.4 mA |
Humidity Accuracy | ±3% |
Temperature Range | -40 to 85 |
Temperature Accuracy | ±1 |
Air pressure Range | 300 – 1100 hPa |
Air Pressure Accuracy | ±1.0 hPa |
Hardware Required
S.no | Components | Value | Qty |
---|---|---|---|
1. | Module | Attiny85 | 1 |
2. | OLED Display | SSD1306 | 1 |
3. | Atmospheric Pressure Sensor | BME280 | 1 |
4. | Ceramic Capacitors | 0.1uF | 2 |
5. | Resistors | 33K | 2 |
6. | Power Supply | 3-5V | 1 |
7. | Jumper wires | – | – |
Circuit Diagram
Connection Table
BME280 | OLED Display | ATtiny85 |
VIN | Vcc | Vcc |
GND | GND | GND |
SCL | – | PB2 |
SDA | – | PB0 |
– | SCL | PB4 |
– | SDA | PB1 |
– | RES | Vcc |
– | DC | GND |
– | CS | PB3 |
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