Best Practices for Wait Commands

As someone really new to coding, I am wondering if someone would be able to help clarify when and where wait commands should be used. I do understand the purpose of them, just not very clear on where they should be placed. For example, our code contains a task to calculate flywheel velocity (flywheel_velocity) and we currently have a 20mS wait command at the end of that task. The flywheel_velocity task is then called within another task used to control the flywheel velocity (flywheel_control). We do not have a wait command at the end of that task. During driver control, we call the flywheel_control task along with other code to control the motion of the bot and have a 20ms wait command at the very end of the driver control task.

So I am wondering if the wait command within the flywheel_velocity task is really necessary? I do not feel it is required because the wait command at the end of the driver control task gets applied to everything that is called within it. I am hoping someone who has a better understanding of all this could either confirm or correct my understanding.

In each task with a while loop, it is good practice to include a wait timer to prevent you overloading your cortex. If you are having trouble with a certain method (for example, if your


flywheel_velocity

task is not giving you the resolution you want), you can lower the delay.

Is your flywheel_velocity its own task with its own for loop? If it is called every time from a for loop in the main method, then it should really be a method instead of a task. I would recommend separating your main for loop and your flywheel for loop and putting a delay on both. This allows you to get better resolution on the flywheel (where you need it) while still allowing your cortex to breathe.

@jonathandamico It would probably be easiest if I just posted our code instead of trying to explain it in a long winded post!

I guess what confuses me is if we call a task that already has a wait command within it into another task that also uses a wait command, do the wait periods start to add up? For example, task 1 has a 10 mS wait command. Task 1 is used with task 2 and task 2 has 20 mS wait command at the end. Does this mean that task one is effectively experiencing a net wait of 30 mS?

Tasks run asynchronous to each other - which is a fancy way of saying that they run alongside and independently of each other.

That being said, a delay in one task won’t affect the other. You shouldn’t get a net wait of 30.

@jonathandamico OK, that makes sense. Thank you! Next question is how do you determine which tasks should be using wait commands and how do you decide on how long, or short, the wait time should be?

All tasks with a while loop should have a wait command. Robotc has a really simple


delay(ms);

and you should have about a 50ms delay in the very while loop. If you have a flywheel task, your delay should be lower (abt 30 msec) to allow resolution to be higher.

Putting a wait of a few milliseconds as it is beneficial to make sure all tasks execute like you want them to.

Please read this post by James Pearman.
http://www.robotc.net/wikiarchive/Multitasking_tips

You could repeat this experiment and see how low you could make the wait going shorter and shorter to see when the scope goes crazy. (like he had in his experiment where the second task got no love)

This next reference gives a vague statement about how much time is allotted to the current running task before giving another task the opportunity to run.

http://www.robotc.net/wikiarchive/VEX2_Functions_Task_Control

Best practise, every task should have 1 call to wait1Msec in it’s while loop. I usually use times of between 10mS and 25mS. Tasks that are controlling motors on ports 2 through 9 do not need to run any faster than 25mS, tasks controlling motors on port1 and port10 may benefit from the faster loop time.

Any blocking functions should also call wait1Msec. I’m always seeing this code (in some Robomatter examples :frowning: )

void waitForRelease() 
{
    while(nLCDButtons != 0){}
    wait1Msec(5);
}

That’s a blocking function that is (IMHO) written incorrectly. It should be like this so it yields to other tasks.

void waitForRelease() 
{
    while(nLCDButtons != 0)
        {
        wait1Msec(5);
        }
}

How long is a time slice?
Just about everyone runs their tasks at the default priority of 7. This means that, without calls to wait1Msec, a task will have exclusive use of the cpu until its time slice runs out. There is a little known system variable in ROBOTC called nOpcodesPerTimeslice, this tells us how many ROBOTC instuctions (an opcode is an instruction) will execute per time slice. When multiple equal priority tasks are running, each one gets an equal share of this amount of time. For example, if three tasks are running then each gets 333 opcodes worth of time. This does not necessarily mean each gets the same amount of absolute time (as in mS) as not all opcodes take the same amount of time to execute, for example, a complex math opcode (pow, exp etc.) will take a lot longer than a simple addition. An example,

#pragma config(Sensor, dgtl1,  ,               sensorLEDtoVCC)
#pragma config(Sensor, dgtl2,  ,               sensorLEDtoVCC)
#pragma config(Sensor, dgtl3,  ,               sensorLEDtoVCC)
//*!!Code automatically generated by 'ROBOTC' configuration wizard               !!*//

task otherTask2()
{
    while(1) {
      SensorValue dgtl3 ] = !SensorValue dgtl3 ];
    }
}
task otherTask()
{
    while(1) {
      SensorValue dgtl2 ] = !SensorValue dgtl2 ];
    }
}

task main()
{
    startTask( otherTask  );
    startTask( otherTask2 );
    
    while(1) {
      SensorValue dgtl1 ] = !SensorValue dgtl1 ];
    }
}

There tasks, all the same priority, all just toggling an LED. If we look at the assembly language for one of these tasks it would be like this.

task otherTask2()
{
    while(1) {
0000: 4E012B00000AS00(slong) = (long) GetProperty(propertySensor, dgtl3:10)
0006: 4E012B04000AS04(slong) = (long) GetProperty(propertySensor, dgtl3:10)
000C: 24025500002B04000800TestAndBranchSLong(0(0x00000000) != S04(slong), L001C)
0016: CB000100S00(slong) = 1                      // long/float
001A: 7905BranchNear(L0020)                  
001C: CB000000S00(slong) = 0                      // long/float
0020: 57012B00000ASetProperty(propertySensor, dgtl3:10, S00(slong))
      SensorValue dgtl3 ] = !SensorValue dgtl3 ];
0026: 79D9BranchNear(L0000)                   // Jump to end Expression
    }
}

A bit complicated, but the point is that there are about 6 opcodes for each loop iteration. This means that the loop will run for approximately (333 / 6) 50 times before this task is swapped out and another run.

This is a very basic overview, it is in reality a bit more complicated, but you get the idea.