- O que é semáforo?
- Como usar o Semaphore no FreeRTOS?
- Explicação do código do semáforo
- Diagrama de circuito
- O que é Mutex?
- Como usar Mutex no FreeRTOS?
- Explicação do código mutex
Em tutoriais anteriores, cobrimos os fundamentos do FreeRTOS com Arduino e o objeto do kernel Queue no FreeRTOS Arduino. Agora, neste terceiro tutorial do FreeRTOS, aprenderemos mais sobre o FreeRTOS e suas APIs avançadas, que podem fazer com que você entenda mais profundamente a plataforma multitarefa.
Semáforo e Mutex (exclusão mútua) são os objetos do kernel usados para sincronização, gerenciamento de recursos e proteção de recursos contra corrupção. Na primeira metade deste tutorial, veremos a ideia por trás do Semaphore, como e onde usá-lo. No segundo semestre, continuaremos com a Mutex.
O que é semáforo?
Em tutoriais anteriores, discutimos sobre as prioridades das tarefas e também sabemos que uma tarefa de prioridade mais alta antecipa uma tarefa de prioridade mais baixa, portanto, durante a execução de uma tarefa de alta prioridade, pode haver a possibilidade de que a corrupção de dados aconteça em uma tarefa de prioridade mais baixa porque ainda não é executado e os dados estão chegando continuamente para esta tarefa de um sensor que causa perda de dados e mau funcionamento de todo o aplicativo.
Portanto, há uma necessidade de proteger os recursos da perda de dados e aqui o Semaphore desempenha um papel importante.
O semáforo é um mecanismo de sinalização no qual uma tarefa em estado de espera é sinalizada por outra tarefa para execução. Em outras palavras, quando uma tarefa1 finaliza seu trabalho, ela irá mostrar um sinalizador ou incrementar um sinalizador em 1 e então este sinalizador é recebido por outra tarefa (tarefa2) mostrando que ele pode realizar seu trabalho agora. Quando a tarefa2 terminar seu trabalho, o sinalizador será reduzido em 1.
Então, basicamente, é um mecanismo de “Dar” e “Pegar” e o semáforo é uma variável inteira que é usada para sincronizar o acesso aos recursos.
Tipos de semáforo no FreeRTOS:
O semáforo é de dois tipos.
- Semáforo Binário
- Contando semáforo
1. Semáforo binário: tem dois valores inteiros 0 e 1. É um pouco semelhante à fila de comprimento 1. Por exemplo, temos duas tarefas, tarefa1 e tarefa2. Tarefa1 envia dados para tarefa2 para que tarefa2 verifique continuamente o item da fila se houver 1, então ela pode ler os dados, caso contrário, tem que esperar até que se torne 1. Depois de obter os dados, tarefa2 diminui a fila e torna-a 0 Isso significa tarefa1 novamente pode enviar os dados para task2.
Pelo exemplo acima, pode-se dizer que o semáforo binário é usado para sincronização entre tarefas ou entre tarefas e interrupção.
2. Semáforo de contagem: Possui valores maiores que 0 e pode ser considerado uma fila de comprimento maior que 1. Este semáforo é usado para eventos de contagem. Neste cenário de uso, um manipulador de eventos 'dará' um semáforo cada vez que um evento ocorrer (incrementando o valor da contagem do semáforo), e uma tarefa manipuladora 'pegará' um semáforo cada vez que processa um evento (diminuindo o valor da contagem do semáforo).
O valor da contagem é, portanto, a diferença entre o número de eventos que ocorreram e o número que foi processado.
Agora, vamos ver como usar o Semaphore em nosso código FreeRTOS.
Como usar o Semaphore no FreeRTOS?
FreeRTOS suporta APIs diferentes para criar um semáforo, pegar um semáforo e dar um semáforo.
Agora, pode haver dois tipos de APIs para o mesmo objeto kernel. Se tivermos que fornecer um semáforo de um ISR, a API de semáforo normal não poderá ser usada. Você deve usar APIs protegidas contra interrupções.
Neste tutorial, usaremos semáforo binário porque é fácil de entender e implementar. Como a funcionalidade de interrupção é usada aqui, você precisa usar APIs protegidas contra interrupção na função ISR. Quando estamos dizendo sincronizando uma tarefa com uma interrupção, significa colocar a tarefa no estado Running logo após o ISR.
Criando um semáforo:
Para usar qualquer objeto de kernel, primeiro temos que criá-lo. Para criar um semáforo binário, use vSemaphoreCreateBinary ().
Esta API não aceita nenhum parâmetro e retorna uma variável do tipo SemaphoreHandle_t. Um nome de variável global sema_v é criado para armazenar o semáforo.
SemaphoreHandle_t sema_v; sema_v = xSemaphoreCreateBinary ();
Dando um semáforo:
Para dar um semáforo, existem duas versões - uma para interrupção e outra para a tarefa normal.
- xSemaphoreGive (): Esta API leva apenas um argumento que é o nome da variável do semáforo como sema_v conforme fornecido acima ao criar um semáforo. Ele pode ser chamado a partir de qualquer tarefa normal que você deseja sincronizar.
- xSemaphoreGiveFromISR (): Esta é a versão API protegida contra interrupção de xSemaphoreGive (). Quando precisamos sincronizar um ISR e uma tarefa normal, xSemaphoreGiveFromISR () deve ser usado a partir da função ISR.
Tomando um semáforo:
Para obter um semáforo, use a função xSemaphoreTake () da API. Esta API usa dois parâmetros.
xSemaphoreTake (SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
xSemaphore: Nome do semáforo a ser usado em nosso caso sema_v.
xTicksToWait: Esta é a quantidade máxima de tempo que a tarefa aguardará no estado Bloqueado até que o semáforo fique disponível. Em nosso projeto, definiremos xTicksToWait como portMAX_DELAY para fazer com que task_1 espere indefinidamente no estado Bloqueado até que sema_v esteja disponível.
Agora, vamos usar essas APIs e escrever um código para realizar algumas tarefas.
Aqui, um botão de pressão e dois LEDs são conectados. O botão de pressão atuará como um botão de interrupção conectado ao pino 2 do Arduino Uno. Quando este botão é pressionado, uma interrupção será gerada e um LED que está conectado ao pino 8 será LIGADO e quando você pressioná-lo novamente estará DESLIGADO.
Portanto, quando o botão é pressionado, xSemaphoreGiveFromISR () será chamado a partir da função ISR e a função xSemaphoreTake () será chamada a partir da função TaskLED.
Para fazer o sistema parecer multitarefa, conecte outros LEDs com o pino 7 que estarão sempre piscando.
Explicação do código do semáforo
Vamos começar a escrever código abrindo o IDE do Arduino
1. Primeiro, inclua o arquivo de cabeçalho Arduino_FreeRTOS.h . Agora, se qualquer objeto de kernel for usado como semáforo de fila, um arquivo de cabeçalho também deve ser incluído para ele.
#include #include
2. Declare uma variável do tipo SemaphoreHandle_t para armazenar os valores do semáforo.
SemaphoreHandle_t interruptSemaphore;
3. Em void setup (), crie duas tarefas (TaskLED e TaskBlink) usando a API xTaskCreate () e, em seguida, crie um semáforo usando xSemaphoreCreateBinary (). Crie uma tarefa com prioridades iguais e depois tente jogar com este número. Além disso, configure o pino 2 como uma entrada e habilite o resistor pull-up interno e conecte o pino de interrupção. Finalmente, inicie o agendador conforme mostrado abaixo.
void setup () { 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. Agora, implemente a função ISR. Faça uma função e nomeie-a igual ao segundo argumento da função attachInterrupt () . Para fazer a interrupção funcionar corretamente, você precisa remover o problema de debounce do botão usando a função millis ou micros e ajustando o tempo de debounce. A partir desta função, chame a função interruptHandler () conforme mostrado abaixo.
longo debouncing_time = 150; volatile unsigned long last_micros; void debounceInterrupt () { if ((longo) (micros () - last_micros)> = debouncing_time * 1000) { interruptHandler (); last_micros = micros (); } }
Na função interruptHandler () , chame a API xSemaphoreGiveFromISR () .
void interruptHandler () { xSemaphoreGiveFromISR (interruptSemaphore, NULL); }
Esta função dará um semáforo ao TaskLed para ligar o LED.
5. Criar um TaskLed função e dentro da enquanto loop, chamar xSemaphoreTake () API e verifique se o semáforo é tomado ou não com sucesso. Se for igual a pdPASS (ou seja, 1), alterne o LED conforme mostrado abaixo.
void TaskLed (void * pvParameters) { (void) pvParameters; pinMode (8, OUTPUT); while (1) { if (xSemaphoreTake (interruptSemaphore, portMAX_DELAY) == pdPASS) { digitalWrite (8,! digitalRead (8)); } } }
6. Além disso, crie uma função para piscar outro LED conectado ao pino 7.
void TaskLed1 (void * pvParameters) { (void) pvParameters; pinMode (7, OUTPUT); enquanto (1) { digitalWrite (7, HIGH); vTaskDelay (200 / portTICK_PERIOD_MS); digitalWrite (7, LOW); vTaskDelay (200 / portTICK_PERIOD_MS); } }
7. A função void loop permanecerá vazia. Não se esqueça disso.
void loop () {}
É isso, o código completo pode ser encontrado no final deste tutorial. Agora, carregue este código e conecte os LEDs e o botão de pressão com o Arduino UNO de acordo com o diagrama de circuito.
Diagrama de circuito
Após fazer o upload do código, você verá um LED piscando após 200ms e quando o botão for pressionado, imediatamente o segundo LED acenderá conforme mostrado no vídeo fornecido ao final.
Desta forma, semáforos podem ser usados no FreeRTOS com Arduino, onde é necessário passar os dados de uma tarefa para outra sem nenhuma perda.
Agora, vamos ver o que é Mutex e como usá-lo no FreeRTOS.
O que é Mutex?
Como explicado acima, o semáforo é um mecanismo de sinalização, da mesma forma, Mutex é um mecanismo de bloqueio diferente do semáforo que tem funções separadas para incremento e decremento, mas no Mutex, a função pega e dá em si mesma. É uma técnica para evitar a corrupção de recursos compartilhados.
Para proteger o recurso compartilhado, atribui-se um cartão token (mutex) ao recurso. Quem tiver este cartão pode acessar o outro recurso. Outros devem esperar até que o cartão seja devolvido. Dessa forma, apenas um recurso pode acessar a tarefa e os outros aguardam sua chance.
Vamos entender Mutex em FreeRTOS com a ajuda de um exemplo.
Aqui temos três tarefas, uma para imprimir dados no LCD, a segunda para enviar dados LDR para a tarefa LCD e a última tarefa para enviar dados de temperatura no LCD. Portanto, aqui duas tarefas estão compartilhando o mesmo recurso, ou seja, LCD. Se a tarefa LDR e a tarefa de temperatura enviam dados simultaneamente, um dos dados pode ser corrompido ou perdido.
Portanto, para proteger a perda de dados, precisamos bloquear o recurso LCD para a tarefa1 até que ele conclua a tarefa de exibição. Em seguida, a tarefa do LCD será desbloqueada e a tarefa2 poderá realizar seu trabalho.
Você pode observar o funcionamento de Mutex e semáforos no diagrama abaixo.
Como usar Mutex no FreeRTOS?
Mutexs também são usados da mesma maneira que semáforos. Primeiro, crie-o e depois dê e receba usando as respectivas APIs.
Criação de um Mutex:
Para criar um Mutex, use a API xSemaphoreCreateMutex () . Como o próprio nome sugere, Mutex é um tipo de semáforo binário. Eles são usados em diferentes contextos e propósitos. Um semáforo binário é para sincronizar tarefas enquanto Mutex é usado para proteger um recurso compartilhado.
Esta API não aceita nenhum argumento e retorna uma variável do tipo SemaphoreHandle_t . Se o mutex não puder ser criado, xSemaphoreCreateMutex () retornará NULL.
SemaphoreHandle_t mutex_v; mutex_v = xSemaphoreCreateMutex ();
Tomando um Mutex:
Quando uma tarefa deseja acessar um recurso, ela levará um Mutex usando a API xSemaphoreTake () . É o mesmo que um semáforo binário. Também leva dois parâmetros.
xSemaphore: Nome do Mutex a ser usado em nosso caso mutex_v .
xTicksToWait: Este é o tempo máximo que a tarefa aguardará no estado Bloqueado para que o Mutex fique disponível. Em nosso projeto, definiremos xTicksToWait como portMAX_DELAY para fazer com que task_1 espere indefinidamente no estado Bloqueado até que mutex_v esteja disponível.
Dando um Mutex:
Após acessar o recurso compartilhado, a tarefa deve retornar o Mutex para que outras tarefas possam acessá-lo. A API xSemaphoreGive () é usada para devolver o Mutex.
A função xSemaphoreGive () leva apenas um argumento que é o Mutex a ser fornecido em nosso caso mutex_v.
Usando as APIs acima, vamos implementar Mutex no código do FreeRTOS usando o IDE do Arduino.
Explicação do código mutex
Aqui, o objetivo desta parte é usar um monitor serial como um recurso compartilhado e duas tarefas diferentes para acessar o monitor serial para imprimir alguma mensagem.
1. Os arquivos de cabeçalho permanecerão iguais a um semáforo.
#include #include
2. Declare uma variável do tipo SemaphoreHandle_t para armazenar os valores de Mutex.
SemaphoreHandle_t mutex_v;
3. Em void setup (), inicialize o monitor serial com taxa de transmissão de 9600 e crie duas tarefas (Tarefa1 e Tarefa2) usando a API xTaskCreate () . Em seguida, crie um Mutex usando xSemaphoreCreateMutex (). Crie uma tarefa com prioridades iguais e depois tente brincar com este número.
void setup () { Serial.begin (9600); mutex_v = xSemaphoreCreateMutex (); if (mutex_v == NULL) { Serial.println ("Mutex não pode ser criado"); } xTaskCreate (Tarefa1, "Tarefa 1", 128, NULL, 1, NULL); xTaskCreate (Tarefa2, "Tarefa 2", 128, NULL, 1, NULL); }
4. Agora, crie funções de tarefa para Tarefa1 e Tarefa2. Em um enquanto circuito de função de tarefa, antes de imprimir uma mensagem no monitor serial que tem que tomar um Mutex usando xSemaphoreTake () , em seguida, imprimir a mensagem e, em seguida, retornar o Mutex usando xSemaphoreGive (). Então dê algum atraso.
void Task1 (void * pvParameters) { while (1) { xSemaphoreTake (mutex_v, portMAX_DELAY); Serial.println ("Olá da Tarefa1"); xSemaphoreGive (mutex_v); vTaskDelay (pdMS_TO_TICKS (1000)); } }
Da mesma forma, implemente a função Task2 com um atraso de 500 ms.
5. O loop vazio () permanecerá vazio.
Agora, carregue este código no Arduino UNO e abra o monitor serial.
Você verá que as mensagens estão sendo impressas da tarefa1 e da tarefa2.
Para testar o funcionamento do Mutex, basta comentar xSemaphoreGive (mutex_v); de qualquer tarefa. Você pode ver que o programa trava na última mensagem de impressão .
É assim que o Semaphore e o Mutex podem ser implementados no FreeRTOS com Arduino. Para obter mais informações sobre o Semaphore e Mutex, você pode visitar a documentação oficial do FreeRTOS.
Códigos completos e vídeo para Semáforo e Mutes são fornecidos abaixo.