弄了個四位帶冒號和小數點的數碼管,想着快到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/】