?導(dǎo)讀:《藍(lán)橋杯單片機(jī)組》專欄文章是博主2018年參加藍(lán)橋杯的單片機(jī)組比賽所做的學(xué)習(xí)筆記,在當(dāng)年的比賽中,博主是獲得了省賽一等獎(jiǎng),國(guó)賽二等獎(jiǎng)的成績(jī)。成績(jī)雖談不上最好,但至少問(wèn)心無(wú)愧。如今2021年回頭再看該系列文章,仍然感觸頗多。為了能更好地幫助到單片機(jī)初學(xué)者,今年特地抽出時(shí)間對(duì)當(dāng)年的文章邏輯和結(jié)構(gòu)進(jìn)行重構(gòu),以達(dá)到初學(xué)者快速上手的目的。需要指出的是,由于本人水平有限,如有錯(cuò)誤還請(qǐng)讀者指出,非常感謝。那么,接下來(lái)讓我們一起開(kāi)始愉快的學(xué)習(xí)吧。
一、基礎(chǔ)理論
1302是變種的SPI,上升沿DS1302寫(xiě)入數(shù)據(jù),下降沿DS1302讀出數(shù)據(jù)! 上沿采樣,下沿輸出。
這里寫(xiě)圖片描述
還有,得會(huì)查這七個(gè)寄存器!
承認(rèn)第一次自己寫(xiě)藍(lán)橋DS1302底層后不好使,然后也沒(méi)找到原因。稍作修改了官網(wǎng)的底層后,雖然好使了,但是心里還是有點(diǎn)膈應(yīng)藍(lán)橋的底層。太多nop,讓我們自己寫(xiě)怎么可能寫(xiě)的出來(lái)嘛?。。。ㄎ业故怯X(jué)得它的那個(gè)底層儼然是根據(jù)邏輯分析的波形進(jìn)行的!)
昨晚(3月17日)又重新推到重寫(xiě)了底層,又輔助邏輯分析儀和datasheet,在即要放棄之際,沒(méi)想到找到了答案!
受益匪淺,感觸良多,特更此文,以作分享。
(同時(shí)DS1302的顯示也都摒棄了1602,直接顯示到數(shù)碼管,也更符合比賽的要求!)
底層能用的兩種方法:
1、加一句話,在DS1302SingleRead()
以及DS1302BurstRead()
后加上一句DS1302_IO = 0
!
2、在DS1302_IO對(duì)應(yīng)的引腳外面上拉一個(gè)4.7K
左右的電阻。
接上拉電阻,再拔下的效果。
DS1302參考電路
/*
*******************************************************************************
* 文件名:ds1302.c
* 描 述:
* 作 者:CLAY
* 版本號(hào):v1.0.0
* 日 期:
* 備 注:
*
*******************************************************************************
*/
#include "config.h"
#include "ds1302.h"
void DS1302ByteWrite(u8 dat)
{
u8 mask;
DS1302_IO = 1;
for(mask=0x01; mask!=0; mask<<=1)
{
if((dat&mask) != 0)
DS1302_IO = 1;
else
DS1302_IO = 0;
DS1302_CK = 1;
DS1302_CK = 0;
}
DS1302_IO = 1; //寫(xiě)完之后確保釋放IO總線
}
u8 DS1302ByteRead()
{
u8 mask, dat=0;
for(mask=0x01; mask!=0; mask<<=1)
{
if(DS1302_IO)
{
dat |= mask;
}
DS1302_CK = 1;
DS1302_CK = 0;
}
return dat;
}
void DS1302SingleWrite(u8 reg,u8 dat)
{
DS1302_CE = 1;
DS1302ByteWrite((reg<<1) | 0x80);
DS1302ByteWrite(dat);
DS1302_CE = 0;
}
u8 DS1302SingleRead(u8 reg)
{
u8 dat;
DS1302_CE = 1;
DS1302ByteWrite((reg<<1) | 0x81);
dat = DS1302ByteRead();
DS1302_CE = 0;
DS1302_IO = 0;//單字節(jié)讀必須加的!
return dat;
}
void DS1302BurstWrite(u8 *dat)
{
u8 i;
DS1302_CE = 1;
DS1302ByteWrite(0xBE);
for(i=0; i<7; i++)
{
DS1302ByteWrite(*dat++);
}
DS1302_CE = 0;
}
void DS1302BurstRead (u8 *dat)
{
u8 i;
DS1302_CE = 1;
DS1302ByteWrite(0xBF);
for(i=0; i<7; i++)
{
dat[i] = DS1302ByteRead();
}
DS1302_CE = 0;
DS1302_IO = 0;//突發(fā)讀必須加
}
void GetRealTime(struct sTime *time)
{
u8 buf[8];
DS1302BurstRead(buf);
time->year = buf[6] + 0x2000;
time->mon = buf[4];
time->day = buf[3];
time->hour = buf[2];
time->min = buf[1];
time->sec = buf[0];
time->week = buf[5];
}
void SetRealTime(struct sTime *time)
{
u8 buf[8];
buf[7] = 0;
buf[6] = time->year;
buf[4] = time->mon;
buf[3] = time->day;
buf[2] = time->hour;
buf[1] = time->min;
buf[0] = time->sec;
buf[5] = time->week;
DS1302BurstWrite(buf);
}
void InitDS1302()
{
struct sTime InitTime[] = { //2018年3月1日 星期四 9:44:00
0x18, 0x03, 0x01, 0x10, 0x40, 0x00, 0x04
};
DS1302_CE = 0;
DS1302_CK = 0;
DS1302SingleWrite(7, 0x00); //撤銷寫(xiě)保護(hù)以允許寫(xiě)入數(shù)據(jù)
SetRealTime(&InitTime);
}
二、動(dòng)手實(shí)驗(yàn)
代碼下載可以到我的Github<傳送門(mén)>。
2.1、DS1302單次讀寫(xiě)操作模式
2018 年 2 月 22 號(hào)星期四 12 點(diǎn) 30 分 00 秒這個(gè)時(shí)間寫(xiě)到DS1302 內(nèi)部,讓 DS1302 正常運(yùn)行,然后再不停的讀取 DS1302 的當(dāng)前時(shí)間,并顯示在我們的液晶屏上。
單字節(jié)讀的指令 (reg<<1)|0x81
單字節(jié)寫(xiě)的指令 (reg<<1)|0x80
2.2、DS1302 的 的 BURST模式
仔細(xì)想一下上節(jié)的程序:
定時(shí)器時(shí)間到了 200ms 后,我們連續(xù)把 DS1302
的時(shí)間參數(shù)的 7 個(gè)字節(jié)讀了出來(lái)。但是不管怎么讀,都會(huì)有一個(gè)時(shí)間差,在極端的情況下就會(huì)出現(xiàn)這樣一種情況:假如我們當(dāng)前的時(shí)間是 00:00:59
,我們先讀秒,讀到的秒是 59,然后再去讀分鐘,而就在讀完秒到還未開(kāi)始讀分鐘的這段時(shí)間內(nèi),剛好時(shí)間進(jìn)位了,變成了 00:01:00 這個(gè)時(shí)間,我們讀到的分鐘就是 01,顯示在液晶上就會(huì)出現(xiàn)一個(gè) 00:01:59
,這個(gè)時(shí)間很明顯是錯(cuò)誤的。出現(xiàn)這個(gè)問(wèn)題的概率極小,但卻是實(shí)實(shí)在在可能存在的。
所以這個(gè)時(shí)候就有了BURST模式
,BURST模式就是一次把7 個(gè)字節(jié)全部讀或?qū)懙骄彌_區(qū),然后再來(lái)進(jìn)行后續(xù)操作!
就是實(shí)現(xiàn)為:將要寫(xiě)的5位地址全部寫(xiě)1,即 讀操作用 0xBF
, 寫(xiě)操作用 0xBE
, 這樣的指令送給 DS1302 之后,它就會(huì)自動(dòng)識(shí)別出來(lái)是 burst 模式。
2.3、百尺竿頭更進(jìn)一步,結(jié)構(gòu)體的應(yīng)用實(shí)例
上面程序的實(shí)現(xiàn)思路,大概是我們把DS1302的7個(gè)字節(jié)的時(shí)間放到一個(gè)緩沖數(shù)組中,然后把數(shù)組中的值稍作轉(zhuǎn)換顯示到液晶上,這里就存在一個(gè)小問(wèn)題,DS1302
時(shí)間寄存器的定義并不是我們常用的“年月日時(shí)分秒”
的順序,而是在中間加了一個(gè)字節(jié)的“星期幾”
,而且每當(dāng)我要用這個(gè)時(shí)間的時(shí)候都要清楚的記得數(shù)組的第幾個(gè)元素表示的是什么,這樣一來(lái),一是很容易出錯(cuò),二是程序的可讀性不強(qiáng)。
當(dāng)然你可以把每一個(gè)元素都定一個(gè)明確的變量名字
,這樣就不容易出錯(cuò)也易讀了,但結(jié)構(gòu)上卻顯得很零散了。于是,我們就可以用結(jié)構(gòu)體來(lái)將這一組彼此相關(guān)的數(shù)據(jù)做一個(gè)封裝
,它們既組成了一個(gè)整體,易讀不易錯(cuò),而且可以單獨(dú)定義其中每一個(gè)成員的數(shù)據(jù)類型,比如說(shuō)把年份用 unsigned int 類型,即 4 個(gè)十進(jìn)制位來(lái)表示顯然比 2 位更符合日常習(xí)慣,而其它的類型還是可以用 2 位來(lái)表示。
struct sTime { //日期時(shí)間結(jié)構(gòu)體定義
unsigned int year;
unsigned char mon;
unsigned char day;
unsigned char hour;
unsigned char min;
unsigned char sec;
unsigned char week;
};
下面以一個(gè)電子鐘實(shí)例來(lái)體會(huì)上面所言的BURST讀寫(xiě)
以及定義結(jié)構(gòu)體
的方便。
電子鐘1602顯示的實(shí)現(xiàn)注意要點(diǎn):(保留)
1、lcd1602.c
中又添加了兩個(gè)函數(shù)叫做LcdOpenCursor()
以及LcdCloseCursor()
,其實(shí)還是兩條指令的區(qū)別,0x0C
- 關(guān)閉光標(biāo)。0x0F
- 開(kāi)啟光標(biāo)!
2、加入了結(jié)構(gòu)體,所以我們初始化的時(shí)候要注意,自己定義的順序。
struct sTime InitTime[] = { //2018年3月1日 星期四 9:44:00
0x18, 0x03, 0x01, 0x10, 0x40, 0x00, 0x04
};
3、SetIndex
時(shí)間設(shè)置索引標(biāo)志,當(dāng)它為0的時(shí)候表示不處于時(shí)間設(shè)置狀態(tài),當(dāng)他不為0,表示位于設(shè)置某位時(shí)間的狀態(tài)。根據(jù)程序我們需要設(shè)置年、月、日、時(shí)、分、秒的高位和低位,所以它的取值范圍是0~12
(閉區(qū)間)。
4、程序還是采用模塊化的編程思想,寫(xiě)程序的時(shí)候注意先實(shí)現(xiàn)大塊的內(nèi)容,然后再去實(shí)現(xiàn)具體的函數(shù)細(xì)節(jié)!
5、另外注意,LeftShiftTimeSet()
和RightShiftTimeSet()
的條件必須是 setIndex !=0
才能設(shè)置!!!
void RightShiftTimeSet()
{
if(setIndex != 0)
{
if(setIndex < 12)
setIndex++;
else
setIndex = 1;
RefreshSetShow();
}
}
void LeftShiftTimeSet()
{
if(setIndex != 0)
{
if(setIndex > 1)
setIndex--;
else
setIndex = 12;
RefreshSetShow();
}
}
小結(jié):本篇文章主要介紹了單片機(jī)學(xué)習(xí)中的一個(gè)重要模塊:DS1302。從基礎(chǔ)理論到試驗(yàn)以及試驗(yàn)踩坑,都有涉及。在該部分也并沒(méi)有太難的知識(shí)點(diǎn),多多練習(xí)該模塊對(duì)比賽大有裨益。
希望大家多多支持我的原創(chuàng)文章。如有錯(cuò)誤,請(qǐng)大家及時(shí)指正,非常感謝。