This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

app_scheduler and app_timer

I've searched and the majority of answers seem to be years old. 

I have an existing app_timer event handler that works perfectly, blinking LEDs. 

I will soon have another that needs to do some I/O and thus needs not to be running out of an ISR. 

I enabled  APP_SCHEDULER_ENABLED and APP_TIMER_CONFIG_USE_SCHEDULER

I put the following code into my "init"

#define SCHED_QUEUE_SIZE                    16        
#define SCHED_MAX_EVENT_DATA_SIZE           192       

   APP_SCHED_INIT(APP_TIMER_SCHED_EVENT_DATA_SIZE, SCHED_QUEUE_SIZE);

    ret = app_timer_init();
    APP_ERROR_CHECK(ret);

and in my "main loop" I put this to make sure that things got called every 50 milliseconds or so.

   do
           {
             /*
             ** read from the input stream.
             */
             serial_ret_code = nrf_serial_read(&serial0_uart, &c, 1, NULL, 50);
             if (NRF_SUCCESS != serial_ret_code)
             {
                app_sched_execute();

                continue;
             }
             else
             {
                app_sched_execute();

                break;
             }

           } while (true);


A couple of things happen.

First, the event handler never gets called (again, this works fine without the app_scheduler) and second I get this in my debugger:


Stopped by vector catch

In the Call Stack window it shows me at address 0x8C4 - somewhere in the softdevice


Parents
  • Hi,

    I'm not sure if I understand all this, but to me it sounds like you are overthinking it. It should be enough to simply call app_sched_event_put() from within your app_timer event handler and then app_sched_execute() from the main loop each time the application wakes up because of an event (scheduler documentation). So in SDK version < 14.2 you could do something like this:

    for (;;)
        {
            if (NRF_LOG_PROCESS() == false)
            {
                power_manage();
                app_sched_execute();
            }
        }

    and in the newly released SDK 15 like this:

        for (;;)
        {
            idle_state_handle();
            app_sched_execute();
        }

  • Hej Martin!

    It's possible I am overthinking things - I'm pretty new to the Nordic environment (a month or so) and am coming from a world where there's an operating system to hide some of the warts.

    My "loop" that exposed my issue was something like this:




    while (true)
    {
        rc = nrf_serial_read ( ..... );  // with a timeout

        app_sched_execute();

        ....
    }

    In a situation like that the sleep function from the Nordic example

       __WFE();
       __SEV();
       __WFE();

    would put the processor to sleep and the serial read function would never return. I replaced that with the following:

       /*
       ** wait for an interrupt of any sort, and
       ** run the app_scheduler
       */
       __WFI();
       app_sched_execute();

    And all was good in the world. And as a bonus the performance of the serial port doing file transfers improved by a factor of two...

    This will probably be fine for this application - it will either be "doing something" or waiting for a command on the serial port. 99% of the time the latter. There are enough, but not too many, interrupts in the background to keep the scheduler running at an acceptable rate.

    BTW it's not actually necessary to explicitly call app_sched_event_put() when using app_timer. I have a "led blinking" callback that's been running for a day with no issue. I suspect the app_timer interface is doing the app_sched_event_put() as it runs.

    What I'd *really* like is some way to have app_sched_execute() run periodically without having to explicitly invoke it as part of a loop. But perhaps sometimes in this world a person doesn't get what they want. :)

  • I might be missing something here, but that first while(true) loop doesn't make sense to me. The point of the scheduler is to schedule operations to be run in the main context. So it would make more sense to do something like: 

    some_isr()
    {
    	... 
    	app_sched_event_put(..., ..., read_serial)
    }
    
    main_while_loop()
    {
    	app_sched_execute();
    	__WFE();
    	__SEV();
    	__WFE();
    }

     

    Furthermore, if you run the app_timer library using the scheduler, then the app_timer's event handlers are already scheduled and executed from the main context. Hence, there is no need to schedule even more things from within these timer handlers. 

     

    What I'd *really* like is some way to have app_sched_execute() run periodically...
    If you use 

    main_while_loop()
    {
    	app_sched_execute();
    	whatever_go_to_sleep_method_you_want();
    }

    then app_sched_execute() will be called whenever it is necessary. No more, no less. It gets called whenever any interrupt occurs, but if the scheduler's queue is empty it returns immediately. If the system is sleeping while waiting for some serial input for example, it will wake up when the serial data is ready, then you queue whatever code you want to run in the main context from within the serial ISR, then the system exits the ISR, then the code returns to main context and executes all the queued code with app_sched_execute(). There is no need to call it periodically to make sure the queue is empty since all the scheduled code is executed immediately anyway. 

Reply
  • I might be missing something here, but that first while(true) loop doesn't make sense to me. The point of the scheduler is to schedule operations to be run in the main context. So it would make more sense to do something like: 

    some_isr()
    {
    	... 
    	app_sched_event_put(..., ..., read_serial)
    }
    
    main_while_loop()
    {
    	app_sched_execute();
    	__WFE();
    	__SEV();
    	__WFE();
    }

     

    Furthermore, if you run the app_timer library using the scheduler, then the app_timer's event handlers are already scheduled and executed from the main context. Hence, there is no need to schedule even more things from within these timer handlers. 

     

    What I'd *really* like is some way to have app_sched_execute() run periodically...
    If you use 

    main_while_loop()
    {
    	app_sched_execute();
    	whatever_go_to_sleep_method_you_want();
    }

    then app_sched_execute() will be called whenever it is necessary. No more, no less. It gets called whenever any interrupt occurs, but if the scheduler's queue is empty it returns immediately. If the system is sleeping while waiting for some serial input for example, it will wake up when the serial data is ready, then you queue whatever code you want to run in the main context from within the serial ISR, then the system exits the ISR, then the code returns to main context and executes all the queued code with app_sched_execute(). There is no need to call it periodically to make sure the queue is empty since all the scheduled code is executed immediately anyway. 

Children
  • I think we're good. I understand exactly what you're saying - The original problem I had was that I didn't realize that I had to put the app_sched_execute() somewhere in userland to schedule things that were put onto the queue in ISRland. I also didn't notice (because I'm a newby with Nordic) that the sleep function for the uart code I borrowed from an example put the processor to sleep. 

    Since my main loop *is* waiting for characters, putting the app_sched_execute() in the uart sleep function, and making that sleep function sleep using __WFI() is fine. It wakes up more often than truly necessary, but not enough to matter. For a different application I'll perhaps do something more like you're suggesting. I know this sort of technique uses more power but my device will always be plugged into the wall and power use is irrelevant. 

    Thanks for the discussion. I'll "verify as answer" and you can take this off your "to do" list. 

Related