a1039752256 发表于 2015-7-14 17:13:33

[新人]数码管时钟

本帖最后由 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);
      }
    }

}

}

欢迎大家对我的作品做评价,特别是程序设计思想方面,好让我能继续进步;)

a1039752256 发表于 2015-7-14 17:15:26

沙发自占自占自占

zhangfanchao 发表于 2016-1-7 15:00:54

他xx的,不会做,先抄袭一个先,楼主是新人吗?我膜拜一下

a1039752256 发表于 2016-5-10 23:33:43

半年下来,边玩边学,收益很大。
今天用了一天的时间改进了时钟的程序,更新如下:
*增加菜单操作
*用宏表示常量
*用结构体储存时间信息

程序在这里:

xianyingzhang 发表于 2023-4-14 09:48:10

有没有原理图,好玩,我也想复刻一个
页: [1]
查看完整版本: [新人]数码管时钟