新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > STM32系統滴答_及不可不知的延時技巧上

        STM32系統滴答_及不可不知的延時技巧上

        作者: 時間:2016-11-19 來源:網絡 收藏
        我想每個單片機愛好者及工程開發設計人員都有過點燈的經歷。流水燈是個好東西,尤其是在調試資源有限的環境中,有時會幫上大忙。

        本文引用地址:http://www.104case.com/article/201611/318494.htm

        然在最初入門時,如何讓這些小燈們按照我們的想法歡快地跑起來呢,絕大多數小朋友的做法是:在一個while循環里加上延時程序,讓小燈在每個狀態下停留一段時間,再進入下一個狀態,這樣小燈們就會在不同的狀態中切換,就可以根據我們設計的程序閃爍了。

        這樣這里就會涉及到一個延時程序的編寫的問題,而一般的做法是一個for循環里去減一個很大的數,直到為0,則延時完成,那個數的值則是根據時鐘頻率和指令運行周期,估算出來的,還記得較久以前看過一篇帖子介紹51單片機精確延時的幾種方法,有一種方法是在keil中設定好時鐘頻率,然后通過軟件仿真試來算延時時間,以達到較精確定時。

        但這些方法一般都不夠方便,延時也不夠精確,更高階一點的方法便是開一個定時器,在定時中斷里面計數達到精確延時的目的。

        STM32的應用中,可考慮利用SysTick系統嘀嗒定時器來實現。但在STM32開發手冊中對它的介紹卻很少,幾乎到沒有的程度。因為它是Cortex內核的部分,CM3為它專門開出一個異常類型,并且在中斷向量表中占有一席之地(異常號15),這樣它可以很方便的移植到不同廠商出CM3內核的芯片上,并且對于有實時操作系統的軟件,它一般會作為整個系統的時基,這個對操作系統非常重要。有關SysTick的詳細介紹可參考《Cortex-M3權威指南》第133頁第八章及第179頁第十三章。

        SysTick總共有四個寄存器:

        1、

        對應于軟件中SysTick->CTRL;

        2、

        對應于軟件中SysTick-> LOAD;

        3、

        對應于軟件中SysTick-> VAL;

        4、


        對應于軟件中SysTick-> CALIB(),沒有用過,也不常用,暫不作介紹。

        這幾個寄存器的偏移量如下圖所示:

        寄存器結構體的定義在CMSISCM3CoreSupport core_cm3.h中,如下

        /**@addtogroupCMSIS_CM3_SysTickCMSISCM3SysTick memorymappedstructureforSysTick @{ */ typedefstruct { __IOuint32_tCTRL;/*!

        SysTick是一個24 位的定時器,即一次最多可以計數 224個時鐘脈沖,這個脈沖計數值被保存到SysTick->VAL當前計數值寄存器中,它只能向下計數,每接收到一個時鐘脈沖SysTick->VAL的值就向下減1,直至0,然后由硬件自動把重載寄存器SysTick->LOAD中的值到SysTick->VAL重新計數,并且當SysTick->VAL值計數到0時,觸發異常,調用void SysTick_Handler(void)函數,可以在此中斷服務函數中處理定時中斷事件了,一般是對設定值進行遞減計數操作。只要不把它在SysTick控制及狀態寄存器SysTick->CTRL中的第0位使能位清除,就永不停息。

        SysTick中斷優先級問題這里需要強調下。

        它屬于系統異常,是內核級中斷,并且優先級是可以設置的,具體設置也是在 core_cm3.h中

        /** *@briefInitializeandstarttheSysTickcounteranditsinterrupt. * *@paramticksnumberofticksbetweentwointerrupts *@return1=failed,0=successful * *Initialisethesystemticktimeranditsinterruptandstartthe *systemticktimer/counterinfreerunningmodetogenerate *periodicalinterrupts. */ static__INLINEuint32_tSysTick_Config(uint32_tticks) { if(ticks>SysTick_LOAD_RELOAD_Msk)return(1);/*Reloadvalueimpossible*/  SysTick->LOAD=(ticks&SysTick_LOAD_RELOAD_Msk)-1;/*setreloadregister*/ NVIC_SetPriority(SysTick_IRQn,(1<<__NVIC_PRIO_BITS)-1); SysTick->VAL=0; SysTick->CTRL=SysTick_CTRL_CLKSOURCE_Msk| SysTick_CTRL_TICKINT_Msk| SysTick_CTRL_ENABLE_Msk; return(0);/*Functionsuccessful*/ }


        其中如下這句就是設置優先級的函數,此函數對內核中斷優先級和外部中斷優先級設置通吃,比較強大,但需要手動算出來搶占和從優先級,不太方便,當跳進此函數,我們可以算出Systick默認優先是最低的(效果相當于SCB->SHP[11] = 0xF0;)

        NVIC_SetPriority(SysTick_IRQn,(1<<__NVIC_PRIO_BITS)-1);

        此時若其它外部中斷優先級設置比它高時,可以剝奪它進而轉向外部中斷。

        可以做如下實驗驗證:

        先設置一事件中斷,把優先級設置高一些,

        voidExti_Config(void) { EXTI_InitTypeDefEXTI_InitStructure; NVIC_InitTypeDefNVIC_InitStructure; EXTI_InitStructure.EXTI_Line=EXTI_Line1; EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Event; EXTI_InitStructure.EXTI_LineCmd=ENABLE; EXTI_Init(&EXTI_InitStructure);  NVIC_InitStructure.NVIC_IRQChannel=EXTI1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1; NVIC_InitStructure.NVIC_IRQChannelSubPriority=1; NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; NVIC_Init(&NVIC_InitStructure); }

        注:中斷分組我在實驗中,最初初始化設置為如下:

        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

        設為第二組。

        voidSysTick_Handler(void) { EXTI_GenerateSWInterrupt(EXTI_SWIER_SWIER1); LED_1=ON; Delay(); }

        系統滴答中斷里觸發外部中斷事件,并點亮LED1 。

        外部中斷處理函數如下

        voidEXTI1_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line1)!=RESET) { EXTI_ClearITPendingBit(EXTI_Line1); LED_0=ON; Delay(); } }

        此延時函數為阻塞延時如下:

        voidDelay(void) { u32i; for(i=0;i<0xFFFFF;i++){} }

        加入延時是為了看出來哪個燈先亮。

        當外部中斷優先級比較高時,它可以搶占Systick中斷先執行,以上代碼實驗結果為,LED0先點亮后,再回到LED1再點亮。

        當把外部中斷設置為與systick相同的優先級時,則systick優先級就會相對較高,例如把上面的優先級改為

        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3; NVIC_InitStructure.NVIC_IRQChannelSubPriority=3;

        則會LED1先亮,執行完SysTick_Handle函數后才輪到EXTI1_IRQHandler執行。

        個人認為,若要實現systick精確延時,最好把systick優先級設置高一些,例如

        NVIC_SetPriority(SysTick_IRQn,0);

        即把SCB->SHP[11] = 0x00;則可達到systick優先級高于任合外部中斷的效果,此時延時會比較精準。

        另外對于SysTick的時鐘源的選擇,要注意它的時鐘源可選擇內部時鐘(FCLK,CM3上的自由運行時鐘,STM32中對應是AHB),或者是外部時鐘( CM3處理器上的STCLK信號,STM32中對應是AHB/8)

        可參考如下圖

        它是在SysTick->CTRL第二位CLKSOURCE時鐘源選擇中設置。

        有關systick延時函數的編寫可參考野火《零死角玩轉stm32-初級篇》。

        至此我們可以簡單的實現一流水燈程序

        while(1) { LED_0=OFF; LED_1=ON; Delay_ms(500); LED_0=OFF; LED_1=ON; Delay_ms(500); }

        然而這樣做真的好嗎?這里用的是阻塞延時哦,CPU的效率很大一部分就耗在了空轉上了,太浪費資源。

        假設系統時鐘頻率為72MHZ或者幾十上百MHZ時,當完成一個循環只需要幾十或十幾納秒級或者更短,而在這個循環之中阻塞延時個幾十至幾百毫秒的話,就像是在高速公路上突然橫出一條坑坑洼洼的泥濘路,那可想整條路都會因此而慢下來,甚至會出現災難性的后果,個人認為,一般在系統初始化過程中,各芯片的時序對時間有要求,可以用下阻塞延時,只需要系統啟動時運行一下,當系統跑起來之后,最好就別再傻呼呼的這么做了。

        這時主要采用的是在定時器里計數,在外部循環中對變量查詢,達到某個值時再執行某個動作,達到延時的效果,而在時間未到時,系統還可以不停的跑圈圈,做別的事情去。

        gticks在定時中斷里每毫秒計數一次

        while(1) { if(500==gticks) { LED_0=OFF; LED_1=ON; }  if(1000==gticks) { LED_0=OFF; LED_1=ON; gticks=0 } Do_others(); }

        以上需要在事件處理過程中對gticks進行處理,增加了代碼的耦合度,更容易出錯,如果在一個事件處理中對gticks清除了,而下個事件中又需要查詢它,這樣就可能導致處理時序的錯亂,相互干擾。

        能否在事件處理中只提供查詢功能,而定時的事情就交給定時自己去做?

        下節高手將登場了,為大家介紹個我曾在一項目中學到的,非阻塞延時的精妙設計。



        評論


        技術專區

        關閉
        主站蜘蛛池模板: 甘德县| 衡南县| 揭东县| 盘锦市| 石家庄市| 施秉县| 晋中市| 道真| 平潭县| 浦县| 酒泉市| 同心县| 于田县| 岢岚县| 万荣县| 锡林郭勒盟| 滨州市| 于都县| 神池县| 桃园县| 宁远县| 江口县| 永胜县| 常州市| 石嘴山市| 龙川县| 巴林左旗| 台江县| 木兰县| 商丘市| 青铜峡市| 张家界市| 玛纳斯县| 临澧县| 阿克| 凤山市| 西丰县| 阜阳市| 团风县| 鲁山县| 浠水县|