prettyprint

2020年4月10日 星期五

Arduino Alarm Clock without RTC Module, with temperature and humidity using DHT11|| CTC Mode || 時鐘、鬧鐘(不使用RTC模組)與溫濕度計

本實驗以Arduino pro mini製作時鐘(含鬧鐘功能)與溫濕度器,嘗試使用最少模組,以ATMega 328P timer1產生1Hz中斷做為計時器,不接外加RTC模組。
使用元件:

  1. LCD 1602A不含I2C模組
  2. DHT11溫濕度感應器
  3. 8歐姆1W喇叭
  4. 10電位計(可變電阻)
  5. 按鈕
  6. 1K、10K電阻與0.1uF電容
  7. Arduino pro mini 16M/5V
根據ATMega 328P datasheet TCCR1A, TCCR1B除頻與Waveform如下:


若是arduino por mini 16M/5V選用除頻(prescaler)1024,CS2:0為100,8M/3.3V除頻256,CS2:0為100,WGM選CTC mode,WGM12:10為100
至於DHT11模組資料讀取,不使用DHT11 Library,因library讀取資料時會disable all interrupts,因本實驗主要以timer1中斷做為計時器用,因此重新根據DHT11 datasheet重新撰寫程式碼。
線路圖如下:



完整程式碼:
#include <LiquidCrystal.h>

// LCD 連接Arduino腳位
const int rs = 4, en = 5, d4 = 6, d5 = 7, d6 = 8, d7 = 9;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

#define DHTPIN 12                     //DHT11
#define LCD_BRIGHTNESS_PIN 11         //調整LCD亮度
#define TOTAL_SETTING_ITEM 9          //設定項目總數
#define TOTAL_ALARM_SETTING_ITEM 4    //設定鬧鐘項目
#define READ_CYCLE 5                  //讀取溫溼度(每隔幾秒)
#define SELECT_BUTTON_PIN A0          //模式選擇紐
#define SET_BUTTON_PIN A1             //設定紐
#define  tone1     3830               //播放聲音長度(microseconds)
#define  tone2     3038    
#define  tone3     2550 

volatile byte dat[5];               //溫溼度40bits)
volatile int hour =0, minute = 0, second=0, year=2020, mon=1, day=1; //時間變數
volatile int alarmHour=0, alarmMinute=1;
volatile bool alarmPlay=false;
volatile int screenoff=120, soffcount=0;          //螢幕休眠時間
volatile bool screenIsOn=true, isAlarmSet=false;
volatile int DISPLAY_MODE = 0; // 0: Wd m/d h:m, 1:TIME(T:H) 2:DATE-WEEKDAY(TIME) 3:SET?
volatile const int MODE_NUM=4;  
volatile bool inSetMode = false;

volatile int isr_num=0;
volatile bool ISR_TRIGGER=false;
String sWeekDay="";
int bright=2;
bool playing=false;

const String weekday[7] = {
  "Sun","Mon","Tue","Wed","Thu","Fri","Sat"
};
volatile const int monthdays[12] = {
  31,28,31,30,31,30,31,31,30,31,30,31
  };
int SETTING_ITEM=0;//0:year,1:month,2:day,3:hour, 4:minute,5:brightness, 6:contrast, 7: alarmSet, 8:exit
int ALARM_SETTING_ITEM=0; //0: ON-OFF 1:Hour, 2:Min
volatile byte musicChar[] = {
  B00111,
  B00101,
  B00100,
  B00100,
  B01100,
  B11100,
  B11000,
  B00000
};



  
int tones[3] = { tone1, tone2, tone3}; 
int speakerOut = 2;

// PLAY TONE  ==============================================
void playTone(int duration, int count) {
  for (int i = 1; i< count; i++) {
    digitalWrite(speakerOut, HIGH);
    delayMicroseconds(duration/2);
    digitalWrite(speakerOut, LOW);
    delayMicroseconds(duration/2);
  }                
}
// TONES  ==========================================

void setup() {
  lcd.begin(16, 2);
  lcd.clear();
  
  noInterrupts();
  TCCR1A=0;
  TCCR1B=0;
  TCNT1=0;
  TCCR1A = _BV(COM1A1);
 
  if (F_CPU==16000000) {      //Arduino pro mino 16M timer 1 CTC Mode,頻率1秒作為計時器
    OCR1A=15625;
    TCCR1B = _BV(WGM12)|_BV(CS12)|_BV(CS10); //CTC mode
  } else {  //Arduino pro mino 8M timer 1 CTC Mode 
    OCR1A=31250;
    TCCR1B = _BV(WGM12)|_BV(CS12);
  }
  
  TIMSK1 = _BV(OCIE1A);

  interrupts();
  
  pinMode(DHTPIN, OUTPUT);
  pinMode(LCD_BRIGHTNESS_PIN, OUTPUT);
  pinMode(A0,INPUT);
  pinMode(A1,INPUT);
  pinMode(speakerOut,OUTPUT);

  analogWrite(LCD_BRIGHTNESS_PIN,25);
  lcd.createChar(0,musicChar);

  sWeekDay =weekday[getWeekDay(year, mon, day)];
  delay(1000);
}
bool isLeapYear(int year) {
  if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) return true;
  return false;
}

int getWeekDay(int year, int month, int day) {  // base on 2020-01-01 Wed
  unsigned long days = 0;
  if (year < 2020) return -1;
  for (int i = 2020; i < year; i++) {
    days += 365;
    if (isLeapYear(i)) days++;
  }
  for (int i=1; i < month; i++) {
    days += monthdays[i-1];
    if (isLeapYear(year) && i == 2) days++;
  }
  
  days += day;
  return ((days+2) % 7);
}

byte readByte() {//讀取溫溼度1byte
  byte data=0;
  unsigned int count=0;
  for (int i = 0; i < 8; i++) {
    while(!digitalRead(DHTPIN)) {
      delayMicroseconds(1);
    }
    delayMicroseconds(30);
    if (digitalRead(DHTPIN)) {
      data |= 1 << (7-i);
      while(digitalRead(DHTPIN)) {
        delayMicroseconds(1);
        if (count > 70) {
          return data;
        }
        count++;
      }
    } 
  }
  return data;
}
bool getHumTemp() {//讀取溫溼度
  unsigned long cnt = 0;
 //每隔READ_CYCLE秒數讀取一次DHT11數值
  if (isr_num==READ_CYCLE-1) {
      pinMode(DHTPIN, INPUT_PULLUP);
      delay(1);
      pinMode(DHTPIN, OUTPUT);
      digitalWrite(DHTPIN, LOW);
   
      delay(25);
    
      digitalWrite(DHTPIN,HIGH);
      delayMicroseconds(38);
      
      pinMode(DHTPIN, INPUT);

      for (cnt = 0; cnt < 100000 && digitalRead(DHTPIN); cnt++) {
        delayMicroseconds(1);
      }
      if (cnt == 100000) {
        //Serial.println("Error: still staying HIGH for 100ms");
        delay(500);
        return false;
      }
      for (cnt = 0; cnt < 80 && !digitalRead(DHTPIN); cnt++) {
        delayMicroseconds(1);
      }
      
      if (digitalRead(DHTPIN) == LOW) {
        //Serial.println("Error: not ready to start sending");
       
        return false;
      }
      
      for (cnt = 0; cnt < 80 && digitalRead(DHTPIN); cnt++) {
        delayMicroseconds(1);
      }
      
      if (digitalRead(DHTPIN) == HIGH) {
        //Serial.println("Error: staying HIGH too long");
        //delay(1000);
        return false;
      }
      for (int i=0; i < 5; i++) {
        dat[i] = readByte();
      }
      
      unsigned int chsum = 0;
      for (int i = 0; i < 4; i++) {
        chsum += int(dat[i]);
      }
      if (chsum % 256 != int(dat[4])) { //檢查第五byte check sum
        return false;
      }
      isr_num=0;
      return true;
  }
  return false;
  
}
//每秒一次中斷計算秒數
ISR(TIMER1_COMPA_vect) {
  second++;
  
  minute = minute + (second/60);
  second %= 60;
  hour = (hour+(minute/60))%24;
  minute %= 60;
  isr_num = (isr_num+1) % READ_CYCLE;
  if (hour == 0 && minute == 0 && second==0) {
    day++;
    int md = monthdays[mon-1];
    if (isLeapYear(year) && mon == 2) {
      md++;
    }
    if (day > md) {
      mon++;
      day -= md;
      if (mon > 12) {
        year++;
        mon=1;
      }
    }
    sWeekDay=weekday[getWeekDay(year, mon,day)];
    if (DISPLAY_MODE==2) {
      lcd.setCursor(0,1);
      lcd.print(year);
      lcd.print("/");
      if(mon < 10) lcd.print("0");
      lcd.print(mon);
      lcd.print("/");
      if(day < 10) lcd.print("0");
      lcd.print(day);
      lcd.print("  ");
      lcd.print(sWeekDay);
    }
    
  }

    if (!inSetMode) {
      
      if(DISPLAY_MODE==0) {
        lcd.setCursor(0,0);
        lcd.print(sWeekDay);
        lcd.print(" ");
        lcd.print(mon);
        lcd.print("/"); 
        if(day < 10) lcd.print("0");
        lcd.print(day);
        lcd.print(" ");
        lcd.print(hour);
        lcd.print(":");
        if (minute < 10) lcd.print("0");
        lcd.print(minute);
        lcd.print("   ");
        
      }  else {
        lcd.setCursor(0,0);
        lcd.print("    ");
        lcd.setCursor(4,0);
        if (hour < 10) lcd.print(" ");
        lcd.print(hour);
        lcd.print(":");
        if (minute < 10) lcd.print("0");
        lcd.print(minute);
        lcd.print(":");
        if (second < 10) lcd.print("0");
        lcd.print(second);
        lcd.print("    ");
      }
      
      
      if (isAlarmSet) {
        if ( minute == alarmMinute && hour == alarmHour && second == 0) {
            alarmPlay=true;
         }
        lcd.setCursor(15,0);
        lcd.write(byte(0));
      }
           
    }
    if (screenIsOn) {
      soffcount++;
      if (soffcount > screenoff) {
        digitalWrite(LCD_BRIGHTNESS_PIN,LOW);
        soffcount=0;
        screenIsOn = false;
      } 
    }
  ISR_TRIGGER = true;
}


void enterSetMode() {
  inSetMode=true;
  display_setting_item();
}
void display_setting_item() {
      lcd.setCursor(0,0);
      lcd.print("Setting...     ");
      lcd.setCursor(0,1);
      lcd.print("                ");
      lcd.setCursor(0,1);
      switch(SETTING_ITEM) {
        case 0:
          lcd.print("Year? ");
          lcd.print(year);
          break;
        case 1:
          lcd.print("Month? ");
          lcd.print(mon);
          break;
        case 2:
          lcd.print("Day? ");
          lcd.print(day);
          break;
        case 3:
          lcd.print("Hour? ");
          lcd.print(hour);
          break;
        case 4:
          lcd.print("Minute? ");
          lcd.print(minute);
          break;
        case 5:
          lcd.print("Bright? ");
          lcd.print(bright);
          break;
        case 6:
          lcd.print("LCD Off(sec)?");
          lcd.print(screenoff);
          break;
        case 7:
          lcd.print("Alarm? ");
          
          break;
        case 8:
          //if (!inSetMode) 
          lcd.print("Exit?");
           break;
      }
}

void SettingItem(){
  soffcount=0;
  switch(SETTING_ITEM) {
        case 0:
          year = (year+1);
          if (year > 2050) year=2020;
          break;
        case 1:
          mon = (mon+1) % 12;
          if (mon == 0) mon=12;
          break;
        case 3:
          hour = (hour+1) % 24;
          break;
        case 4:
          minute = (minute+1) % 60;
          second = 0;
          break;
        case 5:
          bright = (bright+1) % 16;
          analogWrite(LCD_BRIGHTNESS_PIN, bright*16);
          break;
        case 6:
          screenoff = screenoff+10;
          if(screenoff > 300) screenoff=60;
          break;
        case 7:
          setAlarmParam();
          break;
        case 8:
          inSetMode=false;
          DISPLAY_MODE=0;
          lcd.setCursor(0,0);
          lcd.print("                ");
          lcd.setCursor(0,1);
          lcd.print("                ");
          sWeekDay =weekday[getWeekDay(year, mon, day)];
          SETTING_ITEM=0;
          break;
        case 2:
            int monday=1;
            if (isLeapYear(year) && mon == 2){
              monday=29;
            } else {
              monday = monthdays[mon-1];
            }
            day = (day+1) % monday; //    note 
            if (day == 0) day=monday;
          break;
      }
      
      if(inSetMode) {
        display_setting_item();
      }
}
void setAlarmParam() {
  bool setting=true;
  delay(500);
  while(setting) {
    lcd.setCursor(7,1);
    switch (ALARM_SETTING_ITEM) {
      case 0:
      if (isAlarmSet) {
        lcd.print("OFF");
        lcd.print("     ");
        lcd.setCursor(15,0);
        lcd.write(byte(0));
     } else {
        lcd.print("ON");
        lcd.print("      ");
        lcd.setCursor(15,0);
        lcd.print(" ");
      }
      break;
      case 1:
      lcd.print("Hour:");
      lcd.print(alarmHour);
      lcd.print("  ");
      break;
      case 2:
      lcd.print("Minute:");
      if (alarmMinute < 10) lcd.print("0");
      lcd.print(alarmMinute);
      break;
      case 3:
      //Serial.println(ALARM_SETTING_ITEM);
      lcd.print("Finish?");
      lcd.print("  ");
      break;
      
    }
    if(digitalRead(SELECT_BUTTON_PIN) == LOW) {
      ALARM_SETTING_ITEM = (ALARM_SETTING_ITEM+1) % TOTAL_ALARM_SETTING_ITEM;
      while(digitalRead(SELECT_BUTTON_PIN) == LOW) delay(1);
    }
    if(digitalRead(SET_BUTTON_PIN) == LOW) {
      switch (ALARM_SETTING_ITEM) {
        case 0:
        isAlarmSet = !isAlarmSet;
        break;
        case 1:
        alarmHour = (alarmHour+1) % 24;
        break;
        case 2:
        alarmMinute = (alarmMinute+1) % 60;
        break;
        case 3:
        setting=false;
        ALARM_SETTING_ITEM = 0;
        break;
      }
      while(digitalRead(SET_BUTTON_PIN) == LOW) delay(1);
    }
  }
  
}
//鬧鐘響起按任何一個按鈕即停止
void playAlarmTone() {
  playing = true;
  alarmPlay=false;
  for (int cnt=0; cnt < 256; cnt++) {
     for (int i=0; i<3; i++) {
      playTone(tones[i], 100);
      if (digitalRead(SELECT_BUTTON_PIN) == LOW || digitalRead(SET_BUTTON_PIN) == LOW) {
        playing =false;
        isAlarmSet=false;
        while(digitalRead(SELECT_BUTTON_PIN) == LOW || digitalRead(SET_BUTTON_PIN) == LOW) delay(1);
        return;
      }
    }
  }
  
  playing =false;
}


void loop() {
  if (digitalRead(SELECT_BUTTON_PIN) == LOW) {
    if (inSetMode) {
      SETTING_ITEM = (SETTING_ITEM+1)%TOTAL_SETTING_ITEM;
      display_setting_item();
    } else {
        DISPLAY_MODE = (DISPLAY_MODE+1) % MODE_NUM;
        
        lcd.setCursor(0,1);
        lcd.print("                ");
        switch(DISPLAY_MODE) {
          case 0:
            lcd.setCursor(0,1);
            lcd.print("T:");
            lcd.print(dat[2]);
            lcd.print(".");
            lcd.print(dat[3]);
            lcd.print((char)223);
            lcd.print("C  RH:");
            lcd.print(dat[0]);
            lcd.print("%");
            break;
          case 1:
            lcd.setCursor(0,1);
            if (isAlarmSet) {
              lcd.print("Alarm set(");
              lcd.print(alarmHour);
              lcd.print(":");
              if(alarmMinute < 10) lcd.print("0");
              lcd.print(alarmMinute);
              lcd.print(")");
            } else {
              lcd.print("T:");
              lcd.print(dat[2]);
              lcd.print(".");
              lcd.print(dat[3]);
              lcd.print((char)223);
              lcd.print("C  RH:");
              lcd.print(dat[0]);
              lcd.print("%");     
            }   
          break;
          case 2:
            lcd.setCursor(0,1);
            lcd.print(year);
            lcd.print("/");
            if(mon < 10) lcd.print("0");
            lcd.print(mon);
            lcd.print("/");
            if(day < 10) lcd.print("0");
            lcd.print(day);
            lcd.print("  ");
            lcd.print(sWeekDay);
          break;
          case 3:
          lcd.setCursor(0,1);
          lcd.print("SET?");
          break;
        } 
        
    }
    while(digitalRead(SELECT_BUTTON_PIN) == LOW) delay(1);
  } 
  
  if(DISPLAY_MODE == MODE_NUM - 1 && ! inSetMode) {
    if(digitalRead(SET_BUTTON_PIN) == LOW) {
      enterSetMode();
      while(digitalRead(SET_BUTTON_PIN) == LOW) delay(1);
    }
  } 
  if(inSetMode) {
    if(digitalRead(SET_BUTTON_PIN) == LOW) {
      SettingItem();
      while(digitalRead(SET_BUTTON_PIN) == LOW) delay(1);
      
    }
  } 
  if (!screenIsOn) {
    if(digitalRead(SET_BUTTON_PIN) == LOW) {
      screenIsOn=true;
      analogWrite(LCD_BRIGHTNESS_PIN, bright*16);
      soffcount=0;
    }
  }
  
  if(getHumTemp()) {
     if(DISPLAY_MODE == 0) {
        lcd.setCursor(0,1);
        lcd.print("T:");
        lcd.print(dat[2]);
        lcd.print(".");
        lcd.print(dat[3]);
        lcd.print((char)223);
        lcd.print("C  RH:");
        lcd.print(dat[0]);
        lcd.print("%");
     }
  }
  if(alarmPlay && !playing) playAlarmTone();
  
}



2 則留言:

  1. 請問加上DHT11溫濕度感應器的程式跟上述的程式是相同的嗎?

    回覆刪除
    回覆
    1. 檢視Arduino DHT11 library, 當讀取資料時會disable all interrupts,這個實驗是利用每秒一次timer interrupt來做為計時基礎,為避免DHT11 library讀取資料時disable timer interrupt,故捨棄不用DHT11的library,而是根據DHT11 datasheet另寫讀取程式,並控制在兩次timer interrupt之間讀取DHT11資料。

      刪除