Blocking code advice for a Newbie

Hello,
Please forgive questions from a complete beginner. I am hoping to use FreeRTOS for controlling my model railway using Arduino. I have implemented a sketch already which starts a train, follows it around a loop of track using Light dependent resistors under the track and changes points, etc. My single threaded code essentially includes waits until a train reaches the next LDR with a line of code like this - “while (analogRead(StationstarterTC) < threshold);” This is fine for one Arduino controlling one train round the track.
I now want to control two train loops simultaneously with one arduino running round two loops independently. The code logic should be identical for the two loops but I realise it should not be blocking. Is my “while (analogRead(StationstarterTC) < threshold);” blocking code? And if so how should I modify it? Thanks in advance.

The while(analogRead …) statement is NOT blocking, which actually is the problem. With a system like FreeRTOS, you want the tasks that are waiting for something to be blocking for the next time they have something to do.

One crude method would be to have the routine block for a period of time (fairly short) if the sensor isn’t blocked, which would then allow the program to go to the other task and have it check its sensor and block. The would allow several threads to run and sample their sensors.

A better option, if the hardware could support it, would be if the ADC could be programmed to automatically sample and generate an interrupt when the number goes above the threshold, then the ISR for that interrupt could activate the processing task, avoiding the overhead of just reading the sensor all the time.

Maybe a terminology issue. Blocking can mean it just doesn’t return until the event occurs (ADC conversion result, or whatever). In the RTOS world the meaning of Blocking is extended to mean you don’t use any CPU time while you are waiting. Not using any CPU time means other tasks (threads of execution) can run while you are waiting. Ideally then the system will be event driven so no CPU time is wasted polling and instead all CPU cycles spent have the opportunity to do something useful (which may just be to save power if there is no work to be done). Event driven implies the event (ADC conversion end interrupt for example) “unblocks” the thread that is waiting for the event by somehow signaling it to remove it from the Blocked state.

Yes I think it is a terminology issue. My “waiting for sensor loop” must behaving like a regular delay() function which I understand must not be used in a RTOS task. vTaskDelay() should be used instead. I suppose a quick fix for me is to change my tight loop into a loose loop with
“while (analogRead(StationstarterTC) < threshold){
vtaskDelay(100);
}”
Then I am sampling my sensor just 10 times a second and most of the cpu time is available for other tasks. But is this the official and elegant way to do it? This is an Arduino analogue input after all and is not interrupt driven. Thank you!

If you have only the 2 tasks each controlling a separate train you could also try to keep your code basically as it is and run the 2 tasks at the same priority with this FreeRTOS configuration:

#define configUSE_PREEMPTION    1
#define configUSE_TIME_SLICING  1

Then the 2 tasks run alternating controlled by the scheduler every SysTick. With e.g.
#define configTICK_RATE_HZ 100 the SysTick fires every 10ms.
So virtually both tasks run in parallel with each task running every 2nd 10ms for 10ms.
That doesn’t scale well but could be good enough for your application (to start with).

However, with multitasking you need to take care about concurrent HW access e.g. reading the analog sensors. That means if this done using 1 ADC (HW) peripheral the access to it usually has to be protected/serialized to execute the underlying ADC read sequence without getting preempted right in the middle of this HW access sequence causing corrupted results.
In your case you’d need to enclose e.g. the analogRead function by a mutex

xSemaphoreTake(analogMtx);
analogRead();
xSemaphoreGive(analogMtx);

when calling analogRead from multiple tasks. The same applies to all other HW access functions like switching on/off lights etc.
This has to be done carefully because even when missing the required mutex-protection it might seem to work but fails later on randomly.

OK thank you. I will take a look at that since my immediate task is just to run two trains and there are no sensors or actuators shared between the two tasks.