[新人]数码管时钟
本帖最后由 a1039752256 于 2015-7-14 17:11 编辑潜水了那么久,看了那么多人的教程,但由于没什么动力,迟迟都做不出什么作品。两个月前准备做一个数码管时钟,搞到现在终于做好,现在特意和坛友分享一下,一同学习
简单介绍:Arduino Nano + DS1307 + 74HC595 + 四位八段数码管 + dht11 + 光敏 + 蜂鸣器
这个时钟的功能有:时间显示,温湿度显示,整点响铃,亮度调节和串口调时
来几张靓照:
http://image.geek-workshop.com/album/201507/14/150034i8kw0vw5505t8s66.jpg
http://image.geek-workshop.com/album/201507/14/150241f6y009mk11kmqmwq.jpg
http://image.geek-workshop.com/album/201507/14/150604i0e0oirn0if0jhvi.jpg
http://image.geek-workshop.com/album/201507/14/150705uczhtparbpiodmrg.jpg
(白平衡没调好,所以看上去很惨白,但实际是红色)
设计是可拆解式的,方便日后损坏维修,更换零件:
http://image.geek-workshop.com/album/201507/14/150501tootqcosmcrzavgi.jpg
下面详解一下:
http://image.geek-workshop.com/album/201507/14/163700xzr2xhv2hzu28xwv.png
上面是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)
{
int bcd;
Wire.beginTransmission(ADDR);
Wire.write(0x00);
Wire.endTransmission();
Wire.requestFrom(ADDR, 8);
for (int i = 0; i < 7; i++)
{
bcd = Wire.read();
}
output = BcdToDec(bcd & 0x7F);
output = BcdToDec(bcd & 0x7F);
output = BcdToDec(bcd & 0x3F);
output = BcdToDec(bcd & 0x07);
output = BcdToDec(bcd & 0x3F);
output = BcdToDec(bcd & 0x1F);
output = BcdToDec(bcd) + 2000;
output = BcdToDec(bcd);
}
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 = Wire.read();
Wire.beginTransmission(ADDR);
Wire.write(0x00);
Wire.write(time&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 = data;
}
//Update data
int temp = dht11_buf + dht11_buf + dht11_buf + dht11_buf;
if (dht11_buf == temp & 0xFF)//校验数据
{
temperature = dht11_buf;
humidity = dht11_buf;
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 = atoi(CMD);//atoi()函数将字符串转换成数字
Strshift(CMD, cmdlength);//移位字符串(不知道有没有现成的库函数,所以自己写了一个)
time = atoi(CMD);
Strshift(CMD, cmdlength);
time = atoi(CMD);
if (time < 2000 || time > 2099 || time < 1 || time > 12 || time < 1 || time > 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 = temp - '0';
Serial.println(time);
if (time < 1 || time > 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]);
Serial.println("OK");
Serial.println();
SetTime(3, time);//往DS1307写入数据
SetTime(4, time);
SetTime(5, time);
SetTime(6, time);
Serial.println("Exit? (Y/N)");
temp = GetChar();
if (temp == -1) return TIMEOUT;
if (temp == 'N' || temp == 'n'){
Serial.println('N');
goto help;
}
elsereturn 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 = atoi(CMD);
Strshift(CMD, cmdlength);
time = atoi(CMD);
Strshift(CMD, cmdlength);
time = atoi(CMD);
if (time < 0 || time > 23 || time < 0 || time > 59 || time < 0 || time > 59)
{
Serial.println("Invalid. Try again? (Y/N)");
Serial.println();
temp = GetChar();
if (temp == 'Y' || temp == 'y') {
Serial.println('Y');
goto settime;
}
elsereturn 0;
}
Serial.println("OK");
Serial.println();
SetTime(0, time);
SetTime(1, time);
SetTime(2, time);
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;
for (int i = 0; i < length - 1; i++)
{
str = str;
}
if (temp < 48 || temp > 57) break;
}
}
int Getstring(char str[], int length)//从串口获得字符串
{
for (int j = 0; j < length; j++)//清空字符串
{
str = 0;
}
systime = millis();
while (!Serial.available())
{
if (millis() - systime > 10000)
{
Serial.println("Timeout");
Serial.println();
return TIMEOUT;//超时退出函数
}
}
int i = 0;
while (Serial.available())//写入字符串
{
str = 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.”?
http://image.geek-workshop.com/album/201507/14/150034i8kw0vw5505t8s66.jpg
其实那是显示时间的界面;)
因为用了4位的数码管,不能一同显示时分秒,所以用了这样的方法来表示。数字0917代表的是时间9点17分,而下面的小数点则是表示二进制的0111,就是7,映射到时钟表盘上就是7到8的区间,代表的是现在是在35秒到39秒之间!再举一例吧,“1 6.5.2”代表的就是16点52分,30至35秒!这样做弥补了4位数码管不能同时显示时分秒的缺陷,同时充分运用了数码管的小数点资源,还略带透出一点极客范~看了那么多人的作品,好像没看到有人这么做过,不知道我能不能称上第一人呢?→_→
哈哈,程序的具体看下面
int temp = time / 5; //将秒映射到0~11
dvalue = num / 10] | (temp >> 3 & 0x01);//更新显存(后半部分用来显示 秒/5 的二进制)
dvalue = num % 10] | (temp >> 2 & 0x01);
dvalue = num / 10] | (temp >> 1 & 0x01);
dvalue = num % 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;//储存串口指令
int cmdlength = sizeof(CMD) / sizeof(CMD);
bool interrupt = false;//中断标志
unsigned long systime;//system time
char day = {"---", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};//星期
int time;//储存时间信息
int dht11_pin = 11;
int dht11_mark = 1;
unsigned long dht11_timemark = 0;
int dht11_buf;//暂存温湿度数据
int temperature;
int humidity;
int buzzer_pin = 12;
int buzzer_mark = 1 ;
unsigned long buzzer_timemark = 0;
int brightness;//亮度
int duration = 500;//点亮一位数码管的时间
int dvalue;//数码管显存
int num = {B11111100, B01100000, B11011010, B11110010, B01100110,
B10110110, B10111110, B11100000, B11111110, B11110110
};//abcdefgdp
int datapin = 4;//74HC595
int clockpin = 6;
int latchpin = 5;
int dpin = {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, 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 = Wire.read();
Wire.beginTransmission(ADDR);
Wire.write(0x00);
Wire.write(time&B01111111);//将DS1307的秒最高位清零,否则时钟不起振
Wire.endTransmission();
SetTime(7, 10);//使能方波
attachInterrupt(0, update, FALLING);//下降沿进入中断
}
void loop() {
if (interrupt)
{
interrupt = false;
GetTime(time);
int temp = time / 5; //将秒映射到0~11
dvalue = num / 10] | (temp >> 3 & 0x01);//更新显存(后半部分用来显示 秒/5 的二进制)
dvalue = num % 10] | (temp >> 2 & 0x01);
dvalue = num / 10] | (temp >> 1 & 0x01);
dvalue = num % 10] | (temp >> 0 & 0x01);
digitalWrite(13, time & 0x01); //秒为奇数的时候点亮
Serial.print(time);//打印时间
Serial.print("/");
Serial.print(time);
Serial.print("/");
Serial.print(time);
Serial.print(" ");
Serial.print(day]);
Serial.print(" ");
Serial.print(time);
Serial.print(":");
Serial.print(time);
Serial.print(":");
Serial.println(time);
brightness = analogRead(A6);
Serial.println(brightness);
duration = 1024 - brightness;//简单将亮度转换为数码管持续点亮时间
if (duration > 700) duration = 700;//过亮瞎眼
if (time == 0) dht11_mark = 1;//初始化标志位
if (time == 0 && time == 0) buzzer_mark = 1;
if (time == 30 && time == 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 == 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 == 30 && buzzer_mark == 1)//30分响铃一下
{
tone(buzzer_pin, 3000, 100);
buzzer_timemark = millis();
buzzer_mark = 0;
}
if (time == 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 >= 11 && time <= 12)//显示温度
{
Display(num, dpin, duration);
Display(num, dpin, duration);
//摄氏度符号
Display(B11000110, dpin, duration);//o
Display(B10011100, dpin, duration);//C
}
else if (time >= 13 && time <= 14)//显示湿度
{
Display(num, dpin, duration);
Display(num, dpin, duration);
//因数码管显示不了%,所以用rh(relative humidity相对湿度)来表示湿度
Display(B00001010, dpin, duration);//r
Display(B00101110, dpin, duration);//h
}
else
{
for (int i = 3; i >= 0; i--)
{
Display(dvalue, dpin, duration);
}
}
}
}
欢迎大家对我的作品做评价,特别是程序设计思想方面,好让我能继续进步;) 沙发自占自占自占 他xx的,不会做,先抄袭一个先,楼主是新人吗?我膜拜一下 半年下来,边玩边学,收益很大。
今天用了一天的时间改进了时钟的程序,更新如下:
*增加菜单操作
*用宏表示常量
*用结构体储存时间信息
程序在这里: 有没有原理图,好玩,我也想复刻一个
页:
[1]