สถานีตรวจวัดอากาศ ATtiny85 หรือ Weather Station ทำหน้าที่หลัก คือ วัดและตรวจสอบสภาพอากาศ ผ่านเครื่องวัดต่างๆ เช่น เซนเซอร์ DHT22 โมดูลเซนเซอร์วัดความชื้นและอุณหภูมิในตัวเดียว ใช้ไฟเลี้ยง 3 V – 5.5 V แล้วนำมาประมวลผล ด้วย ไมโครคอนโทรลเลอร์ จิ๋ว ATtiny85 แล้วแสดงผลที่จอ OLED 96×64 พร้อมสี 64K โดยการอินเทอร์เฟซแบบ SPI
DHT22 Temperature and Humidity Sensor + PCB
ขั้นตอนการทํางาน
1 : ทำเครื่องโปรแกรม AVR เพื่อใช้อัพโหลดโปรแกรม
ขั้นตอนการใช้บอร์ด Arduino UNO เป็น เครื่องเขียนโปรแกรม ตามลิงค์ด้านล่าง
2 : ติดตั้งไลบรารี TroykaDHT
ไลบรารี TroykaDHT รองรับเซ็นเซอร์ DHT11, DHT21, DHT22 บน ATtiny85 และโปรเซสเซอร์ ATtiny อื่น ๆ
เปิดโปรแกรม Arduino IDE ไปที่ Sketch -> Include Library -> Manage Library…
ที่ช่องค้นหา พิมพ์ Troyka จะพบ TroykaDHT แล้วคลิก Install
INSTALLED แสดงการติดตั้งสำเร็จ แล้วปิดหน้าต่างลงไป
ตรวจสอบที่ Sketch -> Include Library จะพบ ไลบรารี TroykaDHT เพิ่มเข้ามาใน Arduino IDE ของเรา
3 : อัพโหลดโปรแกรมให้กับ ATtiny85
เปิดโปรแกรม Arduino IDE เขียนโปรแกรมและอัพโหลดโค้ดด้านล่างนี้ ไปที่ ATtiny85
// ATtiny85 DHT22
#include <TroykaDHT.h>
DHT dht(2, DHT22); // select DHT22 and comm on PB2
// ATtiny85 OLED
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 0 to 999 displayed "0.0" to "99.9"
void NumberWidget (PGM_P title, int value, int colour) {
uint8_t x0, y0;
Frame(title, &x0, &y0);
Scale = 2;
SetFore(colour);
uint8_t tens = value / 100;
MoveTo(x0 + 1, y0 + 1);
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 Delta = 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) * Delta);
y = y - ((x >> 9) * Delta);
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() {
// TinyWireM.begin();
// Define pins
DDRB = 1 << clk | 1 << cs | 1 << data; // All outputs
PORTB = 1 << clk | 1 << cs; // clk and cs high
InitDisplay();
ClearDisplay();
DisplayOn(1);
dht.begin();
}
void loop()
{
dht.read();
switch (dht.getState()) {
case DHT_OK:
NumberWidget(PSTR("Temp.\212C"), dht.getTemperatureC() * 10 , ORANGE);
NumberWidget(PSTR("Temp.\212F"), dht.getTemperatureF() * 10 , MAGENTA);
NumberWidget(PSTR("Humidity"), dht.getHumidity() * 10 , CYAN);
BarWidget(PSTR("Current"), 50);
break;
case DHT_ERROR_CHECKSUM:
//PlotText(PSTR("Checksum error"));
break;
case DHT_ERROR_TIMEOUT:
ClearDisplay();
PlotText(PSTR("Time out error"));
break;
case DHT_ERROR_NO_REPLY:
ClearDisplay();
PlotText(PSTR("Sensor not connected"));
break;
}
delay(1000);
}
4 : เชื่อมต่ออุปกรณ์เข้ากับ ATtiny85 ตามวงจรด้านล่าง
5 : ผลลัพธ์การทำงาน
ผลลัพธ์การทำงาน จะแสดงอุณหภูมิ องศาเซลเซียส °C , แสดงอุณหภูมิ องศาฟาเรนไฮต์ °F , ค่าชื้นในอากาศ Humidity แสดงว่าประสบความสำเร็จแล้ว
สถานีตรวจวัดอากาศ ATtiny85 ด้วย DHT22
6 : อุปกรณ์ที่ใช้ในโปรเจค
- 1. ATtiny85 ATTINY85-20PU 8-bit AVR
- 2. Jumper (M2M) 20cm Male to Male
- 3. OLED Display 0.95 inch 96×64 SSD1331 Module
- 4. รีซิสเตอร์ 33K Ohm 1/4W
- 5. Breadboard 830 Point MB-102
- 6. 100NF 0.1UF Film Capacitor จำนวน 2 ชิ้น
- 7. DHT22 Temperature and Humidity Sensor + PCB
- 8. รางถ่าน 18650 1 ก้อน
- 9. ถ่านชาร์จ 18650 Panasonic NCR18650B 3.7v 3400mAh
credit : http://www.technoblogy.com/show?2EDJ