
從買來那塊 DS1302 模塊到把它在 STM32 上徹底調通我前后折騰了兩天。網上關于 DS1302 的代碼確實不少但很多都是 51 平臺直接復制過來的要么沒講時序要么對 STM32 的 GPIO 模式切換一筆帶過。這篇學習筆記把整個驅動從零寫一遍包括為什么用 GPIO 模擬時序、命令字節(jié)的坑、BCD 碼換算、寫保護和時鐘暫停位把你可能遇到的坑提前踩平。適合正在學 STM32、想搞懂串行時序、或者需要給手頭項目加一個掉電不丟時間的實時時鐘的開發(fā)者參考。1. 思路先行為什么用 GPIO 模擬 DS1302而不是硬件 SPI1.1 DS1302 是什么這個項目能學到什么DS1302 是一顆非常經典的實時時鐘芯片內部自帶秒、分、時、日、月、星期、年的寄存器還有 31 字節(jié)的非易失性靜態(tài) RAM。它的典型應用就是給嵌入式系統(tǒng)提供“掉電后繼續(xù)走”的時間基準主電源 VCC2 正常供電時芯片工作外部斷電后由 VCC1 上的紐扣電池或大電容維持計時。這種機制對很多需要記錄事件時間戳、定時任務、電子鐘表、數(shù)據(jù)記錄儀之類的項目非常有用。驅動 DS1302 對你理解 STM32 的 GPIO 操作也很有幫助。它用的是三線串行接口CE、SCLK、IO看起來像 SPI但實際時序有自己的一套。手寫這套驅動你會把“時序圖怎么讀”“命令字節(jié)怎么組織”“雙向 IO 怎么處理”“BCD 碼怎么換算”這些問題一次性過一遍。這些基本功在以后驅動傳感器、存儲器、顯示屏時都會反復用到。從我自己的角度看這個項目特別適合作為“學習筆記”的素材因為它的邏輯不復雜但每個環(huán)節(jié)都能挖出細節(jié)。只要認真走一遍以后再遇到類似的串行器件上手速度會快很多。1.2 GPIO 模擬 vs 硬件 SPI我為什么這樣選DS1302 的三線接口在某些代碼里被歸類成“類 SPI”也有人直接用硬件 SPI 去驅動過。但從數(shù)據(jù)手冊的時序來看它跟標準 SPI 并不是一回事DS1302 需要 CE 拉高作為一次傳輸?shù)拈_始數(shù)據(jù)按 LSB 先發(fā)命令字節(jié)帶有固定的高位標志位而且 IO 是半雙工雙向口。用硬件 SPI 不是不行但你需要額外處理 IO 方向切換、調整時鐘極性和相位還可能被 SPI 外設的字節(jié)長度、片選控制等問題困擾。折騰半天最后發(fā)現(xiàn)還是 GPIO 模擬更干凈。GPIO 模擬的好處是每個時鐘沿、每個電平變化都在你自己手里。出問題時可以用邏輯分析儀抓波形一步一步對時序圖定位非??臁S1302 的時鐘頻率上限大約是 2MHz實際應用里我們通常給它 1us 級別的半周期延時這個速度對 GPIO 來說毫無壓力完全不用擔心模擬方式跑不動。這個選擇也符合我自己的習慣能用簡單方案把事情做穩(wěn)就不用復雜方案。特別是學習階段每行代碼都看得懂、每個狀態(tài)都測得出來遠比“外設配置對但說不清為什么”有價值。2. 時序與寄存器最容易翻車的三個關鍵細節(jié)2.1 命令字節(jié)、LSB 和讀寫時序的關鍵點DS1302 的每次操作都是從 CE 拉高開始然后主機通過 IO 發(fā)送一個命令字節(jié)再要么寫一個數(shù)據(jù)字節(jié)要么讀一個數(shù)據(jù)字節(jié)最后 CE 拉低。命令字節(jié)的格式是固定的bit7 固定為 1bit6 用于區(qū)分操作對象0 表示操作時鐘寄存器1 表示操作內部 RAMbit5 到 bit1 是寄存器地址bit0 表示讀或寫0 寫、1 讀。另一個容易忽略的地方是數(shù)據(jù)是 LSB first也就是低位在前。很多從 SPI 轉過來的人習慣性先發(fā)高位結果一上來就讀到完全錯亂的數(shù)據(jù)。寫一個字節(jié)時每次先把當前 bit 放到 IO 上然后產生一個 SCLK 上升沿芯片在這個上升沿采樣數(shù)據(jù)。讀一個字節(jié)時芯片會在 SCLK 下降沿之后把數(shù)據(jù)放到 IO 上所以主機應該在 SCLK 高電平期間讀取 IO或者下降沿后稍作延時再讀。我實測在 SCLK 高電平期間讀是穩(wěn)定的前提是 IO 方向已經切對。還有一個細節(jié)很關鍵讀命令發(fā)送完最后一個 bit 之后IO 要立刻從輸出轉為輸入。如果切換慢了芯片開始輸出數(shù)據(jù)時你的引腳還在驅動總線兩邊打架輕則讀到錯誤數(shù)據(jù)重則可能損傷 IO。這也是我為什么在底層函數(shù)里把“釋放 IO”放在讀字節(jié)循環(huán)前面。2.2 寄存器地址表與 BCD 碼換算DS1302 的時鐘寄存器從秒到年寫地址和讀地址相差 1規(guī)律非常明顯。我整理了一張常用地址表寄存器寫地址讀地址說明秒0x800x81bit7 是時鐘暫停位 CH分0x820x83無特殊標志位時0x840x85bit7 是 12/24 小時模式標志日0x860x87無特殊標志位月0x880x89無特殊標志位星期0x8A0x8B取值范圍 1 到 7年0x8C0x8D兩位年份如 24 表示 2024寫保護0x8E0x8Fbit7 置 1 禁止寫入涓流充電0x900x91用于給備份電容充電時鐘突發(fā)0xBE0xBF一次連續(xù)讀寫全部時鐘寄存器RAM 突發(fā)0xFE0xFF一次連續(xù)讀寫 31 字節(jié) RAM芯片內部的時間數(shù)據(jù)全部用 BCD 碼保存也就是說一個字節(jié)的高四位表示十位低四位表示個位。比如秒寄存器里存 0x45就代表 45 秒而不是十進制的 45 對應的 0x2D。所以讀寫時間時必須有十進制到 BCD、BCD 到十進制的轉換函數(shù)。這個換算本身不難但漏掉任何一個方向都會讓你調半天。2.3 寫保護位、時鐘暫停位和 24 小時模式的坑寫保護是新手第一個容易踩的坑。DS1302 上電后寫保護位不一定處于關閉狀態(tài)如果你不先把寫保護位寄存器0x8E的 bit7 清零那么所有寫入操作都會被忽略時間怎么都寫不進去。很多人調了很久“寫不進去”最后發(fā)現(xiàn)就是少了這一句。第二個坑是秒寄存器里的 bit7也就是時鐘暫停位。當這個位為 1 時芯片內部振蕩器被關閉秒數(shù)根本不會走。如果你發(fā)現(xiàn)模塊能讀到時間但秒數(shù)一直不變十有八九是這個位沒清。初始化時讀秒寄存器把 bit7 清掉再寫回去時鐘才會真正跑起來。第三個坑比較隱蔽是小時寄存器的掩碼處理。秒、分寄存器讀出來時通常用 0x7F 把最高位清掉小時寄存器卻有人用 0x3F。問題在于 24 小時制下小時十位的 2 存儲在 bit6如果你用 0x3F 去掩碼會把 20 點到 23 點的數(shù)據(jù)直接變成 0 點到 3 點。正確做法是設置時間時用 0x7F 清除 12/24 模式的 bit7讀取時也用 0x7F 保留 bit6。這個細節(jié)不踩一次很難意識到我在日志里看到 20 點讀成 00 點的時候還納悶了很久。3. 驅動代碼實現(xiàn)從底層字節(jié)讀寫到時間封裝3.1 引腳定義與 GPIO 初始化開漏模式的便利我先說 GPначала引腳分配。以 STM32 的 GPIOB 為例PB0 接 CEPB1 接 SCLKPB2 接 IO。芯片的 VCC2 接 3.3VGND 接地IO 線上加一個 4.7k 歐姆上拉電阻到 3.3VVCC1 我接了一個紐扣電池座。32.768kHz 晶振的兩端按模塊設計接好通常模塊上已經預留了電容位置。初始化 GPIO 時我對三個引腳用了不同策略。CE 和 SCLK 是主機輸出配置成推挽輸出。IO 是雙向口我選擇了開漏輸出模式并開啟內部上拉配合外部 4.7k 上拉電阻。為什么用開漏因為 STM32 的開漏輸出模式下輸出數(shù)據(jù)寄存器寫 1 時引腳處于釋放狀態(tài)此時可以把這個引腳當輸入用直接讀 GPIO 的輸入數(shù)據(jù)寄存器就能拿到外部電平。這樣 IO 方向切換不再需要重新配置 GPIO 模式寫數(shù)據(jù)時寫 0 和寫 1讀數(shù)據(jù)時寫 1 釋放總線然后讀穩(wěn)定又干凈。如果使用推挽輸出理論上寫數(shù)據(jù)更快但讀取前必須把引腳從輸出模式切換到輸入模式切換時機不對很容易產生毛刺。對比下來開漏加外部上拉更合適尤其適合 DS1302 這種低速半雙工接口。#include main.h #include stdint.h #define DS1302_CE_PORT GPIOB #define DS1302_CE_PIN GPIO_PIN_0 #define DS1302_SCLK_PORT GPIOB #define DS1302_SCLK_PIN GPIO_PIN_1 #define DS1302_IO_PORT GPIOB #define DS1302_IO_PIN GPIO_PIN_2 #define DS1302_CE_LOW() HAL_GPIO_WritePin(DS1302_CE_PORT, DS1302_CE_PIN, GPIO_PIN_RESET) #define DS1302_CE_HIGH() HAL_GPIO_WritePin(DS1302_CE_PORT, DS1302_CE_PIN, GPIO_PIN_SET) #define DS1302_SCLK_LOW() HAL_GPIO_WritePin(DS1302_SCLK_PORT, DS1302_SCLK_PIN, GPIO_PIN_RESET) #define DS1302_SCLK_HIGH() HAL_GPIO_WritePin(DS1302_SCLK_PORT, DS1302_SCLK_PIN, GPIO_PIN_SET) #define DS1302_IO_WRITE(v) HAL_GPIO_WritePin(DS1302_IO_PORT, DS1302_IO_PIN, (v) ? GPIO_PIN_SET : GPIO_PIN_RESET) #define DS1302_IO_READ() HAL_GPIO_ReadPin(DS1302_IO_PORT, DS1302_IO_PIN) static void ds1302_gpio_init(void) { GPIO_InitTypeDef gpio {0}; __HAL_RCC_GPIOB_CLK_ENABLE(); gpio.Pin DS1302_CE_PIN | DS1302_SCLK_PIN; gpio.Mode GPIO_MODE_OUTPUT_PP; gpio.Pull GPIO_NOPULL; gpio.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOB, gpio); gpio.Pin DS1302_IO_PIN; gpio.Mode GPIO_MODE_OUTPUT_OD; gpio.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOB, gpio); DS1302_CE_LOW(); DS1302_SCLK_LOW(); DS1302_IO_WRITE(1); }初始化最后那三行不是擺設。CE 低電平讓芯片處于待機狀態(tài)SCLK 低電平保證沒有時鐘沿觸發(fā)誤動作IO 寫 1 釋放總線避免復位后引腳狀態(tài)不確定拖住總線。3.2 底層讀寫一個字節(jié)實現(xiàn)和原理看代碼前先明確一下 DS1302 的位時序。寫字節(jié)時主機把當前位放到 IO 上然后 SCLK 拉高形成上升沿芯片采樣再拉低準備下一位。讀字節(jié)時芯片在 SCLK 下降沿之后更新輸出所以主機可以在 SCLK 高電平期間讀取 IO。static void ds1302_delay_us(uint32_t us) { for (uint32_t i 0; i us; i) { for (volatile uint32_t j 0; j 16U; j) { __NOP(); } } } static void ds1302_write_byte(uint8_t dat) { uint8_t i; for (i 0; i 8; i) { DS1302_IO_WRITE(dat 0x01); DS1302_SCLK_HIGH(); ds1302_delay_us(1); DS1302_SCLK_LOW(); ds1302_delay_us(1); dat 1; } } static uint8_t ds1302_read_byte(void) { uint8_t i, dat 0; DS1302_IO_WRITE(1); for (i 0; i 8; i) { dat 1; DS1302_SCLK_HIGH(); ds1302_delay_us(1); if (DS1302_IO_READ()) { dat | 0x80; } DS1302_SCLK_LOW(); ds1302_delay_us(1); } return dat; }讀函數(shù)里每次循環(huán)先把 dat 右移一位再根據(jù) IO 電平決定要不要置最高位相當于在收集一個反轉的移位寄存器。為什么不在循環(huán)開始直接讀 IO 再移位也可以但要保證第 0 位落在 bit0而不是最后變成反序。我習慣先移位再置位配合 LSB first 的特性最后自然得到正確的字節(jié)。延時 1us 是故意的。DS1302 的 SCLK 最快大概到 2MHz半個周期就是 250ns 左右不延時也能跑。但實測中如果 STM32 主頻較高、代碼優(yōu)化開得激進沒有延時的時鐘沿翻轉容易超出芯片規(guī)格導致偶發(fā)讀取錯誤。加 1us 延時后時序余量很充足又不影響實際體驗屬于“花小錢買個穩(wěn)”。3.3 寄存器讀寫與時間結構封裝底層寫好之后寄存器讀寫就很直白了。注意我的接口約定所有寄存器參數(shù)統(tǒng)一使用偶數(shù)地址也就是手冊里的“寫地址”函數(shù)內部把 bit0 改成 0 或 1。void ds1302_write_reg(uint8_t reg, uint8_t val) { reg 0xFE; DS1302_CE_HIGH(); ds1302_write_byte(reg); ds1302_write_byte(val); DS1302_CE_LOW(); } uint8_t ds1302_read_reg(uint8_t reg) { uint8_t val; reg | 0x01; DS1302_CE_HIGH(); ds1302_write_byte(reg); val ds1302_read_byte(); DS1302_CE_LOW(); return val; }每次傳輸 CE 都要經歷一次拉低拉高。CE 拉低是固定的結束標志不要省略。如果沒有在讀完最后一個 bit 后把 CE 拉低芯片不會正常結束本次傳輸下一次操作時序會亂。這個坑我在調試突發(fā)模式時踩過后面會細說。接下來是 BCD 轉換和時間結構。typedef struct { uint8_t year; uint8_t month; uint8_t day; uint8_t week; uint8_t hour; uint8_t min; uint8_t sec; } ds1302_time_t; static uint8_t ds1302_dec_to_bcd(uint8_t dec) { return (uint8_t)(((dec / 10) 4) | (dec % 10)); } static uint8_t ds1302_bcd_to_dec(uint8_t bcd) { return (uint8_t)((bcd 4) * 10 (bcd 0x0F)); } void ds1302_set_time(const ds1302_time_t *tm) { ds1302_write_reg(0x8E, 0x00); ds1302_write_reg(0x80, ds1302_dec_to_bcd(tm-sec) 0x7F); ds1302_write_reg(0x82, ds1302_dec_to_bcd(tm-min) 0x7F); ds1302_write_reg(0x84, ds1302_dec_to_bcd(tm-hour) 0x7F); ds1302_write_reg(0x86, ds1302_dec_to_bcd(tm-day) 0x3F); ds1302_write_reg(0x88, ds1302_dec_to_bcd(tm-month) 0x1F); ds1302_write_reg(0x8A, ds1302_dec_to_bcd(tm-week) 0x07); ds1302_write_reg(0x8C, ds1302_dec_to_bcd(tm-year)); ds1302_write_reg(0x8E, 0x80); } void ds1302_get_time(ds1302_time_t *tm) { uint8_t b; b ds1302_read_reg(0x80); tm-sec ds1302_bcd_to_dec(b 0x7F); b ds1302_read_reg(0x82); tm-min ds1302_bcd_to_dec(b 0x7F); b ds1302_read_reg(0x84); tm-hour ds1302_bcd_to_dec(b 0x7F); b ds1302_read_reg(0x86); tm-day ds1302_bcd_to_dec(b 0x3F); b ds1302_read_reg(0x88); tm-month ds1302_bcd_to_dec(b 0x1F); b ds1302_read_reg(0x8A); tm-week ds1302_bcd_to_dec(b 0x07); b ds1302_read_reg(0x8C); tm-year ds1302_bcd_to_dec(b); }設置時間函數(shù)第一行關閉寫保護最后一行為可選的重新開啟寫保護。如果你打算頻繁校時可以留著寫保護打開防止其他代碼誤操作時間寄存器。讀時間函數(shù)里的小時掩碼用了 0x7F而不是常見的 0x3F這一點我在前文反復強調過20 點到 23 點的十位信息在 bit6用錯掩碼會直接把時間讀錯。3.4 main 函數(shù)接入示例接入工程很簡單初始化調用一次 gpio init如果模塊是第一次上電或者需要校時就調用一次 set_time之后循環(huán)讀時間即可。int main(void) { HAL_Init(); SystemClock_Config(); ds1302_gpio_init(); ds1302_time_t t { .year 24, .month 5, .day 18, .week 6, .hour 10, .min 30, .sec 0 }; ds1302_set_time(t); while (1) { ds1302_get_time(t); printf(%02u-%02u-%02u %02u:%02u:%02u week%u\r\n, t.year, t.month, t.day, t.hour, t.min, t.sec, t.week); HAL_Delay(1000); } }注意演示代碼里每次上電都 set_time實際項目不該這么做否則每次斷電重啟時間都會重置。正確做法是只做初始化清除寫保護和時鐘暫停位讓芯片跟著備用電池繼續(xù)走。如果需要判斷“是否需要校時”可以存一個標志位到 DS1302 內置 RAM或者通過按鍵、上位機命令主動校時。4. 調不通怎么辦常見癥狀排查與避坑實錄4.1 讀數(shù)全是 0xFF 或 0x00優(yōu)先排查這五點如果你讀出來的時間全是 0xFF或者全是 0x00不要急著懷疑芯片壞了。按照下面這個順序查絕大多數(shù)問題都能定位。第一查接線。CE、SCLK、IO 三根線有沒有接反VCC2 的電壓是否在 2.0V 到 5.5V 之間GND 是不是和 STM32 共地。很多模塊自帶電池座電池極性反了也會導致怪現(xiàn)象。第二查 IO 上拉。如果用開漏模式但沒有外部上拉只是依賴 STM32 內部上拉讀數(shù)據(jù)時總線可能被拉到一個不穩(wěn)定的中間態(tài)尤其在 IO 方向切換的瞬間。所以外部 4.7k 上拉強烈建議要加。第三查 SCLK 延時。代碼里的 1us 延時是否真的生效。有些工程開了 O3 優(yōu)化空循環(huán)可能被編譯器優(yōu)化掉一部分導致時鐘翻轉過快。第四查 CE 時序。每次讀寫都必須 CE 高電平開始、低電平結束。如果 CE 一直懸空或者沒有正確拉低芯片的傳輸狀態(tài)會混亂。第五查命令字節(jié)。最簡單的方法是買個十幾塊錢的邏輯分析儀抓一下 CE、SCLK、IO 三根線對一下命令字節(jié)是不是 0x80 寫、0x81 讀這樣的正確序列。沒有邏輯分析儀的話可以用 GPIO 翻轉法在讀寫函數(shù)前后翻轉一個調試引腳再用示波器看間隔。我這里遇到過一次比較隱蔽的情況讀出來全是 0xFF追查發(fā)現(xiàn)是 IO 引腳在初始化時被配置成了推挽輸出寫數(shù)據(jù)時正常但讀數(shù)據(jù)前切換輸入模式沒有成功。換成開漏模式后問題再沒出現(xiàn)過。4.2 秒數(shù)不走、分鐘亂跳的處理秒數(shù)完全不動大概率是時鐘暫停位。秒寄存器 bit7 是 CH為 1 時振蕩器關閉。初始化時或者每次 set_time 時把這個位清 0秒針就開始走了。如果清 0 之后走了幾秒又停住可能是代碼某處又把秒寄存器寫成帶 CH 位的數(shù)據(jù)比如沒有掩碼直接寫回了一個含 0x80 的 BCD 值。分鐘亂跳則常發(fā)生在連續(xù)單字節(jié)讀取時。因為秒和分是兩個獨立寄存器讀取過程中如果恰好跨越秒進位邊界你可能讀到“新秒 舊分”的組合看起來就是分鐘突然變化。避免辦法有兩個。第一個是連續(xù)讀兩次秒寄存器如果兩次結果不一致說明跨越了進位重新讀一遍完整時間。第二個更優(yōu)雅直接使用時鐘突發(fā)模式一次讀出秒、分、時、日、月、星期、年 7 個字節(jié)減少跨寄存器的不一致窗口。突發(fā)讀命令是 0xBFCE 拉高后發(fā)命令然后連續(xù)讀 7 個字節(jié)最后 CE 拉低。要注意的是CE 保持高電平期間芯片會持續(xù)輸出數(shù)據(jù)所以必須按需要的長度讀讀完立刻拉低 CE否則會進入循環(huán)輸出時序全部亂掉。4.3 掉電不保存和涓流充電的配置掉電后時間丟失最常見原因是 VCC1 沒有接備用電源或者紐扣電池沒電了。DS1302 正常工作時由 VCC2 供電斷電后切換到 VCC1。注意 VCC1 的電壓不能高于 VCC2 加 0.3V否則內部電源切換電路可能出問題。所以 VCC1 接 3V 紐扣電池或 2.4V 到 3.6V 的超級電容是穩(wěn)妥的。如果你想用超級電容并希望主電源在的時候自動給它充電可以配置涓流充電寄存器 0x90。這個寄存器的用法比較講究高四位 TCS 必須寫成 1010 才會使能充電中間兩位選擇二極管數(shù)量低兩位選擇充電電阻。一個常見配置是 0xA5代表一個二極管加 2k 歐姆電阻也有模塊出廠配置成 0xA6 或 0xA7。如果這個寄存器的 TCS 位不是 1010涓流充電是關閉的。我自己踩過充電相關的一個坑一開始為了省事直接寫 0xA5結果 0.22F 的超級電容很快就充滿了但芯片時間正常。后來換了大容量電容才發(fā)現(xiàn)如果電容太大2k 電阻的充電電流可能偏大需要根據(jù)電容容量調整電阻檔位。所以沒什么特別需求時不要把涓流充電開得太激進寧可電容大一點、充得慢一點。5. 最后再分享三個實用小技巧5.1 讀時間更穩(wěn)的一個小技巧突發(fā)讀模式前面提到突發(fā)模式可以減少跨秒進位的讀數(shù)不一致問題這里直接給一個簡單的實現(xiàn)。時鐘突發(fā)讀命令是 0xBFCE 高電平期間連續(xù)讀 7 個字節(jié)最后 CE 拉低。void ds1302_get_time_burst(ds1302_time_t *tm) { uint8_t buf[7]; uint8_t i; DS1302_CE_HIGH(); ds1302_write_byte(0xBF); for (i 0; i 7; i) { buf[i] ds1302_read_byte(); } DS1302_CE_LOW(); tm-sec ds1302_bcd_to_dec(buf[0] 0x7F); tm-min ds1302_bcd_to_dec(buf[1] 0x7F); tm-hour ds1302_bcd_to_dec(buf[2] 0x7F); tm-day ds1302_bcd_to_dec(buf[3] 0x3F); tm-month ds1302_bcd_to_dec(buf[4] 0x1F); tm-week ds1302_bcd_to_dec(buf[5] 0x07); tm-year ds1302_bcd_to_dec(buf[6]); }這段代碼與單字節(jié)讀取的效果差別在讀數(shù)的連續(xù)性。對精度要求不高的場景用單字節(jié)逐項讀取也夠但如果你做的是數(shù)據(jù)記錄儀這類對時間一致性有要求的設備建議直接用突發(fā)讀。5.2 31 字節(jié) RAM 的妙用和擴展玩法DS1302 除了時間寄存器還有一塊 31 字節(jié)的 RAM地址從 0xC0 到 0xFD寫地址是偶數(shù)讀地址對應加 1。這塊 RAM 在電池供電時會保持數(shù)據(jù)非常適合存放一些掉電不能丟的小參數(shù)比如設備編號、校時標志、上次關機時間。使用方式就是普通的寄存器讀寫只是地址范圍換一下。我自己的擴展玩法是做一個“開機判斷”首次上電時讀 RAM 里的一個標志字節(jié)如果不是預設魔數(shù)就認為模塊從未初始化自動執(zhí)行一次 set_time并把魔數(shù)寫入 RAM。這樣既不會每次開機都重置時間也能在換電池后自動重新校時。這個思路可以移植到很多帶掉電存儲需求的場景里。另外如果你需要輸出更精確的秒信號可以看 DS1302 的 SQW 引腳功能不過很多模塊沒把該引腳引出來。真要高精度長穩(wěn)計時還是直接選帶溫度補償?shù)膶崟r時鐘方案DS1302 的優(yōu)勢是便宜、簡單、入門友好。這塊芯片雖然老但它把串行時序、雙向 IO、寄存器操作這幾個嵌入式基本功都串了起來。我第一次調通的時候看到串口打印出穩(wěn)定的秒數(shù)心里還是挺有成就感的。如果你也卡在某個讀數(shù)的怪問題上試著用邏輯分析儀抓一下波形很多時候一眼就能看出問題。代碼我已經整理到開源倉庫里對照這篇筆記看應該能把 DS1302 在 STM32 上順利跑起來。