嵌入式的实时性---时间片轮询

#创作灵感:在实际的工业生产生活中,很多嵌入式编程的流程之中,几乎都是不允许CPU在当前的状态阻塞太长时间,一般不会超过1ms。以此建立的时间片轮询的算法,成为了工程师必备的本领,不同人的实现方式可能不一样,但是实现逻辑是差不多的。

下面我们以一个实际的单总线半双工通信的发送情况来进行描述,希望能帮助你更好的理解时间片。

工程要求:全程无阻塞,按键KEY1按下(无需松手)的时候,单总线COM发送完整的一帧。COM的常态为低电平,需要发送数据的时候,需要提前拉高电平至少2ms,发送数据的时候,每一位数据的电平持续至少30us.

帧格式包括:帧头(0x55)、数据包长度(1字节)、数据、校验和   四部分

解决逻辑:时间片的本质在于将所谓的时间切片,规划即将到来的每个时间片要做的事情

我的实际方案是:定时器中断切片,每一次中断都将全局变量timetap加1,以此计数时间片。

实际的时候还需要将不同任务的计数timetap区分开(改为不同的变量),原因在后续。

每个任务即将开始的时候,标志位flag=0,此时清空时间片计数timetap,而后flag=1,进入下一个阶段,等待计数完成。下一阶段对flag=1进行判断,确认是计时阶段,然后对timetap的值进行判断,是否符合指定的要求,不符合的话不进入下一个阶段,保留flag的值。

这里我们优先展示计时器0的中断函数

void interrupt ISR(void) 
{
  if (T0IF)   //中断溢出标志位
  {
    T0IF = 0;
    TMR0 = 125;    //计时器重载    每次溢出时间为16us
    KEY1_GAP++;
    KEY2_GAP++;
    Frame->timetap++;     //4个计时单位
    if (KEY1_GAP >= 200)
    {
      KEY1_GAP = 0;
      GAP_400ms++;       //400ms 的计时单位
    }
    if (KEY2_GAP >= 200)       //回收机制   也可以放在主循环里,中断里也可
      KEY2_GAP = 0;

    if (GAP_400ms >= 10)      //注意回收时间不要比任务需要的定时还短,那任务的定时就出不来了
      GAP_400ms = 0;
    if (Frame->timetap >= 200)
      Frame->timetap = 0;
  }
}

1、按键处理

按键的状态可以分为:初次按下、前沿抖动、彻底按下、松手、后沿抖动、彻底松手。

实际抖动可以通过电容的设置来降低抖动,电路图可以是如下情况:

嵌入式的实时性---时间片轮询_第1张图片

有了电容并不是不抖动,只是按键的抖动时间非常短,可以达到几us级别。

   #define Wait 0                          //两段宏定义 用以促进代码理解
   #define Tap_to_Toggle 1
   

   if (KEY1 == 0 && KEY1CheckFlag == Wait)    //第一次KEY1按下,需要判断当前阶段
    {
            KEY1_GAP = 0;              //两个不同的计时单元 一个是400ms的
            GAP_400ms = 0;
            KEY1CheckFlag = Tap_to_Toggle;
    }
    
    if (KEY1CheckFlag == Tap_to_Toggle)
    {
        if (KEY1_GAP >= 14 && KEY1_GAP <= 15)  //大概的计时时间是14到15个GAP之间
        {
            if (KEY1 == 0)          //如果这个时候按键还是按下的状态 那么就是真的彻底按下
            {
                mode = Single;
                Data_Build(mode);    //建立数据包 完善帧的内容
                Frame->state = send;  //Frame 结构体指针 调整状态state为发送状态 
                KEY1_GAP = KEY1_GAP + 2;  //小技巧 因为在14-15之间可能会出现多次KEY1=0的
            }                            //情况,而我们实际只需要判断一次即可,这样做可以跳
        }                                //出当前GAP的判断区间
        if (GAP_400ms >= 2) //          //标志位的返还周期,周期太短的话,出现第二次按键按下   
        {
            KEY1CheckFlag = Wait;
        }
    }

不堵塞的理念就是当按键开始计时的时候,程序不会停留在此等待计时的完成,而是不停的通过while访问来确认计时是否完成,所以我们这里建立了KEY1CheckFlag,以便再次来访问的时候,不知道是处于哪个阶段,可能会导致计时单元KEY1_GAP又重新清0;

2、通信处理

跟前面差不多,先拉高电平,而后依次发送数据,这里我们也是分阶段的。

先制作一帧的结构体,主要包括帧的各种发送信息和发送数据。


typedef struct
{
  enum
  {
  wait,
  send,
  receive,
  }state;
  unsigned char sendflag;    //发送阶段
  unsigned char  timetap;      //用于计时的时间单元
  unsigned char  leadcode;    //前导码
  unsigned char  datalength;    //数据长度
  unsigned char data;         //数据
  unsigned char checksum;        //校验和
} Datatype;

Datatype DataBuff;
Datatype *Frame= &DataBuff;

分阶段发送

void Communte_Send()
{
  if (Frame->sendflag == waitfor)
  {

    Frame->timetap = 0;
    Frame->sendflag = HighLevel;
  }
  if (Frame->sendflag == HighLevel)
  {
    COM = 1;                              // 拉高电平
    if (Frame->timetap >= highlevellimit) // 等待拉高时间2ms
    {
      Frame->timetap = 0;
      Frame->sendflag = LeadCode; //拉高时间结束 进入发送前导码0x55
    }
  }
  // 拉高电平之后,准备开始发送数据
  if (Frame->sendflag == LeadCode) // 发送帧头
  {
    Data_Send(SendData_Head, 1, LengthCode);
  }
  if (Frame->sendflag == LengthCode) // 发送数据长度
  {
    Data_Send(DataLength, 1, DataCode);
  }
  if (Frame->sendflag == DataCode) // 发送数据包
  {
    Data_Send(Frame->data, 1, CheckCode);
  }
  if (Frame->sendflag == CheckCode) // 发送校验码
  {
    Data_Send(Frame->checksum, 1, LowLevel);
  }
  if (Frame->sendflag == LowLevel)   
  {
    Frame->sendflag = waitfor; // 等待下一次发送
    Frame->state = wait;       // 状态调整为等待
    COM = 0;                   // 拉低电平  结束发送
   //Data_Clear();              // 清空所有发送内容
  }
}

这里还有一个函数的设计十分关键,便是这个按位发送的函数Data_Send()

void Data_Send(unsigned char data, unsigned char leng, unsigned char nexttime)
{
  static unsigned char n = 11;    //静态局部变量  放置再次进入被初始化
  if (n == 11)            //这里实际发送的数据只有8位 所以没有用到参数leng,待后续使用
  {
    Frame->timetap = 0;       //开始计时
    n=0;
  }
  if (n < 8)             
  {
    COM = (data >> n) & 0x01;
  }
  if (Frame->timetap >= 2)            //每次计时完毕才给n++
  {
    n++;
    Frame->timetap = 0;
  }
  if (n == 8)      //发送完8位数据
  {
    Frame->sendflag = nexttime;
    n = 11;
  }
}

设计此类函数的时候要十分注意此函数会跳出去执行其他事件,很容易因为某个标志位的修改导致程序不再进入这个判断过程中。

你可能感兴趣的:(单片机,嵌入式硬件)