- ¿Qué es el semáforo?
- ¿Cómo usar Semaphore en FreeRTOS?
- Explicación del código de semáforo
- Diagrama de circuito
- ¿Qué es Mutex?
- ¿Cómo usar Mutex en FreeRTOS?
- Explicación del código mutex
En tutoriales anteriores, hemos cubierto los conceptos básicos de FreeRTOS con Arduino y el objeto del kernel Queue en FreeRTOS Arduino. Ahora, en este tercer tutorial de FreeRTOS, aprenderemos más sobre FreeRTOS y sus API avanzadas, que pueden hacerle comprender la plataforma multitarea más profundamente.
Semaphore y Mutex (Mutual Exclusion) son los objetos del kernel que se utilizan para la sincronización, la gestión de recursos y la protección de recursos contra la corrupción. En la primera mitad de este tutorial, veremos la idea detrás de Semaphore, cómo y dónde usarlo. En la segunda mitad, continuaremos con Mutex.
¿Qué es el semáforo?
En tutoriales anteriores, hemos discutido sobre las prioridades de las tareas y también hemos llegado a saber que una tarea de mayor prioridad se adelanta a una tarea de menor prioridad, por lo que mientras se ejecuta una tarea de alta prioridad, puede existir la posibilidad de que se dañen los datos en una tarea de menor prioridad porque aún no se ejecuta y los datos llegan continuamente a esta tarea desde un sensor que causa la pérdida de datos y el mal funcionamiento de toda la aplicación.
Por lo tanto, existe la necesidad de proteger los recursos de la pérdida de datos y aquí Semaphore juega un papel importante.
El semáforo es un mecanismo de señalización en el que una tarea en estado de espera es señalada por otra tarea para su ejecución. En otras palabras, cuando una tarea1 termina su trabajo, entonces mostrará una bandera o incrementará una bandera en 1 y luego esta bandera es recibida por otra tarea (tarea2) mostrando que puede realizar su trabajo ahora. Cuando task2 termine su trabajo, la bandera disminuirá en 1.
Entonces, básicamente, es un mecanismo de "Dar" y "Tomar" y el semáforo es una variable entera que se usa para sincronizar el acceso a los recursos.
Tipos de semáforo en FreeRTOS:
El semáforo es de dos tipos.
- Semáforo binario
- Contando semáforo
1. Semáforo binario: Tiene dos valores enteros 0 y 1. Es algo similar a la Cola de longitud 1. Por ejemplo, tenemos dos tareas, tarea1 y tarea2. Task1 envía datos a task2 para que task2 verifique continuamente el elemento de la cola si hay 1, luego puede leer los datos; de lo contrario, tiene que esperar hasta que se convierta en 1. Después de tomar los datos, task2 reduce la cola y la convierte en 0 Eso significa task1 nuevamente puede enviar los datos a task2.
Del ejemplo anterior, se puede decir que el semáforo binario se usa para la sincronización entre tareas o entre tareas e interrupción.
2. Semáforo de recuento: Tiene valores superiores a 0 y se puede pensar en una cola de longitud superior a 1. Este semáforo se utiliza para contar eventos. En este escenario de uso, un controlador de eventos 'dará' un semáforo cada vez que ocurre un evento (incrementando el valor de recuento del semáforo), y una tarea del controlador 'tomará' un semáforo cada vez que procese un evento (disminuyendo el valor de recuento del semáforo).
El valor de recuento es, por tanto, la diferencia entre el número de eventos que han ocurrido y el número que se ha procesado.
Ahora, veamos cómo usar Semaphore en nuestro código FreeRTOS.
¿Cómo usar Semaphore en FreeRTOS?
FreeRTOS admite diferentes API para crear un semáforo, tomar un semáforo y dar un semáforo.
Ahora, puede haber dos tipos de API para el mismo objeto del kernel. Si tenemos que dar un semáforo desde un ISR, entonces no se puede usar la API de semáforo normal. Debería utilizar API protegidas contra interrupciones.
En este tutorial, usaremos un semáforo binario porque es fácil de entender e implementar. Como aquí se utiliza la funcionalidad de interrupción, debe utilizar API protegidas contra interrupciones en la función ISR. Cuando decimos sincronizar una tarea con una interrupción, significa poner la tarea en estado de ejecución justo después del ISR.
Creando un semáforo:
Para usar cualquier objeto del kernel, primero tenemos que crearlo. Para crear un semáforo binario, use vSemaphoreCreateBinary ().
Esta API no toma ningún parámetro y devuelve una variable de tipo SemaphoreHandle_t. Se crea un nombre de variable global sema_v para almacenar el semáforo.
SemaphoreHandle_t sema_v; sema_v = xSemaphoreCreateBinary ();
Dando un semáforo:
Para dar un semáforo, hay dos versiones: una para interrupción y otra para la tarea normal.
- xSemaphoreGive (): esta API solo toma un argumento que es el nombre de la variable del semáforo como sema_v como se indica arriba mientras se crea un semáforo. Se puede llamar desde cualquier tarea normal que desee sincronizar.
- xSemaphoreGiveFromISR (): esta es la versión API protegida contra interrupciones de xSemaphoreGive (). Cuando necesitamos sincronizar un ISR y una tarea normal, entonces xSemaphoreGiveFromISR () debe usarse desde la función ISR.
Tomando un semáforo:
Para tomar un semáforo, use la función API xSemaphoreTake (). Esta API toma dos parámetros.
xSemaphoreTake (SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
xSemaphore: Nombre del semáforo a tomar en nuestro caso sema_v.
xTicksToWait: esta es la cantidad máxima de tiempo que la tarea esperará en estado Bloqueado para que el semáforo esté disponible. En nuestro proyecto, configuraremos xTicksToWait en portMAX_DELAY para hacer que task_1 espere indefinidamente en el estado Bloqueado hasta que sema_v esté disponible.
Ahora, usemos estas API y escribamos un código para realizar algunas tareas.
Aquí se interconectan un botón y dos LED. El pulsador actuará como un botón de interrupción que se adjunta al pin 2 de Arduino Uno. Cuando se presiona este botón se generará una interrupción y un LED que está conectado al pin 8 se encenderá y cuando lo presione nuevamente se apagará.
Entonces, cuando se presiona el botón xSemaphoreGiveFromISR () se llamará desde la función ISR y la función xSemaphoreTake () se llamará desde la función TaskLED.
Para que el sistema parezca multitarea, conecte otros LED con el pin 7 que estará siempre parpadeando.
Explicación del código de semáforo
Comencemos a escribir código abriendo el IDE de Arduino
1. Primero, incluya el archivo de encabezado Arduino_FreeRTOS.h . Ahora, si se utiliza cualquier objeto del kernel como semáforo de cola, también se debe incluir un archivo de encabezado.
#incluir #incluir
2. Declare una variable de tipo SemaphoreHandle_t para almacenar los valores de semáforo.
SemaphoreHandle_t interruptSemaphore;
3. En void setup (), cree dos tareas (TaskLED y TaskBlink) usando la API xTaskCreate () y luego cree un semáforo usando xSemaphoreCreateBinary (). Cree una tarea con las mismas prioridades y luego intente jugar con este número. Además, configure el pin 2 como entrada y habilite la resistencia pull-up interna y conecte el pin de interrupción. Finalmente, inicie el programador como se muestra a continuación.
configuración vacía () { pinMode (2, INPUT_PULLUP); xTaskCreate (TaskLed, "Led", 128, NULL, 0, NULL); xTaskCreate (TaskBlink, "LedBlink", 128, NULL, 0, NULL); interruptSemaphore = xSemaphoreCreateBinary (); if (interruptSemaphore! = NULL) { attachInterrupt (digitalPinToInterrupt (2), debounceInterrupt, LOW); } }
4. Ahora, implemente la función ISR. Cree una función y asígnele el mismo nombre que el segundo argumento de la función attachInterrupt () . Para que la interrupción funcione correctamente, debe eliminar el problema de supresión de rebotes del botón mediante la función millis o micros y ajustando el tiempo de supresión de rebotes. Desde esta función, llame a la función interruptHandler () como se muestra a continuación.
tiempo de rebote largo = 150; volatile unsigned long last_micros; void debounceInterrupt () { if ((long) (micros () - last_micros)> = debounce_time * 1000) { interruptHandler (); last_micros = micros (); } }
En la función interruptHandler () , llame a la API xSemaphoreGiveFromISR () .
void interruptHandler () { xSemaphoreGiveFromISR (interruptSemaphore, NULL); }
Esta función le dará un semáforo a TaskLed para encender el LED.
5. Crear un TaskLed función y dentro del tiempo de bucle, llame xSemaphoreTake () de la API y comprobar si el semáforo se toma o no con éxito. Si es igual a pdPASS (es decir, 1), haga que el LED cambie como se muestra a continuación.
void TaskLed (void * pvParameters) { (void) pvParameters; pinMode (8, SALIDA); while (1) { if (xSemaphoreTake (interruptSemaphore, portMAX_DELAY) == pdPASS) { digitalWrite (8,! digitalRead (8)); } } }
6. Además, cree una función para hacer parpadear otro LED conectado al pin 7.
void TaskLed1 (void * pvParameters) { (void) pvParameters; pinMode (7, SALIDA); while (1) { digitalWrite (7, HIGH); vTaskDelay (200 / portTICK_PERIOD_MS); digitalWrite (7, BAJO); vTaskDelay (200 / portTICK_PERIOD_MS); } }
7. La función de bucle vacío permanecerá vacía. No lo olvides.
bucle vacío () {}
Eso es todo, el código completo se puede encontrar al final de este tutorial. Ahora, cargue este código y conecte los LED y el pulsador con el Arduino UNO de acuerdo con el diagrama del circuito.
Diagrama de circuito
Después de cargar el código, verá que un LED parpadea después de 200ms y cuando se presiona el botón, inmediatamente el segundo LED se iluminará como se muestra en el video que se muestra al final.
De esta manera, los semáforos se pueden usar en FreeRTOS con Arduino donde necesita pasar los datos de una tarea a otra sin ninguna pérdida.
Ahora, veamos qué es Mutex y cómo usarlo FreeRTOS.
¿Qué es Mutex?
Como se explicó anteriormente, el semáforo es un mecanismo de señalización, de manera similar, Mutex es un mecanismo de bloqueo a diferencia del semáforo que tiene funciones separadas para incrementar y disminuir, pero en Mutex, la función toma y cede en sí misma. Es una técnica para evitar la corrupción de recursos compartidos.
Para proteger el recurso compartido, se asigna una tarjeta simbólica (mutex) al recurso. Quien tenga esta tarjeta puede acceder al otro recurso. Otros deben esperar hasta que se les devuelva la tarjeta. De esta forma, solo un recurso puede acceder a la tarea y otros esperan su oportunidad.
Vamos a entender mutex FreeRTOS con la ayuda de un ejemplo.
Aquí tenemos tres tareas, una para imprimir datos en LCD, la segunda para enviar datos LDR a la tarea LCD y la última tarea para enviar datos de temperatura en LCD. Entonces, aquí dos tareas comparten el mismo recurso, es decir, LCD. Si la tarea LDR y la tarea de temperatura envían datos simultáneamente, es posible que uno de los datos esté dañado o perdido.
Entonces, para proteger la pérdida de datos, necesitamos bloquear el recurso LCD para la tarea 1 hasta que finalice la tarea de visualización. Luego, la tarea de la pantalla LCD se desbloqueará y la tarea2 podrá realizar su trabajo.
Puede observar el funcionamiento de Mutex y semáforos en el siguiente diagrama.
¿Cómo usar Mutex en FreeRTOS?
Los mutex también se utilizan de la misma forma que los semáforos. Primero, créelo, luego dé y reciba usando las respectivas API.
Creando un Mutex:
Para crear un Mutex, use la API xSemaphoreCreateMutex () . Como sugiere su nombre, Mutex es un tipo de semáforo binario. Se utilizan en diferentes contextos y propósitos. Un semáforo binario sirve para sincronizar tareas, mientras que Mutex se utiliza para proteger un recurso compartido.
Esta API no toma ningún argumento y devuelve una variable de tipo SemaphoreHandle_t . Si no se puede crear el mutex, xSemaphoreCreateMutex () devuelve NULL.
SemaphoreHandle_t mutex_v; mutex_v = xSemaphoreCreateMutex ();
Tomando un Mutex:
Cuando una tarea quiere acceder a un recurso, tomará un Mutex usando la API xSemaphoreTake () . Es lo mismo que un semáforo binario. También toma dos parámetros.
xSemaphore: Nombre del Mutex a tomar en nuestro caso mutex_v .
xTicksToWait: esta es la cantidad máxima de tiempo que la tarea esperará en estado Bloqueado para que Mutex esté disponible. En nuestro proyecto, configuraremos xTicksToWait en portMAX_DELAY para que task_1 espere indefinidamente en el estado Bloqueado hasta que mutex_v esté disponible.
Dar un mutex:
Después de acceder al recurso compartido, la tarea debe devolver el Mutex para que otras tareas puedan acceder a él. La API xSemaphoreGive () se utiliza para devolver el Mutex.
La función xSemaphoreGive () toma solo un argumento que es el Mutex que se dará en nuestro caso mutex_v.
Usando las API anteriores, implementemos Mutex en el código FreeRTOS usando Arduino IDE.
Explicación del código mutex
Aquí el objetivo de esta parte es utilizar un monitor serial como recurso compartido y dos tareas diferentes para acceder al monitor serial para imprimir algún mensaje.
1. Los archivos de encabezado seguirán siendo los mismos que un semáforo.
#incluir #incluir
2. Declare una variable de tipo SemaphoreHandle_t para almacenar los valores de Mutex.
SemaphoreHandle_t mutex_v;
3. En void setup (), inicialice el monitor en serie con una velocidad de 9600 baudios y cree dos tareas (Task1 y Task2) utilizando la API xTaskCreate () . Luego cree un Mutex usando xSemaphoreCreateMutex (). Crea una tarea con las mismas prioridades y luego intenta jugar con este número.
configuración vacía () { Serial.begin (9600); mutex_v = xSemaphoreCreateMutex (); if (mutex_v == NULL) { Serial.println ("No se puede crear mutex "); } xTaskCreate (Task1, "Task 1", 128, NULL, 1, NULL); xTaskCreate (Task2, "Task 2", 128, NULL, 1, NULL); }
4. Ahora, cree funciones de tarea para Task1 y Task2. En un tiempo de bucle de la función de tareas, antes de imprimir un mensaje en el monitor serie tenemos que tener un mutex usando xSemaphoreTake () a continuación, imprimir el mensaje y luego devolver el objeto mutex usando xSemaphoreGive (). Entonces dale un poco de retraso.
void Task1 (void * pvParameters) { while (1) { xSemaphoreTake (mutex_v, portMAX_DELAY); Serial.println ("Hola de Task1"); xSemaphoreGive (mutex_v); vTaskDelay (pdMS_TO_TICKS (1000)); } }
De manera similar, implemente la función Task2 con un retraso de 500ms.
5. El bucle vacío () permanecerá vacío.
Ahora, cargue este código en Arduino UNO y abra el monitor serial.
Verá que los mensajes se están imprimiendo desde task1 y task2.
Para probar el funcionamiento de Mutex, simplemente comente xSemaphoreGive (mutex_v); de cualquier tarea. Puede ver que el programa se bloquea en el último mensaje de impresión .
Así es como Semaphore y Mutex se pueden implementar en FreeRTOS con Arduino. Para obtener más información sobre Semaphore y Mutex, puede visitar la documentación oficial de FreeRTOS.
Los códigos completos y el video para Semaphore y Mutes se dan a continuación.