新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > STM32系統(tǒng)滴答_及不可不知的延時(shí)技巧上

STM32系統(tǒng)滴答_及不可不知的延時(shí)技巧上

作者: 時(shí)間:2016-11-19 來源:網(wǎng)絡(luò) 收藏
我想每個(gè)單片機(jī)愛好者及工程開發(fā)設(shè)計(jì)人員都有過點(diǎn)燈的經(jīng)歷。流水燈是個(gè)好東西,尤其是在調(diào)試資源有限的環(huán)境中,有時(shí)會(huì)幫上大忙。

本文引用地址:http://www.butianyuan.cn/article/201611/318494.htm

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

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

但這些方法一般都不夠方便,延時(shí)也不夠精確,更高階一點(diǎn)的方法便是開一個(gè)定時(shí)器,在定時(shí)中斷里面計(jì)數(shù)達(dá)到精確延時(shí)的目的。

STM32的應(yīng)用中,可考慮利用SysTick系統(tǒng)嘀嗒定時(shí)器來實(shí)現(xiàn)。但在STM32開發(fā)手冊(cè)中對(duì)它的介紹卻很少,幾乎到?jīng)]有的程度。因?yàn)樗荂ortex內(nèi)核的部分,CM3為它專門開出一個(gè)異常類型,并且在中斷向量表中占有一席之地(異常號(hào)15),這樣它可以很方便的移植到不同廠商出CM3內(nèi)核的芯片上,并且對(duì)于有實(shí)時(shí)操作系統(tǒng)的軟件,它一般會(huì)作為整個(gè)系統(tǒng)的時(shí)基,這個(gè)對(duì)操作系統(tǒng)非常重要。有關(guān)SysTick的詳細(xì)介紹可參考《Cortex-M3權(quán)威指南》第133頁第八章及第179頁第十三章。

SysTick總共有四個(gè)寄存器:

1、

對(duì)應(yīng)于軟件中SysTick->CTRL;

2、

對(duì)應(yīng)于軟件中SysTick-> LOAD;

3、

對(duì)應(yīng)于軟件中SysTick-> VAL;

4、


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

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

寄存器結(jié)構(gòu)體的定義在CMSISCM3CoreSupport core_cm3.h中,如下

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

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

SysTick中斷優(yōu)先級(jí)問題這里需要強(qiáng)調(diào)下。

它屬于系統(tǒng)異常,是內(nèi)核級(jí)中斷,并且優(yōu)先級(jí)是可以設(shè)置的,具體設(shè)置也是在 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*/ }


其中如下這句就是設(shè)置優(yōu)先級(jí)的函數(shù),此函數(shù)對(duì)內(nèi)核中斷優(yōu)先級(jí)和外部中斷優(yōu)先級(jí)設(shè)置通吃,比較強(qiáng)大,但需要手動(dòng)算出來搶占和從優(yōu)先級(jí),不太方便,當(dāng)跳進(jìn)此函數(shù),我們可以算出Systick默認(rèn)優(yōu)先是最低的(效果相當(dāng)于SCB->SHP[11] = 0xF0;)

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

此時(shí)若其它外部中斷優(yōu)先級(jí)設(shè)置比它高時(shí),可以剝奪它進(jìn)而轉(zhuǎn)向外部中斷。

可以做如下實(shí)驗(yàn)驗(yàn)證:

先設(shè)置一事件中斷,把優(yōu)先級(jí)設(shè)置高一些,

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); }

注:中斷分組我在實(shí)驗(yàn)中,最初初始化設(shè)置為如下:

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

設(shè)為第二組。

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

系統(tǒng)滴答中斷里觸發(fā)外部中斷事件,并點(diǎn)亮LED1 。

外部中斷處理函數(shù)如下

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

此延時(shí)函數(shù)為阻塞延時(shí)如下:

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

加入延時(shí)是為了看出來哪個(gè)燈先亮。

當(dāng)外部中斷優(yōu)先級(jí)比較高時(shí),它可以搶占Systick中斷先執(zhí)行,以上代碼實(shí)驗(yàn)結(jié)果為,LED0先點(diǎn)亮后,再回到LED1再點(diǎn)亮。

當(dāng)把外部中斷設(shè)置為與systick相同的優(yōu)先級(jí)時(shí),則systick優(yōu)先級(jí)就會(huì)相對(duì)較高,例如把上面的優(yōu)先級(jí)改為

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

則會(huì)LED1先亮,執(zhí)行完SysTick_Handle函數(shù)后才輪到EXTI1_IRQHandler執(zhí)行。

個(gè)人認(rèn)為,若要實(shí)現(xiàn)systick精確延時(shí),最好把systick優(yōu)先級(jí)設(shè)置高一些,例如

NVIC_SetPriority(SysTick_IRQn,0);

即把SCB->SHP[11] = 0x00;則可達(dá)到systick優(yōu)先級(jí)高于任合外部中斷的效果,此時(shí)延時(shí)會(huì)比較精準(zhǔn)。

另外對(duì)于SysTick的時(shí)鐘源的選擇,要注意它的時(shí)鐘源可選擇內(nèi)部時(shí)鐘(FCLK,CM3上的自由運(yùn)行時(shí)鐘,STM32中對(duì)應(yīng)是AHB),或者是外部時(shí)鐘( CM3處理器上的STCLK信號(hào),STM32中對(duì)應(yīng)是AHB/8)

可參考如下圖

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

有關(guān)systick延時(shí)函數(shù)的編寫可參考野火《零死角玩轉(zhuǎn)stm32-初級(jí)篇》。

至此我們可以簡(jiǎn)單的實(shí)現(xiàn)一流水燈程序

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

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

假設(shè)系統(tǒng)時(shí)鐘頻率為72MHZ或者幾十上百M(fèi)HZ時(shí),當(dāng)完成一個(gè)循環(huán)只需要幾十或十幾納秒級(jí)或者更短,而在這個(gè)循環(huán)之中阻塞延時(shí)個(gè)幾十至幾百毫秒的話,就像是在高速公路上突然橫出一條坑坑洼洼的泥濘路,那可想整條路都會(huì)因此而慢下來,甚至?xí)霈F(xiàn)災(zāi)難性的后果,個(gè)人認(rèn)為,一般在系統(tǒng)初始化過程中,各芯片的時(shí)序?qū)r(shí)間有要求,可以用下阻塞延時(shí),只需要系統(tǒng)啟動(dòng)時(shí)運(yùn)行一下,當(dāng)系統(tǒng)跑起來之后,最好就別再傻呼呼的這么做了。

這時(shí)主要采用的是在定時(shí)器里計(jì)數(shù),在外部循環(huán)中對(duì)變量查詢,達(dá)到某個(gè)值時(shí)再執(zhí)行某個(gè)動(dòng)作,達(dá)到延時(shí)的效果,而在時(shí)間未到時(shí),系統(tǒng)還可以不停的跑圈圈,做別的事情去。

gticks在定時(shí)中斷里每毫秒計(jì)數(shù)一次

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

以上需要在事件處理過程中對(duì)gticks進(jìn)行處理,增加了代碼的耦合度,更容易出錯(cuò),如果在一個(gè)事件處理中對(duì)gticks清除了,而下個(gè)事件中又需要查詢它,這樣就可能導(dǎo)致處理時(shí)序的錯(cuò)亂,相互干擾。

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

下節(jié)高手將登場(chǎng)了,為大家介紹個(gè)我曾在一項(xiàng)目中學(xué)到的,非阻塞延時(shí)的精妙設(shè)計(jì)。



評(píng)論


技術(shù)專區(qū)

關(guān)閉