Implementing multitasking on functional queues (no RTOS)

A little about RTOS

When it is necessary to perform several actions (processes / tasks) at the same time on the microcontroller, we usually think about using RTOS (Real Time Operating System). RTOS usually takes up a few extra kilobytes of memory. At the same time, RTOS applications can add more complexity, including when debugging.





Most RTOSs use a proactive scheduling algorithm. By using an interrupt, the currently running process is suspended and the task scheduler is called to determine which process should run next. Processes receive some amount of CPU time in small chunks. The total amount of time a process takes depends on its priority. All processes are usually endless cycles.





There is an interruption of one job, saving and context switching. Job switching operations require several additional operations from the operating system.





Is there a way to get by without RTOS while still being able to multitask?

Is it possible to perform dozens of different tasks simultaneously on simple microcontrollers without resorting to RTOS? Today we will consider an approach that will allow you to perform several tasks at the same time, while additionally using a very small amount of microcontroller memory. An approach (I will call it BezRTOS , I wanted to call it NoRTOS, but this name is already used by Texas Instruments for another) that will allow you to keep interrupt routines fast and at the same time control multitasking in a simple and transparent way.





A few key points that suggest using this approach:





  • . . , , 1 , , . , , , , .





  • , , .. , , , . 





  • RTOS , ,   (, )





  •   RTOS 





  •    





  •   RTOS





  1. : (   ), ( ) (, - ).





  2. ( /).





  3. "" / "" .





  4. .





, ( FIFO):





, :









  • ,













  • , , polling,  SPI, I2C, UART… (, , ... )













  • (, )









  • , ,  





  • -





  • WiFi  









C .





#define Q_SIZE_FAST 16

volatile int F1_last; 	//     
int F1_first; 		//    

void (*F1_Queue[Q_SIZE_FAST])(); //   ()

void DummyF(void){;}

void F1_QueueIni(void){ //  
  F1_last = 0;
  F1_first = 0;
}

int F1_push(void (*pointerQ)(void)){ //    
  if ((F1_last+1)%Q_SIZE_FAST == F1_first)return 1;
  F1_Queue[F1_last++] = pointerQ;
  F1_last %= Q_SIZE_FAST;
  return 0;
}

void (*F1_pull(void))(void){ //     -
                             // 
  void (*pullVar)(void);
  if (F1_last == F1_first)return DummyF;
  pullVar = F1_Queue[F1_first++];
  F1_first %= Q_SIZE_FAST;
  return pullVar;
}
      
      



, , , -, . , , . , . 





, . :





void DelayOnF1(uint64_t delay){
 uint64_t targetTime = delay + millis();
  while(millis() < targetTime) F1_pull()();
}
      
      



 millis()  (volatile uint64_t, 1 ).





, . . 









main.c:





F1_QueueIni();
F2_QueueIni();
F3_QueueIni();
F4_QueueIni();
while(1){
  F1_pull()();
  F2_pull()();
  F3_pull()();
  F4_pull()();
}
      
      







 F1_push(LED_On_Off);
 F1_push(ReadChannelsVoltage);
      
      







F2_push(CalculateTemperatureMiddleValue);
F2_push(CalculateHumidityMiddleValue);
      
      



:





F3_push(Display_ScreenInfo);
F3_push(ResetSensor);
      
      







F4_push(ScanKeyBoard);
F4_push(ReadTouchScr);
      
      



(not nested, tail chaining interrupts) - .





( ) . 





:





struct fParams {  //    
  int IntVar;
  float FloatVar;
};

volatile int FP_last;  //    
int FP_first;  //    
void (*FP_Queue[Q_SIZE_FAST])(struct fParams *); //  
                                                 //   

struct fParams PARAMS_array[Q_SIZE_FAST];        //   

void FP_QueueIni(void){ //  
  FP_last = 0;
  FP_first = 0;
}

int FP_push(void (*pointerQ)(struct fParams *), struct fParams * parameterQ){ //    
  if ((FP_last+1)%Q_SIZE_FAST == FP_first)return 1;
  FP_Queue[FP_last] = pointerQ;
  PARAMS_array[FP_last++] = *parameterQ;
  FP_last %= Q_SIZE_FAST;
  return 0;
}

void FP_pull(void){ //      ,    
  void (*pullVar)(struct fParams *);
  struct fParams * Params;
  if (FP_last == FP_first)return;
  Params = &PARAMS_array[FP_first];
  pullVar = FP_Queue[FP_first++];
  FP_first %= Q_SIZE_FAST;
  pullVar(Params);
}
      
      



:





main.c:





FPQueueIni();  

while(1){  
 FPpull(); 
}  
      
      



:





FP_push(ApmControl,&(struct fParams){1,7.18}); //  AmpContol                                                          //     1  7.18
      
      



, β€œβ€ :





void SIM800_IniCMD(void) {
 __HAL UART_ENABLE_IT(shuart2, UART_IT_IDLE); // IDLE
 __HAL UART_ENABLE_IT(shuart2, UART_IT_RXNE); // IDLE
 ResParse.bytes = 3; //  .  
 Delay_ms_OnMediumQ(32); //   ""   32 
 SIM800_AddCMD((char *) GSM_ATcmd, sizeof (GSM_ATcmd)); //   SIM800  DMA 
 Delay_ms_OnMediumQ(4000); //   ""   4c
 SIM800_AddCMD((char *) GSM_ATcmd_Disable_Echo, sizeof (GSM_ATcmd_Disable_Echo));
...
      
      



, ! , , .





BezRTOS :





void blink(int count) { // LED
	while (count--) {
		HAL _GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET); //   .1
		HAL Delay(100);//  100 .
		HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);//   .0
	}
}
      
      



:





void blink(int count) { // LED
	while (count--) {
		HAL _GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET); //   .1
		Delay_ms_OnMediumQ(100); 	//  100 .   
    													// "" 
		HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); //   .0
	}
}
      
      



:





:





while(SignalNotStable);
      
      



:





while(SignalNotStable){
	S1_pull()(); //   S1  
}
      
      



C++

++ , .





typedef void(*fP)(void);

class fQ {
private:
    int first;
    int last;
    fP * fQueue;
    int lengthQ;
public:
    fQ(int sizeQ);
    ~fQ();
    int push(fP);    //    
    int pull(void);  //      
};

fQ::fQ(int sizeQ){ //  
  fQueue = new fP[sizeQ];
  last = 0;
  first = 0;
  lengthQ = sizeQ;
}

fQ::~fQ(){ //  
  delete [] fQueue;
}

int fQ::push(fP pointerF){ //    
  if ((last+1)%lengthQ == first){
            return 1;
  }
  fQueue[last++] = pointerF;
  last = last%lengthQ;
  return 0;
}

int fQ::pull(void){ //    
  if (last != first){
  fQueue[first++]();
  first = first%lengthQ;
  return 0;
  }
  else{
   return 1;
  }
}
      
      



:





main.cpp:





fQ F1(16); //   
fQ F2(12);
fQ A1(8);

int main(){ 
  for(;;){ 
   A1.pull(); 
   usleep(10000); //  10 
         } 
 return 0; 
 }
      
      



- β„–1: 





if(pin10.in == 1) F1.push(SwithOnRelay);
else F1.push(SwithOffRelay);
      
      



- β„–2:





F2.push(ToggleLED);
      
      



- - 100 :





A1.push(UpdateUI);
      
      



UpdateUI:





void UpdateUI(void){
  pin_CS_DISP.out = 0;
  Delay_ms_OnF1(2); //  2 .   -     F1
  DispLCD(Voltage);
  Delay_ms_OnF2(2); //  2 .   -     F2
  pin_CS_DISP.out = 1;
}
      
      



  • , β€œβ€





  • ,





  • , β€œβ€





  • -









  • ( )





  • , -





  • , , ,





  • β€œβ€





  • , , , . , β€œβ€ β€œβ€ / β€œβ€,  β€œβ€ β€œβ€





:





, : β€œβ€, β€œβ€™, β€œβ€, β€œβ€, β€œ ”. .





Make queues long enough so that jobs are not lost.





For nested interrupts, drop jobs from such interrupt handlers into different queues.





BezRTOS will work where a full RTOS is difficult or even impossible to integrate (eg ATTINYxx, PIC16FXXX).





Having applied this approach successfully for several years, I could not help but write an article about it.





BezRTOS is not an RTOS, it is a method of providing the effect of multitasking and efficient utilization of waiting times, allowing you to keep interrupt handlers fast (there will be only short instructions that require immediate execution, the main "task" will wait a little in the queue ...).








All Articles