日韩一区二区三区精品,欧美疯狂xxxxbbbb牲交,热99re久久免费视精品频,人妻互换 综合,欧美激情肉欲高潮视频

歷史上的今天

今天是:2025年06月14日(星期六)

2019年06月14日 | STM32-使用定時器做延時函數(shù)時遇到的坑

發(fā)布者:SereneVoyage 來源: eefocus關鍵字:STM32  定時器  延時函數(shù) 手機看文章 掃描二維碼
隨時隨地手機看文章

延時函數(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  定時器  延時函數(shù) 引用地址:STM32-使用定時器做延時函數(shù)時遇到的坑

上一篇:STM32的系統(tǒng)時鐘與SystemInit函數(shù)
下一篇:STM32-仿真調試時的SystemInit陷阱

推薦閱讀

作為英菲尼迪在國內(nèi)投放的第二款合資車型,QX50在6月10日完成了全國上市。先不說它33.98萬元-48.98萬元的售價是否能讓消費者滿意(雖然我覺得這價格真的挺有誠意),光說東風英菲尼迪QX50上搭載的那臺被稱為VT-C的2.0T可變壓縮比發(fā)動機就值得好好研究一下。當年馬自達發(fā)布創(chuàng)馳藍天發(fā)動機時,憑借著“可變壓縮比”的噱頭吸引了眾多粉絲,然而其實真實情況大...
全球領先的全渠道客戶體驗和聯(lián)絡中心解決方案提供商Genesys? 日前在其2019年客戶體驗大會(Xperience 19)開幕日上公布了“第14屆Genesys全球客戶創(chuàng)新獎”的獲得者,表彰了微軟、瑞士電信、可口可樂北美業(yè)務服務部、惠而浦公司等全球領先企業(yè)。Xperience 19是Genesys標志性的全球客戶體驗年度盛會,于6月10日至13日在科羅拉多州丹佛市的蓋洛德落基山度...
一、UART原理說明通用異步收發(fā)器簡稱UART(Universal Asynchronous Receiver/Transmitter),它用來傳輸串行數(shù)據(jù):發(fā)送數(shù)據(jù)時,CPU將并行數(shù)據(jù)寫入UART,UART按照一定的格式在一根電線上串行發(fā)出;接收數(shù)據(jù)時,UART檢測另一根電線上的信號,將串行數(shù)據(jù)收集放在緩沖區(qū)中,CPU就可以讀取UART獲得這些數(shù)據(jù)。串口之間以全雙工方式傳輸數(shù)據(jù),最精簡的連線只有三...
對于考慮使用電動汽車的人來說,真正的痛點在于電池的充電時間。據(jù)外媒報道,美國初創(chuàng)公司ATLIS Motor Vehicles開發(fā)的一種電池電芯,據(jù)稱可在不到15分鐘的時間內(nèi)充滿電。(圖片來源:ATLIS公司)ATLIS宣布生產(chǎn)AMV電池電芯,并且正在開發(fā)自己的電池電芯和電池組,以為其ATLIS XP平臺和XT皮卡車提供動力,續(xù)航里程為300-500英里。電動中重型卡車XP平臺,...

史海拾趣

問答坊 | AI 解惑

請教

誰知道MM1007的技術資料.謝謝…

查看全部問答∨

招聘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. ...…

查看全部問答∨

驅動器緩沖器有什么用

PWM輸出到緩沖器,再到隔離電路,最后到驅動器緩沖器有什么用…

查看全部問答∨

求用VS2005或者2008,用SerialPort類寫的 串口通信程序,及相關的說明

如題,本人急需學會這個東西,麻煩各位高手幫忙,不要轉載其它不是用這個類寫的東西進行回答,特別是接收這塊的代碼,請給個詳細點的流程介紹,比如 在這個里面寫的托管能起到什么左右,它和DataReceived這個事件之間的關系是個怎么樣的。謝謝~~不 ...…

查看全部問答∨

ARM本身有FLASH的驅動程序?

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ù)將會被此引腳“吃掉”從而看 ...…

查看全部問答∨

297運用

本帖最后由 paulhyde 于 2014-9-15 09:37 編輯 我按典型電路搭的297,但是振蕩了,誰能指點一下  …

查看全部問答∨

為何我裝CCS會出現(xiàn)錯誤提示?

為何我裝CCS會出現(xiàn)錯誤提示?操作系統(tǒng)64位win7,安裝到最后一個組件前會出現(xiàn)如圖1提示:這個提示是怎么回事?不管,關閉后繼續(xù)安裝又會出現(xiàn)圖2提示:取消安裝,刪除安裝文件夾內(nèi)容,重新安裝,圖2錯誤提示就沒有了,安裝完成。但是圖1的提示還是出 ...…

查看全部問答∨

請推薦TI汽車級3.3VCAN收發(fā)器

本帖最后由 dontium 于 2015-1-23 13:12 編輯 請推薦TI汽車級3.3VCAN收發(fā)器,謝謝! …

查看全部問答∨
小廣播
設計資源 培訓 開發(fā)板 精華推薦

最新單片機文章

 
EEWorld訂閱號

 
EEWorld服務號

 
汽車開發(fā)圈

 
機器人開發(fā)圈

電子工程世界版權所有 京ICP證060456號 京ICP備10001474號-1 電信業(yè)務審批[2006]字第258號函 京公網(wǎng)安備 11010802033920號 Copyright ? 2005-2025 EEWORLD.com.cn, Inc. All rights reserved