前段时间看到了分层思想、时间片轮转和状态机的相关文章正好近期手上有一些小项目,于是就进行了一些尝试。相关的概念就不在细说了,很多嵌入式的公众号文章都有介绍过,下面只叙述下我开发的经历帮助后来者少走弯路也是我个人的一个记录。
我在看到讲述相关思想的文章后在脑子里想着架构出一个相对比较完善的系统。
分层思想不用多说根据相应的软硬件结构进行分层即可,其中的好处会在代码复用时体现出来。
时间片轮转思想在我个人实践的过程中走了很多弯路,本人使用的硬件为清翔电子的最小系统板,在最开始进行开发时使用定时器的方式对固定延时变量(REF20MS等)进行相应的环形累加(if(REF20MS==0)REF20MS=NUM20MS)(NUM20MS为宏定义的定时器计数记录到对应时间所需要的值)。然后当相应的变量计数完成后对相应的标志位进行计数。
在我最初的构想中,希望能够完成一个多个需要相同的延时时间的任务对同一个标记位的检测即可完成延时的效果,但是在实际实现过程中遇到了很多问题。
1、如何判定任务1完成了20ms延时(仅作为举例),判定相应的延时时间的话需要对REF20MS的值进行记录当该变量再一次到达记录值时则说明完成20ms延时,这有一个问题就是定时器中断的时间一定时比单片机主函数中运行的时间要慢的于是当该变量进行记录时定时器未能及时对延时记录变量进行增减从而导致没有延时的现象,当然这种问题可以通过再增加一个记录次数的标记位来解决但是还有更好的解决方式我下面会讲。
2、如何多个任务同时标记并使用同一个延时变量,这个问题使用变量记录加标记位的方式可以去解决但是结构复杂较为繁琐。
在遇到上述问题后我改变了想法,使用一个u32位(u64更好但是51不支持u64)的变量(u32 TimeRef)作为统一的一个时间片(定时器125us中断一次,足够使用一周左右),对每个任务定义一个_DELAY变量(如按键:u32 KEY_DELAY) 在每次需要延时时KEY_DELAY=TimeRef+NUM_20MS;(NUM20MS为宏定义的定时器计数记录到对应时间所需要的值),在使用状态机的思想进行状态转移即可。能够较为简单的时间一些任务轮转的功能,整体代码理解起来也比较简单。整体单片机代码如下:
#include<reg52.h>
#include<intrins.h>
//尝试分层思想与时间片轮转
#define u8 unsigned char
#define u16 unsigned int
#define u32 unsigned long int
//按键引脚映射
sbit KEY1=P3^5;
sbit KEY2=P3^4;
sbit KEY3=P3^3;
sbit KEY4=P3^2;
sbit LED1=P1^0;
u8 KEY_DAT=0;//按键寄存器按键为1存储当前被按下的按键键值,0为无按键
u8 SEG7[10]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};
//时间片为125us
#define NUM_1MS 8
#define NUM_2MS 16
#define NUM_5MS 40
#define NUM_20MS 160
#define NUM_100MS 800
#define NUM_200MS 1600
#define NUM_500MS 4000
#define NUM_1000MS 8000
//时间片轮转变量
u32 TimeRef=0;
//任务延时变量
//按键任务
u32 KEY_DELAY =0;
u8 KEY_STATE =0;
u8 KEY_DATx =0;
//显示任务
u32 SEG_DELAY =0;
u8 SEG_STATE =0;//0位标记是否第一次进入
u8 SEG_NUM[4] ={0,0,0,0};
u8 SEG_I =0;
/函数声明/
//读按键键值
u8 ReadKeyDat(void);
//读按键寄存器
u8 ReadKeyReg(void);
//中断初始化函数
void IsrInit(void);
//数码管显示控制函数
void SegDisplay(u16 num);
void main(void)
{
u16 num=0;
u8 key=0;
IsrInit();
while(1)
{
SegDisplay(num);
key = ReadKeyDat();
if(key==1)
num++;
if(key==2)
num--;
// if(key==3)
// LED1=0;
// if(key==4)
// LED1=1 ;
}
}
//数码管显示控制函数
void SegDisplay(u16 num)
{
u8 i=0;
if(SEG_STATE==0)//分割字符状态
{
SEG_I=0;
for(i=0;i<4;i++)
{
SEG_NUM[SEG_I++]=num%10;
num=num/10;
}
SEG_STATE=1;
}
if(SEG_STATE==1)//显示状态
{
P0=SEG7[SEG_NUM[4-SEG_I]];//[];
P2=~(0x01<<(SEG_I-1));
SEG_I--;
SEG_DELAY=TimeRef+NUM_5MS;
SEG_STATE=2;
}
if(SEG_STATE==2)//延时等待状态
{
if(TimeRef >= SEG_DELAY)
{
//判断是否为最后一次延时
if(SEG_I==0)
SEG_STATE=0;
else
SEG_STATE=1;
}
}
}
//读按键键值
u8 ReadKeyDat(void)
{
ReadKeyReg();
if(KEY_STATE == 0)//第一次读取状态
{
if(KEY_DAT!=0)//读取到按键
{
KEY_DATx=KEY_DAT;
KEY_DELAY=TimeRef+NUM_100MS;
KEY_STATE=1;
}
}
if(KEY_STATE == 1)//延时状态
{
if(TimeRef >= KEY_DELAY)
{
KEY_STATE=2;
}
}
if(KEY_STATE == 2)//第二次读取状态
{
if(KEY_DATx == KEY_DAT)//读取到按键
{
KEY_STATE=0;
return KEY_DATx;
}
KEY_STATE=0;
}
return 0;
}
//读按键寄存器
u8 ReadKeyReg(void)
{
if(KEY1==0)
{
KEY_DAT=1;
return 1;
}
if(KEY2==0)
{
KEY_DAT=2;
return 1;
}
if(KEY3==0)
{
KEY_DAT=3;
return 1;
}
if(KEY4==0)
{
KEY_DAT=4;
return 1;
}
KEY_DAT=0;
return 0;
}
//中断初始化函数
void IsrInit(void)
{
EA=1;
//使能定时器中断并开启定时器
// ET0=1;
// TR0=1;
//ET1=1;
//使能两个外部中断并将其设定为跳沿触发
// EX0=1;
// IT0=1;
// EX1=1;
// IT1=1;
//定时器0,方式2,计数器
TMOD|=0x02;
TH0=0x8d;//125us 中断一次
TL0=0x8d;
ET0=1;
TR0=1;
// //使用定时器1,方式2,计数器
// TMOD|=0x20;
// TH1=0x8d;//125us 中断一次
// TL1=0x8d;
}
//定时器0,提供基础时钟125us中断1次
//统一时间完成标志位可能没有意义//
int Tim0Isr() interrupt 1 using 1
{
TimeRef++;
// P0=SEG7[TimeRef/100%10];//[];
// P2=0xfe;
}
KEIL工程下载:http://www.51hei.com/bbs/dpj-214415-1.html