I’m relatively new to coding, so I had a question about how I would go about running two functions at the same time to achieve something similar to when you are driving forward and dropping the mobile goal intake. I looked at JPearmans code but I was having trouble understanding it. Thanks in advance!
The same way you do tasks in your teleop code.
You can either have tasks that continuously monitor your individual subsystems, or you can have fire-and-forget tasks that perform particular activity on their own. My team uses a combination of both.
Consider this example (with made-up but pretty obvious function names) of the second approach:
task mogoDown() {
setMogoMotors(-100);
waitUntil(SensorValue[mogoPot] < 1000);
setMogoMotors(0);
}
task mogoUp() {
setMogoMotors(100);
waitUntil(SensorValue[mogoPot] > 3000);
setMogoMotors(0);
}
task autonomous() {
startTask(mogoDown);
driveForward(3, seconds);
startTask(mogoUp);
driveForward(2, seconds);
}
The robot starts putting the mogo lift down, but the main (autonomous) task doesn’t wait for it, it drives forward instead. Hopefully the lift will be all the way down before running into the mogo 3 seconds later.
At that point (with mogo nicely seated in your lift), it starts pulling it up while it will keep going forward for another 2 seconds.
Sorry if this is a stupid question, but what is the pseudo code for startTask?
startTask() is an actual RobotC function. It starts the task you give it. Are you using RobotC or PROS? Or something else?
So is running multiple tasks for lots of simultaneous functions the best way to do a detailed autonomous program? I had read that you shouldn’t run more than 2-3 tasks in your program, but I assume you’d need far more if you were using them in this way…?
RobotC allows you to declare (and run) up to 20 tasks. Many tasks isn’t much of issue as long as none of them is hogging CPU. That is, all your tasks need to use proper waits or yields whenever you’re looping.
(for example, the RobotC’s NatLang waitUntil() I used is internally implemented using 5ms wait on every iteration)
I just had a random idea and I was wondering if this would work. If you wanted to multitask, couldn’t you create only one more task which would go through every “state” of your robot, meaning that it will check the position of the mogo lift and of the main lift, etc. It would check these against a global variable. You could then simply set the variable of the position of whatever you would like to change, and every time through the task loop it would move any systems that need to be moved. How does this sound?
Even with proper waits I wouldn’t suggest letting more than 5-6 tasks run at a time but that has never been am issue for me. You need a lot of subsystems for that to happen.
Of course you could. You’d change your whole program flow into a huge state machine and basically implement a very cooperative round-robin task scheduler. This might be doable for a typical auton program, since those don’t really do decisions that would fork the program flow - the code flow is pretty linear. It would still be quite hard and unreadable.
In fact, if you have 4 PID-driven subsystems on your robot, you could do a single PID task for them that would sequentially evaluate the feedback and apply the control for each subsystem based on their individual control variables, then do a single iteration wait. But why bother? It’s the same amount of code running and the cost of the thread context switch under RobotC VM is very likely close to the cost of moving from one statement to another in a single thread. Since for a typical small subsystem control task, the whole loop iteration is faster than the RobotC scheduler timeslice, there should be no pre-emption, your tasks would do their math and give up the CPU voluntarily (don;t forget the wait!) and the VM would move to the next task whose wait has finished. The only difference, if you have a single such task and schedule the computations yourself, is that you have predictable/fixed order of evaluation, thus more consistent loop timing (say, every 9.7-10.3ms instead of every 8.0 - 12.0ms). That might influence your PID derivatives and integrals a little, but given how coarse the motor control is, no observable effect.
Thinking of it, it could have most influence on a hand-crafted gyro integration task, but there are other ways to deal with that.
First to the subsystem. My students did run into the compile-time task limit. I.e. they do have 19-20 tasks declared, not necessarily running at the same time. They do run >10 tasks during teleop though, since in addition to the subsystem tasks (PID for this, PID for that), they chose to implement joystick evaluation on a per-subsystem basis too - imagine a task for controlling the claw. As mentioned above, those tasks never use up their timeslice, they are done in microseconds each. But having them done this way allows them to wait mid-iteration if then need to for more complex behaviors (not that it is the only, the less then the best option, lol).
During auton, I think they have 6-7 tasks actually running - the auton task itself, arm PID, lift PID, MoGo control (more like bang-bang if anything), the main task from the competition template that stays behind ready to kill your robot, then some kind of status task that continuously display some telemetry on the LCD (to help them verify the robot during setup and tuning). Again, none of them should ever use up it’s timeslice and be pre-empted.