新闻中心

EEPW首页>嵌入式系统>设计应用> STM下纯软件实现精确定时

STM下纯软件实现精确定时

作者: 时间:2016-11-21 来源:网络 收藏
先上测试代码(普通版)
#include "stm32f10x.h"
#include "core_cm3.h"
__asm int Dellayus(u32 usec)
{
ALIGN
PUSH.W {r1} //2时钟周期
MOV r1,#18 //1时钟周期
MUL r0,r1 //1时钟周期
SUB r0,#3 //1时钟周期
loop
SUBS r0,#1 //1时钟周期
BNE loop //如果跳转则为3个周期,不跳则只有1个周期
POP {r1} //2时钟周期
BX lr //3个时钟周期
//总共所用周期为(usec*4)-4,此处减4主要用于抵消调用此函数的消耗时钟周期(传参1时钟,BLX跳转3时钟)
}
int main(void)
{
while(1)
{
//运行到此处时32.93us,第1379时钟周期
Dellayus(100);
//运行到此处时132.93us,第8579时钟周期,运行上条指令共花8579-1379=7200时钟周期
//因系统时钟为72MHz,所以每72时钟周期所花时间为1微妙,上条指令所花时间
//等于7200 ÷ 72 = 100微妙,延时完全准确
Dellayus(100);
//运行到此处时232.93us,第15779时钟周期,同样验证上面的结论
}
}
调试:
第一个100微妙延时函数运行前

第一个100微妙延时函数运行后

所花系统时钟周期8579-1379=7200个系统时钟,时间精确为0.00013293-0.0003293=0.0001秒,
也就是100微秒。延时函数通过软件模拟以及硬件调试同样精确,如果延时参数大于255微秒,调用
时会多耗两个时钟周期(0.028微秒)用于从代码段加载常数。可以修改代码修正误差。最大定时不能
超过(232-1) ÷ 18 ≈ 238.6秒。
以上方法可以节约系统定时器资源,避免和其他代码争用定时器。通常用于微秒級延时。
版主的建议非常好,是该增加时钟参数以适应不同的时钟频率,改了一下代码,目前软件仿真测试没问题,等晚上再用JLink
测试一下(理论上应该没问题,已经考虑了闪存预取优化)。
#include "stm32f10x.h"
#include "core_cm3.h"
__asm void Dellayus(u32 usec,u8 freq) //freq参数为系统时钟频率(SYSCLK),单位MHz,且必须能被4整除,且必须大 于等于16,常用的
{ //有24,36,48,56,72,如果一定要使用8MHz则必须满足延时时间大于2微秒,但freq一定不要
//小于8MHz,否则函数将出现混乱。条件为 ((usec >= 1) && (freq >=16)) || ((usec >= 2) && (freq >= 8))
ALIGN
LSR r1,r1,#2 //1时钟周期,除以单次循环所用的时钟数(4个)即得到延时1微妙所需的循环数
MUL r0,r1 //1时钟周期
SUB r0,#3 //1时钟周期
NOP //用于匹配延时周期以及使loop循环处指令在8字节边界对齐,提高精度(因为指令预取单元一次预取8字节指令)
NOP //所以循环时都不用再从闪存内取指令,避免闪存延时影响延时精度
loop
SUBS r0,#1 //1时钟周期
BNE loop //延时循环,每次4个时钟周期,最后一次只需两个时钟周期(如果跳转则为3个周期,不跳则只有1个周期)
NOP
BX lr //3个时钟周期
//本函数内总共所用周期为usec*(freq/4)-2 +9,调用此函数的消耗5个时钟周期(传参2时钟,BLX跳转3时钟)
} //函数最低耗时11个时钟周期,上面usec*(freq/4) - 2为循环代码的耗时(此处减2是因为最后一次循环BNE没有跳转,只消耗1个时钟比跳转的3个时钟节约2个所以减去2才是最终循环的耗时),9就是其它代码的耗时
//比如频率为8MHz时延时2微秒所需延时周期数为2*8=16个时钟,将值带入上面的公式即为 2 * (8 / 4) - 2 + 9 =11 即为函数体耗时,再加上5个周期的调用开销最终消耗16个时钟周期,同时这也是最低延时周期数。
int main(void)
{
while(1)
{
//运行到此处时32.93us,第1379时钟周期
Dellayus(100,72);
//运行到此处时132.93us,第8579时钟周期,运行上条指令共花8579-1379=7200时钟周期
//因系统时钟为72MHz,所以每72时钟周期所花时间为1微妙,上条指令所花时间
//等于7200 ÷ 72 = 100微妙,延时完全准确
Dellayus(100,72);
//运行到此处时232.93us,第15779时钟周期,同样验证上面的结论
}
}
示例代码见附件:
附件1: 软件延时测试.rar (文件大小: 21 KB 下载次数:218次)

附件2: 软件延时测试增强版.rar (文件大小: 22 KB 下载次数:309次)
1、顶,花了不少功夫!
不过在不同时钟条件下,应该就又要修改代码了吧?
比如36M?8M?
所以 建议加入一个时钟参数,这样比较通用一点.
2、谢谢楼主!,我软件测试延时的时候并没有出现当参数大于255微妙的时候会多消耗2个时钟周期的情况,一样很准啊。还有我用J-LINK仿真SW模式,单步,那个SEC没有变化,不知道是怎么回事。还有一个问题:为什么返回值要用int 不太懂汇编
3、需要将J-LINK设为JTAG模式才能读出SEC值,另外返回值用int 当时主要是想以后增加函数功能
4、这个建议非常好,马上上个增强版
5、呵呵,还不如用C的
void Delay_us(unsigned int t)
{
int i;

for(i=0; i<144*t; i++){
;
}
}
比上面汇编的要准: 同时1000个循环,
汇编用时为: 100.844ms
C及时: 100.139ms
6、搞个-O2优化试试,还准不准.
另外换个时钟频率试试,还准不准?
7、
//加个编译项就行了.
#define OSC (8) //定义为8M
#define OSC_D ((OSC*144)/8)
#pragma O3
void Delay_us(unsigned int t)
{
int i;
for(i=0; i
;
}
}
//测试
//运行时间为100.014ms
呵呵,纯为游戏之作.在这种CPU,延时最好是用原子的方法了.
8、用原子的战舰开发板测试,用楼主这种方法,得到的延时总理理论时间的1.5倍,比如要延迟1秒,实际总是1.5秒。
怀疑CPU运行在48M,但是我设置的的确是72M,是不是哪里设置有问题?
9、要完全精确的话还得提前关掉中断哦……
不过一般也不需要这么精确的延时,献上我的,不占用定时器资源哦:

#define RT_TICK_PER_SECOND 100
void hw_tsc_init() // TSC-timestamp counter
{
unsigned int cnts;

cnts = (unsigned int)9000000 / RT_TICK_PER_SECOND;

SysTick->CTRL&=0xfffffffb;//bit2清空,选择外部时钟,也即HCLK/8
SysTick->LOAD=cnts-1;
SysTick->VAL = 0; // write to clean
SysTick->CTRL=0x01 ; //开始倒数 不产生中断
}

void hw_tick_start()
{
unsigned int cnts;

cnts = (unsigned int)9000000 / RT_TICK_PER_SECOND;

SysTick->CTRL&=0xfffffffb;//bit2清空,选择外部时钟,也即HCLK/8
SysTick->LOAD=cnts-1;
SysTick->CTRL=0x01 ; //开始倒数 不产生中断

SysTick->CTRL = 0x03; // 启动systick并产生中断
}
void hw_delay_us(unsigned int us)
{
unsigned int start ,target,cur;

while(us > 500) // 防止us超过systick周期。(假设systick周期超过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,否则死循环。也即us不能等于systick周期
}
else // target <= start
{
do
{
cur = SysTick->VAL;
}while(target<=start); // 假设target为一个很小的值,则cur有可能在循环周期内溢出而大于target,故须加上cur<=start的条件
}
}

void hw_delay_ms(unsigned int ms)
{
while(ms--)
{
hw_delay_us(500);
hw_delay_us(500);
}
}
10、我不用软件延时的...
我一直用systick延时。
11、楼主给力,不过不推荐用楼主这种笨办法,楼主大可以移植ucos,移植成功后,你想定多久有多久,要多精确有多精确。
同时,我觉得用定时器实现精确延时比这个方法靠谱得多。
12、ucos的定时是以系统时基为单位的,比如OS_CFG_TICK_RATE_HZ设为100时就只能精确到10ms,也就是说高于10ms并且步进为10ms(50ms,500ms能较精确,但55ms就不精确了)可以比较 精确定时,那我说延时10us如何用ucos的定时器定时呢?
13、你可以增大ucos的时钟节拍数,然后用OSTimeTick()来实现定时
14、 STM32的定时器多的是,基本都用不完吧



关键词:STM纯软件精确定

评论


技术专区

关闭