-
简介
-
阻塞延时
-
非阻塞延迟
-
非阻塞延迟拓展
-
心得体会
-
1、简介
今天利用上课的摸鱼时间,学习了一下延时发现其中有挺多学问,同样也有些规律,以此记录下自己的心得。
-
2、阻塞延时
void DelayXms(unsigned int xms)
{
unsigned char i, j;
while (xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
上面是我们一般用到的阻塞延迟,它是很简单,很方便甚至在stc-isp可以直接生成(我绝对不会告诉你这个就是在stc-isp上生成的),但是它也有缺点,就是它需要等待for循环的完成,这样就很不优雅,作为时间管理大师的我肯定是无法忍受的,那么怎么才能够一“芯”多用,让多个任务“同时”运行呢?那就要使用接下来说的非阻塞延迟。
-
3、非阻塞延迟
#include "reg52.h"
#define _DELAY_TIME 1000 //宏定义延迟时间
unsigned int u16_cnt; //时间积累变量
unsigned char u8_delay_flag; //延迟标志
/*延迟函数*/
void _DELAY()
{
u16_cnt = _DELAY_TIME;
if(u16_cnt)
{
u16_cnt--;
if(!u16_cnt)
{
u8_delay_flag = 1;
}
}
if(u8_delay_flag == 1)
{
u8_delay_flag = 0;
u16_cnt = _DELAY_TIME;
//执行语句
}
}
以上就是最简单的非阻塞函数,其实并不难理解。
首先要判断什么时候所需时间到了,先定义一个变量,赋给它我们要延迟的时间,进入if判断语句,当时间积累变量不为0时,让它减少,当它为0时,将标志位置1。
那么要如何实现我们所需的功能?这就需要另一个if判断语句,当标志位为1时,将标志位置0,防止多次触发,将时间积累变量重置,然后实现我们所需的功能。
那它又是如何实现非阻塞的呢?我们将这个函数放入while循环里,每次循环都会对时间积累变量进行一次判断,如果成立往下走程序,如果不成立,跳出if判断,回到while循环,我们将之前for循环需要一次执行完的程序,分解为多个判断语句,使得我们不需要去等待,以实现非阻塞。
但这不是完美的,当我们的任务变多,if判断语句就会增加,我们需要在DELAY函数里花费的时间就会变多,导致我们的延时变得不精准,那有没有什么办法既能实现非阻塞延迟又能获得一个相对精准的时间呢?这时大家一定想到了定时器,没错!当我们加上定时器,就能解决以上问题。
-
4、非阻塞延迟扩展
-
定时器
#include "reg52.h"
/*外部变量声明*/
extern unsigned int g_u16_time_cnt; //时间计数值
extern unsigned char g_u8_time_flag; //时间标志位
unsigned int g_u16_time_cnt; //时间计数值
unsigned char g_u8_time_flag; //时间标志位
#define time_delay 1000
void Timer0_Init(void) //1毫秒@12.000MHz
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初始值 (65536 - 1000)%256 1000 = 延迟时间/机械周期 机械周期 = 12*(1/晶振频率)
TH0 = 0xFC; //设置定时初始值 (65536 - 1000)/256
TF0 = 0; //清除TF0标志
ET0 = 1; //使能定时器0中断
EA = 1; //使能中断总开关
TR0 = 1; //定时器0开始计时
}
void Timer0_Isr(void) interrupt 1
{
TR0 = 0; //关闭定时计数器
if(g_u16_time_cnt)
{
g_u16_time_cnt--;
if(!g_u16_time_cnt)
{
g_u8_time_flag = 1;
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
}
}
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TR0 = 1; //打开定时计数器
}
void main()
{
g_u16_time_cnt = time_delay;
g_u8_time_flag = 0;
Timer0_Init();
while(1)
{
if(1 == g_u8_time_flag)
{
g_u8_time_flag = 0;
//执行程序
g_u16_time_cnt = time_delay;
}
}
}
这段代码其实和上面那部分代码并没有什么区别,只是加入了定时器,这里就不过多赘述,不过这里需要注意,中断的时机不能过短,否则会频繁进入中断影响主程序。
-
多任务并行
当然我们实现功能时,肯定不可能只有一个点灯(或者点亮一颗CPU),以51单片机为例,它只内带了两个定时器,那我们需要多个定时器时,两个肯定是不够用的,那么我们就可以多写几个软件定时器,以满足我们的需求。
/*外部变量声明*/
extern unsigned int g_u16_time1_cnt; //时间计数值
extern bit g_u8_time1_flag; //时间标志位
extern unsigned int g_u16_time2_cnt; //时间计数值
extern bit g_u8_time2_flag; //时间标志位
void Timer0_Init(void) //1毫秒@12.000MHz
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初始值 (65536 - 1000)%256 1000 = 延迟时间/机械周期 机械周期 = 12*(1/晶振频率)
TH0 = 0xFC; //设置定时初始值 (65536 - 1000)/256
TF0 = 0; //清除TF0标志
ET0 = 1; //使能定时器0中断
EA = 1; //使能中断总开关
TR0 = 1; //定时器0开始计时
}
void Timer0_Isr(void) interrupt 1
{
TR0 = 0; //关闭定时计数器
if(g_u16_time1_cnt)
{
g_u16_time1_cnt--;
if(!g_u16_time1_cnt)
{
g_u8_time1_flag = 1;
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
}
}
if(g_u16_time2_cnt)
{
g_u16_time2_cnt--;
if(!g_u16_time2_cnt)
{
g_u8_time2_flag = 1;
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
}
}
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TR0 = 1; //打开定时计数器
}
接下来是任务的实现
#include "reg52.h"
#include "intrins.h"
/*管脚定义*/
sbit P10 = P1^0;
sbit P11 = P1^1;
sbit P12 = P1^2;
sbit P13 = P1^3;
sbit P14 = P1^4;
sbit P15 = P1^5;
sbit P16 = P1^6;
sbit P17 = P1^7;
sbit P20 = P2^0;
sbit P21 = P2^1;
sbit P22 = P2^2;
sbit P23 = P2^3;
sbit P24 = P2^4;
sbit P25 = P2^5;
sbit P26 = P2^6;
sbit P27 = P2^7;
/*延迟时间定义*/
#define _TIME1 1000
#define _TIME2 1000
void _init(void)
{
g_u16_time1_cnt = _TIME1;//设置时间积累量
g_u8_time1_flag = 0;//初始化时间标志位
g_u16_time2_cnt = _TIME2;
g_u8_time2_flag = 0;
}
void _task1(void)
{
static unsigned char u8_step = 0;
switch(u8_step)
{
case 0:
if(_testbit_(g_u8_time1_flag))
{
g_u16_time1_cnt = _TIME1;
P10 = 1;
P11 = 1;
P12 = 1;
P13 = 1;
P14 = 1;
P15 = 1;
P16 = 1;
P17 = 1;
u8_step = 1;
}
break;
case 1:
if(_testbit_(g_u8_time1_flag))
{
g_u16_time1_cnt = _TIME1;
P10 = 1;
P11 = 1;
P12 = 1;
P13 = 1;
P14 = 1;
P15 = 1;
P16 = 1;
P17 = 1;
u8_step = 0;
}
break;
default:
break;
}
}
void _task2(void)
{
static unsigned char u8_step = 0;
switch(u8_step)
{
case 0:
if(_testbit_(g_u8_time2_flag))
{
g_u16_time2_cnt = _TIME2;
P20 = 1;
P21 = 1;
P22 = 1;
P23 = 1;
P24 = 1;
P25 = 1;
P26 = 1;
P27 = 1;
u8_step = 1;
}
break;
case 1:
if(_testbit_(g_u8_time2_flag))
{
g_u16_time2_cnt = _TIME2;
P20 = 1;
P21 = 1;
P22 = 1;
P23 = 1;
P24 = 1;
P25 = 1;
P26 = 1;
P27 = 1;
u8_step = 0;
}
break;
default:
break;
}
}
这个可以当成一个模板,当需要多任务并行时,可以使用,同样与定时器那部分的代码差别不大,但是有一点不同,就是它没有使用传统的
if(1 == g_u8_time_flag){
g_u8_time_flag = 0;}
而是使用了库函数_testbit_(),这个函数可以检测数据是否为1,同时将数据清零,这样我们定义标志位时就可以用 bit 这个数据类型,既可以节省存储空间,又可以优化代码,一举两得,不过要注意,这个库函数需要包含#include "intrins.h"这个头文件。
当我们需要多个软件定时器时,可以在定时器初始化时进行补偿,使得定时器更加精确。当我们加任务的时候要注意,一个任务不能把CPU的所有时间占满,要雨露均沾。
-
心得体会
这些需要延迟的函数我觉得都是一个套路,设定一个积累变量,一个标志位,当积累变量到达规定值,将标志位反转,然后执行程序,初始化积累变量,标志位,。
非阻塞延迟其实就是将一个循环,分成多个步骤,每次只运行一个,因为我们与机器所在的时间维度不同,所以我们会感觉任务是同时实现的,我觉得这种思想可以学习,就是无法执行多个任务是,将任务拆分为多个部分,然后运行,以达到同时执行的效果。
本文参考B站UP金善愚的视频
阻塞延时与非阻塞延时之非阻塞延时实现LED闪烁(累计主循环次数)