How to correctly use bc_scheduler?


#1

Hello,
I have read the SDK, but it is not so clear for me… I would like to clarify how to correctly use “bc_scheduler” functions.

“bc_scheduler_register” - it is function for “timing” the next run of some function. I specify script name, parameters and tick when it starts (actual tick + some time in miliseconds)

BUT… when the time comes, it runs the specified function and? It finish forever or I need to use “bc_scheduler_unregister”? I thought, that it runs only once and it is unregistered automatically when it finish.

I am sorry, but I didn’t find any documentation for this, just only SDK and I am not sure If I am using SDK correctly, but there is not so much information for me to understand it. I am using it as I have seen in the examples, but I am just guessing how it works by method of trying and errors :grinning:

I am also not sure about these topics:

  • what difference is between relative and absolute scheduling? (relative is just number of miliseconds from now? and absolute is some number of tick?)
  • why to use “bc_scheduler_plan_current_from_now” instead of calling some function directly?
  • what is difference between “bc_scheduler_plan_current_from_now” and “bc_scheduler_plan_current_relative”? (current spin is not same as current time?)
  • what difference is between “bc_tick_get” and “bc_scheduler_get_spin_tick”?
  • is it possible to schedule something for forever? Or I need to reschedule the task at the end of the task for next run?

Thanks for clarification. I hope it can help to some other beginner…


Why core module blinking SOS by morse code?
#2

Hello, here are some basics from docs. I’ll clarify more after the weekend.
https://www.bigclown.com/doc/firmware/timing-and-scheduler/

relative is number of millisoconds from now
absolute is number of milliseconds from start of the MCU

Registering the task will add it to the list of task and all tasks are called one after another. It is similar to the master-loop but it allows you dynamically register and unregister tasks at runtime.

main()
{
  init_functions();

  while(true)
  {
    task1();
    task2();
  }
}

Every task has to call “plan” function, otherwise it is planned in the time “INFINITY” which means the task is never run again, unless is replanned by bc_scheduler functions.

Martin


#3

Thank you for quick answer! It clarified some of my questions. I am sorry that I am asking for something what is in the documentation, but sometimes I have problem to find something there. Sometimes I know that I have read something there, but I am not able to find it again. I am opening lot of sections and looking for that. I can’t say that it is not in the appropriate section, but the placing is logical for me when just I am on the correct page. I think, that some search engine can be very useful.

You clarified me absolute and relative scheduling and that every task is planned for some time and when it ends it never start again if it is not replanned.

These questions are still unclear:

  • why to use “bc_scheduler_plan_current_now” instead of calling some function directly?
  • what difference is between “bc_scheduler_plan_current_from_now” and “bc_scheduler_plan_current_relative”? (current spin is not same as current time?)
  • what difference is between “bc_tick_get” and “bc_scheduler_get_spin_tick”?
  • “bc_scheduler_unregister” - it is used just before the task starts? For example when I plan some task to some time and I want to cancel it, can I use this function? But when it starts it has no effect, because it is running… but - in the climate example is it used inside the function. Why it is there?

Thank you very much!


#4

I will try to answer my questions by myself, because probably I understand more, how it works. Problem for me was, that I missed the main thing of whole scheduling. That is needed to register the task and then call it by task_id…

Q: why to use “bc_scheduler_plan_current_now” instead of calling some function directly?
A: The function “bc_scheduler_plan_current_now” schedules current task for immediate execution, not any else…

Q: “bc_scheduler_unregister” - is it used just before the task starts? For example when I plan some task to some time and I want to cancel it, can I use this function? But when it starts it has no effect, because it is running… but - in the climate example is it used inside the function. Why it is there?
A: bc_scheduler_unregister is “opposite” function to “bc_scheduler_register”. If you plan to call some task periodically you can schedule it for some time. But when you know, that you will not use it more, you can unregister it. There is some limitation of number of registered_tasks… The reason, why to unregister the taks (as described above) is, for example when you want to call some function after some time, but you don’t know if you will call it later or not. So, you can simply register the task, run it and then unregister it. Pay attention to unregister the task which is not exists or registering the task which is already registered…

Unfortunately, I am still don’t know the difference between tick and spin…

what difference is between “bc_scheduler_plan_current_from_now” and “bc_scheduler_plan_current_relative”? (current spin is not same as current time?) - what difference is between “bc_tick_get” and “bc_scheduler_get_spin_tick”?


#5

Hi,
all registered tasks are called in a round-robin style. List of registered tasks are checked in a loop - this infinite loop is called a “spin” and it is here.

Now let’s talk again about low power :slight_smile: You have two registered tasks A & B which does some computing for few milliseconds every time the scheduler calls them, every spin.

For the first time they are run let’s say in the time “R+100 ms from the start”, the R stands for the time in the moment the reset/start of the processor occured. The are run in the same “spin” and it is effcient for batteries because the MCU is waken up only once and two tasks are executed.

Every of that two tasks plans itself to wake up again after 100 ms. But wait! The first task A was doing some computing that took 50ms and second task B did also some computing which took 30ms. If you plan running the task again with the CURRENT time, then they will not run in a single “spin” but the scheduling will drift.

If we use current real time for planning, then in the next “while” loop, next spin the task will be sheduled like:

Current time R+100ms:
task A - R+100ms first run, 50ms computing, next run after 100ms = next task run scheduled in time R+250ms instead of R+200ms (100ms increments)
task B - R+150ms ms first run (50ms later, because the previous task took 50ms to run), 30ms computng, next run after 100ms = next task run scheduled in time R+280ms instead of R+200ms (100ms increments)

And the tick_spin time value is exactly used for this reason

It creates some “fixed” reference in current scheduler cycle so it does not matter if some previous task took longer time to compute. This way you can be sure that your task is scheduled every 100ms no matter what the previous task did for a tens of milliseconds.

Does this explained it for you? :slight_smile:

Here is the complete scheduler infinite loop:

void bc_scheduler_run(void)
{
    static bc_scheduler_task_id_t *task_id = &_bc_scheduler.current_task_id;

    while (true)
    {
        _bc_scheduler.tick_spin = bc_tick_get();

        for (*task_id = 0; *task_id <= _bc_scheduler.max_task_id; (*task_id)++)
        {
            if (_bc_scheduler.pool[*task_id].task != NULL)
            {
                if (_bc_scheduler.tick_spin >= _bc_scheduler.pool[*task_id].tick_execution)
                {
                    _bc_scheduler.pool[*task_id].tick_execution = BC_TICK_INFINITY;

                    _bc_scheduler.pool[*task_id].task(_bc_scheduler.pool[*task_id].param);
                }
            }
        }
        if (_bc_scheduler.sleep_bypass_semaphore == 0)
        {
            bc_system_sleep();
        }
    }
}

#6

GREAT!!! :clap::clap: Thanks a lot for this excelent explanation! I will try it. It can be very useful!