// 基於DHT22之溫溼度控制器: 繼電器採高態動作;按鈕採低態觸發
// 使用函式庫https://github.com/RobTillaart/DHTlib
// 改用millis()取代delay()以讀取dht11並顯示在OLED
// https://docs.arduino.cc/built-in-examples/digital/BlinkWithoutDelay
// 使用按鈕開關切換OLED上的溫度顯示為℃或℉
// 使用Pin Change Interrupt: d2為功能選擇;d3為+1(平時為℃/℉切換);d4為-1
// 新增🗲🛇↑↓圖示,可透過DOWN按鈕循迴選擇切換溫、溼度開關是否動作之設定
// 完成主要功能設計,僅剩相關設定值於eeprom的寫入與讀取尚未完成!
#include <dht.h>
#define dhtPin 8 //DHT11的DATA OUT接D8
#include "U8g2lib.h" //U8g2 by oliver
#include "myFont.h" //自建中文字型資料
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
#include "imageData.h"
dht DHT;
float TvalueC, TvalueF, Hvalue; //儲存dht22目前的測量值
unsigned long previousMillis = 0;
const long sampling_period = 3000; //dht11讀取時間間隔(最低不得小於1秒)
const byte funPin=2; // function setting Pin 閥值設定按鈕
const byte incPin=3; // increment ( or temperature unit chaning) Pin 上數+1按鈕
const byte decPin=4; // decrement Pin 下數-1按鈕
int debounceDelay=200; //debounce delay (ms) 除彈跳時間,注意:太小無法有效除開關彈跳
const byte actPinT = 11; //溫度控制開關驅動接腳
const byte actPinH = 9; //溼度控制開關驅動接腳
const byte normalPin = 10;
volatile boolean Tunit = HIGH; //溫度單位選擇: 預設HIGH為攝氏℃; LOW為華氏℉
volatile byte ThresholdSet = 0; //閥值設定選擇(由funPin按鈕): 預設0為平常模式; 1為溫度開關動作閥值設定; 2為溼度開關動作閥值設定
volatile byte ActionSet = 0; //開關功能設定(由decPin按鈕): 預設0為平常模式; 1為溫度開關功能設定; 2為溼度開關功能設定
volatile float TvalueC_act = 30.0; //溫度預設動作閥值 (※注意:因浮點數為32bit而非8bit,須避免讀取8bit後,其他24bit中途被ISR修改之問題)
volatile float TvalueF_act = 86.0; // (本程式不會有問題,因閥值的讀取僅在控制輸出程式段使用,其必定發生在閥值設定結束之後)
volatile float Hvalue_act = 70.0; //溼度預設動作閥值 (https://www.arduino.cc/reference/en/language/variables/variable-scope-qualifiers/volatile/)
volatile byte Tsw = 0; //0停用溫度控制開關; 1低溫動作; 2高溫動作
volatile byte Hsw = 0; //0停用溼度控制開關; 1低溼動作; 2高溼動作
void setup()
{
pinMode(decPin, INPUT_PULLUP);
pinMode(incPin, INPUT_PULLUP);
pinMode(funPin, INPUT_PULLUP);
PCICR |= B00000100; // Enable PCIE2 Bit3 = 1 (Port D)
PCMSK2 |= B00011100; // Enable PCINT18, PCINT19 , and PCINT20 (Pins D2, D3, and D4)
pinMode(actPinT, OUTPUT);
digitalWrite(actPinT, LOW); //溫度開關驅動採高態動作
pinMode(actPinH, OUTPUT);
digitalWrite(actPinH, LOW); //濕度開關驅動採高態動作
pinMode(normalPin, OUTPUT);
digitalWrite(normalPin, LOW);
u8g2.begin();
u8g2.enableUTF8Print(); // 啟用顯示UTF-8編碼字串
u8g2.setContrast(10); // 調整oled亮度0-255
//u8g2.setFlipMode(1); // 旋轉180度顯示
u8g2.firstPage(); //顯示開機畫面
do {
u8g2.drawXBMP( 10, 0, bmp_logoWidth, bmp_logoHeight, bmp_logo);
u8g2.drawXBMP( 52,12, bmp_thermoWidth, bmp_thermoHeight, bmp_thermometer);
u8g2.drawXBMP(114,12, bmp_dropWidth, bmp_dropHeight, bmp_raindrop);
u8g2.setFont(myFont);
u8g2.setCursor(68, 26);
u8g2.print("溫溼度");
u8g2.setCursor(60, 48);
u8g2.print("感測控制");
} while (u8g2.nextPage());
delay(3000); //開機畫面持續3秒
}
void loop()
{
////////////// 獲得目前溫溼度測量值 //////////////
unsigned long currentMillis = millis(); //改用millis()取代delay()以讀取dht11
if (currentMillis - previousMillis >= sampling_period) {
previousMillis = currentMillis;
get_temp_humi();
}
/////////////// 更新OLED顯示畫面 ///////////////
if ((ThresholdSet==0)) { //正常模式
if (ActionSet==0) OLED_display(TvalueC, TvalueF, Hvalue); //顯示目前溫、溼度測量值
else OLED_display(TvalueC_act, TvalueC_act, Hvalue_act);
}
else if ((ThresholdSet==1)) {
OLED_display(TvalueC_act, TvalueF_act, Hvalue); //顯示溫度設定值、溼度測量值
}
else if ((ThresholdSet==2)) {
OLED_display(TvalueC, TvalueF, Hvalue_act); //顯示溫度測量值、溼度設定值
}
//////////////// 控制動作輸出 ////////////////
if ((ThresholdSet!=0)||(ActionSet!=0)) { //安全起見,進行閥值或動作設定時不能有控制輸出; 此亦可同時
digitalWrite(actPinT, LOW); // 避免32bit浮點閥值在讀取過程後24bit被ISR修改的問題
digitalWrite(actPinH, LOW);
}
else {
if (Tsw==0) {
digitalWrite(actPinT, LOW);
}
else if (Tsw==1) {
if (TvalueC<=TvalueC_act) digitalWrite(actPinT, HIGH);
else digitalWrite(actPinT, LOW);
}
else if (Tsw==2) {
if (TvalueC>=TvalueC_act) digitalWrite(actPinT, HIGH);
else digitalWrite(actPinT, LOW);
}
if (Hsw==0) {
digitalWrite(actPinH, LOW);
}
else if (Hsw==1) {
if (Hvalue<=Hvalue_act) digitalWrite(actPinH, HIGH);
else digitalWrite(actPinH, LOW);
}
else if (Hsw==2) {
if (Hvalue>=Hvalue_act) digitalWrite(actPinH, HIGH);
else digitalWrite(actPinH, LOW);
}
}
if (digitalRead(actPinT) && digitalRead(actPinH)) digitalWrite(normalPin, HIGH);
else digitalWrite(normalPin, LOW);
delay(300);
}
ISR(PCINT2_vect) {
if (digitalRead(funPin) == LOW) { //閥值設定選擇: 0退出閥值設定; 1溫度閥值設定; 2溼度閥值設定
if (ActionSet==0){ //進行動作設定時,無法切換閥值設定
if (debounced()) ThresholdSet = (ThresholdSet+1)%3;
}
}
else if (digitalRead(incPin) == LOW) {
if (ThresholdSet==0) { //正常模式下改變溫度單位: 0攝氏; 1華氏
if (ActionSet==0){
if (debounced()) Tunit = !Tunit;
}
else if (ActionSet==1) {
if (debounced()) Tsw = (Tsw+1)%3;
}
else if (ActionSet==2) {
if (debounced()) Hsw = (Hsw+1)%3;
}
}
else if (ThresholdSet==1){ //溫度動作閥值設定(遞增1)
if (Tunit==HIGH) {
if (debounced() && (TvalueC_act<50.0)) TvalueC_act = TvalueC_act+1.0;
}
else {
if (debounced() && (TvalueF_act<122.0))TvalueF_act = TvalueF_act+1.0;
}
}
else if (ThresholdSet==2) { //溼度動作閥值設定(遞增1)
if (debounced() && (Hvalue_act<90.0)) Hvalue_act = Hvalue_act+1.0;
}
}
else if (digitalRead(decPin) == LOW) {
if (ThresholdSet==0){ //正常模式下才能動作設定選擇: 0退出動作設定; 1溫度動作設定; 2溼度動作設定
if (debounced()) ActionSet = (ActionSet+1)%3;
}
else if (ThresholdSet==1){ //溫度動作閥值設定(遞減1)
if (Tunit==HIGH) {
if (debounced() && (TvalueC_act>0.0)) TvalueC_act = TvalueC_act-1.0;
}
else {
if (debounced() && (TvalueC_act>32.0)) TvalueF_act = TvalueF_act-1.0;
}
}
else if (ThresholdSet==2) { //溼度動作閥值設定(遞減1)
if (debounced() && (Hvalue_act>20.0)) Hvalue_act = Hvalue_act-1.0;
}
}
}
boolean debounced() { //消除開關彈跳副程式
static unsigned long lastMillis=0; //record last millis
unsigned long currentMillis=millis(); //get current elapsed time
if ((currentMillis-lastMillis) > debounceDelay) {
lastMillis=currentMillis; //update lastMillis with currentMillis
return true; //debounced
}
else return false; //not debounced
}
void get_temp_humi() { //獲取溫溼度數據子函數
int dht_reading = DHT.read22(dhtPin); //讀取DHT22的溫濕度感測值
TvalueC = DHT.temperature;
TvalueF = (TvalueC*1.8)+32.0; //攝身轉華氏
Hvalue = DHT.humidity;
}
void OLED_display(float TC, float TF, float Hu) { //將溫濕度數據顯示在OLED
byte thrSet = ThresholdSet; //注意:須以thrSet(而非ThresholdSet)為功能選擇指標
byte actSet = ActionSet; // 避免畫面更新時ThresholdSet被ISR中途修改而產生殘影
boolean tempUnit = Tunit;
byte tempSW = Tsw;
byte humiSW = Hsw;
u8g2.firstPage(); //第一分頁資料傳給SSD1306
do {
////////////////////// 溫度區顯示規劃 //////////////////////
if ((thrSet==1) || (actSet==1)) u8g2.setDrawColor(1); else u8g2.setDrawColor(0);
u8g2.drawBox(0, 0, 64, 64);
if ((thrSet==1) || (actSet==1)) u8g2.setDrawColor(0); else u8g2.setDrawColor(1);
u8g2.drawXBMP( 1, 2, bmp_thermoWidth, bmp_thermoHeight, bmp_thermometer);
u8g2.setFont(myFont); // 使用自訂的中文字型
u8g2.setFontPosTop(); // 注意:因須重設字型,須連帶呼叫本函式,但會使
// 每個字體的點陣起始位置改以左上角為原點
if (actSet==0) {
u8g2.setCursor(18, 6);
if ((thrSet==0)||(thrSet==2)) u8g2.print("溫度");
else if (thrSet==1) u8g2.print("閥值");
}
else {
if (tempSW==0) u8g2.drawXBMP( 17, 2, bmp_stopActWidth, bmp_stopActHeight, bmp_stopAct);
else if (tempSW==1) u8g2.drawXBMP( 18, 2, bmp_downArrowWidth, bmp_downArrowHeight, bmp_downArrow);
else if (tempSW==2) u8g2.drawXBMP( 18, 2, bmp_upArrowWidth, bmp_upArrowHeight, bmp_upArrow);
u8g2.drawXBMP( 36, 2, bmp_lightningWidth, bmp_lightningHeight, bmp_lightning);
}
u8g2.setCursor(54, 46);
if(tempUnit) u8g2.print("℃"); else u8g2.print("℉");
u8g2.setFont(u8g2_font_logisoso34_tn); //變更字型以顯示溫濕度數值
u8g2.setFontPosTop();
u8g2.setCursor(9, 21); //溫度僅顯示四捨五入至整數位
if(tempUnit) u8g2.print(TC,0); else u8g2.print(TF,0);
////////////////////// 溼度區顯示規劃 //////////////////////
if ((thrSet==2) || (actSet==2)) u8g2.setDrawColor(1); else u8g2.setDrawColor(0);
u8g2.drawBox(65, 0, 64, 64);
if ((thrSet==2) || (actSet==2)) u8g2.setDrawColor(0); else u8g2.setDrawColor(1);
u8g2.drawXBMP( 65, 2, bmp_dropWidth, bmp_dropHeight, bmp_raindrop);
u8g2.setFont(myFont); // 使用自訂的中文字型
u8g2.setFontPosTop(); // 注意:因須重設字型,須連帶呼叫本函式,但會使
// 每個字體的點陣起始位置改以左上角為原點
if (actSet==0) {
u8g2.setCursor(81, 6);
if ((thrSet==0)||(thrSet==1)) u8g2.print("溼度");
else if (thrSet==2) u8g2.print("閥值");
}
else {
if (humiSW==0) u8g2.drawXBMP( 81, 2, bmp_stopActWidth, bmp_stopActHeight, bmp_stopAct);
else if (humiSW==1) u8g2.drawXBMP( 82, 2, bmp_downArrowWidth, bmp_downArrowHeight, bmp_downArrow);
else if (humiSW==2) u8g2.drawXBMP( 82, 2, bmp_upArrowWidth, bmp_upArrowHeight, bmp_upArrow);
u8g2.drawXBMP(100, 2, bmp_lightningWidth, bmp_lightningHeight, bmp_lightning);
}
u8g2.setCursor(113,46);
u8g2.print("%");
u8g2.setFont(u8g2_font_logisoso34_tn); //變更字型以顯示溫濕度數值
u8g2.setFontPosTop();
u8g2.setCursor(73, 21); //濕度僅顯示四捨五入至整數位
u8g2.print(Hu,0);
////////////////////// 顯示區框線繪製 //////////////////////
u8g2.setDrawColor(1);
u8g2.drawFrame(0, 0, 128, 64); //畫出螢幕框
u8g2.drawVLine(64, 0, 64); //畫出中央分隔線
} while (u8g2.nextPage()); //繼續傳下一個分頁資料傳給SSD1306
}