You are on page 1of 22

Ultima actualización: 07/03/2007 INTRODUCCIÓN: Gracias a la labor del amigo Reinier Torres Labrada, del foro sobre microcontroladores

TODOPIC, ponemos en uControl esta Introducción a los Sistemas Operativos para Microcontroladores. Realmente, Reinier esta haciendo un muy buen trabajo con este tema, y debido a que no hay demasiada información sobre RTOS para PIC en español, es que vamos a ir recopilando en esta pagina sus post sobre el tema. Como podrán ver, abundan los ejemplos, y sus explicaciones son muy claras.

Sistemas Operativos para microcontroladores

RTOS
para

PIC

ÍNDICE: > Introducción > Presentación de la guía > Un poco de teoría sobre los Sistemas Operativos (SO) > Diferencias entre usar un SO o usar las librerías del compilador de turno > ¿Qué es un Sistema Operativo? > Clasificación de los SO > Introducción al RTOS de CCS > Funciones del RTOS > Controlando la ejecución de las tareas > Yield vs delay > Coordinar para no dañar > En sus marcas, listos, ¡FUERA! > RTOS mail > Datos de Contacto > Bibliografía

> Presentación de la guía: Las siguientes son palabras de Reinier, presentando en el foro su idea de armar esta Introducción a los Sistemas Operativos para Microcontroladores: "Hola amigos He visto que en el hilo "RTOS para PIC GNU" ha aparecido una interesante controversia sobre los RTOS, sin embargo lo que más curioso me resulta es que muchos no conocen prácticamente nada sobre el tema, y como aquí todos estamos para aprender y compartir lo poco que sabemos, quisiera comenzar por explicar algunos de los principios básicos de los SO y como podemos aplicarlos al campo de los microcontroladores. Espero que esta nueva idea sea de utilidad y despierte el interés en muchos de ustedes. Yo soy muy nuevo en este campo de los SO y los microcontroladores, pero con la ayuda de ustedes podremos hacer muchas cosas interesantes y beneficiosas para todos. De hecho todavía no he comenzado a utilizar ningún RTOS o algo parecido para meter en un PIC, ni siquiera me he metido con el código de ningún SO, pero en la maestría que estoy cursando tengo los SO (desde la óptica del diseño) como asignatura obligada y no quisiera que después de un montón de horas delante de un profesor que produce más sueño que interés por su asignatura (aunque el tema es interesantísimo), todo ese conocimiento se quedara en la nota al final del semestre. Bueno amigos... sin más muela (preámbulo) comencemos a trabajar" [Volver al Índice]

> Un poco de teoría sobre los Sistemas Operativos (SO) Haciendo un poco de historia, y los más añejaditos lo saben mejor que yo que soy un niño de 26, los SO aparecieron más o menos por allá por los 60. Lo que ocurrió por aquel entonces, cuando las computadoras, eran tan “poderosas” como nuestros PIC’s (algunos de gama alta son hasta más poderosos que aquellas computadoras), era que muchos programadores (matemáticos locos la mayoría), no estaban interesados en conocer los detalles de las interfaces, controladores

1 de 22

(matemáticos locos la mayoría), no estaban interesados en conocer los detalles de las interfaces, controladores y demás cacharros electrónicos que adornan a una computadora, y por otro lado algunos programadores ya habían escrito controladores para esos dispositivos y por tanto los que venían detrás simplemente querían utilizarlos y ya (algo parecido a lo que hacemos en el foro). Pues bien, con el tiempo se acumularon programas útiles para manejar dispositivos y tareas complejas que debían programar personas no interesadas en la electrónica, entonces la gente, que trabajaba en equipo, organizó todo aquello de manera que los programadores, los programas y el hardware (HW) pudieran coexistir sin que se produjera una guerra hubiteana (guerra en la que los hombres y los bits se pelean hasta que los hombres se mueren de alguna clase de infarto o pierden a la mujer). Surgieron así los primeros intentos de crear un SO y los hombres mantuvieron la Paz sobre la tierra y también la guerra “fría”, aunque las cosas se calentaran a intervalos. Pasó el tiempo y llegó Bill Gates con DOS y luego Windows y Steve Jobs con su Mac, unos tipos más listos y cabrones que ingenieros o programadores…. y después también apareció Linus Torvalds con Linux, uno que estaba más loco que El Quijote y entonces las personas corrientes creyeron que en el mundo solo existen a lo sumo tres SO (Windows, Mac, y Linux), pero no es así hay un montón de SO, algunos muy poderosos y otros no tanto, dependiendo de en que lugar y para que se hayan diseñado. Como es lógico suponer, en la parte de la historia que les acabo de contar está el por qué los SO son útiles, pero eso lo iremos viendo poco a poco porque hay muchas formas en las que un SO nos puede servir. Vamos a ver algunas: 1) Un SO convierte a las computadoras en equipos útiles. Pone una o varias capas de Software (SW) sobre el HW, y con eso podemos escribir programas y utilizar programas ya escritos por otros para hacer otros programas escritos por nosotros mismos, sin tener que meternos en detalles tales como “bueno ahora mi procesador de textos ya trabaja ¿y como guardo el documento?”. Seguramente si usted le dice al programador del procesador de texto que para guardar el documento tiene que tirarse meses escribiendo las rutinas que guardan el doc. seguramente le da un ataque. 2) Un SO es una herramienta poderosa en la gestión de procesos. Nosotros mismos cuando programamos los PIC´s tenemos que leer el puerto serie, escribir en una LCD, controlar el motor y todas esas cosas “las hacemos a la vez”. Pero cada una de esas tareas podemos considerarla un proceso, y un SO puede ayudarnos a gestionarlos eficientemente, aprovechando al máximo el procesador. Por ejemplo en vez de poner al PIC a esperara a que el motor se mueva un poco poniendo una demora, podemos utilizar ese tiempo en ir escribiendo un poco en la LCD y después volver a donde nos quedamos con el motor. 3) Un SO nos ayuda a diseñar más rápido sistemas mas complejos. Imagínense ahora una aplicación donde tengamos que hacer cálculos, encender y apagar cosas, comunicarnos con otros dispositivos y otras tareas más, y que además tengamos que hacerlo rápido y trabajando con otras personas. Entonces el concepto de divide y vencerás nos pude ser útil y un SO es el arma que podemos utilizar para derrotar a nuestros enemigos: el tiempo y la complejidad. Pues sí nos auxiliamos del SO para dividir y gestionar los procesos, y con ello no eliminamos la complejidad pero le damos una vuelta elegante, y no matamos al tiempo pero le damos menos oportunidad de que sea él el que nos mate y si no que el jefe nos bote. 4) Un SO nos puede ayudar a hacer sistemas más estables. Ahora tenemos unas rutinas que demoran cierto tiempo en hacer unos cálculos y como somos listos, hemos activado el WatchDog (WD) para que si la cosa se cuelga en el campo se reinicie el sistema. Pero ¿que pasa si la rutina demora más de lo debido y el WatchDog nos reinicia sin que haya ningún problema real?. Otra vez la gestión de recursos nos puede ayudar en eso, ya que si nuestra tarea demora más de lo debido el SO puede quitarle el procesador para atender al WatchDog y darle el procesador a otra tarea que también lo necesita por algún tiempo. Este caso también sirve cuando aún sin el WD ponemos una llamada a una función que se queda esperando indefinidamente por que ocurra algo que no ocurre y el código que le sigue no se ejecuta en los tiempos establecidos. Bueno amigos, hasta aquí hemos visto algunas de las cosas en que puede ayudarnos un SO, y sería maravilloso poder contar con esas herramientas para programar nuestros PIC. Más adelante seguiremos aprendiendo un poco sobre la teoría de los SO, para después meterle el cuerpo a los programas. Hay algunos libros interesantes sobre los SO, les recomiendo uno que puede ayudarles mucho en esta primera parte: Sistemas Operativos Diseño e Implementación. Andrew S. Tanenbaum. [Volver al Índice]

> Diferencias entre usar un SO o usar las librerías del compilador de turno. Nocturno, un integrante del foro, le plantea a Reinier la siguiente pregunta, que es aprovechada para avanzar en la explicación de los SO:

2 de 22

en la explicación de los SO: "Por lo que leo en el capítulo 1 de tu artículo. y con otras técnicas. mientras aprovechamos al máximo nuestros preciados recursos del PIC. Hay muchas formas de hacer eso. no veo diferencias entre usar un SO o usar las librerías del lenguaje que estés utilizando en cada momento. en nuestro caso eso sería todo nuestro flamante PIC. ¿pero cuál es la principal ventaja que los diferencia?" Desde el punto de vista de la programación no estás haciendo otra cosa que utilizar las librerías del lenguaje u otras creadas por tí o por un buen amigo que te las haya cedido. Supongamos ahora que estamos atendiendo un teclado. ya que es quién hace la mayor parte del trabajo. le dice a la función que atiende el teclado: toma el procesador por el tiempo que te corresponde y así todo el mundo puede tener el procesador para el solo durante un tiempo. ninguna ventaja. sin embargo en este problema el SO puede hacer algo más que en principio no está bajo nuestro control. Bien hasta ahora nada nuevo. por ejemplo una demora por SW es hacer trabajar al procesador con trabajo inútil y ese tiempo que se la pasa saltando de aquí para allá lo podemos utilizar en hacer otras cosas y la memoria de programas también. cuando todo el mundo haya recibido su poco de tiempo de procesador. si es así. y una desventaja: consumo de memoria de programa para el dispatcher y complicaciones de la vida del programador. que no está conectado a ninguna fuente de interrupción. después veremos como hacer eso. este código debe ejecutarse todo en tal tiempo". y los periféricos. Esas dos acciones las hago sirviéndome del SO y le dejo a él el problema de despertarme. que nos ayudan a gestionar nuestras tareas (también veremos el concepto y la implementación en el futuro). entonces podemos decirle al SO: "Oye. y cuando tengo el dato o código de la tecla. es decir ponemos a un vigilante a que reparta equitativamente el tiempo del procesador. es asunto de ese alguien tomarlo. el SO le quita el procesador a ese que se lo quiere coger todo para él solo y se lo da a otra función que hace algo parecido o para que se atienda una interrupción. Yo se que cosas como esta se pueden hacer blandiendo interrupciones a derecha e izquierda. creado por el compilador. le mando un mensaje a alguien (una función). y poner el mensaje donde corresponde. etc. la directiva #use RTOS lo que hace es indicarle al compilador que ponga en el programa final. cuando se cumpla el plazo establecido. etc. más adelante veremos muchas más. me voy a dormir un tiempo. el objetivo de nuestro SO es mantener ocupado al procesador con trabajo útil. y luego nos ofrece unas funciones de librería como por ejemplo RTOS_WAIT( ). Entonces. nos damos cuenta. RTOS_RUN( ). no es fácil 3 de 22 . despiértame cuando pase ese tiempo. una de las personalidades más reconocidas en el mundo académico de los SO. una de ellas sería: 1-Espera que se oprima o suelte una tecla 2-Espera un tiempo a que termine el rebote 3-Lee el teclado 4-Procesa la tecla 5-Hacer algo con el código del teclado 6-Regresa al punto 1 Pero una forma sencilla utilizando un SO tendría la siguiente forma: 1-Espera que se oprima o suelte una tecla 2-Me dormiré hasta que termine el rebote 3-Lee el teclado 4-Procesa la tecla 5-Manda un mensaje (el código del teclado) a la función adecuada 6-Regresa al punto 1. Como vemos nuestro programa se queda siempre haciendo lo mismo en un lazo infinito. Ahora vemos que la cosa cambió ligeramente porque le hemos dicho a alguien. De hecho si revisamos. pongo el dato en algún lugar e indico que hay un dato. sin embargo todavía no hemos terminado con la introducción teórica a los SO. la memoria. De todos ellos el más importante es el procesador. [Volver al Índice] > ¿Qué es un Sistema Operativo? Según Tanenbaum. no queda más remedio que encuestar a nuestro teclado para saber si han oprimido o soltado una tecla. No me cabe duda que un SO tendrá muchas más aportaciones que las librerías. Sin embargo la ventaja del SO radica en que le permite al programador contar con una herramienta para gestionar varias cosas fundamentales en el sistema: el procesador. el RTOS de CCS y las funciones que brinda el lenguaje para desarrollar aplicaciones utilizando esta potencialidad del lenguaje. Hasta aquí hemos visto algunas de las ventajas. etc. entonces tenemos que hacernos cargo de los rebotes. procesarlo y notificarlo. RTOS_ENABLE( ). pero como veremos más adelante los SO nos ayudan en otras cosas. que por ejemplo. no llamo directamente a la función. pero para los más aventajados esto puede aclarar algunas de sus dudas. por ejemplo. el código del dispatcher (luego vamos a ver que es) del RTOS.

Es entonces. para que otra tarea o proceso pueda utilizarlo. Esto es bueno porque ayuda a los programadores a desarrollar soluciones más rápidamente y con menor probabilidad de errores ya que si la función está bien escrita es poco probable que falle.: A continuación veremos como se clasifican los SO en cuanto a dos características esenciales: la administración del recurso fundamental (el procesador) y el destino del SO. que nos ayuden a "indicarle" o "pedirle" que es lo que queremos hacer. más que en la gestión eficiente de nuestros recursos. Esta característica se pone también de manifiesto en aplicaciones donde no se usan los SO. En cuanto a la administración del procesador existen dos clasificaciones: 1) SO cooperativo (no preemptive): En este caso es el programador quién tiene la responsabilidad de entregarle el procesador al núcleo del SO. o un programa de computadora). solamente me voy a referir a las ya mencionadas. porque puede que determinados requisitos temporales en la ejecución de algunas funciones tengan que cumplirse. una de las personalidades más reconocidas en el mundo académico de los SO. para que éste lo entregue a la próxima tarea que esté solicitándolo o programada para ejecutarse. para ello debe ofrecernos un conjunto de mecanismos. 2) El SO como administrador de recursos: En este caso el SO. En nuestro caso lo usual es que nos rompamos el coco manipulando interrupciones. cambiando contadores y chequeando banderas… ¡Uf solo de pensarlo me dan ganas de llorar! Sin embargo un SO puede hacerse cargo de todos esos temas de manera eficaz y eficiente. Sin embargo esta visión de los sistemas operativos es poco aplicable a nuestro entorno. que las llamadas a funciones que ejecutemos nunca se queden esperando mucho tiempo por determinados eventos. poniendo demoras. Por supuesto que el SO no es mago ni adivino. en el sentido en que hoy se clasifican a las llamadas al sistema. la característica destacable es simplificar al programador los detalles del funcionamiento de los dispositivos conectados al sistema. pero para abreviar lo más posible. incluso ahorrando memoria y tiempo. muy importante. evitar los lazos infinitos y entregar el procesador cuando no lo necesitemos. no es fácil dar una definición definitiva de lo que es un SO. las implementaciones de los SO. un programador. se conoce comúnmente como Llamadas al Sistema o API (Aplication Programmer Interface). Entonces en este caso la máquina extendida queda limitada a algunas llamadas a funciones del SO y al uso de las librerías que hasta el momento hemos utilizado habitualmente. 2) SO de tiempo compartido (preemptive): En este caso el programador debe contar con los mismos mecanismos que en el anterior. sin tener que profundizar demasiado en los detalles del funcionamiento de sus diferentes componentes. se caracterizan por potenciar la administración de recursos del SO. Se dice que estas funciones son una abstracción del proceso de E/S en puerto. y nosotros los programadores concentrarnos en la implementación de la solución. ya que en nuestro mundo todo es pequeño en cuanto a capacidad y el crear una máquina extendida poderosa consume recursos que usualmente no tendremos. por lo que es esta la faceta de personalidad que más a menudo encontraremos en los RTOS. relativamente sencillos. un ejemplo típico son las funciones output_x() e input_x() del compilador CCS.Según Tanenbaum. Veamos esto con más detenimiento: 1) El SO como máquina extendida: En esta faceta de la personalidad del SO. Esta interfaz que el SO ofrece al programador o el usuario. Un programador puede utilizar estas funciones sin conocer que para que los datos se pongan en los pines o se lean. El problema consiste en que los SO tienen la característica de comportarse para el usuario (que puede ser una persona cualquiera. como un tipo con "doble personalidad". en un sistema sin SO. La función de la máquina extendida es ofrecer al programador una "interfaz" gracias a la cual se utilizan los recursos del sistema. hay que cambiar varios registros en el uC. se comporta como un administrador de nuestros recursos. La ventaja de tener alguien que administre eficientemente los recursos consiste en que el SO ofrezca al usuario un conjunto de reglas para compartir y hacer uso de los recursos disponibles con eficacia y eficiencia. el programador tiene que estar muy pendiente de la implementación de su sistema. [Volver al Índice] > Clasificación de los SO. pero el SO tiene la facultad de quitarle el procesador y dárselo a otro si usted se ha excedido en su tiempo o hay alguien que tiene 4 de 22 . Un ejemplo de administración de los recursos es el uso de la CPU. En el caso de los uC. Los SO se pueden clasificar de distintas maneras.

esto sirve para conocer que tiempo 5 de 22 . Otro caso de SO.facultad de quitarle el procesador y dárselo a otro si usted se ha excedido en su tiempo o hay alguien que tiene mayor prioridad. así que si en versiones posteriores han aparecido nuevas características. En la próxima entrega ya tendremos código para ejecutar y ver como funciona un RTOS. la idea es ejecutar determinadas tareas de forma que parece que cada tarea se está ejecutando en un sistema independiente. ejecutar las tareas cumpliendo estrictamente con los requisitos temporales de cada una. éste señor es el administrador de nuestros recursos. El RTOS de CCS es un Sistema Operativo de Tiempo Real que implementa la técnica de multiprocesamiento cooperativo (non preemptive). a cuál de ellas le entrega el procesador.249. Las razones de mi elección las expongo a continuación: Sencillez en la implementación de aplicaciones. Este parámetro. de los disponibles. por lo que es responsabilidad del programador asegurarse de que el control del procesador retorna al planificador de tareas en algún momento. donde el procesador y el resto de los recursos son sólo para ella. no importa si algunas cosas se ejecutan o no cumpliendo tiempos estrictos. pero la idea sigue siendo la misma. es el que se utilizará para la ejecución de las tareas. luego veremos un ejemplo de esto. sin violaciones de ninguna índole. [Volver al Índice] > Introducción al RTOS de CCS Después de teorizar un poco sobre los Sistemas Operativos. Abundante experiencia en el foro con este compilador. así como sus parámetros de configuración: #USE RTOS : Opciones del RTOS: timer: especifica que temporizador. Así que cuando programemos nuestra aplicación. Este temporizador solamente debe ser utilizado por el RTOS y típicamente se escoge Timer0. LINUX. Directivas del preprocesador Existen dos directivas del preprocesador para el uso del RTOS. Aunque el concepto de "tiempo real" es muy controversial. en menos tiempo que este. ellas son: #USE RTOS: Se utiliza para indicarle al compilador que se va a utilizar el RTOS #TASK: Se utiliza para indicarle al compilador se la función definida a continuación es una tarea a ejecutar por el RTOS Vamos a ver más detenidamente cada una de las directivas. y comenzar a utilizar esas nuevas características. statistics: le indica al compilador que lleve las estadísticas de las tareas. pero eso no importa porque el RTOS funciona y para lo que queremos hacer. Los RTOS pueden ser utilizados también para computadoras grandes. minor_cycle: especifica la cantidad de tiempo mínima que una tarea tendrá para ejecutarse. y los tiempos de ejecución de cada tarea deben ser múltiplos de esta cantidad. utilizaremos el RTOS que viene con el compilador de CCS. Si por ejemplo decimos que el tiempo mínimo de ejecución para todas las tareas es de 1ms. Lo realmente importante de este dato es que ayuda a establecer la frecuencia con que se ejecutan las tareas. La versión del compilador que tengo instalada en estos momentos es la 3. les ruego que lo informen para tomar las debidas providencias en el momento oportuno. tenemos que asegurarnos de que no llamamos a una función que se queda esperando por algún evento largo como es el caso de gets(). o dentro de un lazo infinito o demasiado extenso. es calculado por el compilador en el momento de la compilación. En nuestro caso. El RTOS está integrado en el propio compilador de CCS. si no se especifica. nos sirve bien. para comenzar. En cuanto al destino hay unas cuantas clasificaciones pero me voy a concentrar en los RTOS. Gran cantidad de dispositivos soportados por el compilador de CCS. Planificador de tareas Uno de los elementos fundamentales de cualquier SO es el planificador de tareas. Windows. La política de planificación empleada por CCS no la conozco. Un RTOS (Sistema Operativo de Tiempo Real) es un sistema operativo concebido para dispositivos pequeños como los uC. las tareas cambian de prioridades en función de satisfacer las exigencias de los humanos que actúan como usuarios. debemos conocer que cada tarea se ejecutará. son los de propósito general como UNIX. Su misión fundamental es determinar dentro de las tareas que están listas para ejecutarse. en este caso a diferencia de los RTOS. vamos a introducirnos en la programación de aplicaciones empleando un RTOS.

Sin embargo. la estadística realmente importante es la que nos indica si nuestra tarea se ha sobrepasado en su tiempo mínimo de ejecución. Sin embargo hoy utilizaremos solamente la función rtos_run(). Cada cadena será transmitida desde dentro de una tarea del RTOS y tendrán el formato “Ejecutada tarea #”. tiempo mínimo de ejecución de cada tarea 10ms int8 test. rtos_run(). entonces esta tarea es marcada con el valor overrun. tiempo para ejecutar: 10ms Frecuencia de ejecución de la Tarea 2: 2seg. Las especificaciones de tiempo del sistema y de cada tarea son las siguientes: Temporizador para el RTOS: Timer0 Tiempo mínimo en que debe ejecutarse una tarea: 10ms Frecuencia de ejecución de la Tarea 1: 1seg. sin embargo como veremos más adelante. #task (rate=2s. //A partir de aquí comenzará la ejecución de las tareas } //Implementación de las tareas void Tarea1() { printf("Ejecutada tarea 1\r"). que le indica al planificador que comience a ejecutar las tareas. #task (rate=3s. minor_cycle=10ms) //temporizador Timer0. Si la tarea no recibe mensajes.xmit=PIN_C6. Hasta aquí hemos visto una pequeña explicación de las directivas para utilizar el RTOS. #TASK: Opciones para las tareas: rate: especifica con que frecuencia se ejecuta la tarea. El ejemplo: Se quiere implementar una aplicación en un PIC16F877. //Definición de las prototipos de función de las tareas #task (rate=1s. max: especifica que cantidad de tiempo debe consumir esta tarea en su ejecución. max=5ms) //Ejecutar cada 2 segundos y consumir como máximo 5ms void Tarea2(). tiempo para ejecutar: 5ms Frecuencia de ejecución de la Tarea 3: 3seg. Este parámetro es útil para informar al programador que una tarea se ha pasado de su tiempo de ejecución.bits=9) #use RTOS(timer=0. este parámetro debe ser igual a minor_cycle de #use_rtos o un múltiplo de este valor. } 6 de 22 . [Volver al Índice] > Funciones del RTOS EL RTOS de CCS ofrece un conjunto de funciones que veremos cada una en su momento y con sus debidos ejemplos. entonces debe dejarse en blanco para no consumir memoria RAM innecesariamente. max=250us) //Ejecutar cada 3 segundo y consumir como máximo 250us void Tarea3(). si se sobrepasa este tiempo y están activadas las estadísticas. la utilidad de esto es mejor verla con un ejemplo. void main() { setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1). y si el RTOS fuera de tiempo compartido seguramente especificaría el tiempo en que el planificador le retira el procesador para dárselo a otra tarea.rcv=PIN_C7.h" #use rs232(baud=9600. donde se utilice el RTOS para transmitir por el puerto serie de este uC. max=10ms) //Ejecutar cada 1 segundo y consumir como máximo 10ms void Tarea1().parity=N. tiempo para ejecutar: 250us El código lo pongo a continuación y posteriormente les doy una explicación: Código: #include "D:\Documentos\Projects\RTOS\RTOS. esto sirve para conocer que tiempo consume cada tarea en ejecutarse. tres cadenas de caracteres.statistics: le indica al compilador que lleve las estadísticas de las tareas. queue: especifica el tamaño de la cola de mensajes de la tarea.

pero después comprobarán que las tareas no se ejecutan en orden secuencial porque la Tarea 2 se ejecutará cada 2 segundos y la Tarea 3 cada 3 segundos. Todos los LED parpadean. Para lograr este objetivo el RTOS de CCS nos ofrece dos funciones: RTOS_DISABLE( ) y RTOS_ENABLE( ) Al crear una tarea el RTOS la marca como activa (enable).void Tarea2() { printf("Ejecutada tarea 2\r"). [Volver al Índice] > Controlando la ejecución de las tareas En la pasada entrega sobre programación con el RTOS de CCS vimos como es que el planificador programaba la ejecución de las tareas que estaban activas para ser ejecutadas. cada uno con su frecuencia correspondiente durante 5s. el resto apagado. uno verde en RB1 y otro amarillo en RB2. y por cierto nada despreciables. La no ejecución secuencial de cada tarea se debe al parámetro rate de la directiva #task. Sin embargo en muchas ocasiones notaremos que no hace falta ejecutar la tarea hasta que se cumplan ciertas condiciones. un ejemplo que se me ocurre es la lectura de teclados y eliminación de rebotes. Vamos a encender y apagar los LEDs con una frecuencia determinada y a intervalos regulares según el siguiente esquema: Led Rojo parpadea con una frecuencia de 250ms por un período de 5s. y cuando comienza a ejecutarse el planificador de tareas la pondrá en la cola de ejecución para ejecutarla cuando le llega su turno. La solución que les propongo es la siguiente: . Led Amarillo parpadea con una frecuencia de 450ms por un período de 15s. Hasta el momento el RTOS no nos ofrece ninguna ventaja respecto al método tradicional. registros y Dios sabe cuantos engendros de control de ejecución condicional para una función estándar? De forma similar a como se le dice al planificador que la tarea tal debe ser ejecutada. Este programa puede ser fácilmente modificado para cualquier aplicación específica. a veces hace falta poner a “dormir” una tarea y “despertarla” cuando hace falta que se ejecute. Al ejecutarlo pueden comprobar que primero se ejecuta la Tarea 1. Como pueden ver es un ejemplo sencillo pero que muestra el funcionamiento del RTOS. La ventaja principal con respecto al método tradicional consiste en que usted hace la consulta para comprobar si hay que ejecutar la función. Este es un mecanismo simple. y la implementación de una función a la cual llamaremos si se cumple la condición de su ejecución. sin embargo combinemos esta característica con lo aprendido en el ejemplo de la entrega anterior y comprobaremos que si hay ventajas. banderas. } Este código funciona y si lo simulan con el Proteus comprobarán que las cadenas salen por el puerto serie. sin embargo ahora solamente le dice al RTOS. el resto apagado. ¿Alguien se acuerda de los molestos temporizadores. Tenemos un total de 4 tareas en la aplicación y el control de la ejecución será el siguiente: 7 de 22 .1 tarea para controlar el tiempo en que todos los LEDs parpadean.3 tareas para controlar el parpadeo de cada LED y el tiempo que se ejecutan . que sin el uso del RTOS controlaríamos mediante una bandera (flag). Comenzamos por el LED rojo nuevamente y repetimos el ciclo. el resto apagado. Pero como es lógico en todo momento las tareas no tienen por que encontrarse en condiciones de ser ejecutadas. que le dice al planificador con que frecuencia ejecutar cada tarea. Veamos esto con un ejemplo: Tenemos una aplicación en la que hemos colocado tres LEDs uno rojo en RB0. } void Tarea3() { printf("Ejecutada tarea 3\r"). habilita a la tarea tal y ponla a ejecutarse cuando le corresponda y se olvida de todos los problemas asociados respecto al tema de cuando le corresponde ejecutarse la tarea y demás. podemos avisarle para que no la ejecute. Led Verde parpadea con una frecuencia de 350ms por un período de 10s.

rtos_run(). iB2. if (iCountR++==20) { iCountR = 0. Cuando LED_V concluye habilita LEDS y se autodeshabilita. #task (rate=250ms. output_bit( PIN_B0.xmit=PIN_C6. iCount = 0.h" #use rs232(baud=9600. iCountR = 0. void main() { setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1). } //cada tarea tiene un contador para llevar la cantidad de veces que se pasa por la //función y cuando se cumple el tiempo establecido entonces habilita y deshabilita //las tareas correspondientes void LED_R() { iB0 = !iB0.parity=N. max=10ms) void LED_R(). max=10ms) void LED_V(). #task (rate=450ms. rtos_disable(LED_A). Lo considero una //deficiencia sustancial de este RTOS void Task_Disabler() { rtos_disable(LED_V). #task (rate=350ms. 8 de 22 . } //esta es una función truculenta porque las tareas no pueden crearse deshabilitadas //y no se pueden deshabilitar hasta que el RTOS esté funcionando. #task (rate=10ms. iLEDS = 0. minor_cycle=10ms) int1 int1 int8 int8 int8 int8 iB0. es por eso que el tiempo mínimo de ejecución es de 10ms #use RTOS(timer=0. rtos_enable(LED_V). max=10ms) void LEDS().Tenemos un total de 4 tareas en la aplicación y el control de la ejecución será el siguiente: Al inicio solamente la tarea LED_R estará habilitada. #task (rate=5s.bits=9) //Como el reloj de este micro se ha puesto a correr con 20MHz el Timer0 no tiene //mayor resolución que 10ms. rtos_disable(LED_R).rcv=PIN_C7. rtos_disable(LEDS). } } iB0). rtos_disable(Task_Disabler). una vez que LED_R ha concluido inicia la tarea LED_V y se autodeshabilita. iCountV = 0. Cuando LEDS se inicia por primera vez habilita LED_R. LED_V y LED_A cuando le toca de nuevo el turno se autodeshabilita y deshabilita a LED_V y LED_A Veamos el código: Código: #include "D:\Documentos\Projects\RTOS\RTOS. iB1. Cuando LED_V concluye habilita LED_A y se autodeshabilita. iCountA = 0. max=10ms) void LED_A(). max=10ms) void Task_Disabler().

//cada tarea sobre todo LED_R que debe continuar rtos_disable(LEDS). para eliminar rebotes en un teclado. Sin embargo lo he dejado así porque en el futuro vamos a ver cómo podemos hacer esto con las propias funciones del RTOS. rtos_enable(LEDS). estará ocupado en perder el tiempo. iCountA = 0. vamos a ver el método de las esperas eficientes. rtos_enable(LED_A). pero hay otros mucho más elaborados que iremos viendo poco a poco. } } iB1). este es un problema del programa. output_bit( PIN_B2. if (iCountV++==27) { iCountV = 0. rtos_enable(LED_A). iB2). a mi llevó menos de dos horas elaborar el texto y escribir el código.} void LED_V() { iB1 = !iB1. void LEDS() { if(!iLEDS) { rtos_enable(LED_R). esperar a que el conversor AD termine y quién sabe cuantas cosas más. Normalmente estas demoras se hacen poniendo al procesador a decrementar contadores y dar saltos recursivos como si fuese un loco. output_bit( PIN_B1. nuestro microcontrolador. [Volver al Índice] > Yield vs delay ¿Cuantas veces en nuestras aplicaciones tenemos que situar demoras para esperar la ocurrencia de un evento determinado? Por ejemplo. iCountR = 0. iLEDS = 1. } } Cuando corran y simulen el ejemplo verán que a veces los LEDs se quedan encendidos o apagados indistintamente. if (iCountA++==33) { iCountA = 0. } else { rtos_disable(LED_V). Durante todo el tiempo de la demora. } } void LED_A() { iB2 = !iB2. rtos_disable(LED_V). consumiría un montón de páginas de explicación y otro montón para el código. Este es un método simple. iLEDS = 0. //ejecutándose otros 5 segundos más rtos_enable(LED_R). rtos_enable(LED_V). Me imagino que sin RTOS me tardaría más de un día completo. ya que lo deseable sería que los LEDs se quedaran apagados cuando termina la función. Como la vez anterior. rtos_disable(LED_A). les dejo de tarea hacerlo sin RTOS para ver como les queda. iCountV = 0. y es por eso que a este mecanismo se le llama espera ocupada. 9 de 22 . La próxima entrega será yield() vs delay(). //Hay que habilitar y deshabilitar explícitamente rtos_disable(LED_A).

#task (rate=30ms. ya que hay que entregar explícitamente el procesador al RTOS para que se lo de a otra tarea.rcv=PIN_C7. El tiempo de procesador para esta tarea debe ser de 10ms. me he pasado de tiempo o no y en consecuencia entregar el procesador al RTOS.parity=N. que especificamos al declarar la función como una tarea del RTOS. Esta debe ejecutarse en un tiempo de 10ms y debe ejecutarse cada 40ms. sin embargo no ocurre lo mismo para el resto de las tareas que están esperando que se les entregue el procesador. esta tarea también enviará.La tarea No. Debe colocar en esta tarea código para que la tarea ceda el procesador en algún momento al RTOS. Para la tarea que está dormida esto no representa nada. En el ejemplo que les traigo hoy vamos a emplear rtos_yield() para ceder el procesador y rtos_overrun() para conocer si una tarea se ha pasado de tiempo. y eso en ocasiones no es lo que deseamos. debería poderse utilizar dentro de una misma tarea para comprobar si desde que me cedieron el procesador para ejecutar. por el puerto serie. El tiempo máximo de ejecución de esta tarea es de 10ms y debe ejecutarse cada 30ms. minor_cycle=10ms. para ello podemos auxiliarnos de la función rtos_overrun(). ha obligado a los diseñadores de SO a crear mecanismos para proteger los datos y hacer uso de esos recursos de forma ordenada y segura. 2 debe esperar a que la tarea No. para indicar que ha concluido. porque pone a dormir a la tarea durante un período completo del valor rate. Código: #include "D:\Documentos\Projects\RTOS\RTOS.La tarea No. max=10ms) 10 de 22 .ocupado en perder el tiempo. Aún así es mejor que el procesador de nuestro PIC esté haciendo algo útil y no perdiendo el tiempo. los recursos del sistema y la no linealidad en la ejecución de las tareas en un sistema que emplea SO.bits=9) #use RTOS(timer=0. Sin embargo yield() no es una función realmente poderosa. Estos mecanismos se clasifican en dos grupos: La sincronización y la coordinación que comenzaremos a ver en la próxima entrega. Esta función no tiene valor si se usa dentro de una tarea para comprobar si ella misma se ha pasado porque la actualización de las estadísticas se hace después de ceder el procesador. sin embargo. . en ese sentido. El uso eficiente del procesador. a ella le da lo mismo ocupar al procesador en hacer nada que en hacer algo productivo. con las siguientes características: . el más sencillo de ellos es aquel que le permite a una tarea decirle al RTOS: ponme a dormir hasta que me toque de nuevo mi turno de ejecutarme. int1 bLed = 0. un mensaje cada vez que le cede el procesador al RTOS. int1 bT1Flag = 0. . Para ese efecto el RTOS de CCS implementa la función rtos_yield(). y es por eso que a este mecanismo se le llama espera ocupada. por lo que si una tarea consume más tiempo de la cuenta puede hacer que el sistema colapse. El uso de rtos_overrun(). 1. 1 termine para enviar por el puerto serie un mensaje similar al de la tarea No. Cuando llegue a ese valor imprime el siguiente mensaje: “Tarea contadora completada” y además pone una bandera a 1. Ejemplo: Implemente en un PIC16F877 una aplicación en la que se ejecuten tres tareas. podemos comprobar si alguna tarea se ha pasado de tiempo. Sin embargo con la función de las estadísticas habilitadas. al menos en este RTOS. Este mecanismo es muy bueno puesto que mientras la tarea “se duerme” nuestro microcontrolador puede dedicarse a realizar otras tareas útiles y hacer de la espera ocupada una espera eficiente. y con ello implementar mecanismos adecuados para que la tarea en cuestión reajuste su dinámica y ceda el procesador oportunamente.xmit=PIN_C6. 1 Tendrá un contador el cual se incrementa en un lazo hasta que alcanza el valor de 1000. Sin embargo un RTOS nos ofrece un conjunto de herramientas para eliminar este molesto inconveniente. En las entregas futuras veremos otras funciones que nos ofrece este RTOS para hacer esperas más eficientes vinculadas al uso de recursos compartidos en nuestras aplicaciones. existe una tarea que se encarga de hacer parpadear un LED conectado en RB0.Por último. Ya sabemos que el RTOS de CCS es cooperativo. Otro caso en que yield() nos puede ser útil es para entregar el procesador cuando nos hemos pasado de tiempo en la ejecución de alguna tarea. statistics) //se utilizan las estadísticas //hace falta para usar rtos_overrun() int32 iT1Counter = 0. considero que esta es una de las debilidades de este RTOS.h" #use rs232(baud=19200. cada 100ms y enviar un menaje por el puerto serie en caso de que la Tarea 1 o la Tarea 2 se hayan pasado en algún momento de su tiempo de ejecución.

for(iT1Counter = 0. me ha servido para ilustrar el uso de las funciones rtos_yield() y rtos_overrun() de la manera más sencilla que encontré. else { printf("dormir T2\r"). dejándole parte de la temporización de las tareas que debe realizar nuestra aplicación y por otra utilizando el tiempo de las demoras ocupadas para hacer que otras tareas ejecuten su código en ese tiempo. Hay muchísimas aplicaciones en las que pueden ser útiles. } void tarea2() { //Aunque esta tarea no tiene que preocuparse mucho por ceder el procesador //porque no tiene lazos infinitos o algo parecido puse de ejemplo tambien a //rtos yield if(bT1Flag) printf("Espera por Tarea1 concluida\r"). max=10ms) void tarea2(). iT1Counter++) { if(!(iT1Counter%100)) //mecanismo para ceder el procesador cada cierto tiempo rtos_yield(). //cuando la tarea entra en contexto se comienza a ejecutar //la línea a continuación de esta } printf("Tarea contadora completada\r"). rtos_yield(). #task (rate=40ms. if(rtos_overrun(Tarea2)) printf("Tarea 2 overrun\r"). iT1Counter <= 1000. El tiempo invertido para hacer el código y el texto. } void Tarea1() { bT1Flag = 0. sin embargo. rtos_run(). Por una parte. void main() { setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1). sin embargo. Sin embargo esas ventajas no son nada comparado con dos herramientas 11 de 22 . max=10ms) void Tarea1(). } Como pueden observar. #task (rate=100ms. max=10ms) void Tarea3(). bT1Flag = 1. if(rtos_overrun(Tarea1)) //Si las tareas se pasaron de su tiempo de ejecución //se envían los mensajes por el pto serie printf("Tarea 1 overrun\r"). output_bit( PIN_B0. Las posibilidades de estas funciones están ahora en sus manos y en lo que sus cabezas puedan crear para ellas.#task (rate=30ms. ya el tema de los RTOS es bastante complicado como para meternos a hacer programas complejos que demuestren su uso. fue de 2:30 hrs. [Volver al Índice] > Coordinar para no dañar Hasta ahora hemos visto al RTOS como un elemento que nos ayuda a “simplificar” el código de nuestra aplicación. la decisión de que ejemplo utilizar me tomó más de un día. } } void Tarea3() { bLed = !bLed. bLed). este ejemplo realmente no se corresponde con una aplicación que haga algo útil.

Con esto queda claro que proteger a los recursos de un acceso no seguro es lo más importante en la coordinación. si cuando uno de los dos va a utilizar la camisa el otro ya se la llevó dejará una marca para indicarle al otro hermano que no podrá utilizar la camisa hasta que el que la marcó haya hecho uso de ella. Cuando se inicia el programa usted inicializa el semáforo en algún valor positivo que determina la cantidad de tareas que pueden utilizar el recurso al mismo tiempo. Sin embargo esas ventajas no son nada comparado con dos herramientas fundamentales que nos ofrecen los SO: la coordinación y la sincronización. La coordinación es un término en la programación para SO. El problemas de la coordinación también se conoce como el problema de la concurrencia o acceso concurrente a recursos. de ellas hoy vamos a ver solamente una de ellas: la coordinación. A la sección de código que utiliza el recurso compartido se le conoce como sección crítica.código en ese tiempo. Un RTOS que implementa un mecanismo de coordinación con sincronización es el LMOS de Darukur. es un concepto orientado a evitar que diferentes tareas puedan acceder a los datos o recursos y poner al sistema en un estado inestable o inseguro. de forma coordinada (para eso se pusieron de acuerdo e hicieron unas reglas simples). Pero hay una regla adicional. así que deben compartirla como buenos hermanos”. llevaremos a ustedes. Para utilizar estas funciones primero hay que crear una variable entera que hará las funciones de percha y. como es el caso del puerto serie o la memoria EEPROM. El semáforo es el elemento que le permite a la tarea reclamar el recurso compartido o esperar por él si ya está en uso. pero en la tienda solo había una así que decidió comprarla de todas formas. o cualquier otro. Las funciones rtos_wait() y rtos_signal() se utilizan para marcar el momento de inicio y fin del código que utiliza el recurso compartido. entonces la decisión de ambos es colocar la camisa en una percha. Para implementar las reglas mostradas en el ejemplo anterior. que hablando con propiedad. 12 de 22 . que veremos dentro de algún tiempo en este foro. Por el momento este simple cursillo es un buen método (no el único) para acercarse al mundo de los RTOS. pero el RTOS de CCS no los implementa. de manera compartida (porque es la única). Cuando llegó a casa llama a sus hijos (ambos usan la misma talla de camisa). según el tipo de aplicación en que se vaya a utilizar. que si tiene en cuenta el factor tiempo. y les dice: “he comprado esta camisa. no importa durante que tiempo alguien (una tarea) esté utilizando el recurso. que se basa en la protección de recursos compartidos. pero en la tienda solamente había una." Este es un mecanismo en que los hermanos se han puesto de acuerdo para utilizar un recurso (la camisa). en este caso el recurso compartido es un recurso de memoria. rtos_wait() y rtos_signal() son los marcadores de inicio y fin del código que hace uso de nuestro recurso compartido. Hay mecanismos de coordinación que implementan también el problema de la sincronización. hasta que no lo libere nadie más podrá utilizarlo. mientras que para tareas que solamente leen datos puede que no se usen secciones críticas o se permita más de una tarea que acceda simultáneamente al recurso compartido. debidamente documentado gracias a un proyecto que Darukur y un servidor. Las palabras de la madre no son alentadoras porque a ambos les gusta mucho la camisa y sin embargo deben compartirla. El recurso compartido puede ser una o varias variables del sistema. y que no tiene en cuenta restricciones temporales". O puede ser un periférico del sistema. Esto puede considerarse una limitante o una ventaja. se llama semáforo. el de la “sincronización” que veremos en la próxima entrega. Veamos la coordinación con un ejemplo sencillo pero bien claro: "Supongamos que una madre ha comprado una camisa muy bonita. el RTOS de CCS tiene dos funciones rtos_wait() y rtos_signal(). normalmente es uno para las tareas que modificarán el recurso compartido. Destripando un poco más a la coordinación diremos que: "la coordinación es el mecanismo que debe implementar un SO para asegurar el acceso seguro a recursos compartidos del sistema. a mi me gusta llamarlo “coordinación” para establecer una mejor diferencia con respecto al otro mecanismo. Ejemplos que ponen de manifiesto el problema de la coordinación hay muchísimos pero en aras de mantener el curso lo más sencillo posible y adecuarlo un poco más a las aplicaciones que normalmente se nos presentan yo utilizaré un problema más cercano a nuestro entorno. Veamos como funciona esto en términos de programación: Usted crea una variable entera que será su semáforo o marcador de uso del recurso compartido. y cada vez que uno de los dos decida utilizarla se la ponga (dejando el perchero vacío). ella quería comprar dos.

aunque en su momento pondremos un ejemplo mejor al respecto. Por otro lado el uso de Periféricos puede complicar más las cosas que el uso simple de la memoria y puede ocurrir que para ser eficientes estemos obligados a poner un rtos_yield() dentro del código de la sección crítica. El código: #include "D:\Documentos\Projects\RTOS\RTOS. 100 botellas. //Esta es la cantidad de botellas en la estera //constituye nuestro recurso compartido #task (rate=50ms. pero eso se los dejo de tarea. ésta se da cuenta que el robot llenó una caja por lo que lee la variable Cantidad y le resta 12 botellas. todo ello gracias a que nuestro RTOS es cooperativo y en este caso. el RTOS decrementará la variable y permitirá que la tarea continúe su ejecución. //Este es nuestro semáforo int16 iCantidad.bits=9) #use RTOS(timer=0. mientras que dos robots llenadores de cajas. Al final del código de la sección crítica hay que colocar un rtos_signal(sem) para que el RTOS incremente el semáforo permitiendo que otra tarea pueda utilizar el recurso compartido.rcv=PIN_C7. Por lo tanto podemos escribir el código de modo que no haya ningún rtos_yield() intercalado con el código que accede a un recurso compartido. max=10ms) void R_Llenador1(). donde no sabemos cuando el RTOS nos quitará el procesador el uso de secciones críticas es OBLIGATORIO. el RTOS comprobará el estado del semáforo. La cinta es alimentada desde un almacén de botellas que tiene un robot que incrementa la variable Cantidad cada vez que coloca una botella en la cinta. mientras la tarea esté ejecutándose tiene todos los recursos para ella sola hasta que entregue el procesador. Analicemos un poco en detalle el problema: Supongamos que la tarea asociada al robot despachador comienza a ejecutarse. minor_cycle=10ms) int8 semaphore. Si el semáforo es mayor que cero.parity=N. #task (rate=50ms. quedando 101 botellas. El robot despachador está conectado a RB0 y cada vez que pone una botella en la cinta transportadora le da un pulso al microcontrolador para indicárselo. pero ese no es el tema de este curso. volverá a dormir a la tarea hasta el próximo turno y así sucesivamente.h" #use rs232(baud=9600. El ejemplo de hoy es el siguiente: Elabore un programa para un PIC16F877 que permita mantener actualizada la cantidad de botellas que hay en un tramo de cinta transportadora en una embotelladora. Moraleja: aprenda a programar con secciones críticas o no programe con RTOS. de esta forma la tarea se asegura el uso exclusivo del recurso compartido… a no ser que aparezcan las interrupciones en la palestra y las cosa se complique. primero que nada. En la próxima entrega utilizaremos la sincronización para resolver el problema de que la cinta se quede vacía o se llene demasiado rápido. Los robots llenadores están conectados uno a RB1 y el otro a RB2 e igualmente dan un pulso al microcontrolador cada vez que despachan una caja. y se va adormir esperando que el robot le notifique que ha puesto una botella en la cinta. decrementan en 12 la variable cantidad cada vez que toman 12 botellas de la cinta transportadora. Como es de suponer este problema lo podemos resolver de muchas maneras y lógicamente sin el uso de los RTOS. ejemplo de ellos es el FreeRTOS). max=10ms) void R_Despachador(). si no. 13 de 22 .Cuando una tarea llegue a la sección de código que hace uso del recurso compartido. debe. Además el mecanismo de notificación de cada robot es un poco deficiente.xmit=PIN_C6. y por eso es importante que dominemos esta técnica. lee el valor de la variable Cantidad. cuando rescatemos a las interrupciones del olvido. ejecutar la función rtos_wait(sem). Como es de suponer el robot despachador debe llenar la cinta más rápido de lo que los robots llenadores la vacían. el RTOS le quitará el procesador a la tarea y se lo cederá a otra que le toque ejecutarse. Sin embargo en un sistema de tiempo compartido (los hay para PIC. Ahora le toca de nuevo a la tarea del robot despachador ejecutarse y como ya se despachó una botella le suma uno al valor previamente leído y actualiza la variable Cantidad. lo decrementará y le dará el procesador a la tarea para que se siga ejecutando. si éste es mayor que cero. sin embargo si el semáforo está en cero. Como datos adicionales suponga que el robot despachador despacha una botella cada 250ms y que los robots llenadores llenan una caja cada 6 segundos. para evitar problemas como los mostrados. quedando Cantidad = 88 botellas. en ese momento el RTOS le da permiso a la tarea del robot llenador 1 para que ejecute su código. Cuando le corresponda nuevamente a la tarea que pidió el acceso al recurso compartido. Este ejemplo puede mejorarse semánticamente y evitarnos el uso de la sección crítica. La duración del pulso es de 100ms. lo cual es falso.

rtos_signal(semaphore). rtos_run(). rtos_wait(semaphore). void main() { semaphore = 1. pero entonces el despachador //no nos daría problemas aunque nos vayamos a dormir dentro de //la sección crítica. // A dormir por otros 100ms para evitar poner dos veces la misma botella } void R_Llenador1() { rtos_wait(semaphore). rtos_signal(semaphore). setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1). max=10ms) void R_Llenador2(). //Este sí es un código lógico. //Inicializamos esta variable para tener algunas botellas en //la estera. //Reclamamos el recurso y aquí comienza la secc crítica Botellas = iCantidad. rtos_yield(). if(input(PIN_B1)==1) iCantidad -= 12. } rtos_signal(semaphore). //Solo una tarea puede utilizar el recurso cada vez iCantidad = 100. } void R_Llenador2() { rtos_wait(semaphore). //Liberamos el semáforo y aquí se acaba la sec crítica rtos_yield(). if(input(PIN_B2)==1) iCantidad -= 12.#task (rate=50ms. } Este programa lo simulé en Proteus poniendo en RB0 una fuente digital de tipo pattern con los siguientes parámetros: First Edge at (Secs)=1 Desmarcar el check mark: Equal Mark/Space Timing? ‘Mark’ Time (Secs) = 100m ‘Sapce’ Time(Secs) = 150m Marcar el check mark: Continuos Secuence of Pulses Marcar el check mark: Standard Hig-Low Pulse Train Para RB1 y RB2 se utiliza una fuente del mismo tipo con los siguientes parárametros cambiados Para RB1: First Edge at (Secs)=5 ‘Mark’ Time (Secs) = 100m ‘Sapce’ Time(Secs) = 5. //Ya sabemos que este código no es eficiente pero iCantidad = Botellas. //Leemos la cantidad de botellas a una variable temporal if(input(PIN_B0)==1) { Botellas++. //sí es didáctico y por eso lo he utilizado así.9 Para RB2: First Edge at (Secs)=7 ‘Mark’ Time (Secs) = 100m 14 de 22 . } void R_Despachador() { int Botellas. rtos_yield().

en realidad los autores de los libros más reconocidos en el tema de los SO. Es decir. un momento. además de llevar la actualización del total de botellas en la cinta. hay una salida en falso del corredor del carril No. Para la implementación de demoras existen varios mecanismos más o menos eficientes. sin embargo un SO no nos ofrece este tipo de mecanismos que podemos llamar un poco primitivos. Con este mecanismo simple vamos a tratar. para en consecuencia hacer algo. vamos a poner que la cantidad máxima de botellas que pueden estar en la cinta es de 100 y que la cantidad mínima de botellas es 24. En el mundo de los uC es frecuente la utilización de demoras para esperar a que ocurran ciertos hechos o eventos. han llamado a estos mecanismos y al que veremos hoy. Supongamos ahora que le hemos colocado a nuestros robots una entrada que cuando está en nivel bajo le indica al robot que no coloque o extraiga botellas de la cinta transportadora. entonces el robot despachador trabajará hasta que llene la cinta transportadora. se decrementa y se entra en la sección crítica. si los robots llenadores dejan de trabajar. En la entrega anterior vimos la coordinación.RB5. si te devuelve falso. 2. o esperar a que la USART saque un dato que está transmitiendo antes de poder escribir nuevamente en el registro de transmisión. La función se llama rtos_await(expr) lo que tienes que pasarle es una expresión lógica que la función evaluará. de aquellos que se dedican a implementar esperas eficientes para eventos por los cuales un proceso debe esperar antes de continuar su ejecución. Ahora bien. [Volver al Índice] > En sus marcas.9 Simulen y vean como cada vez que se pasa por rtos_wait() y si el semáforo es mayor que cero. indicarle a los robots que se detengan cuando la cantidad de botellas en la cinta esté fuera del rango especificado. En los casos en que no conocemos cuanto debemos esperar se pueden utilizar las interrupciones. Para lograr lo anterior. ya que si no evaluamos bien nuestras expresiones o no colocamos la función en el lugar adecuado. y la sincronización es el tema que vamos a tratar hoy en nuestro cursillo de los RTOS para uC. En un caso como el anterior diríamos que ha fallado la sincronización de los corredores en el arranque. sin embargo también son frecuentes aquellas esperas en las que no sabemos cuanto podemos demorarnos. listos. en RB3 al robot despachador. en el otro esperamos a que se produzcan ciertos hechos que determinan el “estado” del sistema. estaremos sometiéndonos a riesgos durante la ejecución de nuestro código. aunque parezca poco esta función es bien poderosa. vamos sincronizar el proceso de llenado/vaciado de la cinta transportadora. vamos a verla con más detenimiento. si el semáforo es 0 entonces la tarea espera a que esté disponible el recurso (semáforo>0) para ejecutar el código de la sección crítica. si esta resulta verdadera entonces continúas la ejecución normal de tu programa.‘Mark’ Time (Secs) = 100m ‘Sapce’ Time(Secs) = 5. de evitar que el robot despachador de botellas llene demasiado la cinta y por el otro que los robots llenadores de cajas traten de llenar las cajas cuando no hay suficientes botellas en la cinta transportadora. La duración de estas demoras puede ser conocida. Para la implementación de demoras eficientes los SO han creado los mecanismos de sincronización de procesos. Además vamos a utilizar el puerto serie para Tx la cantidad de botellas que hay en la cinta en cada momento. como en el caso de esperar a que una LCD coloque los datos en determinados registros antes de enviarle otro dato. por ejemplo: si el robot llenador no tiene botellas que poner en la cinta los robots llenadores trabajarán hasta que la cantidad de botellas se lo permita. Vamos a utilizar como referencia el problema anterior para ver el mecanismo de la sincronización en ejecución.. pero no en todos los casos la espera tiene la misma naturaleza. ¡FUERA! Un momento. en RB4 al robot llenador1 y en RB5 al robot llenador2 Para hacer la sincronización de tareas el RTOS de CCS nos ofrece una sola función. entonces rtos_await() le cederá el procesador al RTOS y la tarea quedará bloqueada hasta que exp se cumpla para entonces continuar en la línea siguiente a la llamada a la función. pero eso implica que tenemos que saber donde ponerla. Las entradas a los robots (salidas de nuestro PIC) las hemos colocado en los pines RB3. Las ventajas de esto son evidentes. “mecanismos de sincronización de procesos”. en uno esperamos por un recurso físico al que queremos acceder y que no está disponible. pero a mi me gusta distinguir entre aquellos que se dedican especialmente a implementar esperas eficientes para la protección de recursos. Es por estas razones que muchas veces esperamos a la ocurrencia de estos eventos haciendo suposiciones y blandiendo demoras. no tan rápido que. por un lado. es cierto que esta función es poderosa y simple. Notemos que en ambos casos la tarea o proceso debe esperar. por lo que cada una de las tareas que atiende a los robots deberá. Por ejemplo puede ocurrir que 15 de 22 . pero en los uC no tenemos interrupciones ilimitadas ni tampoco existe una biblioteca de mecanismos de interrupción disponibles para todos los casos que se nos presentan.

rtos_run(). // A dormir por otros 50ms para evitar poner dos veces la misma botella } 16 de 22 . minor_cycle=10ms) int8 semaphore. los robots estan deshabilitados 0). si no se podia antes. Bien todos esos problemas los veremos más adelante cuando la señorita Némesis (diosa griega de la venganza y la fortuna) haga acto de presencia para echarnos a perder toda la dicha que los RTOS traen al mundo de la programación con uC. max=10ms) void R_Llenador2().parity=N. ya que un //robot revisor de llenado o una persona puede retirar botellas //de la cinta //Al comenzar todos output_bit( PIN_B3. void main() { semaphore = 1. Desde aqui puedes descargar el código fuente y la simulación con Proteus (57Kb.bits=9) #use RTOS(timer=0. //Le decimos al robot despachador que no ponga mas botellas rtos_signal(semaphore).0w \r". //Solo una tarea puede utilizar el recurso cada vez iCantidad = 120. //Esta es la cantidad de botellas en la estera //constituye nuestro recurso compartido #task (rate=50ms.h" #use rs232(baud=9600. poner botellas rtos_wait(semaphore). rtos_yield().rcv=PIN_C7. if(iCantidad >= 100) output_bit( PIN_B3. //Este es nuestro semáforo int16 iCantidad. 1).iCantidad). //A partir de aqui. max=10ms) void R_Llenador1(). El código: #include "D:\Documentos\Projects\RTOS\RTOS. 0). mas adelante veremos //que esto no siempre es posible hacerlo o que las cosas se complican un poco mas //de lo que hemos visto hasta ahora. 0). //Liberamos el semáforo y aquí se acaba la sec crítica printf("%3. //sí es didáctico y por eso lo he utilizado así. //Reclamamos el recurso y aquí comienza la secc crítica if(input(PIN_B0)==1) iCantidad++. #task (rate=50ms. después la cantidad de botellas en la cinta se mantendrá más o menos sobre las 20 y nunca por debajo de 12. output_bit( PIN_B5. output_bit( PIN_B4. setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1). //Inicializamos esta variable para tener algunas botellas en //la estera. //Esperemos a que se vacie un poco la cinta output_bit( PIN_B3. max=10ms) void R_Despachador(). } void R_Despachador() { //Note como hacemos la sincronizacion fuera de la seccion critica. #task (rate=50ms. Las compuertas AND de conjunto con las formas de onda hacen la función de los robots. 0).estaremos sometiéndonos a riesgos durante la ejecución de nuestro código. Por ejemplo puede ocurrir que alguna tarea se quede bloqueada esperando por siempre a que la expresión lógica se evalúe de verdadera y esto nunca ocurra o que la expresión esté mal diseñada y la tarea no se bloquee cuando haga falta. rtos_await(iCantidad<100). normalmente deberiamos tener un sensor que nos reporte //en algun momento el total de botellas en la cinta. formato ZIP) Cuando corran el ejemplo noten como la cantidad de botellas en la cinta va descendiendo hasta que al llegar a las 24 botellas.xmit=PIN_C6.

iCantidad).void R_Llenador1() { //El robot debe esperar a que la cinta tenga suficientes botellas para sacar antes //de comenzar a trabajar. //Esperemos a que se llene un poco la cinta output_bit( PIN_B4. [Volver al Índice] > RTOS mail Hasta el momento solamente hemos visto mecanismos que nos permiten simplificar el diseño de nuestros programas. sin embargo el uso de estas herramientas presupone un montón de complicaciones que he tratado de no mostrar hasta el momento.0w \r". //Esperemos a que se llene un poco la cinta output_bit( PIN_B5. El párrafo anterior describe lo que hace nuestro programa cuando llamamos a una función. //Le decimos al robot que no saque mas botellas rtos_signal(semaphore). //Le decimos al robot que no saque mas botellas rtos_signal(semaphore). 1). 0). Es lógico que un mecanismo como el que acabo de describir no se utilice en las técnicas de programación anterior porque la ejecución secuencial del código presupone que no se 17 de 22 . es frecuente pasarle algún parámetro para que esta pueda hacer su tarea. y si hay que devolver algún resultado entonces que nos envíe un acuse de recibo. Cuando hacemos una llamada a una función. printf("%3. if(iCantidad <= 24) output_bit( PIN_B4. 1). rtos_yield(). sin embargo nunca hemos visto que una función le envíe un dato a otra (no en la llamada a la función. if(input(PIN_B1)==1) iCantidad -= 12. si no se podia antes. sacar botellas rtos_wait(semaphore). Simplemente tendremos que ser un poco más cuidadosos con la semántica de nuestros programas y pensar un poco más en la concepción y modelación de las soluciones a nuestros problemas. // A dormir por otros 50ms para evitar poner dos veces la misma botella } void R_Llenador2() { rtos_await(iCantidad>24).iCantidad). //A partir de aqui. if(iCantidad <= 24) output_bit( PIN_B5. sacar botellas rtos_wait(semaphore). mientras la función trabaja. en aras de mostrar lo útil que puede ser un RTOS. nuestro programa espera pacientemente a que la función retorne y nos devuelva el resultado. 0). En la próxima entrega vamos a ver el uso de las funciones para el paso de mensajes entre tareas. rtos_yield(). if(input(PIN_B2)==1) iCantidad -= 12. pero hoy vamos a ver una nueva potencialidad de los RTOS que es una cuestión realmente novedosa en cuanto la visión de la programación para uC a la cual estamos acostumbrados. que puede ser un valor de retorno. pero aún con esas complicaciones el uso de los RTOS sigue siendo una bendición para cualquier desarrollador de sistemas con uC. un arreglo cambiado o simplemente el cambio en el estado de algún periférico o salidas del uC. rtos_await(iCantidad>24). //A partir de aqui. } Hasta ahora solamente hemos visto ventajas del uso de los RTOS. printf("%3.0w \r". si no se podia antes. que es otra de las ventajas que los RTOS traen al mundo de la programación para uC y cuando terminemos de ver todas esas cosas buenas comenzaremos a estudiar las desgracias y cómo enfrentarlas. sino fuera de ésta) para que cuando le toque ejecutarse tome esos valores y los procese.

Un ejemplo de lo anterior puede se el uso del conversor AD. digamos 200 muestras. y es por ello que los RTOS nos ofrecen un poderoso mecanismo para hacer eso.) Vale. primero entender bien el concepto. Este mecanismo es eficaz porque se ha utilizado durante mucho tiempo. pero los RTOS brindan una solución elegante para hacer esto en el contexto de la ejecución de tareas. cuando una tarea o subrutina de atención a interrupciones necesita notificarle algo a otra tarea llama a una función que pone el dato en la cola de la tarea en cuestión. En la declaración de la tarea el parámetro queue = 5.ustedes llegan y consultan si hay mensajes nuevos en el hilo. procesar y enviar acuse de recibo si es necesario de la otra parte. Posteriormente le toca ejecutarse a la función que promedia los resultados.lo escribo. cuando a la tarea que recibió el mensaje le toca ejecutarse debe. hago lo siguiente: . la declaración de una tarea con cola de mensajes sería como sigue: El código: #task(rate = 1s. Estos mecanismos pueden funcionar también entre funciones pero tendremos el problema de tratar con un montón de estructuras de datos. (Todo esto está muy bien. procese los datos y coloque la bandera en el estado que notifica que ya se procesó. una vez hecha la conversión se produce una interrupción que es atendida en la correspondiente subrutina de atención a esa interrupción. luego ponerlo en práctica. no nos ofusquemos. Para hacer lo anterior los RTOS implementan un mecanismo de mensajes. éstas deben procesar rápidamente el evento de interrupción y pude que pongamos el dato en algún lugar y levantemos una bandera para que cuando a la función encargada de procesar los datos le toque ejecutarse lea la bandera. amigos míos. en algún lugar del programa lo mandamos a convertir. en vez de llamadas a funciones metidas dentro de un código que se ejecuta más o menos estructuradamente. por ejemplo un post. para eso tenemos el parámetro queue. me escribirán un mensaje a mi correo privado. por supuesto llegan cuando su tarea de leer el foro está activa . Sí. lo que tenemos es unas cuantas tareas de las que no tenemos un control de cuando ni como se ejecutarán. Pero yo quiero código y ejemplos de verdad. ésta comprueba si hay. Lo primero que tenemos que hacer es decirle al compilador que le cree una cola de mensajes a aquella tarea a la cual queremos pasarle mensajes. con este parámetro le indicamos al RTOS que reserve memoria y cree una cola de mensajes para la tarea. Realmente puede ser una verdadera pesadilla hacer un programa productivo. si las hay hace el cálculo que pone en otra variable y deja el contador en cero. no para notificar a otra función sino para notificárselo a ella misma. El funcionamiento de ese mecanismo es simple. y como siempre. este es el equivalente a realizar una tarea . este mecanismo también es relativamente simple. Lo más cercano al método descrito en el poner datos en algún lugar para que una función lo procese es el uso de las interrupciones. en algún lugar consultar su cola de mensajes.si hay un mensaje nuevo. complejas expresiones lógicas a procesar… ¿se acuerdan de eso? Ahora imagínense hacer todo lo anterior cuando. si hay mensajes debe leerlos y procesarlos. como pueden ver este mecanismo es bastante parecido a lo que hacemos habitualmente. no vaya a ser que me vuelvan loco) Aquí se ha puesto de manifiesto un ejemplo del sistema de mensajes más simple utilizado por un SO: elaborar y enviar de una parte y consultar si hay un mensaje. queue=5) void Tarea1(void). Para el envío y recepción de mensajes tenemos las siguientes funciones: RTOS_MSG_SEND( ) RTOS_MSG_POLL( ) RTOS_MSG_READ( ) 18 de 22 . este es el equivalente a ponerlo en la cola de mensajes del hilo sobre RTOS . leemos el dato lo ponemos en una variable e incrementamos un contador. un RTOS implementa una función similar a la de los correos. banderas y contadores.utilice en las técnicas de programación anterior porque la ejecución secuencial del código presupone que no se requiera de un mecanismo como este.lo mando al foro. le dice al compilador que cree una cola de 5 bytes para la Tarea1. dándome un acuse de recibo (no hagan esto si no es estrictamente necesario. no vaya a ser que cuando le toque ejecutarse nuevamente procese los mismos resultados nuevamente o haga algo indebido. normalmente tratarán de leerlo y poner en práctica los nuevos conocimientos . dentro de la directiva #task. max=20ms.si se sienten impresionados. Si yo quiero pasarles un mensaje.

max=10ms. max=10ms) void R_Despachador(). output_bit( PIN_B5. byte). #task (rate=50ms. 0). Si la cinta se está vaciando demasiado rápido. minor_cycle=10ms) int8 semaphore. permite leer un byte de la cola de mensajes. //Este es nuestro semáforo int8 iCantidad. no se puede llamar desde otra tarea para conocer cuantos mensajes tiene la tarea fulana. así que si queremos enviar un float o una estructura tendremos que descomponer antes ese dato en bytes y luego componerlos cuando la cola de mensajes sea leída.RTOS_MSG_READ( ) RTOS_MSG_SEND(task.parity=N. Devuelve en un entero la cantidad de bytes ocupados dentro de la cola de mensajes. ya que un //robot revisor de llenado o una persona puede retirar botellas //de la cinta //Al comenzar todos output_bit( PIN_B3. //Esta es la cantidad de botellas en la estera //constituye nuestro recurso compartido #task (rate=50ms. permite enviar un byte de datos a la cola de mensajes de una tarea. rtos_run().rcv=PIN_C7.xmit=PIN_C6. para ello pondremos una tarea adicional que recibirá de las otras tareas el estado de la cantidad de botellas dentro de la cinta. queue=2) //la cola tiene 2 byte aunque solamente necesitamos 1 void Supervisor(). Además. //Inicializamos esta variable para tener algunas botellas en //la estera. #task (rate=50ms. Esta es la única función del RTOS de CCS que puede llamarse desde fuera de una tarea. normalmente deberiamos tener un sensor que nos reporte //en algun momento el total de botellas en la cinta. setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1). int RTOS_MSG_POLL(). void main() { semaphore = 1. El código: #include "D:\Documentos\Projects\RTOS\RTOS. 0). con este mecanismo evitamos que la tarea supervisora tenga que leer el recurso compartido iCantidad. si se está llenando muy rápido pues entonces se deshabilita al robot que despacha botellas hacia la cinta. lo que permite que desde una subrutina de atención a interrupción se le envíen mensajes a una tarea. y que tenga que sincronizarse con los robots. } void R_Despachador() 19 de 22 . por lo que si el dato se pierde no se podrá recuperar. después explico por que hacen falta dos) donde se reflejará la última escritura realizada por cualquiera de los tres robots.bits=9) #use RTOS(timer=0. Para lograr esto cada vez que un robot ejecuta la tarea de llenado de cajas o despacho de botellas le notifica a la tarea reguladora la cantidad de botellas que hay en la cinta. delegaremos en esta tarea la transmisión de la cantidad de botellas que hay en la cinta.h" #use rs232(baud=9600. El parámetro task es para el nombre de la tarea y byte es un dato de 8 bits (un char o un int8). max=10ms) void R_Llenador2(). //Solo una tarea puede utilizar el recurso cada vez iCantidad = 120. La cola de mensajes tendrá dos bytes (aunque solo se necesita uno. vamos a incluir un servicio especial para balancear la cantidad de botellas que hay dentro de la cinta transportadora. max=10ms) void R_Llenador1(). si se llama a la función y no hay mensajes en la cola se pueden obtener datos falsos. int8 RTOS_MSG_READ(). #task (rate=1s. Cuando se ejecuta esta función se lee el byte y se saca de la cola. En el ejemplo que hemos estado viendo sobre la embotelladora. los robots estan deshabilitados 0). output_bit( PIN_B4. es la función que permite a una tarea conocer si tiene mensajes en su cola de mensajes. La cantidad media de botellas a tener en la cinta es 50. esta tarea se encargará de inhabilitar los robots llenadores de cajas.

iCantidad). rtos_yield(). } void Supervisor() { int8 iBotellas. iCantidad). //Esperemos a que se llene un poco la cinta output_bit( PIN_B5. //Le decimos al robot despachador que no ponga mas botellas rtos_msg_send(Supervisor. //Enviamos un mensaje con la cant de botellas //Esperamos a que haya algun mensaje en la cola iBotellas = rtos_msg_read(). si no se podia antes. //Leemos el mensaje 20 de 22 .void R_Despachador() { //Note como hacemos la sincronizacion fuera de la seccion critica. si no se podia antes. si no se podia antes. //Le decimos al robot que no saque mas botellas rtos_msg_send(Supervisor. //Esperemos a que se vacie un poco la cinta output_bit( PIN_B3. rtos_await(iCantidad>24). iCantidad). rtos_signal(semaphore). //Le decimos al robot que no saque mas botellas rtos_msg_send(Supervisor. 1). poner botellas rtos_wait(semaphore). 1). //Enviamos un mensaje con la cant de botellas rtos_signal(semaphore). //Reclamamos el recurso y aquí comienza la secc crítica if(input(PIN_B0)==1) iCantidad++. //sí es didáctico y por eso lo he utilizado así. //A partir de aqui. if(iCantidad >= 100) output_bit( PIN_B3. rtos_await(rtos_msg_poll()>0). mas adelante veremos //que esto no siempre es posible hacerlo o que las cosas se complican un poco mas //de lo que hemos visto hasta ahora. sacar botellas rtos_wait(semaphore). if(input(PIN_B1)==1) iCantidad -= 12. //A partir de aqui. 1). 0). 0). if(input(PIN_B2)==1) iCantidad -= 12. //Liberamos el semáforo y aquí se acaba la sec crítica rtos_yield(). // A dormir por otros 50ms para evitar poner dos veces la misma botella } void R_Llenador2() { rtos_await(iCantidad>24). if(iCantidad <= 24) output_bit( PIN_B4. rtos_await(iCantidad<100). //Enviamos un mensaje con la cant de botellas rtos_signal(semaphore). //A partir de aqui. sacar botellas rtos_wait(semaphore). rtos_yield(). 0). // A dormir por otros 50ms para evitar poner dos veces la misma botella } void R_Llenador1() { //El robot debe esperar a que la cinta tenga suficientes botellas para sacar antes //de comenzar a trabajar. //Esperemos a que se llene un poco la cinta output_bit( PIN_B4. if(iCantidad <= 24) output_bit( PIN_B5.

0). rtos_enable(R_Llenador1). //Leemos el mensaje //Lo que hacemos ahora es comprobar la cantidad de botellas que hay en la estera //y en funcion de eso habilitamos y deshabilitamos las tareas y los robots que hacen falta //para controlar la cantidad de botellas en la estera if(iBotellas > 50) { output_bit( PIN_B3. rtos_enable(R_Llenador2).com [Volver al Índice] > Bibliografía: . output_bit( PIN_B5. la razón no la conozco pero con 1 byte no funciona. Bueno espero que les sirva. Tanenbaum. debe ser algún problema de la implementación de CCS o alguna limitación de los PIC que obliga a ello.com Ciudad de La Habana. Pasa en unos días para ver las novedades. en ningún momento esto se ha probado en una planta de ese tipo. [Volver al Índice] //Transmitir la cantidad de botellas //No llenar mas cajas > Datos de Contacto: Los datos de contacto de Reinier son los siguientes: e-mail: reiniertl@gmail. 21 de 22 . } printf("%3. [Volver al Índice] En construcción. noten como la cantidad de botellas en la cinta transportadora se mantiene sobre las 50 botellas.0w \r". Andrew S. output_bit( PIN_B4. } Este programa se puede simular con el mismo fichero de Proteus que publiqué antes. 0).Sistemas Operativos Diseño e Implementación. //No despachar mas botellas rtos_disable(R_Despachador).iBotellas). 0). } else { rtos_enable(R_Despachador).iBotellas = rtos_msg_read().. PD: Lo de esta embotelladora es un invento.neurona. rtos_disable(R_Llenador1). incluyendo la redacción y programación.. El tiempo que utilicé para este ejemplo fue de 3:30 horas.permanentemente. En el caso de la cola de mensajes deben especificar n+1 bytes de los que necesiten. rtos_disable(R_Llenador2). Cuba WEB provisional: http://reinier-torres-labrada.

Pasa en unos días para ver las novedades. www.com 22 de 22 .ucontrol.Buenos Aires .ar | Desarrollo de sistemas de automatización y control | Pehuajó .com.Argentina e-mail: arielpalazzesi@gmail.