Might sound dumb but using FreeRTOS APIs before the scheduler has kicked in might lead to undefined issues?
For instance here, Write happens before the scheduler kicks in which instead of busy-wait for the I2C TX xfer to finish relies on a signal from ISR via xSemaphoreGiveFromISR but when I run it, I continue to see consecutive writes happen quite frequently OR in some instances an ISR never gets to signal, however when I run in debug mode, I see things work fine.
SemaphoreHandle_t writeSemaphore;
Sensor::Sensor()
{
writeSemaphore = xSemaphoreCreateBinary();
}
void Sensor::Write(uint8_t reg, uint8_t size)
{
// I2C write call...
// block until signalled from ISR
xSemaphoreTake(writeSemaphore, portMAX_DELAY);
}
void Sensor::Start()
{
// configure I2C...
if (!Write(0x5, 1))
{
return 0;
}
// start a FreeRTOS task for RX transfer
if (xTaskCreate(Sensor::Process, "Process", 100, this, 0, &mTaskHandle) != pdPASS)
{
APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
}
}
void Sensor::Process(void* instance)
{
auto* app = static_cast<Sensor*>(instance);
app->Run();
}
void Sensor::Run()
{
while(true)
{
Read();
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // signalled by ISR
vTaskDelay(pdMS_TO_TICKS(DELAY_MS_PER_READ));
}
}
int main()
{
sensor.Start();
vTaskStartScheduler();
for (;;)
{
APP_ERROR_HANDLER(NRF_ERROR_FORBIDDEN);
}
}
Before you start the scheduler, you can’t make a call that would block. If you had configASSERT defined, you would (I think) see assertions about that happening.
Also, interrupts will be disabled, so ISRs will not get called until the scheduler is started.
FreeRTOS doesn’t switch to an infinite loop just because the scheduler hasn’t been started. It calls configASSERT() and if that isn’t defined just returns immediately.
Every call to a FreeRTOS API will disable the interrupts until the scheduler is started. This is done as a safety measure, as interrupts can’t safely make calls to reschedule tasks, and may well not have there resources fully set up.
Things like what you are describing should be done AFTER starting the scheduler, not before.
How do I get this to work after the scheduler has stared? Start() is run only once and so once a Write; it’s just Read that’s run in a task which is triggered after the scheduler kicks in
Just to confirm again, as long as I don’t use any FreeRTOS primitives in the Start/Write call that would disable the interrupt since the scheduler hasn’t yet kicked in, which means busy-wait approach via while(flag_set_by_interrupt) would work fine.
Moving at the start of Run() before while is justified where task notification can be used which would cause Sensor::Run to remain blocked until signalled by the ISR.
Can you elaborate a bit on it? There’s no timer involved in here
Before starting the Scheduler, you can use bare-metal-like code (with the understanding that you will need to re-enable interrupts to use them). IF the same ISRs will be used after you start the scheduler, you will need to be careful to have them change their behavior when the scheduler starts.
Even if you current code isn’t using Timers, if you enable FreeRTOS to support timers, and enable the Timer Startup Callback Hook option, you can use that callback to do early initialization to avoid making a new task of your own.
Is there ever a reason to use RTOS primitives before the scheduler generally given extra handling of interrupts?
Also on that note, don’t the interrupts of logically higher priority than configMAX_SYSCALL_INTERRUPT_PRIORITY still get to preempt and aren’t masked out? Is it valid to say calling FreeRTOS APis before the schedulee has started would completely disable all the interrupts even? Just trying to distinguish it from after the scheduler has started.
Any brief example on the timer hook? Still trying to wrap my head around it
Are there ever reasons to use FreeRTOS primatives before starting the scheduler? Yes. You of course prefer to create most of your primatives before starting the scheduler. You also may want to preset the state of Semaphores, or preload Queues with data (with 0 wait times).
As to high priority interrupts bein masked, it depends on the port. For most, the “Disable interrupt” call only disables those that are allowed to use the API.
The timer functionality uses a task to process the timer API operations. As a convience, that task has. A callback function that it can call at startup. Since the Timer task is often given a High task priority, this callback tends to be run very quickly after starting the scheduler. I tend not to use it, as I find putting that code at the beginning of an appropriate task to work better.
Yes, creation of objects/primitives but what about actually using APIs?
I’m still unclear about how interrupt masking would take place in a scenario when FreeRTOS is called before the scheduler runs.
Also, not sure if that tells something but in the logic analyzer, I see two I2C writes instead of just one. Does it have to do with having Write() before the scheduler starts? I only see a single write operation when I move Write() at the start of Run
Well, “Create” functions are still part of the API. As I have mentioned, trying to “block” a task before the Scheduler has started just doesn’t work, which YES, can cause routines that expect it them to actually block to malfunction. You really need to give a proper definition for configASSERT so it will catch problems like that.
The issue is that ANY FreeRTOS call, (at least any that use a critical section) will disable the interrupt if called before the Scheduler starts. (The kernel considers pre-scheduler calls to be done inside an already existing nested critical section)
Note, Run() is done as part of a Task AFTER the FreeRTOS scheduler has started, which was one of the options I suggested to you.
Can you point me to the code? Let’s say I call ulTaskNotifyTake before the scheduler starts, which calls taskENTER_CRITICAL which disables the interrupts of priority lower than configMAX_SYSCALL_INTERRUPT_PRIORITY, but that means interrupts with a higher priority can still happen, no?
Basically I am trying to understand how does calling a FreeRTOS API before and after the scheduler differ.
The following is what I tried (included Write() inside the task itself) and seems to work fine. Any further suggestions?
void ISR()
{
// ensures the expected HW event is received
// vTaskNotifyGiveFromISR(sensor->taskHandle, &xHigherPriorityTaskWoken);
}
void Sensor::Run()
{
Write(0x5, 1);
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // block till signalled by ISR
while(true)
{
Read();
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // signalled by ISR
vTaskDelay(pdMS_TO_TICKS(DELAY_MS_PER_READ));
}
}
The key point to the difference is that the counter for the depth of Critical Section nesting is set by static initialization to a positive value so that no call to portEXIT_CRITICAL will re-enable the interrupts before the scheduler is started. This value is then reset to 0 as the scheduler is started and then the interrupts are enabled just before (or as) starting the first task;
If a FreeRTOS API function is called before the scheduler has been started then interrupts will deliberately be left disabled, and not re-enable again until the first task starts to execute
so the interrupts disabled by the FreeRTOS API before the scheduler is run, and the user would be responsible for enabling the disabled interrupt back? Also what crashes can we expect if the interrupt is enabled before the scheduler has begun anyway?
Do not alter the microcontroller interrupt enable bits or priority flags using any method other than calls to taskENTER_CRITICAL() and taskEXIT_CRITICAL().
Doesn’t taskENTER_CRITICAL merely disable the interrupts of priority < configMAX_SYSCALL_INTERRUPT_PRIORITY? Does it modify the priority as well?
The “crash” would happen if any of the ISRs that were enabled pre-scheduler did an operation to try to invoke the scheduler. Since most ISRs are apt to do things that end with a signaling to some task, and then a check if a task was woken and we need a
rescheduling, these will crash the system unless the ISRs also check to see if the scheduler hasn’t started yet.
The critical section routine disable the interrupts because a large source of questions turned out to be people doing things in ISRs that shouldn’t be done before the scheduler starts because they enabled interrupts early.
The simple answer is you are trying to do things not the normal way, and really need to fully understand things or fact the problems. It doesn’t appear you understand the reasons things are done the way they are, and are likely to cause yourself to repeat the problems that were the reason they were made that way.