本文作者:Miler Shao
近日有個工程師網絡留言反映:
最近在做一個項目關于輸入捕捉的,使用的單片機為STM8S105,用到STM8的輸入捕捉功能。利用庫函數TIME1CH1 捕捉PE0的頻率很準,但此程序基礎上將PE0口的頻率改為由 TIME2產生150HZPWM波形,同樣使用TIME1CH1 捕捉,捕捉到的頻率卻不對。用示波器查看TIME2 產生的PWM很準的。怎么回事呢? 上面是該工程師的留言,咋看之下,紅色語句貌似就是互相矛盾的表述。呵呵將就下,很多人表述個東西就是任性,讓你猜。 順便提醒下,如果在類似論壇或郵件咨詢STMCU的技術問題時,最基本的一條要先把芯片型號說完整,不要說STM8,STM32,或者STM32F1,STM32F4等。對于STM8一定要說出包括數字在內的前10字符,對于STM32一定要說出包括數字在內的前11字符。這樣人家才能看出該芯片的管腳數和FLASH容量大小,很多問題跟二者息息相關。再就是盡可能把問題描述清楚。 
拉回來繼續剛才的話題。他提供的信息除了上面一段話外,再就是系統時鐘配置和定時器初始化代碼以及輸入捕捉測量的函數。他的意思應該是說在利用TIM1的CH1的捕捉功能對外來脈沖做頻率測量時,當待測信號頻率低到一定程度時就測不準,頻率較高時則正常。
void TimeCapture(void) { TIM1_ICPolarity = TIM1_ICPOLARITY_FALLING TIM1_ICPrescaler = TIM1_ICPSC_DIV8 。。。【為了節省篇幅,此處省卻部分初始化代碼】 /* wait a capture on CC1 */ while((TIM1->SR1 & TIM1_FLAG_CC1) !=TIM1_FLAG_CC1); /* Get CCR1 value*/ ICValue1 = TIM1_GetCapture1(); TIM1_ClearFlag(TIM1_FLAG_CC1); /* wait a capture on cc1 */ while((TIM1->SR1 & TIM1_FLAG_CC1) !=TIM1_FLAG_CC1); /* Get CCR1 value*/ ICValue2 = TIM1_GetCapture1(); TIM1_ClearFlag(TIM1_FLAG_CC1); /* Compute clock frequency */ ClockFreq = (TIM1_ICPSC_DIV8* TIM1ClockFreq) / (ICValue2 - ICValue1); } 從代碼來看,他使用的STM8S TIM1輸入捕捉的例程,下降沿捕捉。結合他的描述和現象分析,代碼配置應該沒啥問題了。我看到他的初始化代碼里使用16M HIS做系統時鐘,供給TIM1的時鐘沒有分頻,即16M。他對輸入捕捉事件做了8分頻,就是上面代碼中的紅色語句。  他做這個8分頻,意味著每8個下降沿才捕捉1次。150Hz本來就夠慢了的,還要每8個脈沖才捕捉一次,意味著相鄰兩次捕捉動作的間隔就更長。而這個例程代碼并沒有考慮到計數器溢出問題,或者說它是假設中間不會發生計數器溢出而設計的。
直覺和經驗告訴我,很可能兩次捕捉間隔過長導致中途有計數器溢出事件發生,如果這樣自然測算不準了。于是提醒該他在做150Hz信號輸入捕捉測量時中途可能發生計數器溢出,建議其提高待測信號頻率驗證。后來他反饋當待測信號頻率提高到2.4K以上后就正確了【當然他這個數字是隨意選定的】,意思是說頻率高于一定數字后就OK了。 看來的確是因為相鄰兩次捕捉動作的間隔過長導致中途有計數器溢出事件。我們可以大致計算下,假設定時器時鐘為16M,不發生溢出情況下能捕捉的最大間隔,即最慢頻率應該是16M/65536=24.4Hz, 這是最理想的情況,如果剛好錯過了第一個捕捉沿后就得等到下個周期的捕捉沿,要想不發生溢出,待測頻率最少也得48Hz以上。  現在他的待測信號頻率為150Hz時,加上做了捕捉事件8分頻,實測信號頻率相當于18.7Hz,遠低于不發生溢出要求的最慢頻率48Hz,不可避免的會發生溢出,導致測量錯誤。
在代碼邏輯不變的情況下,我們可以考慮將定時器時鐘適當做分頻處理,當然不可無限制分頻,得兼顧測量精度需求。至于那個捕捉預分頻參數可以根據待測信號特征和測量需求調整。 總之,上面STM8S定時器捕捉參考例程有其使用前提或局限性的。例程是通過查詢方式實現,每次開啟定時器后必須立馬進入捕捉,測量過程中還不可發生溢出,否則測量就出問題。如果有些應用場合中途溢出難免,那就得對代碼做些調整。 其實,在STM32定時器應用時也可能碰到過類似問題。曾經有人反映用參考例程里的輸入捕捉代碼測量頻率時發現待測信號低到一定程度時就出現測量不正確的問題。 下面是STM32F1系列固件庫輸入捕捉項目例程里的代碼: STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Examples\TIM\InputCapture voidTIM3_IRQHandler(void) { if(TIM_GetITStatus(TIM3, TIM_IT_CC2) == SET) { /* Clear TIM3 Capture compare interruptpending bit */ TIM_ClearITPendingBit(TIM3, TIM_IT_CC2); if(CaptureNumber == 0) { /* Get the Input Capture value */ IC3ReadValue1 = TIM_GetCapture2(TIM3); CaptureNumber = 1; } else if(CaptureNumber == 1) { /* Get the Input Capture value */ IC3ReadValue2 = TIM_GetCapture2(TIM3); /* Capture computation */ if(IC3ReadValue2 > IC3ReadValue1) { Capture = (IC3ReadValue2 - IC3ReadValue1); //N=0,未發生溢出; } else { Capture = ((0xFFFF - IC3ReadValue1) +IC3ReadValue2); //N=1發生1次溢出; } /* Frequency computation */ TIM3Freq = (uint32_t) SystemCoreClock /Capture; CaptureNumber = 0; } } } 這個例程代碼使用了捕捉中斷,程序也是基于一個前提:待測信號時間長度不會長于65536個定時器計數脈沖,相比上面的STM8S的例程應該說有所改進,中途最多允許溢出一次。偶爾有些人對上面例程紅色代碼的第2條感到疑惑,那就是指中途發生過一次溢出的情形。比如第一次捕捉到的計數器的值是0Xeb3f, 中途發生過一次溢出后計數器重新開始計數,再第二次捕捉到的計算器的值完全比前一個捕捉值小。那有無可能中途只溢出一次,第2次捕捉到的數據比第一次還大呢?不可能,不信你可以把問題簡化下。在紙上書寫從0~9選擇任一個數字開始的10個連續的數看看。如果測量時中途發生1次以上的溢出事件時,該例程就有問題了。 
也就是說,STM32參考代碼里該例程也是使用前提的,盡管說它能滿足大部分的應用,作為代碼還是其局限性,當然代碼有局限性是絕對的,沒局限性的代碼猶如沒缺陷的人一樣不復存在。[就此打住,別扯太遠了。] STM32定時器的工作頻率高,很多情況下完全可以先做時鐘預分頻再做測量操作。如果個別應用情形下,計數器會不可避免地可能溢出多次怎么辦呢?其實只要搞清了原理,代碼怎么寫完全你自己掌握。在兩次捕捉之間,我們可以開啟溢出中斷,并對溢出中斷次數計數,假設中間發生了N次溢出中斷,始、止兩個捕捉數據value1和value2。除第一個溢出不做滿量程計算外,其它N-1 個溢出中斷為滿量程計數。 整個計數脈沖個數=(65536-value1)+(N-1)*65536+value2 上面等式中的N 可以是任意非負整數。不難看出當N=0和N=1時,整個計數脈沖個數跟上面ST官方參考例程里兩處紅色代碼描述的是吻合一致的。 或許有細心的人看到,官方例程里用的是65535,我這里是65536。我認為這里應該是65536,不過具體到應用上,這1個脈沖誤差完全可以忽略不計了。 上面提到的定時器都是假設其為16位的,所以計數器滿量程計數個數為65536。其實STM32家族中有不少系列都有32位的定時器,比如STM32F0,STM32F3,STM32F4,STM32F7,STM32L4系列中都帶32位定時器。 搞清了原理,具體的代碼實現也就不難了,多了個溢出中斷次數計數,兩次捕捉間的計數脈沖個數統一用上面的等式即可。弄清了原理,就能自主地對參考性的東西做甄別和借鑒,然后自行調整相關軟硬件設計達到滿足應用需求之目的。 |