Principle of non blocking delay

In the first part, I wrote a program that it's best to give up the CPU when the delay occurs, but if it's just to give up, I don't know when to come back. If the delay is not enough when I come back, it's unreasonable As shown in the figure below

First, program A yield() gives up the CPU and hopes to wait for 10ms before executing the next step. As A result, the section of program B is relatively short and yield() returns to program A soon. This situation does not meet the expectation of program A

So how to achieve non - blocking delay? We can not only give up the CPU, but also come back at that time
We need a timer to trigger an interrupt every period of time. In the interrupt, a count value increases automatically. For example, an interrupt is generated every 5ms. The count value tick + +. Through the value of tick, we can know how long the time has passed systick is often used as this timer in cortex-M

When program A finishes executing the first segment, it needs to delay for 10ms and give up the CPU (at this time, tick is 2). It's program B's turn to execute A segment of the program, and B also gives up the CPU after execution. But at this time, tick is 3. If A has not delayed enough and no other program is executed, the CPU will wait. When the tick value increases to 4, it means that A must have delayed enough and return to A again, which is the same as that of B
This method can achieve non blocking delay, but its disadvantages are also obvious. The accuracy is not high (when A is returned in the figure, the delay has actually exceeded 10ms). It is related to the interrupt cycle of systick. If higher accuracy is required, such as one interrupt in 1ms, the error can be reduced, but it will also bring more interrupt overhead

###Timer in protothread

struct timer { 
  int start;          //Record the tick value at startup
  int interval;       //Number of tick s to be timed
};
// Timer timeout judgment
static int timer_expired(struct timer *t)
{ 
  // clock_time() will get the current tick value. If the current value - start value exceeds the number of delayed ticks, it is a timeout
  return (int)(clock_time() - t->start) >= (int)t->interval; // Thinking: what about spillovers?
}

clock_ If the time function needs to return the current tick value, it is necessary to enable a timer in the corresponding CPU and self increment a global tick variable, such as

volatile int global_tick;

void systick_handler(void)
{
  global_tick++;
}
int clock_time(void)
{ 
  return (int)global_tick; 
}
void timer_set(struct timer *t, int interval)
{ 
  t->interval = interval;       // Store the number of tick s that need to be delayed
  t->start = clock_time();     //Gets the current tick value
}

Set timer_set and PT_WAIT_UNTIL can be used together to complete the function of delay
timer_set records the current tick value, PT_WAIT_UNTIL will detect timer_ Whether the expired timeout occurs. After the timeout, continue to execute the following program, so as to realize non blocking delay

struct timer  input_timer;
PT_THREAD(input_thread(struct pt *pt))
{
  PT_BEGIN(pt);

 
  timer_set(&input_timer, 1000);  // Delay 1000 tick s
  PT_WAIT_UNTIL(pt, timer_expired(&input_timer));


  timer_set(&input_timer, 100);  // Delay 100 tick s
  PT_WAIT_UNTIL(pt, timer_expired(&input_timer));
  
  timer_set(&input_timer, 300);
  PT_WAIT_UNTIL(pt, timer_expired(&input_timer));


  timer_set(&input_timer, 2000);
  PT_WAIT_UNTIL(pt, timer_expired(&input_timer));
   
  PT_END(pt);
}

Of course, this method also defines struct timer input_timer; And timer_set and PT_WAIT_UNTIL or something, without delay_ms is so concise. I'll try to make it elegant later. This is just to illustrate the principle

Example engineering code
https://gitee.com/kalimdorsummer/c_language_program_template

Tags: Programming

Posted by bedrosamo on Fri, 13 May 2022 17:46:23 +0300