做延時函數(shù),可以使用簡單的循環(huán)等待,如下面這樣的:
void Delay(uint32_t nCount)
{
for(; nCount != 0; nCount--);
}
但是有個問題,就是這個nCount值怎么???
我們可以通過多次試驗,來確定調用時使用的循環(huán)次數(shù)。
但是還要考慮下,如果硬件有變化,例如外接晶振變化,或類似的主芯片替換等情況下,這個值有可能會變化。另外,編譯的優(yōu)化選項變化,也可能導致循環(huán)次數(shù)的變化。也就是說,這樣寫的延時函數(shù),對外部的依賴項比較多,稍不注意,可能最終的延時時間不準確。
更好的延時方式是使用定時器,這樣能更準確的定時,并且移植性也更好一些。
但是使用定時器做延時函數(shù)時,也是有一些需要注意的事情的,否則,可能會掉入坑中還茫然不知。例如我本人,就掉了幾次坑,花了好長時間才爬出來......
先簡單說明下我的開發(fā)環(huán)境,芯片類型是stm32F030C8,集成開發(fā)環(huán)境用的是Keil5 MDK-ARM,仿真器使用JLINK。
通常我們使用定時器來做延時函數(shù),比較常見的例子就是這樣的:
#include "delay.h"
static int8_t fac_us=0;//us
static int16_t fac_ms=0;//ms
static int flag_HCLK_Div8=1;
void delay_init()
{
if(flag_HCLK_Div8){
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);//選擇外部時鐘 HCLK/8
fac_us=SystemCoreClock/48000000; //為系統(tǒng)時鐘的1/8
} else {
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);//選擇外部時鐘
fac_us=SystemCoreClock/1000000; //為系統(tǒng)時鐘
}
fac_ms=(int16_t)fac_us*1000;//每個ms需要的systick時鐘數(shù)
}
//延時N us
void delay_us(int32_t nus)
{
int32_t temp;
SysTick->LOAD=nus*fac_us; //時間加載
SysTick->VAL=0x00;
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //開始倒數(shù)
do
{
temp=SysTick->CTRL;
}
while(temp&0x01&&!(temp&(1<<16)));//等待時間到達
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //關閉計數(shù)器
SysTick->VAL =0X00;
}
//延時N ms
void delay_ms(int16_t nms)
{
int32_t temp;
SysTick->LOAD=(int32_t)nms*fac_ms;//時間加載(SysTick->LOAD為24bit)
SysTick->VAL =0x00;
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //開始倒數(shù)
do
{
temp=SysTick->CTRL;
}
while(temp&0x01&&!(temp&(1<<16)));//等待時間到達
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //關閉計數(shù)器
SysTick->VAL =0X00;
}
然后我就開始使用了。
我是這么使用的:
主循環(huán)調用 delay_ms(),
中斷里面調用 delay_us() ,這是考慮到中斷里面要盡量做少的操作,所以使用短的延時。
然而,在運行過程中,發(fā)現(xiàn)有時候會遇到主循環(huán)有快速結束等待的情況,遠遠沒有達到我希望的延時時間!
對著代碼左看右看,沒看出來毛病。后來,在主循環(huán)中替換使用那種簡單的循環(huán)等待的延時函數(shù),就不再出問題了。這才確定到問題就在這個delay_*()延時函數(shù)上。
再仔細分析延時耗時,發(fā)現(xiàn)問題:這兩個函數(shù)使用的是同一個定時器硬件:SysTick。
例如,若主循環(huán)中希望延時1000ms,調用delay_ms(1000),
SysTick->LOAD的值設置為1000ms了。
若在這時,又進入了中斷,有個延時100us的操作,調用delay_us(100),
SysTick->LOAD的值設置為100us了。
兩次設置的是同一個寄存器,顯然,后一次的設置,覆蓋了前一次的設置值!
然后,啟動定時器的倒數(shù)計時:
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;
等100us時間到了,判斷循環(huán)中的結束條件,
while(temp&0x01&&!(temp&(1<<16)));
符合,則延時完成,繼續(xù)進行中斷里面的其他操作。
等退出中斷后,主循環(huán)繼續(xù)執(zhí)行。此時還在延時函數(shù)delay_ms()中等待呢,查看判斷條件:
while(temp&0x01&&!(temp&(1<<16)));//等待時間到達
看temp的賦值:temp=SysTick->CTRL;
、、、、-- 開啟了倒數(shù)計時,并且SysTick倒數(shù)到0了。
這里,我們需要判斷 SysTick->CTRL 中相關字段的意義:
最低位(第0位):ENABLE,是SysTick 定時器的使能位
第16位:COUNTFLAG,如果在上次讀取本寄存器后, SysTick 已經(jīng)計到了 0,則該位為 1。
再來看這兩個位的現(xiàn)狀:
考慮到delay_us()執(zhí)行完成了,也就是說,SysTick 已經(jīng)計到了 0了,即 SysTick->CTRL&(1<<16) 的值置1了。
并且,跳出循環(huán)后還執(zhí)行了一句:
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;
它的意思就是關閉SysTick定時器的使能,即 (SysTick->CTRL&0x01)的值為0。
所以,此時,在delay_ms()中的判斷條件已經(jīng)不滿足了:
即temp&0x01為0,而且,temp&(1<<16)也是0,所以,會立即結束循環(huán)。
基本上,相當于,外面的延時1s,被里面的delay_us截斷,同時也結束了。
主循環(huán)的延時1s,在最壞的情況下(延時剛啟動就遇到中斷),可能才過了約100us,就結束了!
教訓:對于同一個定時器,這樣寫法的延時函數(shù),不能在主循環(huán)與中斷里面同時調用!
當在主循環(huán)中處于延時等待狀態(tài)下,中斷里面的延時,會修改定時器的狀態(tài),從而導致主循環(huán)的延時不準確了。
再從另一個更通用的角度來看,其實就是對于同一個全局變量(SysTick),在兩個線程中同時訪問,并且沒有做訪問保護。所以,產(chǎn)生問題,就是遲早的事情了。
解決方法,使用兩個定時器,就能解決了:一個在主循環(huán)中調用,一個在中斷里面調用。
進一步思考:若有多個中斷,而且都存在延時的調用,就需要多個定時器嗎,這可能會導致定時器都不夠用呢,那又該怎么辦?這其實就是涉及一個設計思路了??梢哉f,在中斷里面調用延時函數(shù),本身就不是一個好主意,能避免則避免。如果不能避免,就需要采用另外的一種方式來解決問題了,也并不需要多個定時器,一個定時器就可以了,我們看了下面這個問題再來說。
上面這種主循環(huán)與中斷里面同時調用延時函數(shù)的問題,還有另一種表現(xiàn)形式:
類似于我在上一篇博客中的延時函數(shù):
#include "timer.h"
#include "stdio.h"
#include "gpio.h"
#include "stm32f0xx_tim.h"
volatile unsigned int gTimer;
void TIM1_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 系統(tǒng)中TIM1用的是APB2,TIM14時鐘用的是APB1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); //tim1時鐘使能,APB1時鐘8M
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV2; //分頻系數(shù)為2 //是對APB1的2倍頻進行分頻,分頻系數(shù)為2,所以頻率還是8M
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上計數(shù)
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重復計數(shù)設置 //對于TIM1是必須設置的
// 計算定時周期: t=(9+1)*1/f=2/(8M/(7+1))=10*8/8M(s)=10us
TIM_TimeBaseInitStructure.TIM_Period = 9; //定時10us //最大65536
TIM_TimeBaseInitStructure.TIM_Prescaler = 7; //時鐘8M
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);
TIM_ClearITPendingBit(TIM1,TIM_IT_Update);//清除TIM1的中斷待處理位:TIM 中斷源
TIM_ITConfig(TIM1,TIM_IT_Update,ENABLE); //允許定時器1更新中斷
TIM_Cmd(TIM1,ENABLE); //使能定時器1
// 設置中斷優(yōu)先級
NVIC_InitStructure.NVIC_IRQChannel = TIM1_BRK_UP_TRG_COM_IRQn; //定時器1中斷
NVIC_InitStructure.NVIC_IRQChannelPriority = 0; //優(yōu)先級0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void TIM1_BRK_UP_TRG_COM_IRQHandler(void)
{
if(TIM_GetITStatus(TIM1,TIM_IT_Update) != RESET) //溢出中斷
{
if(gTimer>0){
gTimer--;
}
}
TIM_ClearITPendingBit(TIM1,TIM_IT_Update); //清除中斷標志位
}
//毫秒的延時函數(shù)
void delay_ms_tim(uint32_t nTimer)
{
gTimer=nTimer*100;
while(gTimer);
}
//微秒的延時函數(shù),實際以10us為最小單位
void delay_us_tim(uint32_t nTimer)
{
gTimer=nTimer/10;
while(gTimer);
}
若在中斷與主循環(huán)中同時使用delay_ms_tim() delay_us_tim(),也會有同樣的問題。
當主循環(huán)中執(zhí)行了delay_ms_tim(1000)時,設置了gTimer=1000*100=100000;
若此時中斷進入,執(zhí)行了delay_us_tim(100),則設置了gTimer=100/10=10;
這樣,后一次的設置,就覆蓋了前一次對gTimer的賦值,從而導致兩個延時函數(shù)會同時結束,也就是說,delay_ms_tim(1000)實際延時時間可能只比100us略多,而我們期望的是1s,差距巨大!
這里的問題,我們可以看出,是因為使用了同一個全局變量gTimer。
那么,我們就有了一個不一樣的解決方法:并不需要使用多個定時器,而是使用同一個計數(shù)器,多個計數(shù)變量。
只是增加計數(shù)變量的話,不影響TIM1_Init()函數(shù),下面的代碼就不展示它了。我們增加一個gTimer變量,相應修改兩個延時函數(shù)如下:
volatile unsigned int gTimer_ms;
volatile unsigned int gTimer_us;
void TIM1_BRK_UP_TRG_COM_IRQHandler(void)
{
if(TIM_GetITStatus(TIM1,TIM_IT_Update) != RESET) //溢出中斷
{
if(gTimer_ms>0){
gTimer_ms--;
}
if(gTimer_us>0){
gTimer_us--;
}
}
TIM_ClearITPendingBit(TIM1,TIM_IT_Update); //清除中斷標志位
}
//毫秒的延時函數(shù)
void delay_ms_tim(uint32_t nTimer)
{
gTimer_ms=nTimer*100;
while(gTimer_ms);
}
//微秒的延時函數(shù),實際以10us為最小單位
void delay_us_tim(uint32_t nTimer)
{
gTimer_us=nTimer/10;
while(gTimer_us);
}
這樣,這兩個延時函數(shù)就不會互相影響了。
相應的,如果需要更多獨立作用的延時函數(shù),就可以增加相應的gTimer變量即可。這樣子,就實現(xiàn)了一個定時器多個延時函數(shù)的功能了。
另外,我還遇到過一個bug,會導致死循環(huán):
void TIM1_BRK_UP_TRG_COM_IRQHandler(void)
{
if(TIM_GetITStatus(TIM1,TIM_IT_Update) != RESET) //溢出中斷
{
gTimer--;
}
TIM_ClearITPendingBit(TIM1,TIM_IT_Update); //清除中斷標志位
}
void delay_ms_tim(uint32_t nTimer)
{
gTimer=nTimer;
while(gTimer);
}
有時,調用這個延時函數(shù)會變成死循環(huán)!
為什么?
幾經(jīng)折騰,終于搞明白是時序的問題,在gTimer=1時,若其它的中斷里面,做了較多的事情,導致一次定時器的中斷周期過去了,還沒有處理完成,則
gTimer--,變?yōu)?;
gTimer--,變?yōu)?G-1;4294967295
然后的等待時間之長,幾乎就可以認為是進入了死循環(huán)了。
這里的問題有兩個:
1,是邏輯不完善的問題,解決方法:在 gTimer--; 時,加一個判斷即可解決:
if(gTimer>0){
gTimer--;
}
2,是軟件設計問題,在那個中斷里面,做的事情太多了,導致耗時超過了1ms!這是不合理的,耗時較多的操作,不應該在中斷里面做!不過,這就需要調整軟件中的設計了。
最后,還有一個注意的事情,就是定時的最小值。
在我的系統(tǒng)中,使用了默認的8M的時鐘,基本上最小定時為:10us,若是再小,就不準確了。
這個定時的最小值,應該與是否使用外部晶振,以及定時器的頻率設置有關系,不同的芯片,也會有不同。我沒有詳細研究,使用不同方案的同學們,需要關注自己的最小定時間隔。比較安全的做法,就是不要用太小的定時間隔。畢竟,追求極限是好事,但是,沒有掌握好極限,出了問題,就是麻煩事了。
上一篇:STM32的系統(tǒng)時鐘與SystemInit函數(shù)
下一篇:STM32-仿真調試時的SystemInit陷阱
推薦閱讀
史海拾趣
關于Fuji Terminal Industry Co., Ltd.(富士端子工業(yè)有限公司)在電子行業(yè)中的發(fā)展起來的相關故事,由于直接關于該公司的詳細發(fā)展歷程和具體故事可能較為有限,且該公司可能更多地專注于特定領域(如端子、連接器等的制造),以下是根據(jù)一般行業(yè)經(jīng)驗和公司可能經(jīng)歷的發(fā)展路徑,構建的五個相關故事:
1. 初創(chuàng)與專業(yè)化定位
在電子行業(yè)的早期,F(xiàn)uji Terminal Industry Co., Ltd.作為一家新興企業(yè),憑借對端子技術的深入研究和市場需求的敏銳洞察,選擇了專業(yè)化的發(fā)展道路。公司創(chuàng)始人或核心團隊憑借在電子元件領域的豐富經(jīng)驗,決定專注于端子、連接器等基礎電子元件的研發(fā)與生產(chǎn)。通過不斷的技術積累和產(chǎn)品優(yōu)化,公司逐漸在行業(yè)內(nèi)樹立了專業(yè)、可靠的形象。
2. 技術創(chuàng)新與品質提升
隨著電子行業(yè)的快速發(fā)展,F(xiàn)uji Terminal Industry Co., Ltd.意識到技術創(chuàng)新和品質提升是企業(yè)持續(xù)發(fā)展的關鍵。公司加大了研發(fā)投入,引進先進生產(chǎn)設備和技術人才,致力于開發(fā)更高性能、更可靠的端子產(chǎn)品。同時,公司建立了嚴格的質量控制體系,確保每一件產(chǎn)品都能達到客戶的高標準要求。這些努力不僅提升了公司的市場競爭力,也贏得了客戶的廣泛信賴。
3. 市場拓展與國際化戰(zhàn)略
在穩(wěn)固國內(nèi)市場的同時,F(xiàn)uji Terminal Industry Co., Ltd.積極實施國際化戰(zhàn)略,將目光投向了更廣闊的國際市場。公司通過參加國際展會、建立海外銷售網(wǎng)絡等方式,不斷拓展海外市場。憑借其優(yōu)質的產(chǎn)品和服務,公司成功打入了歐美、亞洲等多個國家和地區(qū)的市場,實現(xiàn)了業(yè)務的快速增長。
4. 供應鏈整合與成本控制
面對日益激烈的市場競爭,F(xiàn)uji Terminal Industry Co., Ltd.深知供應鏈整合和成本控制的重要性。公司積極與上下游企業(yè)建立長期穩(wěn)定的合作關系,通過優(yōu)化供應鏈管理、降低采購成本、提高生產(chǎn)效率等方式,有效控制了生產(chǎn)成本。同時,公司還注重與客戶的溝通與協(xié)作,共同應對市場變化和挑戰(zhàn)。
5. 可持續(xù)發(fā)展與環(huán)保責任
隨著全球對環(huán)保問題的日益關注,F(xiàn)uji Terminal Industry Co., Ltd.積極響應可持續(xù)發(fā)展的號召,將環(huán)保理念融入企業(yè)的生產(chǎn)經(jīng)營中。公司致力于開發(fā)環(huán)保型端子產(chǎn)品,減少生產(chǎn)過程中的環(huán)境污染和能源消耗。同時,公司還加強了廢棄物的回收和再利用工作,為構建綠色、低碳的電子信息產(chǎn)業(yè)鏈貢獻了自己的力量。
需要注意的是,由于直接關于Fuji Terminal Industry Co., Ltd.的詳細發(fā)展歷程和具體故事可能較為有限,以上故事是基于一般行業(yè)經(jīng)驗和公司可能經(jīng)歷的發(fā)展路徑構建的。實際情況可能因公司戰(zhàn)略、市場環(huán)境等因素而有所不同。
Ettinger,這個源于英國的皮具奢侈品牌,由Gerry Ettinger在1934年創(chuàng)立。自創(chuàng)立之初,Ettinger就致力于提供高品質的皮具產(chǎn)品,憑借其精湛的皮具制作工藝和獨特的英格蘭式設計風格,逐漸在市場上樹立了良好的品牌形象。初期,Ettinger主要專注于手工制作皮具,通過不斷提升產(chǎn)品質量和設計水平,贏得了消費者的青睞。
在快速發(fā)展的電子行業(yè)中,Ettinger始終堅持傳統(tǒng)工藝和品質至上的原則。公司不僅仍然由Ettinger家族所有,所有的產(chǎn)品制作也依然保持純手工制作,以確保每一件產(chǎn)品都達到最高的品質標準。這種對品質和工藝的執(zhí)著追求,使得Ettinger在競爭激烈的電子行業(yè)中獨樹一幟,贏得了消費者的信任和尊重。
為了進一步擴大市場份額,Calmos Systems Inc公司積極尋求與行業(yè)內(nèi)外的合作伙伴建立合作關系。公司與多家知名企業(yè)簽訂了戰(zhàn)略合作協(xié)議,共同開發(fā)新產(chǎn)品、拓展新市場。同時,公司還積極參加各類行業(yè)展會和交流活動,與業(yè)內(nèi)同行進行深入交流,了解行業(yè)動態(tài)和發(fā)展趨勢。通過這些努力,公司的品牌知名度和影響力不斷提升,為公司的長期發(fā)展打下了堅實的基礎。
A/D Electronics Inc在創(chuàng)立初期,以其卓越的技術研發(fā)團隊在模擬到數(shù)字轉換器(ADC)領域取得了重大突破。公司研發(fā)出一款高精度、低噪聲的ADC芯片,這一創(chuàng)新產(chǎn)品迅速在市場中獲得認可,為公司的初步發(fā)展奠定了堅實基礎。隨著技術的不斷迭代,A/D Electronics Inc陸續(xù)推出了一系列高性能的電子產(chǎn)品,滿足了市場對于高效、穩(wěn)定電子元件的日益增長需求。
隨著電子技術的快速發(fā)展,線纜行業(yè)也在不斷革新。Cables To Go公司緊跟技術潮流,不斷引進新技術、新工藝和新材料,推動產(chǎn)品升級換代。公司還與多所高校和科研機構建立了合作關系,共同研發(fā)新型線纜產(chǎn)品和技術。這些技術革新不僅提升了產(chǎn)品的性能和品質,還為公司的可持續(xù)發(fā)展注入了新的動力。
請注意,以上故事都是基于假設和虛構的,不代表Cables To Go公司的實際發(fā)展情況。如需了解該公司的真實發(fā)展故事,建議查閱相關新聞報道或公司官方資料。
招聘WAP聯(lián)盟運營總監(jiān)和市場總監(jiān) 招聘WAP聯(lián)盟運營總監(jiān)和市場總監(jiān) WAP聯(lián)盟運營總監(jiān)和市場總監(jiān)(2名) 最低學歷:大專以上學歷工作經(jīng)驗:3年以上薪水范圍:月薪1萬到1萬5千(底薪 + 業(yè)績獎金)簡歷請發(fā):mw2009@fly4our.com 聯(lián)系電話:13381487182 聯(lián)系人:秦小姐 崗位職責: 1. ...… 查看全部問答∨ |
|
求用VS2005或者2008,用SerialPort類寫的 串口通信程序,及相關的說明 如題,本人急需學會這個東西,麻煩各位高手幫忙,不要轉載其它不是用這個類寫的東西進行回答,特別是接收這塊的代碼,請給個詳細點的流程介紹,比如 在這個里面寫的托管能起到什么左右,它和DataReceived這個事件之間的關系是個怎么樣的。謝謝~~不 ...… 查看全部問答∨ |
|
ARM上電時把FLASH中的數(shù)據(jù)LOAD到SDRAM中。 然后程序在SDRAM中運行。 那請問,ARM本身就帶有FLASH的驅動嗎? 要不然怎么能上電就能讀取FLASH呢?… 查看全部問答∨ |
NMAKE : U1073: don't know how to make WinCE5.0 生成系統(tǒng)時出現(xiàn): BUILD: [01:0000000913:ERRORE] NMAKE : U1073: don\'t know how to make \'D:\\WINCE500\\platform\\common\\lib\\ARMV4I\\retail\\oal_memory_s3c2440a.lib\' BUILD: [01:0000000915:ERRORE] NMAKE.EXE ...… 查看全部問答∨ |
UART的輸入引腳必須設置成GPIO_Mode_IN_FLOATING模式么? 如果設置成該模式的話,接收引腳必須外接上拉才能正確接收到數(shù)據(jù),否則外部輸入的數(shù)據(jù)將會被此引腳“吃掉”從而看 ...… 查看全部問答∨ |
|
為何我裝CCS會出現(xiàn)錯誤提示?操作系統(tǒng)64位win7,安裝到最后一個組件前會出現(xiàn)如圖1提示:這個提示是怎么回事?不管,關閉后繼續(xù)安裝又會出現(xiàn)圖2提示:取消安裝,刪除安裝文件夾內(nèi)容,重新安裝,圖2錯誤提示就沒有了,安裝完成。但是圖1的提示還是出 ...… 查看全部問答∨ |
設計資源 培訓 開發(fā)板 精華推薦
- Microchip 升級數(shù)字信號控制器(DSC)產(chǎn)品線 推出PWM 分辨率和 ADC 速度業(yè)界領先的新器件
- 意法半導體STM32MP23x:突破成本限制的工業(yè)AI應用核心
- 意法半導體推出用于匹配遠距離無線微控制器STM32WL33的集成的匹配濾波芯片
- ESP32開發(fā)板連接TFT顯示屏ST7789跳坑記
- 如何讓ESP32支持analogWrite函數(shù)
- LGVL配合FreeType為可變字體設置字重-ESP32篇
- 使用樹莓派進行 ESP32 Jtag 調試
- ESP32怎么在SPIFFS里面存儲html,css,js文件,以及網(wǎng)頁和arduino的通訊
- ESP32 freeRTOS使用測試
- 蘋果被判侵犯3G專利,需向西班牙公司TOT賠償1.1億美元
- 從設計概念到 FPGA 原型僅需數(shù)分鐘,印度 InCore 完成 SoC Generator 平臺硅驗證
- 消息稱因難尋客戶,三星推遲美國芯片工廠的完工時間
- BOE(京東方)聯(lián)合榮耀打造榮耀Magic V5 以領先LTPO技術打造行業(yè)新標桿
- 華為ADS 4發(fā)布:多傳感器融合,提升自動駕駛安全性
- 曉鶯說:線控制動變革風云
- 大眾商用車推出AirConsole 將其信息娛樂系統(tǒng)擴展為游戲機
- 福州大學發(fā)明新機器視覺傳感器 可使機器人對極端光照做出超快反應
- 蘋果獲沉浸式虛擬顯示器相關的專利
- 英特爾汽車“折戟”,十年布局一夜歸零