TM1650+DS3231+STC15LE計數數碼管小時鐘

弄了個四位帶冒號和小數點的數碼管,想着快到1000天紀念日了,於是準備弄個計日的小東西,由於自己DIY的比較醜,就網上淘了一個,但是網上的不符合要求呢,沒事,反正網上八成用的是51單片機,基本都可以在線編程了,所以買個回來復原下電路,然後自己在寫程序唄。

找了個全白一體的模塊,電路印刷也比較整齊的,顯示效果如圖:

 

圖1 數碼管顯示效果圖

圖2 模塊電路板

由於電路是雙面印刷,爲保險起見,還是解焊了數碼管,用萬用表把電路測試一遍,確保恢復的是正確的電路。主控芯片是STC15LE,sop8封裝,數碼管驅動芯片是TM1650,sop16封裝,時鐘是DS3231模塊,帶有16個八位寄存器,也是sop16封裝,數碼管是八段4位的,第二位小數點被替換成了冒號。

 

圖3 數碼管背面

手動畫出的電路復原圖,還比較簡單,畢竟就是個時鐘和顯示功能,並不複雜。然後開始寫程序。

 

圖4 模塊電路板背面

 

圖5 手畫電路圖

 

圖6 電路圖草稿:)

先到官網上找到TM1650的芯片資料:

TM1650是一種帶鍵盤掃描接口的LED(發光二極管顯示器)驅動控制專用IC

特性說明:

  • 兩種顯示模式( 8段×4 位 和 7 段×4 位)
  • 支持單個按鍵7x4bit(28個按鍵)和組合按鍵( 4個)
  • 8級亮度可調
  • 段驅動電流大於25mA,位驅動電流大於150mA
  • 高速2線串行接口( CLK,DAT)
  • 振盪方式:內置RC振盪
  • 內置上電覆位電路
  • 內置數據鎖存電路
  • 支持3-5.5V電源電壓
  • 抗干擾能力強

重點是引腳定義:

 

圖7 TM1650引腳定義

 

圖8 引腳定義

官網也有例程,其實用起來很簡單,用TM1650可以很方便的驅動四位數碼管,例:

先定義不同的字節對應的顯示狀態:

uchar CODE[] = {0xFC,0x84,0xBA,0xAE,0xC6,0x6E,0x7E,0xA4,0xFE,0xEE,0x00};//0-9,全滅狀態

顯示:

TM1650_Set(0x68, CODE[0]); //第一位顯示0,第2、3、4位依次爲0x6A、0x6C、0x6E
TM1650_Set(0x48,0x21); //設置亮度,亮度從低到高依次爲

TM1650的顯示內容、亮度狀態設置和獲取都是通過訪問設置寄存器實現的,具體就是通過I2C總線發送地址、數據,所以看看文檔裏關於寄存器的定義就可以實現,而TM1650的掃描按鍵功能通過I2C總線發送0x49命令獲取,根據I2C總線發回的值判斷按下的鍵,同時要注意,當按揭釋放後,第6bit會變爲0,據此判斷按鍵釋放。

key = Scan_Key();
if(key==0x47)  //key set
{
    ……//do something here
    while(Scan_Key()==0x47);
}

DS3231也是I2C總線的,網上資料非常多,這裏不復述,引用封裝好的函數,可以很方便的完成時間獲取、設置和寄存器的訪問、設置。本程序中除了用DS3231獲取設置時間,還利用DS3231產生的1HZ方波對51引發外部中斷,完成調整時的冒號閃爍功能以及顯示內容切換(主要在月-日顯示與年-周顯示之間切換)。外部中斷函數如下:

void int2() interrupt 10   //DS3231輸出1HZ方波,下降沿更新
{
    //display time now, flash colon
    if(dspmode == 0){
        sec=!sec; //sec標識冒號的顯示與隱藏,實現閃爍
    }
    //when adjust date
    else if(dspmode == 1){
        secMode++;
        secMode%=8; //secMode標示顯示子模式
    }
    //display date now
    else if(dspmode == 2){
    }
    AutoLight(); //根據當前小時信息進行時鐘亮度調整
}

由於模塊自帶只有兩個按鍵,所以設計功能邏輯的時候還畢竟麻煩,主函數裏一堆switch—case,先根據dspmode變量判斷顯示模式,然後根據setmode判斷調整模式,當setmode爲0時爲該顯示模式下的正常顯示狀態,即不進行調整,當該模式下檢測到set按鍵則跳轉到對應的設置狀態,當調整模式爲0時檢測到按鍵爲add,則跳轉到不同的顯示模式,而除此以外的調整模式下按鍵add爲調整狀態。主要涉及時間、日期、年、星期、積日的顯示和設定,因而邏輯畢竟繁雜,但是不難。僞代碼如下:

switch(dspmode){
    case 0://時:分模式
         switch(setmode){
          case 0: 
               //顯示模式
             case 1:
              //調整小時 
             case 2: 
              //調整分 
             case 3: 
             //調整秒 
         }
          break;
     case 1: //月:日----年-星期模式
          switch(setmode){
               case 0:
                      //顯示模式
               case 1:
               //調整年份 
               case 2:
                     //調整月份
               case 3:
                     //調整日期 
               case 2:
                //調整星期 
          }
          break; 
     case 2://日期計數模式
          //顯示當前日期累計值     
          break;
}

 

51單片機內置時鐘中斷,當調整模式時閃爍調整位,代碼如下:

void timer0() interrupt 1
{
     uchar flag;
     TH0=(65536-50000)/256;
     TL0=(65536-50000)%256;
     flag++;
     if(flag==5)
     {
          flag=0;
          flash=!flash;
     }
}

通過flag標示閃爍與否,閃爍速度比DS3231輸出中斷快以示區別。

代碼總覽(代碼參考:http://www.pudn.com/Download/item/id/2881058.html),因爲STC15LE程序存儲空間只有4kb,而程序一度達到4080字節,因而主函數用了整個大的switch-case語句,畢竟重用部分少沒有另設函數,所以代碼看起來沒那麼清晰。

//#include <15f104.h>
#include<reg51.h>
#include<intrins.h>
#include"ds3231.h"
#include"tm1650.h"

#define uchar unsigned char
#define uint  unsigned int

sfr INT_CLKO = 0x8f;                //外部中斷與時鐘輸出控制寄存器
sfr IE2       = 0xaf;               //中斷使能寄存器2
sfr T2H       = 0xD6;               //定時器2高8位
sfr T2L       = 0xD7;               //定時器2低8位

uint beginYear=2014;
uchar beginMonth=6;
uchar beginDate=7;
uint year,day;
uchar i, setmode,flash,dspmode;
uchar month,date,dayofweek;
uchar hour,minute; //顯示緩衝
uchar second;
uchar secMode;
uchar CODE[] = {0xFC,0x84,0xBA,0xAE,0xC6,0x6E,0x7E,0xA4,0xFE,0xEE,0x00};//0-9,全滅
bit sec;//1HZ秒點閃爍

//延時
void delaySt(){
     uint j;
     uchar k;
     for(k=0;k<10;k++){
          for(j=0;j<12000;j++){
               _nop_();
          }
     }
}
//從DS3231獲取當前日期
void GetDateNow()
{
     uchar dstmp,dstmp2,dstmp3;
     dstmp = ReadDS3231(RTC_YR_REG_ADDR);
     dstmp2 = ReadDS3231(RTC_MON_REG_ADDR); 
     dstmp3 = ReadDS3231(RTC_DAY_REG_ADDR)-1; 
     year = dstmp/16*10 + dstmp%16 + ((dstmp2 & 0x80) ? 2000 : 1900);
     month = dstmp2&0x7F;
     date = ReadDS3231(RTC_DATE_REG_ADDR);
     hour = ReadDS3231(RTC_HR_REG_ADDR);
     dayofweek = dstmp3?dstmp3:7;
}
//從DS3231獲取當前時間
void GetTimeNow()
{
     hour = ReadDS3231(RTC_HR_REG_ADDR);
     minute = ReadDS3231(RTC_MIN_REG_ADDR);
}
//判斷是否閏年
bit RunYear(uint yearc){
     return (yearc%400==0||(yearc%100!=0&&yearc%4==0))?1:0;
}
//返回當月天數
uchar DayInMonth(uint yearc,uchar monthc){
    int day[12]={31,28,31,30,  31,30,31,31,  30,31,30,31};
    if(monthc==2){
        return RunYear(yearc)?29:28;
    }else{
        return day[monthc-1];
    }
} 
//計算當年天數
uint DaysInYear(uint yearc,uchar monthc,uchar datec){
     uint daytmp = 0;
     for(i=1;i<monthc;i++){
         daytmp += DayInMonth(yearc,i);
     }
     return daytmp + datec;
}
//計算從開始日期到現在天數
void CalDay(){
     uint daytmp;
     uint i;
     uint beginDays;
     uint lastDays;
     daytmp = 0;
     GetDateNow();
     beginDays = DaysInYear(beginYear,beginMonth,beginDate);
     lastDays = DaysInYear(year,month/16*10+month%16,date/16*10+date%16);
     for(i = beginYear;i<year;i++){
         daytmp += RunYear(year)?366:365;     
     }
     day = daytmp + lastDays - beginDays + 1;
}
//顯示年
void display_year(){
    TM1650_Set(0x68, CODE[year/1000]);
    TM1650_Set(0x6A, CODE[year/100%10]);
    TM1650_Set(0x6C, CODE[year/10%10]);
    TM1650_Set(0x6E, CODE[year%10]);
}
//顯示日期
void display_date()
{
    uchar dsptmp ;
    if(secMode<5){//month-date
        if(month/16)     {
            dsptmp = CODE[month/16];
            TM1650_Set(0x68,dsptmp );
        }          else      {
            dsptmp = 0x00;
            TM1650_Set(0x68,dsptmp);
        }
        TM1650_Set(0x6A, CODE[month%16]+1);
        TM1650_Set(0x6C, CODE[date/16]);
        TM1650_Set(0x6E, CODE[date%16]);
    }else{//year-dayofweek
        if(year>=10)     {
            dsptmp =  CODE[year/10%10];
            TM1650_Set(0x68,dsptmp);
        }     else {
            dsptmp = 0x00;
            TM1650_Set(0x68,dsptmp);
        }
        TM1650_Set(0x6A, CODE[year%10]);
        TM1650_Set(0x6C, 0x02); //0x02--the center short line
        TM1650_Set(0x6E, CODE[dayofweek]);
     }
}
//顯示天數計數
void display_day()
{
    CalDay();
    if(day/1000)TM1650_Set(0x68, CODE[day/1000]);else TM1650_Set(0x68, 0x00);
    if(day>=100)TM1650_Set(0x6A, CODE[day/100%10]);else TM1650_Set(0x6A, 0x00);
    if(day>=10)TM1650_Set(0x6C, CODE[day/10%10]);else TM1650_Set(0x6C,0x00);
    TM1650_Set(0x6E, CODE[day%10]);
}
//根據當前時間調整亮度
void AutoLight(){ //auto set the light level of module
    if((hour>=0x19)||(hour<7))
        TM1650_Set(0x48,0x21);
    else
        TM1650_Set(0x48,0x51);
}
//顯示時間
void display_time()
{
    if(hour/16)TM1650_Set(0x68, CODE[hour/16]);else TM1650_Set(0x68, 0x00);
    TM1650_Set(0x6A, CODE[hour%16]+(sec?1:0));
    TM1650_Set(0x6C, CODE[minute/16]);
    TM1650_Set(0x6E, CODE[minute%16]);
}
//初始化單片機定時器
void Timer_init()           //定時器,用於調整時間時數碼管的閃爍
{
     TMOD=0X01;
     TH0=(65536-50000)/256;
     TL0=(65536-50000)%256;
     TR0=0;
     ET0=1;
     INT_CLKO=0x10;//開啓外部中斷2,下降沿觸發
     IE2 |= 0x04;
     EA=1;
}
//從ds3231讀取初始化信息
void DS3232_init()
{
     uchar modetmp = ReadDS3231(MODE_REG_ADDR);     
     if(modetmp==0)
     {
          //bcd8421
          WriteDS3231(RTC_SEC_REG_ADDR,0x00);  //second
          WriteDS3231(RTC_MIN_REG_ADDR,0x11); //minute
          WriteDS3231(RTC_HR_REG_ADDR,0x20); //hour
          WriteDS3231(RTC_YR_REG_ADDR,0x17);//year
          WriteDS3231(RTC_MON_REG_ADDR,0x82);//month  ,&0x08==cent
          WriteDS3231(RTC_DAY_REG_ADDR,0x02);//day of week
          WriteDS3231(RTC_DATE_REG_ADDR,0x13);//date
          WriteDS3231(MODE_REG_ADDR,0x11);// 用鬧鐘寄存器判斷是否掉電
          //hex, default is 2014.6.7
          WriteDS3231(BEGIN_YR_REG_ADDR,0x0E);//beginyear
          WriteDS3231(BEGIN_MON_REG_ADDR,0x86);//beginmonth
          WriteDS3231(BEGIN_DATE_REG_ADDR,0x07);//begindate
     }
     else{
         dspmode = modetmp >>4 -1;
         setmode = modetmp % 16 -1;
     }
}
void main()
{
     uchar temp;
     uchar dstmp;
     uchar numtmp;
     uchar dayInMontmp;
     uchar yearAdj;//int month-date,year-day mode ,the adjust bit of year
     uchar key; 
     Timer_init();
     TM1650_Set(0x48,0x51);//初始化爲6級灰度,開顯示
     DS3232_init();      // DS3231掉電時初始化一下
     WriteDS3231(RTC_CTL_REG_ADDR,0x00);  //設置INT位爲1HZ方波輸出
    while(1)
    {
          delaySt();
          AutoLight();
          switch(dspmode){
               case 0:   //hour--minute mode**************************
                    GetTimeNow();
                    key = Scan_Key();
                    if(key==0x47)  //key set
                    {
                         setmode++;
                         setmode %= 4;
                         while(Scan_Key()==0x47);//if key be released, its 6th bit turn to 0 ,thus its 0x07
                    }
                    switch (setmode)
                    {
                         case 0:          //GetTimeNow adjust      hour:minute
                         {
                              INT_CLKO=0x10;
                              TR0=0;//adjust flash disable
                         if(key==0x77) //key add
                              {
                                 while(Scan_Key()==0x77); //if key be released, its 6th bit turn to 0 ,thus its 0x37
                                 dspmode = 1;
                                 setmode = 0;
                                 secMode = 0;
                              } 
                              break;
                         }
                         case 1:                            //hour adjust
                         {
                              INT_CLKO=0x00;
                              sec=1;
                              TR0=1;
                              minute = ReadDS3231(RTC_MIN_REG_ADDR);
                              if(key==0x77)
                              {
                                   while(Scan_Key()==0x77);
                                   temp=ReadDS3231(RTC_HR_REG_ADDR);
                                   temp=(temp>>4)*10 + (temp & 0x0F);
                                   temp++;
                                   temp %= 24;
                                   temp = ((temp/10)<<4)|(temp%10);
                                   WriteDS3231(RTC_HR_REG_ADDR,temp);
                              }
                              if(flash) hour = ReadDS3231(RTC_HR_REG_ADDR);
                              else      hour = 0xaa;
                              break;
                         }
                         case 2:     //minute adjust
                         {
                           INT_CLKO=0x00;
                           sec=1;
                           TR0=1;
                           hour = ReadDS3231(RTC_HR_REG_ADDR);
                           if(key==0x77)
                           {
                                 while(Scan_Key()==0x77);
                                 temp=ReadDS3231(RTC_MIN_REG_ADDR);
                                 temp=((temp>>4)*10)+(temp&0x0f);
                                 temp++;
                                 temp %= 60;
                                 temp = ((temp/10)<<4)|(temp%10);
                                 WriteDS3231(RTC_MIN_REG_ADDR,temp);
                           }
                           if(flash) minute = ReadDS3231(RTC_MIN_REG_ADDR);
                           else       minute = 0xaa;
                           break;
                         }
                         case 3:          //second adjust
                         {
                           INT_CLKO=0x00;
                           sec=1;
                           TR0=0;
                           hour= 0xaa;
                           if(key==0x77)
                           {
                                 while(Scan_Key()==0x77);
                                 WriteDS3231(RTC_SEC_REG_ADDR,0x00);
                           }
                           minute = ReadDS3231(RTC_SEC_REG_ADDR);
                           break;
                         }
                         default:
                           break;
                    }
                    display_time();
                    break;
               case 1:          //month--date ;;; year-dayofweek  mode***************************
                    GetDateNow();
                    key = Scan_Key();
                    if(key==0x47)  //key set
                    {
                         setmode++;
                         setmode %= 5;
                         while(Scan_Key()==0x47);//if key be released, its 6th bit turn to 0 ,thus its 0x07
                    } 
                    switch (setmode)
                    {
                         case 0:     //display
                              INT_CLKO=0x10;
                              TR0=0;
                              if(key==0x77) //key add
                              {
                                 while(Scan_Key()==0x77); //if key be released, its 6th bit turn to 0 ,thus its 0x37
                                 dspmode = 2;
                                 setmode = 0;
                              } 
                              display_date();
                              break;
                         case 1:  //adjust year
                              INT_CLKO=0x00;
                              TR0=1;
                              display_year();
                              if(key==0x77)  //key add
                              {
                                 yearAdj = 0;
                                 temp = 1;
                                 while(Scan_Key()==0x77);
                                 while(temp){   //enter adjust
                                        if(flash){ 
                                              display_year();
                                        }
                                        GetDateNow();
                                        key = Scan_Key();
                                        if(key == 0x47){
                                              yearAdj ++;
                                              yearAdj %= 5;
                                              while(Scan_Key()==0x47);
                                        }
                                        switch(yearAdj){
                                              case 0:
                                                    if(!flash){
                                                                 TM1650_Set(0x68, 0x00);
                                                                 TM1650_Set(0x6A, 0x00);
                                                                 TM1650_Set(0x6C, 0x00);
                                                                 TM1650_Set(0x6E, 0x00);
                                                          }
                                                     break;
                                              case 1:
                                                    if(key == 0x77){
                                                          while(Scan_Key()==0x77);
                                                          numtmp = year%10;
                                                          year -= numtmp;
                                                          numtmp = (++numtmp)%10;
                                                          year += numtmp;
                                                          WriteDS3231(RTC_YR_REG_ADDR,ReadDS3231(RTC_YR_REG_ADDR)&0xf0|numtmp);
                                                    }
                                                    if(!flash){
                                                          TM1650_Set(0x6E, 0x00);
                                                    }
                                                    break;
                                              case 2:
                                                    if(key == 0x77){
                                                          while(Scan_Key()==0x77);
                                                          numtmp = year/10%10;
                                                          year -= numtmp*10;
                                                          numtmp = (++numtmp)%10;
                                                          year += numtmp*10;
                                                          WriteDS3231(RTC_YR_REG_ADDR,ReadDS3231(RTC_YR_REG_ADDR)&0x0f|(numtmp<<4));
                                                    }
                                                    if(!flash){
                                                          TM1650_Set(0x6C, 0x00);
                                                    }
                                                    break;
                                              case 3:
                                                    if(key == 0x77){
                                                          while(Scan_Key()==0x77);
                                                          dstmp = ReadDS3231(RTC_MON_REG_ADDR);
                                                          numtmp = dstmp &0x80?1:0;
                                                          year -= numtmp*100;
                                                          numtmp = (++numtmp)%2;
                                                          year += numtmp*100;
                                                          if(numtmp == 1){
                                                                 WriteDS3231(RTC_MON_REG_ADDR,dstmp|0x80);     
                                                          }
                                                          else{
                                                                 WriteDS3231(RTC_MON_REG_ADDR,dstmp&0x7f);     
                                                          }
                                                    }
                                                    if(!flash){
                                                          TM1650_Set(0x68, 0x00);
                                                          TM1650_Set(0x6A, 0x00);
                                                    }
                                                    break;
                                              case 4:
                                                    temp = 0;
                                                    setmode = 2;
                                                    break;
                                              default:
                                                    break;
                                        }
                                 }
                              }
                              break;
                         case 2:  //adjust month*****************************
                              INT_CLKO=0x00;
                              TR0=1;
                              secMode = 1;

                              dstmp = ReadDS3231(RTC_MON_REG_ADDR);
                              month = dstmp&0x7f;
                              if(key==0x77)  //key add
                              {
                                 temp= month;
                                 temp=((temp>>4)*10)+(temp&0x0f);
                                 temp++;
                                 temp %= 12;
                                 temp = temp == 0?12:temp;
                                 month = ((temp/10)<<4)|(temp%10);
                                 temp = month|(dstmp&0x80);
                                 WriteDS3231(RTC_MON_REG_ADDR,temp);
                                 while(Scan_Key()==0x77);
                              }
                              if(!flash){month = 0xaa;}

                              display_date();
                              break;
                         case 3:  //adjust date*****************************
                              INT_CLKO=0x00;
                              TR0=1;
                              dstmp = ReadDS3231(RTC_DATE_REG_ADDR);
                              if(key==0x77)  //key add
                              {

                                 temp=dstmp;
                                 temp=((temp>>4)*10)+(temp&0x0f);
                                 temp++;
                                 dayInMontmp = DayInMonth(year,month);
                                 temp %= dayInMontmp;
                                 temp = temp == 0?dayInMontmp:temp; 

                                 temp = ((temp/10)<<4)|(temp%10);
                                 date = temp;
                                 WriteDS3231(RTC_DATE_REG_ADDR,temp);
                                 while(Scan_Key()==0x77);
                              }
                              if(!flash){date = 0xaa;}
                              secMode = 1;
                              display_date();
                              break;
                         case 4:  //adjust day in week*****************************
                              INT_CLKO=0x00;
                              TR0=1;
                              secMode = 6;
                              dstmp = ReadDS3231(RTC_DAY_REG_ADDR)-1;
                              dayofweek = dstmp?dstmp:7;
                              if(key==0x77)  //key add
                              {
                                 //(ReadDS3231(RTC_DAY_REG_ADDR)-1)?(ReadDS3231(RTC_DAY_REG_ADDR)-1):7];
                                 temp = (dstmp+1) % 7;
                                 dayofweek = temp?temp:7;
                                 temp++;
                                 WriteDS3231(RTC_DAY_REG_ADDR,temp);
                                 while(Scan_Key()==0x77);
                              }
                              if(!flash){dayofweek = 0x0A;}
                              display_date();
                              break;
                         default:
                              break;
                    }
                    break;
                    
               case 2://display day count mode
                    key = Scan_Key();
                    if(key==0x47)  //key set
                    {
                         setmode = 0;
                         while(Scan_Key()==0x47);//if key be released, its 6th bit turn to 0 ,thus its 0x07
                    } 
                    switch (setmode)
                    {
                          case 0:     
                              INT_CLKO=0x10;
                              TR0=0;
                              if(key==0x77) //key add
                              {
                                 while(Scan_Key()==0x77); //if key be released, its 6th bit turn to 0 ,thus its 0x37
                                 dspmode = 0;
                                 setmode = 0;
                              } 
                              display_day();
                              break;
                    }
                    break;
               default:
                    dspmode = 0;
                    break;
        }
        WriteDS3231(MODE_REG_ADDR,dspmode*16 + setmode + 17);//set mode into ds3231 register
    }      
}

void timer0() interrupt 1
{
     uchar flag;
     TH0=(65536-50000)/256;
     TL0=(65536-50000)%256;
     flag++;
     if(flag==5)
     {
        flag=0;
        flash=!flash;
     }
}

void int2() interrupt 10   //DS3231輸出1HZ方波,每個下降沿更新時間,冒號閃爍
{
     //display time now, flash colon
     if(dspmode == 0){
        sec=!sec;
     }
     //when adjust date
     else if(dspmode == 1){
        secMode++;
        secMode%=8;
     }
     //display date now
     else if(dspmode == 2){
     }
}     

 

最終效果見圖1和下圖:

PS:

中間遇到顯示亂碼和顯示值跳動問題,一直沒解決,後來發現是中斷函數中調用了某些函數導致函數重入破壞堆棧了,後來就把中斷函數中的函數全部移到主函數中就OK了。

後面又改了下程序,弄成起始日期也可以設置的,就是在第三個模式下增加了日期調整功能,又加了200行代碼,差點單片機空間不夠燒不進去了。主要是隻有兩個按鍵造成程序的複雜和龐大,需要代碼的留言或郵箱聯繫,因爲增加的功能有點故弄玄虛,沒有貼出來的必要。。。

附錄:

代碼見【http://download.csdn.net/download/atp1992/10208348

參考資料【 http://download.csdn.net/download/atp1992/10208353 】

原文:【http://www.straka.cn/blog/nixie_tube_clock/】

發佈了34 篇原創文章 · 獲贊 25 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章