|
本帖最后由 a1039752256 于 2015-7-14 17:11 编辑
潜水了那么久,看了那么多人的教程,但由于没什么动力,迟迟都做不出什么作品。两个月前准备做一个数码管时钟,搞到现在终于做好,现在特意和坛友分享一下,一同学习
简单介绍:Arduino Nano + DS1307 + 74HC595 + 四位八段数码管 + dht11 + 光敏 + 蜂鸣器
这个时钟的功能有:时间显示,温湿度显示,整点响铃,亮度调节和串口调时
来几张靓照:
(白平衡没调好,所以看上去很惨白,但实际是红色)
设计是可拆解式的,方便日后损坏维修,更换零件:
下面详解一下:
上面是Arduino的io分配,接线还是相对简单的,只要给相应的元件接上限流电阻就行了
时钟芯片采用DS1307模块,读写数据挺简单,坛里有不少好教程,但我却在I2C地址上琢磨了好几天,最后硬试几遍才试了出来。(原来I2C的地址是7位的!一定要牢记这一点,不能再犯前车之鉴!)
读写DS1307的程序是根据pdf写的,用的是Arduino自带的I2C库。之所以没有用rtc库,是想到用库会让程序的体积白白变大不少,另外就是觉得一味用库学不到什么真东西。
下面是自己写的程序,请大神指教;) - int BcdToDec(int bcd)
- {
- return (bcd >> 4) * 10 + (bcd & 0x0F);
- }
- int DecToBcd(int dec)
- {
- return (dec / 10) << 4 | (dec % 10);
- }
- void GetTime(int output[8])
- {
- int bcd[8];
- Wire.beginTransmission(ADDR);
- Wire.write(0x00);
- Wire.endTransmission();
- Wire.requestFrom(ADDR, 8);
- for (int i = 0; i < 7; i++)
- {
- bcd[i] = Wire.read();
- }
- output[0] = BcdToDec(bcd[0] & 0x7F);
- output[1] = BcdToDec(bcd[1] & 0x7F);
- output[2] = BcdToDec(bcd[2] & 0x3F);
- output[3] = BcdToDec(bcd[3] & 0x07);
- output[4] = BcdToDec(bcd[4] & 0x3F);
- output[5] = BcdToDec(bcd[5] & 0x1F);
- output[6] = BcdToDec(bcd[6]) + 2000;
- output[7] = BcdToDec(bcd[7]);
- }
- void SetTime(int mode, int value)
- {
- if (mode == 0 || mode == 1) value = constrain(value,0,59);
- if (mode == 2) value = constrain(value,0,23);
- if (mode == 3) value = constrain(value,1,7);
- if (mode == 4) value = constrain(value,1,31);
- if (mode == 5) value = constrain(value,1,12);
- if (mode == 6) value = constrain(value,2000,2099)-2000;
- int temp = DecToBcd(value);
- Wire.beginTransmission(ADDR);
- Wire.write(mode);
- Wire.write(temp);
- Wire.endTransmission();
- }
复制代码
时钟的中断来自DS1307输出的1Hz方波。本来打算在中断程序中读时间,但I2C好像跟中断程序冲突,所以用了中断标志位的方法。 - void update()//中断函数
- {
- interrupt = true;
- }
复制代码
另外还有一点要注意,就是DS1307一旦断电(拆电池),秒寄存器的最高位(CH)会置1,芯片停振,也没有方波输出,所以我加上了CH清零的函数 - Wire.beginTransmission(ADDR);
- Wire.write(0x00);
- Wire.endTransmission();
- Wire.requestFrom(ADDR, 1);
- time[0] = Wire.read();
- Wire.beginTransmission(ADDR);
- Wire.write(0x00);
- Wire.write(time[0]&B01111111);//将DS1307的秒最高位清零,否则时钟不起振
- Wire.endTransmission();
复制代码
数码管是动态扫描,本来打算直接用io驱动,但是想想好像有点浪费io,所以加了74HC595,节省下了5个io,可以用来日后功能升级。
贴一下显示函数: - void Display(int value, int pin, int duration)//显示函数
- {
- shiftOut(datapin, clockpin, LSBFIRST, value);
- digitalWrite(latchpin, HIGH);
- digitalWrite(latchpin, LOW);
- digitalWrite(pin, HIGH);
- delayMicroseconds(duration);
- digitalWrite(pin, LOW);
- }
复制代码
显示函数很简单,一目了然;)
数码管扫描周期大概是10ms,肉眼基本看不到闪烁感。数码管的亮度可以由持续时间(duration)来控制,持续时间由光敏电阻来调节,以达到简单的根据环境调光功能 - brightness = analogRead(A6);
- duration = 1024 - brightness;//简单将亮度转换为数码管持续点亮时间
- if (duration > 700) duration = 700;//过亮瞎眼
复制代码
至于dht11用的是别人的程序,这里没什么好说,就贴程序给大家参考一下吧 - void GetTemp()//读dht11
- {
- digitalWrite(dht11_pin, HIGH);
- delayMicroseconds(30);
- pinMode(dht11_pin, INPUT);
- digitalWrite(dht11_pin, INPUT_PULLUP);
- dht11_timemark = micros();
- while (digitalRead(dht11_pin) && micros() - dht11_timemark <= 50);//超时退出死循环
- dht11_timemark = micros();
- while (!digitalRead(dht11_pin) && micros() - dht11_timemark <= 90);
- dht11_timemark = micros();
- while (digitalRead(dht11_pin) && micros() - dht11_timemark <= 90);
- //Not appreciated,but stable
- for (int j = 0; j < 5; j++)
- {
- int data = 0;
- for (int i = 0; i < 8; i++)
- {
- dht11_timemark = micros();
- while (!digitalRead(dht11_pin) && micros() - dht11_timemark <= 60);
- delayMicroseconds(30);
- int state = digitalRead(dht11_pin);
- data <<= 1;
- data |= state;
- dht11_timemark = micros();
- while (digitalRead(dht11_pin) && micros() - dht11_timemark <= 50);
- }
- dht11_buf[j] = data;
- }
- //Update data
- int temp = dht11_buf[0] + dht11_buf[1] + dht11_buf[2] + dht11_buf[3];
- if (dht11_buf[4] == temp & 0xFF)//校验数据
- {
- temperature = dht11_buf[2];
- humidity = dht11_buf[0];
- Serial.print("Temperature : ");
- Serial.print(temperature);//更新温度
- Serial.print("\t");
- Serial.print("Humidity : ");
- Serial.println(humidity);
- }
- }
复制代码
蜂鸣器也没什么好说,用了tone()函数
因为从一开始没打算加按键,所以调时只能通过串口调时
直接贴程序吧(有点长),看注释应该能懂 - int Command()//随便输入什么来进入调时程序
- {
- int temp = 0;
- delay(5);
- while (Serial.available()) Serial.read();//清空串口
- Serial.println();
- help:
- Serial.print("--------------------");//分割线
- Serial.print("--------------------");
- Serial.print("--------------------");
- Serial.println();
- Serial.println("Input the following command:");
- Serial.println(" 0-----For help");
- Serial.println(" 1-----Set date");
- Serial.println(" 2-----Set time");
- Serial.println(" 9-----Exit");
- Serial.println();
- temp = GetChar();//读取串口指令
- if (temp == -1) return TIMEOUT;//超时退出函数
- else temp -= '0';//将char型的数字转换为int型
- switch (temp)
- {
- default :
- {
- Serial.println("Invalid. Try again.");
- Serial.println();
- goto help;
- }
- case 0: goto help;
- case 1:
- {
- setdate:
- Serial.print("--------------------");
- Serial.print("--------------------");
- Serial.print("--------------------");
- Serial.println();
- Serial.println("Date?(YYYY/MM/DD)");
- if (Getstring(CMD, cmdlength) == -1) return TIMEOUT;//超时退出函数
- Serial.println(CMD);
- time[6] = atoi(CMD);//atoi()函数将字符串转换成数字
- Strshift(CMD, cmdlength);//移位字符串(不知道有没有现成的库函数,所以自己写了一个)
- time[5] = atoi(CMD);
- Strshift(CMD, cmdlength);
- time[4] = atoi(CMD);
- if (time[6] < 2000 || time[6] > 2099 || time[5] < 1 || time[5] > 12 || time[4] < 1 || time[4] > 31)//检验数据
- {
- Serial.println("Invalid. Try again? (Y/N)");
- Serial.println();
- temp = GetChar();
- if (temp == 'Y' || temp == 'y') {
- Serial.println('Y');
- goto setdate;
- }
- else return 0;//放弃修改时间返回0
- }
- Serial.println("OK");
- Serial.println();
- Serial.println("Day of week?(1 for Monday, 7 for Sunday)");//1是星期一,7是星期天
- temp = GetChar();
- if (temp == -1) return TIMEOUT;
- time[3] = temp - '0';
- Serial.println(time[3]);
- if (time[3] < 1 || time[3] > 7)
- {
- Serial.println("Invalid. Try again? (Y/N)");
- Serial.println();
- int temp = GetChar();
- if (temp == 'Y' || temp == 'y') {
- Serial.println('Y');
- goto setdate;
- }
- else return 0;
- }
- Serial.println(day[time[3]]);
- Serial.println("OK");
- Serial.println();
- SetTime(3, time[3]);//往DS1307写入数据
- SetTime(4, time[4]);
- SetTime(5, time[5]);
- SetTime(6, time[6]);
- Serial.println("Exit? (Y/N)");
- temp = GetChar();
- if (temp == -1) return TIMEOUT;
- if (temp == 'N' || temp == 'n') {
- Serial.println('N');
- goto help;
- }
- else return 1;//成功修改时间返回1
- }
- case 2:
- {
- settime:
- Serial.print("--------------------");
- Serial.print("--------------------");
- Serial.print("--------------------");
- Serial.println();
- Serial.println("Time?(HH:MM:SS)");
- if (Getstring(CMD, cmdlength) == -1) return TIMEOUT;
- Serial.println(CMD);
- time[2] = atoi(CMD);
- Strshift(CMD, cmdlength);
- time[1] = atoi(CMD);
- Strshift(CMD, cmdlength);
- time[0] = atoi(CMD);
- if (time[2] < 0 || time[2] > 23 || time[1] < 0 || time[1] > 59 || time[0] < 0 || time[0] > 59)
- {
- Serial.println("Invalid. Try again? (Y/N)");
- Serial.println();
- temp = GetChar();
- if (temp == 'Y' || temp == 'y') {
- Serial.println('Y');
- goto settime;
- }
- else return 0;
- }
- Serial.println("OK");
- Serial.println();
- SetTime(0, time[0]);
- SetTime(1, time[1]);
- SetTime(2, time[2]);
- Serial.println("Exit? (Y/N)");
- temp = GetChar();
- if (temp == -1) return TIMEOUT;
- if (temp == 'N' || temp == 'n') {
- Serial.println('N');
- goto help;
- }
- else return 1;
- }
- case 9: return 0;
- }
- }
复制代码
- void Strshift(char str[], int length)//作用是清掉前面的数字和非数字
- {
- while (1)
- {
- char temp = str[0];
- for (int i = 0; i < length - 1; i++)
- {
- str[i] = str[i + 1];
- }
- if (temp < 48 || temp > 57) break;
- }
- }
- int Getstring(char str[], int length)//从串口获得字符串
- {
- for (int j = 0; j < length; j++)//清空字符串
- {
- str[j] = 0;
- }
- systime = millis();
- while (!Serial.available())
- {
- if (millis() - systime > 10000)
- {
- Serial.println("Timeout");
- Serial.println();
- return TIMEOUT;//超时退出函数
- }
- }
- int i = 0;
- while (Serial.available())//写入字符串
- {
- str[i] = Serial.read();
- i++;
- if (i >= length) break;
- delay(2);
- }
- }
- int GetChar()//作用是取第一字节数据,然后清空串口
- {
- systime = millis();
- while (!Serial.available())
- {
- if (millis() - systime > 5000)
- {
- Serial.println("Timeout");
- Serial.println();
- while (Serial.available()) Serial.read();
- return TIMEOUT;
- }
- }
- delay(5);
- int temp = Serial.read();
- while (Serial.available()) Serial.read();
- return temp;
- }
复制代码
最后说一下这个数码管时钟的特色吧,不知道你们有没有对第一幅图片感到奇怪?“0 9.1.7.”?
其实那是显示时间的界面;)
因为用了4位的数码管,不能一同显示时分秒,所以用了这样的方法来表示。数字0917代表的是时间9点17分,而下面的小数点则是表示二进制的0111,就是7,映射到时钟表盘上就是7到8的区间,代表的是现在是在35秒到39秒之间!再举一例吧,“1 6.5.2”代表的就是16点52分,30至35秒!这样做弥补了4位数码管不能同时显示时分秒的缺陷,同时充分运用了数码管的小数点资源,还略带透出一点极客范~ 看了那么多人的作品,好像没看到有人这么做过,不知道我能不能称上第一人呢?→_→
哈哈,程序的具体看下面 - int temp = time[0] / 5; //将秒映射到0~11
- dvalue[3] = num[time[2] / 10] | (temp >> 3 & 0x01);//更新显存(后半部分用来显示 秒/5 的二进制)
- dvalue[2] = num[time[2] % 10] | (temp >> 2 & 0x01);
- dvalue[1] = num[time[1] / 10] | (temp >> 1 & 0x01);
- dvalue[0] = num[time[1] % 10] | (temp >> 0 & 0x01);
复制代码
最后的最后,贴出主程序 - /* Clock */
- /* DS1307 + DHT11 + 7-digital segment + AT24C32 + Arduino Nano */
- #include <Wire.h>
- #define TIMEOUT -1//超时
- #define ADDR 0x68//DS1307 I2C地址
- char CMD[16];//储存串口指令
- int cmdlength = sizeof(CMD) / sizeof(CMD[0]);
- bool interrupt = false;//中断标志
- unsigned long systime;//system time
- char day[8][4] = {"---", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};//星期
- int time[8];//储存时间信息
- int dht11_pin = 11;
- int dht11_mark = 1;
- unsigned long dht11_timemark = 0;
- int dht11_buf[5];//暂存温湿度数据
- int temperature;
- int humidity;
- int buzzer_pin = 12;
- int buzzer_mark = 1 ;
- unsigned long buzzer_timemark = 0;
- int brightness;//亮度
- int duration = 500;//点亮一位数码管的时间
- int dvalue[4];//数码管显存
- int num[10] = {B11111100, B01100000, B11011010, B11110010, B01100110,
- B10110110, B10111110, B11100000, B11111110, B11110110
- };//abcdefgdp
- int datapin = 4;//74HC595
- int clockpin = 6;
- int latchpin = 5;
- int dpin[4] = {17, 16, 15, 14}; //数码管d0,d1,d2,d3
- void update()//中断函数
- {
- interrupt = true;
- }
- void Display(int value, int pin, int duration)//显示函数
- {
- shiftOut(datapin, clockpin, LSBFIRST, value);
- digitalWrite(latchpin, HIGH);
- digitalWrite(latchpin, LOW);
- digitalWrite(pin, HIGH);
- delayMicroseconds(duration);
- digitalWrite(pin, LOW);
- }
- void setup() {
- Wire.begin();
- Serial.begin(9600);
- pinMode(buzzer_pin, OUTPUT);
- pinMode(datapin, OUTPUT);
- pinMode(clockpin, OUTPUT);
- pinMode(latchpin, OUTPUT);
- for (int i = 0; i < 4; i++)
- {
- pinMode(dpin[i], OUTPUT);
- }
- pinMode(13, OUTPUT);
- pinMode(dht11_pin, OUTPUT);
- digitalWrite(dht11_pin, LOW);
- delay(18);
- GetTemp();//获取温度
- Wire.beginTransmission(ADDR);
- Wire.write(0x00);
- Wire.endTransmission();
- Wire.requestFrom(ADDR, 1);
- time[0] = Wire.read();
- Wire.beginTransmission(ADDR);
- Wire.write(0x00);
- Wire.write(time[0]&B01111111);//将DS1307的秒最高位清零,否则时钟不起振
- Wire.endTransmission();
- SetTime(7, 10);//使能方波
- attachInterrupt(0, update, FALLING);//下降沿进入中断
- }
- void loop() {
- if (interrupt)
- {
- interrupt = false;
- GetTime(time);
- int temp = time[0] / 5; //将秒映射到0~11
- dvalue[3] = num[time[2] / 10] | (temp >> 3 & 0x01);//更新显存(后半部分用来显示 秒/5 的二进制)
- dvalue[2] = num[time[2] % 10] | (temp >> 2 & 0x01);
- dvalue[1] = num[time[1] / 10] | (temp >> 1 & 0x01);
- dvalue[0] = num[time[1] % 10] | (temp >> 0 & 0x01);
- digitalWrite(13, time[0] & 0x01); //秒为奇数的时候点亮
- Serial.print(time[6]);//打印时间
- Serial.print("/");
- Serial.print(time[5]);
- Serial.print("/");
- Serial.print(time[4]);
- Serial.print(" ");
- Serial.print(day[time[3]]);
- Serial.print(" ");
- Serial.print(time[2]);
- Serial.print(":");
- Serial.print(time[1]);
- Serial.print(":");
- Serial.println(time[0]);
- brightness = analogRead(A6);
- Serial.println(brightness);
- duration = 1024 - brightness;//简单将亮度转换为数码管持续点亮时间
- if (duration > 700) duration = 700;//过亮瞎眼
- if (time[0] == 0) dht11_mark = 1;//初始化标志位
- if (time[1] == 0 && time[0] == 0) buzzer_mark = 1;
- if (time[1] == 30 && time[0] == 0) buzzer_mark = 1;
- if (Serial.available())////随便输入什么来进入调时程序(调时时数码管不显示)
- {
- if(Command()==1)//返回1代表修改时间成功
- {
- tone(buzzer_pin, 3000, 100);//响铃
- delay(150);
- tone(buzzer_pin, 3000, 100);
- }
- }
- }
- if (time[1] == 0 && buzzer_mark > 0)//整点报时响两下
- {
- if (buzzer_mark == 1)
- {
- tone(buzzer_pin, 3000, 100);
- buzzer_timemark = millis();
- buzzer_mark = 2;
- }
- if (buzzer_mark == 2 && millis() - buzzer_timemark >= 150)
- {
- tone(buzzer_pin, 3000, 100);
- buzzer_timemark = millis();
- buzzer_mark = 0;
- }
- }
- if (time[1] == 30 && buzzer_mark == 1)//30分响铃一下
- {
- tone(buzzer_pin, 3000, 100);
- buzzer_timemark = millis();
- buzzer_mark = 0;
- }
- if (time[0] == 5 && dht11_mark > 0)//每分钟第5秒收集温湿度数据
- {
- if (dht11_mark == 1)
- {
- pinMode(dht11_pin, OUTPUT);
- digitalWrite(dht11_pin, LOW);
- dht11_timemark = millis();
- dht11_mark = 2;
- }
- if (millis() - dht11_timemark >= 18)//相当于长延时
- {
- GetTemp();
- dht11_mark = 0;
- }
- }
- if (millis() - systime >= 10 || millis() - systime < 0)//大约每10毫秒扫描一次数码管
- {
- systime = millis();
- if (time[0] >= 11 && time[0] <= 12)//显示温度
- {
- Display(num[temperature / 10], dpin[3], duration);
- Display(num[temperature % 10], dpin[2], duration);
- //摄氏度符号
- Display(B11000110, dpin[1], duration);//o
- Display(B10011100, dpin[0], duration);//C
- }
- else if (time[0] >= 13 && time[0] <= 14)//显示湿度
- {
- Display(num[humidity / 10], dpin[3], duration);
- Display(num[humidity % 10], dpin[2], duration);
- //因数码管显示不了%,所以用rh(relative humidity相对湿度)来表示湿度
- Display(B00001010, dpin[1], duration);//r
- Display(B00101110, dpin[0], duration);//h
- }
- else
- {
- for (int i = 3; i >= 0; i--)
- {
- Display(dvalue[i], dpin[i], duration);
- }
- }
- }
- }
复制代码
欢迎大家对我的作品做评价,特别是程序设计思想方面,好让我能继续进步;) |
|