新聞中心

EEPW首頁(yè) > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > STM下純軟件實(shí)現(xiàn)精確定時(shí)

STM下純軟件實(shí)現(xiàn)精確定時(shí)

作者: 時(shí)間:2016-11-21 來(lái)源:網(wǎng)絡(luò) 收藏
先上測(cè)試代碼(普通版)
#include "stm32f10x.h"
#include "core_cm3.h"
__asm int Dellayus(u32 usec)
{
ALIGN
PUSH.W {r1} //2時(shí)鐘周期
MOV r1,#18 //1時(shí)鐘周期
MUL r0,r1 //1時(shí)鐘周期
SUB r0,#3 //1時(shí)鐘周期
loop
SUBS r0,#1 //1時(shí)鐘周期
BNE loop //如果跳轉(zhuǎn)則為3個(gè)周期,不跳則只有1個(gè)周期
POP {r1} //2時(shí)鐘周期
BX lr //3個(gè)時(shí)鐘周期
//總共所用周期為(usec*4)-4,此處減4主要用于抵消調(diào)用此函數(shù)的消耗時(shí)鐘周期(傳參1時(shí)鐘,BLX跳轉(zhuǎn)3時(shí)鐘)
}
int main(void)
{
while(1)
{
//運(yùn)行到此處時(shí)32.93us,第1379時(shí)鐘周期
Dellayus(100);
//運(yùn)行到此處時(shí)132.93us,第8579時(shí)鐘周期,運(yùn)行上條指令共花8579-1379=7200時(shí)鐘周期
//因系統(tǒng)時(shí)鐘為72MHz,所以每72時(shí)鐘周期所花時(shí)間為1微妙,上條指令所花時(shí)間
//等于7200 ÷ 72 = 100微妙,延時(shí)完全準(zhǔn)確
Dellayus(100);
//運(yùn)行到此處時(shí)232.93us,第15779時(shí)鐘周期,同樣驗(yàn)證上面的結(jié)論
}
}
調(diào)試:
第一個(gè)100微妙延時(shí)函數(shù)運(yùn)行前

第一個(gè)100微妙延時(shí)函數(shù)運(yùn)行后

所花系統(tǒng)時(shí)鐘周期8579-1379=7200個(gè)系統(tǒng)時(shí)鐘,時(shí)間精確為0.00013293-0.0003293=0.0001秒,
也就是100微秒。延時(shí)函數(shù)通過(guò)軟件模擬以及硬件調(diào)試同樣精確,如果延時(shí)參數(shù)大于255微秒,調(diào)用
時(shí)會(huì)多耗兩個(gè)時(shí)鐘周期(0.028微秒)用于從代碼段加載常數(shù)??梢孕薷拇a修正誤差。最大定時(shí)不能
超過(guò)(232-1) ÷ 18 ≈ 238.6秒。
以上方法可以節(jié)約系統(tǒng)定時(shí)器資源,避免和其他代碼爭(zhēng)用定時(shí)器。通常用于微秒級(jí)延時(shí)。
版主的建議非常好,是該增加時(shí)鐘參數(shù)以適應(yīng)不同的時(shí)鐘頻率,改了一下代碼,目前軟件仿真測(cè)試沒(méi)問(wèn)題,等晚上再用JLink
測(cè)試一下(理論上應(yīng)該沒(méi)問(wèn)題,已經(jīng)考慮了閃存預(yù)取優(yōu)化)。
#include "stm32f10x.h"
#include "core_cm3.h"
__asm void Dellayus(u32 usec,u8 freq) //freq參數(shù)為系統(tǒng)時(shí)鐘頻率(SYSCLK),單位MHz,且必須能被4整除,且必須大 于等于16,常用的
{ //有24,36,48,56,72,如果一定要使用8MHz則必須滿足延時(shí)時(shí)間大于2微秒,但freq一定不要
//小于8MHz,否則函數(shù)將出現(xiàn)混亂。條件為 ((usec >= 1) && (freq >=16)) || ((usec >= 2) && (freq >= 8))
ALIGN
LSR r1,r1,#2 //1時(shí)鐘周期,除以單次循環(huán)所用的時(shí)鐘數(shù)(4個(gè))即得到延時(shí)1微妙所需的循環(huán)數(shù)
MUL r0,r1 //1時(shí)鐘周期
SUB r0,#3 //1時(shí)鐘周期
NOP //用于匹配延時(shí)周期以及使loop循環(huán)處指令在8字節(jié)邊界對(duì)齊,提高精度(因?yàn)橹噶铑A(yù)取單元一次預(yù)取8字節(jié)指令)
NOP //所以循環(huán)時(shí)都不用再?gòu)拈W存內(nèi)取指令,避免閃存延時(shí)影響延時(shí)精度
loop
SUBS r0,#1 //1時(shí)鐘周期
BNE loop //延時(shí)循環(huán),每次4個(gè)時(shí)鐘周期,最后一次只需兩個(gè)時(shí)鐘周期(如果跳轉(zhuǎn)則為3個(gè)周期,不跳則只有1個(gè)周期)
NOP
BX lr //3個(gè)時(shí)鐘周期
//本函數(shù)內(nèi)總共所用周期為usec*(freq/4)-2 +9,調(diào)用此函數(shù)的消耗5個(gè)時(shí)鐘周期(傳參2時(shí)鐘,BLX跳轉(zhuǎn)3時(shí)鐘)
} //函數(shù)最低耗時(shí)11個(gè)時(shí)鐘周期,上面usec*(freq/4) - 2為循環(huán)代碼的耗時(shí)(此處減2是因?yàn)樽詈笠淮窝h(huán)BNE沒(méi)有跳轉(zhuǎn),只消耗1個(gè)時(shí)鐘比跳轉(zhuǎn)的3個(gè)時(shí)鐘節(jié)約2個(gè)所以減去2才是最終循環(huán)的耗時(shí)),9就是其它代碼的耗時(shí)
//比如頻率為8MHz時(shí)延時(shí)2微秒所需延時(shí)周期數(shù)為2*8=16個(gè)時(shí)鐘,將值帶入上面的公式即為 2 * (8 / 4) - 2 + 9 =11 即為函數(shù)體耗時(shí),再加上5個(gè)周期的調(diào)用開(kāi)銷最終消耗16個(gè)時(shí)鐘周期,同時(shí)這也是最低延時(shí)周期數(shù)。
int main(void)
{
while(1)
{
//運(yùn)行到此處時(shí)32.93us,第1379時(shí)鐘周期
Dellayus(100,72);
//運(yùn)行到此處時(shí)132.93us,第8579時(shí)鐘周期,運(yùn)行上條指令共花8579-1379=7200時(shí)鐘周期
//因系統(tǒng)時(shí)鐘為72MHz,所以每72時(shí)鐘周期所花時(shí)間為1微妙,上條指令所花時(shí)間
//等于7200 ÷ 72 = 100微妙,延時(shí)完全準(zhǔn)確
Dellayus(100,72);
//運(yùn)行到此處時(shí)232.93us,第15779時(shí)鐘周期,同樣驗(yàn)證上面的結(jié)論
}
}
示例代碼見(jiàn)附件:
附件1:軟件延時(shí)測(cè)試.rar (文件大小: 21 KB 下載次數(shù):218次)

附件2:軟件延時(shí)測(cè)試增強(qiáng)版.rar (文件大小: 22 KB 下載次數(shù):309次)
1、頂,花了不少功夫!
不過(guò)在不同時(shí)鐘條件下,應(yīng)該就又要修改代碼了吧?
比如36M?8M?
所以建議加入一個(gè)時(shí)鐘參數(shù),這樣比較通用一點(diǎn).
2、謝謝樓主!,我軟件測(cè)試延時(shí)的時(shí)候并沒(méi)有出現(xiàn)當(dāng)參數(shù)大于255微妙的時(shí)候會(huì)多消耗2個(gè)時(shí)鐘周期的情況,一樣很準(zhǔn)啊。還有我用J-LINK仿真SW模式,單步,那個(gè)SEC沒(méi)有變化,不知道是怎么回事。還有一個(gè)問(wèn)題:為什么返回值要用int不太懂匯編
3、需要將J-LINK設(shè)為JTAG模式才能讀出SEC值,另外返回值用int當(dāng)時(shí)主要是想以后增加函數(shù)功能
4、這個(gè)建議非常好,馬上上個(gè)增強(qiáng)版
5、呵呵,還不如用C的
voidDelay_us(unsignedintt)
{
inti;

for(i=0;i<144*t;i++){
;
}
}
比上面匯編的要準(zhǔn):同時(shí)1000個(gè)循環(huán),
匯編用時(shí)為:100.844ms
C及時(shí):100.139ms
6、搞個(gè)-O2優(yōu)化試試,還準(zhǔn)不準(zhǔn).
另外換個(gè)時(shí)鐘頻率試試,還準(zhǔn)不準(zhǔn)?
7、
//加個(gè)編譯項(xiàng)就行了.
#define OSC (8) //定義為8M
#define OSC_D ((OSC*144)/8)
#pragma O3
void Delay_us(unsigned int t)
{
int i;
for(i=0; i
;
}
}
//測(cè)試
//運(yùn)行時(shí)間為100.014ms
呵呵,純?yōu)橛螒蛑?在這種CPU,延時(shí)最好是用原子的方法了.
8、用原子的戰(zhàn)艦開(kāi)發(fā)板測(cè)試,用樓主這種方法,得到的延時(shí)總理理論時(shí)間的1.5倍,比如要延遲1秒,實(shí)際總是1.5秒。
懷疑CPU運(yùn)行在48M,但是我設(shè)置的的確是72M,是不是哪里設(shè)置有問(wèn)題?
9、要完全精確的話還得提前關(guān)掉中斷哦……
不過(guò)一般也不需要這么精確的延時(shí),獻(xiàn)上我的,不占用定時(shí)器資源哦:

#defineRT_TICK_PER_SECOND100
voidhw_tsc_init() //TSC-timestampcounter
{
unsignedintcnts;

cnts=(unsignedint)9000000/RT_TICK_PER_SECOND;

SysTick->CTRL&=0xfffffffb;//bit2清空,選擇外部時(shí)鐘,也即HCLK/8
SysTick->LOAD=cnts-1;
SysTick->VAL=0;//writetoclean
SysTick->CTRL=0x01;//開(kāi)始倒數(shù)不產(chǎn)生中斷
}

voidhw_tick_start()
{
unsignedintcnts;

cnts=(unsignedint)9000000/RT_TICK_PER_SECOND;

SysTick->CTRL&=0xfffffffb;//bit2清空,選擇外部時(shí)鐘,也即HCLK/8
SysTick->LOAD=cnts-1;
SysTick->CTRL=0x01;//開(kāi)始倒數(shù)不產(chǎn)生中斷

SysTick->CTRL=0x03; //啟動(dòng)systick并產(chǎn)生中斷
}
voidhw_delay_us(unsignedintus)
{
unsignedintstart,target,cur;

while(us>500) //防止us超過(guò)systick周期。(假設(shè)systick周期超過(guò)500us)
{
hw_delay_us(500);
us-=500;
}

start=SysTick->VAL;
target=(start-us*9);

if(start
{
target+=(9000000/RT_TICK_PER_SECOND);
do
{
cur=SysTick->VAL;
}while(cur<=start||cur>target);//target不能等于start,否則死循環(huán)。也即us不能等于systick周期
}
else//target<=start
{
do
{
cur=SysTick->VAL;
}while(target<=start); //假設(shè)target為一個(gè)很小的值,則cur有可能在循環(huán)周期內(nèi)溢出而大于target,故須加上cur<=start的條件
}
}

voidhw_delay_ms(unsignedintms)
{
while(ms--)
{
hw_delay_us(500);
hw_delay_us(500);
}
}
10、我不用軟件延時(shí)的...
我一直用systick延時(shí)。
11、樓主給力,不過(guò)不推薦用樓主這種笨辦法,樓主大可以移植ucos,移植成功后,你想定多久有多久,要多精確有多精確。
同時(shí),我覺(jué)得用定時(shí)器實(shí)現(xiàn)精確延時(shí)比這個(gè)方法靠譜得多。
12、ucos的定時(shí)是以系統(tǒng)時(shí)基為單位的,比如OS_CFG_TICK_RATE_HZ設(shè)為100時(shí)就只能精確到10ms,也就是說(shuō)高于10ms并且步進(jìn)為10ms(50ms,500ms能較精確,但55ms就不精確了)可以比較精確定時(shí),那我說(shuō)延時(shí)10us如何用ucos的定時(shí)器定時(shí)呢?
13、你可以增大ucos的時(shí)鐘節(jié)拍數(shù),然后用OSTimeTick()來(lái)實(shí)現(xiàn)定時(shí)
14、STM32的定時(shí)器多的是,基本都用不完吧



關(guān)鍵詞: STM純軟件精確定

評(píng)論


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

關(guān)閉