Arduino:I2C 16x2 LCD 攝氏溫度、濕度與時間顯示電路


Clock with Thermo-hygrometer Using Arduino and I2C 16x2 LCD


功能

如圖一與圖二所示,指撥開關 DIP Switch ON 時 LCD 顯示攝氏溫度、相對濕度與時間;指撥開關 DIP Switch OFF 時則可調整時間。其中 Hour 按鈕開關用來調整時,Minute 按鈕開關則可以調整分。
LCD 顯示攝氏溫度、相對濕度與時間
圖一    LCD 顯示攝氏溫度、相對濕度與時間



電路接線圖

Arduino:I2C 16x2 LCD 顯示攝氏溫度、濕度與時間的電路接線圖
圖二    Arduino:I2C 16x2 LCD 顯示攝氏溫度、濕度與時間的電路接線圖




LCD 與感測器操作所需函數庫

LiquidCrystal_I2C.hDHT.h 是兩個必要引入的標頭檔。為了能成功引入這兩個標頭檔,我們可在 https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library  網址下載 Arduino-LiquidCrystal-I2C-library-master 資料夾,並在 https://github.com/adafruit/DHT-sensor-library 下載 DHT-sensor-library 資料夾,如圖三所示再將此二資料夾複製到電腦的 C: > 使用者 > user > 文件 > Arduino > libraries 資料夾下 ( 若 libraries 資料夾不存在,則自行建立) ,即可在程式中引用上述的兩個標頭檔。


圖三    libraries 資料夾



LCD 與感測器操作所需的建構式與方法說明

LiquidCrystal_I2C(uint8_t lcd_addr, uint8_t lcd_cols, uint8_t lcd_rows, uint8_t charsize = LCD_5x8DOTS)

這是 LiquidCrystal_I2C 類別提供的建構式,用來建構基於該類別的物件,物件建立後才可使用該類別所提供的方法 (method)。

參數說明:
  • lcd_addr: 若 LCD 模組採用的是 PCF8754 晶片提供的 I2C 介面,位址設置為 0x27;若採用的是 PCF8574AN,則位址設置為 0x3F
  • lcd_cols:LCD 的行數 (Columns),在此為 16。
  • lcd_rows:LCD 的列數 (Rows),在此為 2。
  • charsize:LCD 顯示字元大小,預設為 5x8 像素。

DHT(uint8_t pin, uint8_t type, uint8_t count = 6)

這是 DHT 類別提供的建構式。

參數說明:
  • pin:指定連接到 DHT11 、DHT21 或 DHT22 的 Arduino 接腳。
  • type:指定採用的 DHT 系列溫溼度感測器類型,本電路採用的是 DHT11。 
  • count:感測器數量,預設值為 6,是一個可以不使用的參數,當不使用在編譯時也不會引發編譯器發出警告訊息。

void DHT物件.begin(uint8_t usec = 55)

這是 DHT 類別提供的方法,用來初始化 DHT 物件。

參數說明:
  • usec:視情況在讀取 DHT 前會傳入的上拉 (pull-up) 時間,單位為微秒,預設值為 55。

float DHT物件.readTemperature(bool S = false, bool force = false)

這是 DHT 類別提供的方法,用來讀取 DHT 溫濕度感測器偵測到的溫度值,回傳溫度值的資料型態為浮點數。

參數說明:
  • S:設定刻度 (Scale) 用,當為 true 時,表示讀取華氏溫度值 (Fahrenheit);反之為 false 時,則表示讀取攝氏溫度值 (Celcius)。
  • force:設定是否在強制模式,預設為 false。

float DHT物件.readHumidity(bool force = false)

這是 DHT 類別提供的方法,用來讀取 DHT 溫濕度感測器偵測到的濕度值,回傳濕度值的資料型態為浮點數。

參數說明:
  • force:設定是否在強制模式,預設為 false。

void LiquidCrystal_I2C物件.begin()

這是 LiquidCrystal_I2C 類別提供的方法,用來初始化 LiquidCrystal_I2C 物件。


void LiquidCrystal_I2C物件.clear()

這是 LiquidCrystal_I2C 類別提供的方法,用來清除目前顯示的所有字元。下一次列印/寫入的操作將從螢幕的第一個位置開始。

void LiquidCrystal_I2C物件.createChar(uint8_t  location, uint8_t charmap[])

這是 LiquidCrystal_I2C 類別提供的方法,用來將自建的字元碼儲存進字元產生器唯讀記憶體 (character generator ROM, CGROM) 中。

參數說明:
  • location:自建的字元碼在 CGROM 中所要儲存的起始位址,若是要儲存 5x8 大小字元,會使用到 0~7 這 8 個位址;若是要儲存 5x10 大小字元,則會使用到 0~3 這 4 個位址。
  • charmap[]:存放自建的字元碼的陣列位址。

size_t LiquidCrystal_I2C物件.write(uint8_t value)

這是 LiquidCrystal_I2C 類別提供的方法,用來將 CGROM 中起始位址為 value 的資料列印在螢幕上。執行後回傳值為一無號整數 1。


void LiquidCrystal_I2C物件.setCursor(uint8_t col, uint8_t row)

這是 LiquidCrystal_I2C 類別提供的方法,用來將螢幕上的游標定位至第 col 行第 row 列位置。


void LiquidCrystal_I2C物件.printstr(const char c[])

這是 LiquidCrystal_I2C 類別提供的方法,用來將字串列印在游標定位處。由於 printstr() 方法執行時,其實是呼叫 print() 來完成列印工作,因此也可以直接使用 print() 方法。


自建的度符號字元碼

如圖四所示:

圖四    度符號的 5x8 字元碼



程式碼

 C++
#include <DHT.h>
#include <LiquidCrystal_I2C.h>
int I2C_Address = 0x27; /* 宣告 I2C 位址 */
LiquidCrystal_I2C  I2C_LCD(I2C_Address, 16, 2); /* 建構 LiquidCrystal_I2C 物件為每列
                                                                                        最多 16 字元,2 列 */
int dhtDataPin = A0;
int DIP_Switch = 7;
int hourSetPb = 6;
int minuteSetPb = 5;
DHT myDHT(dhtDataPin, DHT11); 

byte degreeCharacter[] = {0b00110,      /* 度符號的自建字元 */
                                           0b01001,
                                           0b01001,
                                           0b00110,
                                           0b00000,
                                           0b00000,
                                           0b00000,
                                           0b00000};
float tempInDegreesCelsius = 0.0, humidity = 0.0;
int displayData[] = {0,0,0,0,0,0}; /* 顯示暫存區初值對應的顯示格式為 00:00:00 */
                    /* displayData[5]:時的十位數; displayData[4]:時的個位數 */
                    /* displayData[3]:分的十位數; displayData[2]:分的個位數 */
                    /* displayData[1]:秒的十位數; displayData[0]:秒的個位數 */
unsigned long CLK = 0;
byte seconds = 0;
byte minutes = 0;
byte hours = 0;
void minSetPB();
void hrSetPB();
bool SW;
void displayLCD();  /* 宣告 LCD 顯示函式 */
int hourLast = 0;
int minuteLast = 0;

void setup() {
  myDHT.begin();
  I2C_LCD.begin(); /* 啟動 LCD */
  I2C_LCD.clear(); /* 清除螢幕 */
  I2C_LCD.createChar(0, degreeCharacter); 
  I2C_LCD.setCursor(2,0);
  I2C_LCD.printstr(":");
  I2C_LCD.setCursor(5,0);
  I2C_LCD.printstr(":");
  I2C_LCD.setCursor(15,0);
  I2C_LCD.printstr("C");
  I2C_LCD.setCursor(14,0); 
  I2C_LCD.write(0); 
  I2C_LCD.setCursor(14,1); 
  I2C_LCD.printstr("%");
  displayLCD();
  CLK = millis();   /* 取得系統時間 */
}

void loop(){
  SW = digitalRead(DIP_Switch); /* 讀取指撥開關狀態 */
  while(SW)  /* 指撥開關 ON 時 SW = "0",計時模式;指撥開關 OFF 時 SW = "1",調整模式 */
  {                 
    seconds = 0;        /* 秒數歸零 */
    displayData[1] = 0;
    displayData[0] = 0
    SW = digitalRead(DIP_Switch); /* 持續讀取指撥開關狀態 */
    minSetPB();            /* 調整分 */
    hrSetPB();              /*  調整時 */
    displayLCD();       /*  LCD 顯示 */
    hourLast = 0;
    minuteLast = 0;
    CLK = millis();     /* 取得系統時間 */
  }
   
  if (hourLast == 2 && minuteLast == 50){
    seconds++;  /* 修正秒數誤差 (測試發現每 2 小時 50 分約慢 1 秒) */
    displayData[1] = seconds/10; // 取得秒之十位數
    displayData[0] = seconds%10; // 取得秒之個位數    
    displayLCD(); // 顯示時間
    hourLast = 0;
    minuteLast = 0;
  }

  while(millis() - CLK < 999); /* 等待 1 秒時間到 */
  CLK = millis(); // 儲存目前系統時間
  if (++seconds == 60){
    seconds = 0;  
    minuteLast++;
    if (++minutes == 60){
      minutes = 0;
      hourLast++;
      if(++hours == 24){
        hours = 0;
      }
    } 
    if(minuteLast == 60
      minuteLast = 0;
  }  
  if(seconds == 0){
    displayData[3] = minutes/10/* 取得分之十位數 */
    displayData[2] = minutes%10/* 取得分之個位數 */
  }
  if(minutes == 0){
    displayData[5] = hours/10/* 取得時之十位數 */
    displayData[4] = hours%10/* 取得時之個位數 */
  }
  displayData[1] = seconds/10/* 取得秒之十位數 */
  displayData[0] = seconds%10/* 取得秒之個位數 */   
  displayLCD(); /* LCD 顯示 */ 

void minSetPB()
{
  int buttonStatus = digitalRead(minuteSetPb);
  if(buttonStatus == 0){
    delay(200);
    buttonStatus = digitalRead(minuteSetPb);
    if (buttonStatus == 1){
      seconds = 0;
      minutes++;
      if(minutes == 60)
        minutes = 0;
      displayData[3] = minutes/10; /* 取得分之十位數 */
      displayData[2] = minutes%10/* 取得分之個位數 */
      displayLCD();
    }
  }  
}

void hrSetPB()
{
  int buttonStatus = digitalRead(hourSetPb);
  if(buttonStatus == 0){
    delay(200);
    buttonStatus = digitalRead(hourSetPb);
    if (buttonStatus == 1){
       hours++;
      if (hours == 24)
        hours = 0;
      displayData[5] = hours /10;
      displayData[4] = hours%10;
      displayLCD();
    }       
  }
}

void displayLCD()
  tempInDegreesCelsius = myDHT.readTemperature(0);
  humidity  = myDHT.readHumidity();
  I2C_LCD.setCursor(9,0);
  I2C_LCD.print(tempInDegreesCelsius);  
  I2C_LCD.setCursor(9,1);
  I2C_LCD.print(humidity);
  I2C_LCD.setCursor(0,0);
  I2C_LCD.print(displayData[5]);
  I2C_LCD.print(displayData[4]); 
  I2C_LCD.setCursor(3,0);
  I2C_LCD.print(displayData[3]);
  I2C_LCD.print(displayData[2]);
  I2C_LCD.setCursor(6,0);
  I2C_LCD.print(displayData[1]);
  I2C_LCD.print(displayData[0]);
}





實際 LCD 顯示

如圖五所示:


圖五    實際 LCD 顯示結果




留言

這個網誌中的熱門文章

三段式電子開關電路

陣列 (C++)

首數、尾數與位數

分壓偏壓 BJT 放大電路的直流分析及其近似解的條件

MOSFET 共汲極放大電路 (源極隨耦器) 小訊號分析

為什麼理想的 OPA 電壓放大器有虛短路與虛斷路現象

具有倒數計時自動回復功能的行人穿越道號誌控制電路