使用元件:
- 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();
}


