本帖最后由 王小琪 于 2023-1-9 18:59 编辑
延时函数是基本上在所有的项目中都会遇到的一个功能、和软件工程师的HELLO WORLD!一样,几乎每个玩单片机的上手第一个项目就是点灯,因此许多人自称“点灯工程师”,点灯常用的闪烁基本逻辑就是亮0.5s,灭0.5s,重复动作就是闪烁的效果。上面就用到了延时函数,由此可见一斑,延时函数是多么的重要,于是我在这里抛砖引玉,介绍几种延时函数的方式,下面用野火核心板进行演示,演示的功能都是一样的,间隔一段时间LED状态进行翻转,同时进行串口打印。
一、软件延时 // 软件延时函数,使用不同的系统时钟,延时不一样
void Delay(__IO uint32_t nCount)
{
for(; nCount != 0; nCount--);
}
int main(void)
{
HSE_SetSysClock(RCC_PLLMul_9);//SYSCLK 8*9=72M
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init();
while(1)
{
LED0=!LED0;
printf("\r\n今天是个好日子~");
Delay(5000000);
}
}
通过下面的对比可以看到,通过改变系统时钟SYSCLK,时间间隔改变了,因为单片机执行一步指令大概在几ns,频率越快,执行的动作越快,相对应的时间就越短。
还有一个问题就是Delay(5000000)差不多是0.8s,如果我希望延时刚好1s,很难找到这个临界值,导致延时不准。
总结:
优点:移植方便,无论你用51,还是PIC,还是STM32基本都可以用这个延时函数,移植性强。
缺点:不精准,对不同的单片机,延时时间也是不一样的,只能大概知道延时时间。
二、滴答定时器
下面展示的是main.c和delay.c部分函数
int main(void)
{
HSE_SetSysClock(RCC_PLLMul_9);//SYSCLK 8*5=40M
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init();
delay_init();
while(1)
{
LED0=!LED0;
printf("\r\n今天是个好日子~");
delay_ms(1000);
}
}
//SYSTICK的时钟固定为HCLK时钟的1/8
//SYSCLK:系统时钟
void delay_init()
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 HCLK/8
fac_us=9;//该处的值为SYSCLK/8M,对应关系为72M--9;64M--8;56M--7…………
fac_ms=(u16)fac_us*1000;//代表每个ms需要的systick时钟数
}
//延时nus,nus为要延时的us数.
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD=nus*fac_us; //时间加载
SysTick->VAL=0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
//延时nms
//注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms
//对72M条件下,nms<=1864
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD=(u32)nms*fac_ms; //时间加载(SysTick->LOAD为24bit)
SysTick->VAL =0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
可以看到滴答定时器延时比较准,delay_ms(1000)就刚好是1s,有一点需要注意的是,修改了SYSCLK之后,在delay.c文件里面的系数也要修改。
总结:
优点:延时精准。
缺点:不适用其他型号单片机,不方便移植。
三、使用定时器进行定时,不使用中断
int main(void)
{
HSE_SetSysClock(RCC_PLLMul_9);//SYSCLK 8*9=72M
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init();
TIM3_Init();
while(1)
{
LED0=!LED0;
printf("\r\n今天是个好日子~");
TIM3_Delayms(1000);
}
}
void TIM3_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); ///使能TIM3时钟
TIM_TimeBaseInitStructure.TIM_Period = 50000-1; //自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler = 72-1; //定时器分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化TIM3
}
//微秒级延时
void TIM3_Delayus(u16 xus)
{
TIM_Cmd(TIM3,ENABLE); //启动定时器
while(TIM3->CNT < xus);
TIM3->CNT = 0;
TIM_Cmd(TIM3,DISABLE); //关闭定时器
}
//毫秒级延时
void TIM3_Delayms(u16 xms)
{
int i;
for(i=0;i<xms;i++)
{
TIM3_Delayus(1000);
}
}
可以看到延时也比较准,TIM3_Delayms(1000)就刚好是1s,有一点需要注意的是,修改了SYSCLK之后,在time.c文件里面的预分频系数需要改。
总结:
优点:延时精准。
缺点:不适用其他型号单片机,不方便移植。
当然延时函数的方法有很多种,也欢迎其他小伙伴一起谈论。
|