使用元件:
- LCD 1602A不含I2C模組
- DHT11溫濕度感應器
- 8歐姆1W喇叭
- 10電位計(可變電阻)
- 按鈕
- 1K、10K電阻與0.1uF電容
- 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(); }