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