El objetivo principal del procesador es ejecutar la secuencia de instrucciones de los programas de entre un posible conjunto de programas. Cuando es uno sólo el programa a ejecutar, podemos intuir fácilmente cuál es la secuencia de operaciones que se está ejecutando sin más que seguir la traza del proceso en curso. La ayuda de los entornos de programación puede ser de valor incalculable cuando nuestra aplicación se complica y son varios los procesos que deben ser procesados.
En un caso monoprocesador la programación concurrente está relacionada con la forma en la que se decide cuál es el orden de ejecución y de interacción de las tareas a ejecutar, cuánto tiempo de procesador se asigna a cada uno de dichos procesos, o cuál debe ser la forma de acceso a los recursos compartidos.
La forma de conseguir que un sistema con un único procesador que sólo puede ejecutar una instrucción simultáneamente parezca que está ejecutando varios programas a la vez es intercalando en el tiempo su ejecución.
Sabemos que podemos seguir el comportamiento que tiene el procesador a través de la información que tenemos de la traza: cuando hay varias tareas en ejecución las instrucciones de cada una de ellas se irán intercalando dependiendo de la política que exista de la asignación de recursos. ¿Tenemos algún tipo de control sobre la forma en que se ejecutan dichos procesos? ¿Cómo debemos escribir nuestro código para ejecutar varias tareas en paralelo, para que una de ellas tenga una mayor prioridad de uso de tiempo de procesador, o simplemente capture al completo al procesador en determinados momentos?
Control de tareas en RobotC:
En RobotC es posible crear tareas para que se ejecuten simultáneamente. Ya he usado tareas en algún otro artículo como el de Manejo de Bluetooth en RobotC, donde una tarea se encargaba de recibir mensajes y otra de mandarlos. En este artículo ahondaremos sobre el tema, explicando diversas funciones.
Para definir una tarea en RobotC se usa la palabra reservada task, seguida del nombre que queramos darle a la tarea, y entre corchetes el código que queremos que ejecute dicha tarea. Por ejemplo:
task HolaMundo()
{
nxtDisplayCenteredTextLine(3, "Hola Mundo");
}
Siempre tiene que haber una tarea principal denominada task main(), desde la que lanzaremos y controlaremos a las demás. Esta tarea principal se iniciará automáticamente cuando empiece el programa, y en ella es donde debemos iniciar el resto de tareas. Para iniciar las tareas necesitamos utilizar la función StartTask:
StartTask(nombre_tarea): Inicia la tarea especificada.
Podemos detener una tarea en cualquier momento con la función:
StopTask(nombre_tarea): Detiene la tarea especificada, matándola.
También se pueden detener todas las tareas de golpe con las funcion:
StopAllTasks(): Detiene todas las tareas.
Existe una función que permite que la tarea que se está ejecutando en este momento hago uso de todo el tiempo de CPU. Esta función es:
hogCPU(): Suspende todas las tareas excepto la actual, dandole por tanto todo el tiempo de CPU a esta tarea.
Esta función tiene la utilidad de poderle dar a una tarea toda la potencia de la CPU cuando queramos. Por desgracia no funciona del todo bien, y no permite por ejemplo un correcto control de motores si tenemos otra tarea que ya los está manejando. Es importante que luego, una vez terminado lo que deseamos hacer, volvamos a reiniciar los procesos que hemos suspendido. Para ello utilizaremos la función:
releaseCPU(): Reinicia las tareas suspendidas (el scheduler) con la función hogCPU.
De momento no veremos más funciones para no complicar las cosa.
Bump&Go con control de tareas:
El Bump&Go es un programa en el que el robot se mueve aleatoriamente por una habitación esquivando los obstáculos que detecta a su paso. Ya vimos cómo programarlo en RobotC en el artículo Bump&Go en RobotC. En este caso lo vamos a implementar con multitasking (multitareas). Puesto que es un programa sencillo no necesitaremos muchas tareas. Necesitamos lo siguiente:
- Una tarea principal que se encargue de controlar a las otras,
- una tarea que se encargue de mover al robot en línea recta y, por último,
- una tarea que se encargue de esquivar los obstáculos cuando el robot los detecte.
Puesto que tanto la tarea de mover el robot como la de esquivar obstáculos hacen uso de un recurso compartido (los motores) no podemos dejar que se ejecuten a la vez, o interferirían entre sí. Por tanto una vez detectemos el obstáculo pararemos un momento la tarea de moverse en línea recta, y una vez hayamos esquivado el obstáculo la volveremos a poner en marcha.
El código del programa es el siguiente:
#pragma config(Sensor,S4,contacto,sensorTouch)
#pragma config(Motor,motorA,motor_der,tmotorNormal,PIDControl,encoder)
#pragma config(Motor,motorC,motor_izq,tmotorNormal, PIDControl, encoder)
//*!Code automatically generated by 'ROBOTC' configuration wizard!*/
void esquivarObstaculo()
{
int tiempoMA, tiempoGiro;
nxtDisplayClearTextLine(3);
nxtDisplayCenteredTextLine(6, “ESQUIVANDO”);
tiempoMA = 200 + random(300);
motor[motor_der] = -50;
motor[motor_izq] = -50;
wait1Msec(tiempoMA);
tiempoGiro = 200 + random(400);
motor[motor_izq] = 0;
wait1Msec(tiempoGiro);
}
task moverRobot()
{
while(true){
wait1Msec(300);
nxtDisplayClearTextLine(6);
nxtDisplayCenteredTextLine(3, “MOVIENDOSE”);
motor[motor_der] = 50;
motor[motor_izq] = 50;
}
}
task controlChoques()
{
while (true){
wait1Msec(300);
if (SensorValue[contacto]){
StopTask(moverRobot);
esquivarObstaculo();
StartTask(moverRobot);
}
}
return;
}
task main()
{
StartTask(moverRobot);
StartTask(controlChoques);
while(true)
{
wait1Msec(300);
nxtDisplayCenteredTextLine(0, “PRINCIPAL”);
}
return;
}
Y aquí una breve explicación de algunas líneas:
1 a 3 – Definimos los sensores y motores
6 a 19 – Esta función se encarga de esquivar obstáculos
22 – Aquí empieza la tarea mover robot. Las tareas han de ser declaradas antes de ser utilizadas por otras tareas como la main, por eso task main será la ultima que escribamos.
24 – Bucle infinito
25 – Un pequeño retardo que nos asegura que la tarea anterior haya terminado. Importante ponerlo antes de hacer nada.
26 a 29 – Decimos que se está moviendo, borrando otros mensajes, y ponemos los motores en marcha a la misma potencia.
37 – Dentro de la tarea control de choques solo actuaremos si el sensor de contacto esta presionado.
38 – Antes de manejar detendremos la otra tarea que controla los motores.
39 – Esquivamos el obstáculo.
40 – Y volvemos a poner en marcha al robot.
49 y 50 – En la tarea principal (task main) iniciamos las otras dos tareas.
54 – Dentro de un bucle infinito mostramos por pantalla que la tarea principal esta activa. Si se terminara esta tarea se acabaría el programa, por lo que siempre la necesitamos tener activa (en ejecución o en espera).
Espero que os haya quedado claro. En cualquier caso si tenéis alguna duda podéis consultarnos en nuestro foro.