关于STM32驱动DS1302实时时钟的一点思考
關(guān)于STM32驅(qū)動DS1302實時時鐘的一點思考
之前用51驅(qū)動過DS1302,沒用多久就輸出了正確的時間。當(dāng)時以為這塊芯片其實沒啥,很簡單。但是現(xiàn)在用STM32做項目,用到同樣的芯片,以為這有何難,只要把那個程序拿過來復(fù)制黏貼改一下IO設(shè)置不就行了?但是事情遠(yuǎn)沒有想想的那么簡單。? ? ? ? ? ? ? ?
經(jīng)過3天的掙扎,現(xiàn)在才知道當(dāng)時自己是多么天真。
關(guān)于DS1302的基本操作可以看這里:http://www.cnblogs.com/qsyll0916/p/7712695.html
好了,廢話少說了,進(jìn)入正題。
首先DS1302讀寫方式屬于3線SPI。CE、SCK、IO。其中IO口屬于雙向IO口,我們讀寫都要經(jīng)過這個IO口。在用51開發(fā)的時候,因外他是準(zhǔn)雙向IO,不需要我們額外關(guān)心他的輸入輸出設(shè)置。需要輸出的時候直接寫? ? ??? ? ??P0^1 = 1;? ?
需要檢測外部輸入的時候直接寫? ? ???if(P0^1 == 1)? ?,
都很方便,但是方便的同時帶來的是讀寫速度上的限制。那么在STM32中,每個IO口都有8種輸出模式。復(fù)雜的同時也意味著每一種模式都是專門定制的,帶來了速度上的優(yōu)勢。所以在移植這個程序的時候,就需要注意這個雙向IO的設(shè)置問題。一開始我也不是很懂,各種百度查資料,各種問人。最后才知道有兩種方式可以實現(xiàn)雙向的IO讀寫設(shè)置。
第一:
#define DS1302_DATIN PBin(6) #define DS1302_DATOUT PBout(6)#define DS1302_DAT_INPUT() {GPIOB->CRL&= 0XF0FFFFFF;GPIOB->CRL|= 8<<24;} //設(shè)置成上拉或者下拉輸入模式,需要外接上拉電阻 #define DS1302_DAT_OUTPUT() {GPIOB->CRL&= 0XF0FFFFFF;GPIOB->CRL|= 3<<24;} //設(shè)置成最大50M的通用推挽輸出通過簡單的寄存器操作,可以實現(xiàn)輸入輸出的快速切換。需要在端口處接上拉電阻。
第二:
GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStruct.GPIO_Pin = DS1302_IO_PIN; //這里最好設(shè)成開漏,當(dāng)然也可以普通推挽,但是需要后面一直切換輸入輸出模式GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD; //開漏輸出,需要接上拉,不需要切換輸入輸出了。GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //輸出速度50MHz GPIO_Init(DS1302_PORT, &GPIO_InitStruct);把IO配置成開漏輸出模式。必須外接上拉電阻,不然讀出的全是低電平。這樣就不用一直切換輸入輸出模式,可以直接像51那樣,直接使用。
雙向IO的配置基本上就是上面所說的兩種情況,但是可能還有其他方式,但是我目前就只知道這個方式,后面學(xué)習(xí)了在補充。
配置完IO之后就開始對IO進(jìn)行操作,從而讀寫DS1302,獲取實時時間。讀寫的時候特別需要注意。網(wǎng)上很多教程都沒有提到這一點,估計是太基礎(chǔ)了吧。但是這個小小的問題卻困擾了我很長時間。就是在進(jìn)行對DS1302的寫命令和數(shù)據(jù)的操作的時候,我們需要按照從低位到高位依次發(fā)送數(shù)據(jù)。發(fā)送數(shù)據(jù)的時候:
在51里面我們經(jīng)常會這樣寫:{? ??SCIO = byte_1 & 0x01; byte_1 >> = 1;? ? }
那么在32中我們可以這樣寫嗎?:? ?{? ? PBout(6) =??byte_1 & 0x01; byte_1 >> = 1;? ? }
絕對不行。雖然看上去沒有什么問題,也不會報錯,但是卻就是不同正確通信。很氣人。
原因是PBout(6)這樣的操作是屬于STM32的位帶操作。但是在CM3中不允許位帶操作賦值除0和1以外的數(shù)。
也就是說上面那種操作方式是給??PBout(6)?賦值2,3,4,之類的數(shù),但是stm32卻不能理解這是什么意思。因為它只認(rèn)識0和1!!!
所以我們可以簡單的這樣處理:
if((byte_1 & t) != 0) //之前的問題出在這里,32的位帶操作不能賦值0和1之外的值。 {DS1302_DATOUT = 1;}else{DS1302_DATOUT = 0;}這樣就可以正常通信了。但是如果不能像上面那樣處理的話,現(xiàn)象是一直讀出85這個數(shù)。關(guān)于讀出85這個數(shù),除了上面我提到的這種特殊情況,網(wǎng)上還是有很多人有這方面的經(jīng)驗的,我總結(jié)了一下,大致是下面這幾種情況:
1、讀取完時間后沒有把DATA_IO引腳拉低。導(dǎo)致顯示問號和85等一些亂七八糟的東西。但是我加了。
2、電壓不夠,小于4.6V。但是這個網(wǎng)上有爭議,我接的是5V,實測4.8V,應(yīng)該沒問題。
3、沒有接上拉電阻。我只在需要雙向IO的地方加了上拉電阻,利用的是板子上預(yù)留的IIC的SDA,上面有一個4.7K的上拉,我把IO接在了這里。應(yīng)該也沒問題
4、仿真時序不對。但是我之前用這個時序在51上面實現(xiàn)過一樣的功能,現(xiàn)在移植到32上應(yīng)該也沒什么問題啊,延時時間也仿真了,嚴(yán)格按照1us的延時仿真的。
但是在我的實驗過程中還是發(fā)現(xiàn)了很多其他的現(xiàn)象,再次也記錄下來,防止有人遇到相同的問題,就能不浪費那么多時間。
1、確實要注意DS1302的電壓,最好不要用STM32開發(fā)板上面的3.3V,反正我是沒做出來。如果用外部電源給DS1302供電的話,需要將外部電源和開發(fā)板共地,不然讀出全是85.
2、DS1302如果需要修改時間。需要把初始化函數(shù)里面的上電保護去掉,再次下載重置的時間,然后再把上電保護那段給添加上去,防止復(fù)位后時間被重置。
目前我的問題就是這么多。在一一解決了上述問題之后,就能準(zhǔn)確的讀取時間了,實測一天24H誤差不超過2s,還是很準(zhǔn)的了。
好了,下面就是源程序了。
首先是DS1302的頭文件,主要是一些位帶操作和預(yù)定義
#ifndef __DS1302_H #define __DS1302_H#include <stm32f10x.h> #include "hardware.h"//相對應(yīng)的IO口配置 #define DS1302_PORT GPIOB#define DS1302_SCK_PIN GPIO_Pin_7 //時鐘 #define DS1302_IO_PIN GPIO_Pin_6 //雙向IO口, #define DS1302_CE_PIN GPIO_Pin_5 //片選使能,當(dāng)需要讀寫的時候,置高位#define DS1302_SCK PBout(7) //位帶操作,可直接給高低電平,但是切記不能給0.1之外的數(shù)。切記 #define DS1302_CE PBout(5) #define DS1302_DATIN PBin(6) #define DS1302_DATOUT PBout(6) //存放時間 typedef struct _time{ u8 second;u8 minute;u8 hour;u8 date;u8 month;u8 week;u8 year;}my_time;void DS1302_Init(void); void ds1302_readtime(void); void display_real_time(void); //顯示實時時間#endif
DS1302操作源文件:
#include "ds1302.h" #include "spi.h"//READ_RTC_ADDR[7] = {0x81, 0x83, 0x85, 0x87, 0x89, 0x8b, 0x8d};//讀取時間的命令地址,已經(jīng)通過讀寫操作來直接實現(xiàn)這些地址 //WRITE_RTC_ADDR[7] = {0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c};//寫時間的命令地址my_time TIME = {0}; //顯示時間的結(jié)構(gòu)體 u8 init_time[] = {0x00,0x28,0x21,0x27,0x12,0x06,0x17}; //初始化時間:秒 分 時 日 月 周 年static void ds1302_gpio_init(void); static void ds1302_writebyte(u8 byte_1);//寫一個字節(jié); byte是保留字,不能作為變量 static void ds1302_writedata(u8 addr,u8 data_);//給某地址寫數(shù)據(jù),data是c51內(nèi)部的關(guān)鍵字,表示將變量定義在數(shù)據(jù)存儲區(qū),故此處用data_; static u8 ds1302_readbyte(void);//讀一個字節(jié) static u8 ds1302_readdata(u8 addr);//讀取某寄存器數(shù)據(jù); static void DS1302_delay_us(u16 time); //簡單延時1us//基本IO設(shè)置 static void ds1302_gpio_init(void) {GPIO_InitTypeDef GPIO_InitStruct; //開啟GPIOD的時鐘 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //設(shè)置GPIO的基本參數(shù) GPIO_InitStruct.GPIO_Pin = DS1302_SCK_PIN | DS1302_CE_PIN ; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; //這兩個普通端口設(shè)為推挽輸出 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //輸出速度50MHz GPIO_Init(DS1302_PORT, &GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = DS1302_IO_PIN; //這里最好設(shè)成開漏,當(dāng)然也可以普通推挽,但是需要后面一直切換輸入輸出模式GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD; //開漏輸出,需要接上拉,不需要切換輸入輸出了。GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //輸出速度50MHz GPIO_Init(DS1302_PORT, &GPIO_InitStruct);}//寫一個字節(jié) //數(shù)據(jù)和地址都是從最低位開始傳輸?shù)?/span> static void ds1302_writebyte(u8 byte_1) {u8 i = 0;u8 t = 0x01;for(i = 0;i<8;i++){if((byte_1 & t) != 0) //之前的問題出在這里,32的位帶操作不能賦值0和1之外的值。 {DS1302_DATOUT = 1;}else{DS1302_DATOUT = 0;}DS1302_delay_us(2);DS1302_SCK = 1; //上升沿寫入DS1302_delay_us(2);DS1302_SCK = 0; DS1302_delay_us(2);t<<= 1;}DS1302_DATOUT = 1; //釋放IO,后面讀取的話會準(zhǔn)確很多DS1302_delay_us(2); //因為如果寫完之后IO被置了低電平,開漏輸出模式下讀取的時候會有影響,最好先拉高,再讀取 }//地址寫數(shù)據(jù) static void ds1302_writedata(u8 addr,u8 data_) { DS1302_CE = 0; DS1302_delay_us(2); DS1302_SCK = 0; DS1302_delay_us(2); DS1302_CE = 1; DS1302_delay_us(2); //使能片選信號 ds1302_writebyte((addr<<1)|0x80); //方便后面寫入,轉(zhuǎn)化之后是地址寄存器的值, ds1302_writebyte(data_);DS1302_CE = 0; DS1302_delay_us(2);//傳送數(shù)據(jù)結(jié)束,失能片選DS1302_SCK = 0; DS1302_delay_us(2);//拉低,準(zhǔn)備下一次寫數(shù)據(jù) }//讀取一個字節(jié),上升沿讀取 static u8 ds1302_readbyte(void) {u8 i = 0;u8 data_ = 0;//因為上面已經(jīng)把端口設(shè)置為開漏,電路外部接了山拉電阻,可以不切換輸入輸出模式,直接使用。// DS1302_DAT_INPUT(); DS1302_SCK = 0;DS1302_delay_us(3);for(i=0;i<7;i++) //這里發(fā)現(xiàn)設(shè)為8的話輸出數(shù)據(jù)不對,很亂 {if((DS1302_DATIN) == 1) {data_ = data_ | 0x80; //低位在前,逐位讀取,剛開始不對,估計是這個的問題 }data_>>= 1;DS1302_delay_us(3);DS1302_SCK = 1;DS1302_delay_us(3);DS1302_SCK = 0;DS1302_delay_us(3);}return (data_); }//讀取寄存器的值 static u8 ds1302_readdata(u8 addr) {u8 data_ = 0;DS1302_CE = 0; DS1302_delay_us(2);DS1302_SCK = 0; DS1302_delay_us(2);DS1302_CE = 1; DS1302_delay_us(2); //讀寫操作時CE必須為高,切在SCK為低時改變 ds1302_writebyte((addr<<1)|0x81); //寫入讀時間的命令data_ = ds1302_readbyte(); DS1302_SCK = 1; DS1302_delay_us(2);DS1302_CE = 0; DS1302_delay_us(2);DS1302_DATOUT = 0; DS1302_delay_us(3); //這里很多人說需要拉低,但是我發(fā)現(xiàn)去掉這個也可以顯示啊,不過為了保險,還是加上。DS1302_DATOUT = 1; DS1302_delay_us(2);return data_; }void DS1302_Init(void) {u8 i = 0;ds1302_gpio_init(); //端口初始化 DS1302_CE = 0; DS1302_delay_us(2);DS1302_SCK = 0; DS1302_delay_us(2); i = ds1302_readdata(0x00); //讀取秒寄存器,if((i & 0x80) != 0)//通過判斷秒寄存器是否還有數(shù)據(jù)來決定下次上電的時候是否初始化時間,就是掉電保護 {ds1302_writedata(7,0x00); //撤銷寫保護,允許寫入數(shù)據(jù),0x8e,0x00for(i = 0;i<7;i++){ds1302_writedata(i,init_time[i]);}}ds1302_writedata(7,0x80);//打開寫保護功能,防止干擾造成的數(shù)據(jù)寫入。 }//************ void ds1302_readtime(void) //讀取時間 {u8 i;for(i = 0;i<7;i++){init_time[i] = ds1302_readdata(i);} } static void DS1302_delay_us(u16 time) { u16 i = 0; while(time--){i = 5; //自己定義while(i--);} }//顯示實時時間 void display_real_time(void) {ds1302_readtime(); //先獲取時間到緩沖區(qū)//BCD碼轉(zhuǎn)換ASCII碼TIME.year = ((init_time[6]&0x70)>>4)*10 + (init_time[6]&0x0f); //高三位加低四位TIME.month = ((init_time[4]&0x70)>>4)*10 + (init_time[4]&0x0f);TIME.date = ((init_time[3]&0x70)>>4)*10 + (init_time[3]&0x0f);TIME.week = ((init_time[5]&0x70)>>4)*10 + (init_time[5]&0x0f);TIME.hour = ((init_time[2]&0x70)>>4)*10 + (init_time[2]&0x0f);TIME.minute = ((init_time[1]&0x70)>>4)*10 + (init_time[1]&0x0f);TIME.second = ((init_time[0]&0x70)>>4)*10 + (init_time[0]&0x0f); OLED_ShowNum(48,0,TIME.hour,2,16);OLED_ShowChar(64,0,':');OLED_ShowNum(72,0,TIME.minute,2,16);OLED_ShowChar(88,0,':');OLED_ShowNum(96,0,TIME.second,2,16);}在驅(qū)動DS1302的時候,我遇到的基本上就是上面這些情況了。如果還有朋友遇到其他情況,可以一起討論。
?
總結(jié)
以上是生活随笔為你收集整理的关于STM32驱动DS1302实时时钟的一点思考的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: char s[] 和 char *s 的
- 下一篇: DS1302的读写