簡(jiǎn)介:
手頭有一個(gè)項(xiàng)目,需要一個(gè)“精確”的頻率計(jì),作為DIY一族,準(zhǔn)備動(dòng)手制作一個(gè)。在制作頻率計(jì)之前,需要一個(gè) 能精確測(cè)量晶振頻率的東西。之前嘗試過NTP對(duì)時(shí),發(fā)現(xiàn)精度太差,不好使。所以準(zhǔn)備改用電波對(duì)時(shí)。百度搜了一下,動(dòng)手做電波表的朋友還真不少, 但講得不夠詳細(xì),解碼部分還是“保密”的。只好嘗試自立更生了。
BPC電波鐘模塊
電波對(duì)時(shí)需要一個(gè)接收頭,接收授時(shí)中心發(fā)出的電波信號(hào)。我們國(guó)家的授時(shí)信號(hào)頻率為68.5Khz,發(fā)射臺(tái)在商丘。 接收頭實(shí)際上是一個(gè)窄帶信號(hào)接收器,其帶寬只有幾個(gè)赫茲,通常采用晶體濾波器來限制接收帶寬。由于工作頻率比較低,放大部分是比較 容易設(shè)計(jì)的,有一定無線電基礎(chǔ)的都可以做出來。
這里我們直接從某寶上買來一個(gè)完整的電波鐘模塊。花了15大洋,省了很多事。
BPC模塊上用到的引腳有4條。
VCC :電源,1.5~3.5V
GND :地
SIG :BPC授時(shí)信號(hào)輸出
EN :模塊使能(低電平使能,高電平關(guān)閉模塊)
BPC解碼和校時(shí)
電波鐘模塊輸出的BPC信號(hào)如下圖,1分鐘包含3個(gè)幀,一個(gè)BPC幀的周期為20秒,除了第“0”秒外,其余19秒每秒一個(gè) 脈沖。方波秒脈沖有0.1S,0.2S,0.3S,0.4S四種脈沖寬度狀態(tài),分別表示四進(jìn)制的0, 1, 2, 3,現(xiàn)有的時(shí)間編碼都以二進(jìn)制表示時(shí)間信息, 是為了采用微處理器解碼方便。但四進(jìn)制只是數(shù)值的一種表示方式,并不影響微處理器把它作為二進(jìn)制處理,或者采取簡(jiǎn)單的變換就可將1位 四進(jìn)制數(shù)變成2位二進(jìn)制數(shù)。
P0設(shè)在每分鐘0,20, 40秒,以缺少秒脈沖使幀與幀隔開,同時(shí)作為幀起始預(yù)告。
P1為幀標(biāo)志,P1=0表示幀起于第1秒,P1=1表示幀起始于21秒,P1=2表示幀起始于41秒。幀標(biāo)志是必需的,它用來確定整分的起始。 例如:當(dāng)接收完一組包含著“10時(shí)38分”的時(shí)間編碼時(shí),如果幀標(biāo)志標(biāo)明該幀為第二幀,就可以把下一幀的P1位置標(biāo)定為10時(shí)38分41秒, 再過20秒便是10時(shí)39分的起始。
P2為預(yù)留位。用于需要擴(kuò)充信息。
時(shí)&分表示了時(shí)間
其他各位數(shù)據(jù)在本案中沒有用到,不做詳細(xì)說明
解碼MCU采用了TI公司的MSP430G2211IPW14,MSP系列的MCU以低功耗著稱,非常適合于電池供電的應(yīng)用。本例中, MCU大部分時(shí)間工作在約20uA的低功耗模式下。
BPC解碼軟件使用了MSP430G2211的TimeA,TimeA的計(jì)數(shù)器由32768Hz的晶振提供時(shí)鐘,從0~0xFFFF循環(huán)計(jì)數(shù)。每2秒 循環(huán)一周。BPC信號(hào)接在MCU的中斷請(qǐng)求上,在上下跳變沿均產(chǎn)生中斷,中斷服務(wù)程序中讀取TimerA計(jì)數(shù)器。根據(jù)脈沖寬度解碼BPC編碼的信號(hào)。 由于BPC信號(hào)的最小脈寬為0.1秒,軟件中還加入了濾波處理,可以濾除脈寬較窄的干擾信號(hào)。
if (MCU_INT_GET(BPC_SIGNAL_PORT, BPC_SIGNAL_PIN)) { static uint16 wPrevToggle = 0; //前一次跳變時(shí)刻 static uint16 wLastPulseWidth = 0; //前一次脈沖寬度 static uint8 bLastPulsePolarity = 0; //前一次脈沖極性(1:正脈沖,0:負(fù)脈沖) static uint8 ucBpcBitPos = 0xFF; //BPC解碼位置,0xFF表示解碼狀態(tài)機(jī)復(fù)位 static uint8 ucBpc[2]; //BPC碼數(shù)據(jù),只取包含“時(shí):分:秒”信息的前面2字節(jié)。 uint16 wCurToggle; //本次跳變時(shí)刻 uint16 wCurPulseWidth; //本次脈沖寬度 uint16 wBpcSecond; //BPC解碼得到的時(shí)間秒數(shù) uint8 bCurPulsePolarity; //本次脈沖極性(1:正脈沖,0:負(fù)脈沖) uint8 ucTmp; MCU_INT_XOR_EDGE(BPC_SIGNAL_PORT, BPC_SIGNAL_PIN); MCU_INT_CLEAR(BPC_SIGNAL_PORT, BPC_SIGNAL_PIN); //正跳變,表示的是負(fù)脈沖(結(jié)束) bCurPulsePolarity = MCU_INT_GET_EDGE(BPC_SIGNAL_PORT, BPC_SIGNAL_PIN) ? 0 : 1; wCurToggle = GetCurTimerA(); wCurPulseWidth = wCurToggle - wPrevToggle; if ((wCurPulseWidth 《 PULSE_FILTER_OUT) || (bCurPulsePolarity == bLastPulsePolarity)) { //本次跳變脈寬過短,將當(dāng)作干擾毛刺被過濾掉,脈寬同前一次合并 //本次脈寬極性同前一次相同(跟在干擾毛刺后),脈寬也同前一次合并 wLastPulseWidth += wCurPulseWidth; } else { //前一次脈寬數(shù)據(jù)有效,可以處理了。 if (!bLastPulsePolarity && (wLastPulseWidth 》 PULSE_10ms * 100)) { //每幀數(shù)據(jù)頭部有1秒時(shí)間的空檔期,表示幀起始 ucBpcBitPos = 14; //只用到前14bit數(shù)據(jù) ucBpc[0] = ucBpc[1] = 0; } else if (ucBpcBitPos != 0xFF) { if (!bLastPulsePolarity) { //負(fù)脈沖 if (wLastPulseWidth 《 PULSE_10ms * 55) //BPC編碼正脈沖寬度最小600ms,《550ms為非法,解碼狀態(tài)機(jī)復(fù)位 ucBpcBitPos = 0xFF; } else { //正脈沖 ucTmp = 0xFF; if (wLastPulseWidth 《 PULSE_10ms * 45) { if (wLastPulseWidth 》 PULSE_10ms * 35) //400ms脈寬 ucTmp = 3; else if (wLastPulseWidth 》 PULSE_10ms * 25) //300ms脈寬 ucTmp = 2; else if (wLastPulseWidth 》 PULSE_10ms * 15) //200ms脈寬 ucTmp = 1; else if (wLastPulseWidth 》 PULSE_10ms * 5) //100ms脈寬 ucTmp = 0; } if (ucTmp == 0xFF) { //脈寬非法,解碼狀態(tài)機(jī)復(fù)位 ucBpcBitPos = 0xFF; } else { //保存合法數(shù)據(jù) ucBpcBitPos -= 2; ucBpc[ucBpcBitPos 》》 3] |= ucTmp 《《 (ucBpcBitPos & 0x07); if (ucBpcBitPos == 0) { //一幀數(shù)據(jù)接收完成 ucBpcBitPos = 0xFF; //解碼狀態(tài)機(jī)復(fù)位,等待下次數(shù)據(jù) wBpcSecond = ((ucBpc[1] 《《 2) | (ucBpc[0] 》》 6)) & 0x0F; //小時(shí) wBpcSecond *= 3600; wBpcSecond += ((uint16)(ucBpc[0] & 0x3F)) * 60; //分鐘 wBpcSecond += ((ucBpc[1] 》》 4) & 0x03) * 20 + 21; //秒數(shù) //如果相臨近的兩次BPC校時(shí)都是準(zhǔn)確的(沒有誤碼),守時(shí)中斷應(yīng)該在BPC //信號(hào)的邊界前后,因此,秒數(shù)只可能差0或1秒。據(jù)此判斷校時(shí)成功 if ((wBpcSecond - s_wRealSecond) 《 2) s_wEvent |= BPC_FINISHED; s_wRealSecond = wBpcSecond; s_wTarSecond = wCurToggle + COUNT_1S; } //if (ucBpcBitPos == 0 } } } wPrevToggle = wCurToggle; wLastPulseWidth = wCurPulseWidth; bLastPulsePolarity = bCurPulsePolarity; } }
守時(shí)信號(hào)輸出
TimerA的通道0工作在比較器模式,用作守時(shí)和UART波特率發(fā)生器。時(shí)間以12小時(shí)內(nèi)的秒數(shù)表示,從00:00:00或12:00:00 開始計(jì)數(shù),在每秒開始時(shí)將時(shí)間讀數(shù)從UART口發(fā)出去。MSP430G2211沒有專用的UART,需要軟件實(shí)現(xiàn)。UART數(shù)據(jù)格式為8位、無校驗(yàn)、1200波特率??偣?16bit數(shù)據(jù),需要用2個(gè)字節(jié)表示,加上每字節(jié)的起始位、停止位,總共20位。
#pragma vector=TIMER0_A0_VECTOR __interrupt void Uart_ISR(void) { static int8 iBits = 0; static uint16 wMask; TACCTL0 &= ~TAIFG; //清中斷 //每秒起始位置把16bits實(shí)時(shí)時(shí)間通過UART發(fā)送出去 if ((iBits == 0) || (iBits == 10)) { MCU_IO_CLR(UART_TX_PORT, UART_TX_PIN); //起始位 } else if ((iBits == 9) || (iBits == 19)) MCU_IO_SET(UART_TX_PORT, UART_TX_PIN); //停止位 else { //發(fā)送數(shù)據(jù)位 if (s_wRealSecond & wMask) MCU_IO_SET(UART_TX_PORT, UART_TX_PIN); else MCU_IO_CLR(UART_TX_PORT, UART_TX_PIN); wMask 《《= 1; } iBits++; if (iBits == 20) { //實(shí)時(shí)時(shí)間發(fā)送完畢,準(zhǔn)備在下一秒再次發(fā)送 s_wTarSecond += COUNT_1S; TACCR0 = s_wTarSecond; s_wRealSecond++; if (s_wRealSecond 》= ((uint16)12*3600)) s_wRealSecond = 0; iBits = 0; wMask = 1; s_wEvent |= SECOND_EVENT; } else TACCR0 += UART_BIT_WIDTH; __low_power_mode_off_on_exit(); }
守時(shí)時(shí)鐘校準(zhǔn)
本系統(tǒng)需要依靠標(biāo)稱頻率為32768Hz晶振提供時(shí)間基準(zhǔn)來守時(shí),晶振負(fù)載電容對(duì)頻率有微調(diào)作用。為了測(cè)定晶振實(shí)際工作 頻率,可測(cè)量一段時(shí)間內(nèi)的積累誤差。例如在10:00:00時(shí)進(jìn)行第一次BPC校時(shí),6個(gè)小時(shí)后在16:00:00進(jìn)行第二次BPC校時(shí),用串口工具接收并打印出 第二次BPC校時(shí)前后從UART口輸出的守時(shí)信號(hào)
校時(shí)之前
【2017-10-11 15:58:47:850】CB 0D
【2017-10-11 15:58:48:850】CC 0D
校時(shí)之后
【2017-10-11 15:59:47:827】07 0E
【2017-10-11 15:59:48:827】08 0E
從打印出來的數(shù)據(jù)可以看出,在校時(shí)前后兩點(diǎn)(0x0E08-0x0DCC = 60秒),對(duì)應(yīng)PC機(jī)系統(tǒng)時(shí)間為59:48:827-58:48:850 = 59.977秒,也就是說6小時(shí)內(nèi)積累的誤差為-0.023秒,可以忽略不計(jì)。否則可能需要調(diào)整負(fù)載電容來對(duì)頻率進(jìn)行微調(diào),或者也可以調(diào)整代碼中的 COUNT_1S的宏定義值來重新標(biāo)定“1秒”。
機(jī)芯驅(qū)動(dòng)
本設(shè)計(jì)初衷是要制作一個(gè)能夠準(zhǔn)確計(jì)時(shí)(無累積誤差)的東西。因此完成守時(shí)信號(hào)輸出就OK了,但既然動(dòng)了手,就準(zhǔn)備弄個(gè) 完整的電波鐘玩玩。拆解了一個(gè)多時(shí)不用的石英鐘,只將機(jī)芯步進(jìn)馬達(dá)的線圈引出,其他電路統(tǒng)統(tǒng)拆掉。這里我范了一個(gè)錯(cuò)誤,本以為馬達(dá)是1秒 走一步或二步,按此設(shè)計(jì)了驅(qū)動(dòng)代碼,結(jié)果馬達(dá)跑得非常別扭,走走退退,不知怎回事,弄了半天才發(fā)現(xiàn)問題所在,實(shí)際上這個(gè)機(jī)芯步進(jìn)馬達(dá)是每秒16步 的,差得也太遠(yuǎn)了。因此,建議朋友在拆機(jī)前先測(cè)一下原機(jī)的驅(qū)動(dòng)波形。
這種步進(jìn)電機(jī)的驅(qū)動(dòng)信號(hào)為正負(fù)交替的脈沖信號(hào)。脈沖寬度需要有個(gè)合理的范圍,拆機(jī)時(shí)沒有先用示波器測(cè)一下,只好自己 湊了。不過,就算測(cè)了也只能供參考,因?yàn)樵瓩C(jī)是1.5V供電的,現(xiàn)在改成3V,脈寬肯定需要調(diào)窄,理論上升到2倍電壓后脈寬應(yīng)是原來的1/4.我的這個(gè)馬達(dá) 用12ms脈寬驅(qū)動(dòng),工作得很Happy.朋友自己制作時(shí)可以自行調(diào)整MOTOR_PULSE_DUTY。
為了實(shí)現(xiàn)正負(fù)極性的交替,使用了2個(gè)IO端口(石英鐘機(jī)芯馬達(dá)不分極性直接連在這兩個(gè)端口上就行了),輸出兩路移相的方波信號(hào)。兩路方波信號(hào)之間的相差即為脈沖寬度。當(dāng)鐘面顯示 的時(shí)間同實(shí)際時(shí)間有偏差時(shí),需要改變脈沖周期以調(diào)整電機(jī)速度,使兩者趨于一致。機(jī)芯驅(qū)動(dòng)使用了TimeA的通道1,在其中斷服務(wù)中實(shí)現(xiàn)
#pragma vector=TIMER0_A1_VECTOR __interrupt void Motor_drv_ISR(void) { if (TACCTL1 & CCIFG) { // 步進(jìn)電機(jī)驅(qū)動(dòng)信號(hào),一個(gè)周期分4個(gè)Stage,電機(jī)走2步。 // S1,S3提供動(dòng)力輸出。 // ------ // | S1 | // | | // --------------| |--------------| | // S0 S2 | | // | | // ------ // S3 static uint8 ucStage = 0; //步進(jìn)電機(jī)每走一步分2個(gè)stage。 uint16 wS0S2; //S0,S2時(shí)間長(zhǎng)度 TACCTL1 &= ~CCIFG; //清中斷 if (ucStage == 2 * STEP_1S - 1) { //每秒末進(jìn)入此處 ucStage = 0; s_wDisplaySecond++; if (s_wDisplaySecond 》= ((uint16)12 * 3600)) s_wDisplaySecond = 0; } else ucStage++; //先假定鐘面時(shí)間總是偏“快”的,算一下“快”了多少 if (s_wDisplaySecond 》 s_wRealSecond) wS0S2 = s_wDisplaySecond - s_wRealSecond; else //超了一圈(12小時(shí)) wS0S2 = ((uint16)12 * 3600) - (s_wRealSecond - s_wDisplaySecond); //如果算下來“快”了9小時(shí)以內(nèi),認(rèn)為其確實(shí)“快”了,否則認(rèn)為實(shí)際是“慢”了3小時(shí)不到。 //之所以不以6小時(shí)分界,是因?yàn)椴竭M(jìn)馬達(dá)可以無限放慢,有限地加快。 if (wS0S2 《 2) //偏快不多,正常運(yùn)行 wS0S2 = MOTOR_PERIOD - MOTOR_PULSE_DUTY; //正常運(yùn)行 else if (wS0S2 《 ((uint16)9 * 3600)) //偏快,降速運(yùn)行 wS0S2 = MOTOR_PERIOD_SLOW - MOTOR_PULSE_DUTY; else //偏慢,加速運(yùn)行 wS0S2 = MOTOR_PERIOD_FAST - MOTOR_PULSE_DUTY; //步進(jìn)電機(jī)驅(qū)動(dòng)信號(hào)4個(gè)stage一個(gè)循環(huán),走2步 switch(ucStage & 0x03) { case 0: MCU_IO_CLR(MOTOR_DRVN_PORT, MOTOR_DRVN_PIN); TACCR1 += wS0S2; break; case 1: MCU_IO_SET(MOTOR_DRVP_PORT, MOTOR_DRVP_PIN); TACCR1 += MOTOR_PULSE_DUTY; break; case 2: MCU_IO_SET(MOTOR_DRVN_PORT, MOTOR_DRVN_PIN); TACCR1 += wS0S2; break; case 3: MCU_IO_CLR(MOTOR_DRVP_PORT, MOTOR_DRVP_PIN); TACCR1 += MOTOR_PULSE_DUTY; break; } } __low_power_mode_off_on_exit(); }
使用方法
由于系統(tǒng)無法讀取鐘面顯示的時(shí)間,因此在系統(tǒng)上電啟動(dòng)時(shí),必須先把鐘面撥到00:00:00的默認(rèn)位置。上電時(shí),MCU默認(rèn)為 鐘面顯示時(shí)間和系統(tǒng)實(shí)際時(shí)間為00:00:00.系統(tǒng)在00:00:02啟動(dòng)BPC對(duì)時(shí),對(duì)時(shí)成功后,“實(shí)際時(shí)間”就準(zhǔn)確了,這時(shí),從UART輸出的守時(shí)信號(hào)也是準(zhǔn)確的了。 但鐘面時(shí)間需要一段時(shí)間后才能逐步同實(shí)際時(shí)間一致。以后,系統(tǒng)會(huì)在每天的00:00:02和12:00:02各啟動(dòng)BPC對(duì)時(shí)一次,如果對(duì)時(shí)成功或20分鐘內(nèi)不能對(duì)時(shí) 則自動(dòng)關(guān)閉BPC模塊以節(jié)約電池。對(duì)時(shí)成功,照明等會(huì)點(diǎn)亮2秒。
在每天的17:00-21:00, 05:00-9:00兩個(gè)時(shí)段內(nèi)是BPC發(fā)射臺(tái)是關(guān)閉的,在這兩個(gè)時(shí)段內(nèi)開機(jī)是無法對(duì)時(shí)的。
系統(tǒng)提供了一個(gè)按鈕,短按按鈕可以點(diǎn)燈5秒,以便夜間照明。長(zhǎng)按2秒以上可以立即打開BPC對(duì)時(shí),對(duì)時(shí)成功或5分鐘內(nèi)不成功 則自動(dòng)關(guān)閉BPC模塊。這些業(yè)務(wù)邏輯都在主程序中實(shí)現(xiàn)。
實(shí)物圖
原理圖
JI為下載接口。MSP430G2211晶振匹配電容內(nèi)置,可配置,所以不需要外接電容
責(zé)任編輯:wv
-
電波鐘
+關(guān)注
關(guān)注
0文章
4瀏覽量
7167
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論