Synergy e ThreadX: introdução a multitarefa

As aplicações de microcontroladores estão cada vez mais complexas, envolvendo mais memória e periféricos mais e mais complexos. Este aumento de complexidade torna necessário utilizar ferramentas de software para facilitar o desenvolvimento de aplicações, de forma que o projetista ou equipe de desenvolvimento possa concentrar seus esforços no design de alto nível da aplicação, ao invés de perder tempo com a escrita de software de mais baixo nível.

Dentre as ferramentas de software comumente utilizadas, o emprego de sistemas operacionais de tempo real (RTOS) vem ganhando cada vez mais espaço, vez que um RTOS permite abordar problemas complexos de uma forma mais simples do que no paradigma de processamento em tarefa única.

Neste artigo vamos abordar a plataforma Synergy da Renesas, que procura solucionar o problema da otimização do tempo de desenvolvimento (time-to-market) adotando um caminho pouco usual: oferecer uma solução de silício baseada na linha Cortex-M da ARM e incluir uma plataforma de software baseada em Eclipse, GCC e no ThreadX, um poderoso sistema operacional de tempo real comercial. Apesar de muitos fabricantes contarem com suporte de RTOS, a diferença aqui é que a Renesas e a Express Logic (a fabricante do ThreadX) customizaram o RTOS e incluíram uma pilha de software com drivers para todos os periféricos internos disponíveis na linha Synergy (o chamado SSP ou Synergy Support Package)! O resultado disso é que é muito mais fácil e rápido desenvolver aplicações complexas, como as que envolvam comunicação (especialmente Ethernet, Internet e USB), interfaces Homem-Máquina (IHMs) gráficas com displays coloridos e com telas de toque, etc. A linha Synergy subdivide-se em quatro linhas:

  • S1: microcontroladores baseados em Cortex-M0+ (32MHz, 64 a 256kb de Flash, 16 a 24kb de RAM, 4kb de EEPROM, encapsulamentos de 36 a 64 pinos e capazes de operar de 1.6 a 5.5V);
  • S3: microcontroladores baseados em Cortex-M4 (48MHz, até 1Mb de Flash, até 192kb de RAM, 16kb de EEPROM, encapsulamentos de 64 a 145 pinos e capazes de operar de 1.6 a 5.5V);
  • S5: microcontroladores baseados em Cortex-M4 (120MHz, até 2Mb de Flash, 640kb de RAM, 64kb de EEPROM, encapsulamentos de 100 a 176 pinos e capazes de operar de 2.7 a 3.6V);
  • S7: microcontroladores topo de gama baseados em Cortex-M4 (240MHz, até 4Mb de Flash, 640kb de RAM, 64kb de EEPROM, encapsulamentos de 100 a 224 pinos e capazes de operar de 2.7 a 3.6V);

Todos os microcontroladores Synergy incluem interfaces de comunicação como CAN, I2C, SPI, UART e USB, ADCs de 12 ou 14 bits, DAC de 12 bits, entradas capacitivas e segurança (diagnóstico do ADC, checagem de erros na RAM, CRC, etc). Além disso, as linhas S5 e S7 incluem também ethernet, controlador de display WVGA 800×480 com 32-bits de cores, acelerador 2D e CODEC JPEG, interface QSPI para memória externa e interface MMC/SD.

Multitarefa

A migração de um paradigma de programação monotarefa para um paradigma de multitarefa preemptiva requer um conhecimento mínimo acerca do funcionamento de um sistema multitarefas e de programação concorrente. O principal conceito que o programador precisa ter em mente é de que em sistemas multitarefa preemptiva (ou multi-threading no caso da maioria dos RTOS), o tempo de uso do processador é dividido entre diversas tarefas, cada uma com um slot (ou quantum). Periodicamente o escalonador suspende uma tarefa e troca para a seguinte, de forma que aparentemente todas as tarefas parecem executar ao mesmo tempo. Esta característica se contrapõe ao conceito de tarefa única que somente é interrompida por interrupções de periféricos (no caso de sistemas monotarefa).

Do ponto de vista do usuário (neste caso falamos do usuário do RTOS, ou seja, o programador/desenvolvedor do sistema embarcado), uma tarefa ou thread de um RTOS pode ser considerada como um programa relativamente isolado num sistema monotarefas (o nível de isolação vai depender da disponibilidade e estratégia de uso de módulos MPU ou MMU). Entretando, em razão da existência do agendador do RTOS, não podemos garantir que a thread não será interrompida em qualquer ponto e a qualquer momento (ou seja, implementar comunicação bit-banging pode ser muito difícil ou quase impossível).

Além disso, a programação multitarefa introduz alguns conceitos que podem não ser familiares aos programadores de sistemas embarcados tradicionais:

  • Race conditions (condição de corrida): quando múltiplas threads acessam um mesmo recurso global (seja ele uma variável global ou um periférico), pode ocorrer perda de dados caso uma thread seja interrompida no meio de uma transação, ou seja, se ocorrer a troca de contexto durante a leitura ou escrita do recurso global. Para se evitar este problema existem diversos recursos que os RTOS podem disponibilizar: sessões críticas, exclusão mútua, etc;
  • Deadlock (impasse): ocorre quando duas ou mais threads necessitam de recursos alocados (bloqueados) por outra thread reciprocamente, desta forma cada thread tem apenas uma parcela dos recursos que necessita as thread permanecem aguardando uma pela outra indefinidamente;
  • Priority inversion (inversão de prioridade): ocorre quando uma thread de alta prioridade aguarda por um recurso bloqueado por uma thread de baixa prioridade;
  • Starvation (inanição): ocorre quando o sistema tem muitas threads de alta prioridade que acabam tomando muito ou todo o tempo da CPU, fazendo com que uma thread de baixa prioridade seja pouco ou nunca executada.

Além dos conceitos acima a programação multitarefa inclui alguns outros conceitos e técnicas importantes tais como: comunicação entre tarefas (conhecido genericamente como IPC – Inter-Process Communication), sincronização de tarefas e alocação de memória em sistemas multitarefa.

Porquê eu Usaria um RTOS?

Neste momento o leitor deve estar se perguntando: se um RTOS é tão complicado, porquê eu deveria utilizar um? A resposta a esta pergunta não é simples. O fato é que as aplicações de sistemas embarcados estão ficando cada vez mais complexas, utilizando pilhas de comunicação e outras camadas de software, um RTOS pode facilitar o design de aplicações complexas porque podemos ter a disposição um conjunto de drivers e ferramentas funcional, permitindo que o projetista tenha um foco de mais alto nível no projeto da aplicação. Desta forma, a resposta simples é: um RTOS pode facilitar o projeto e manutenção de uma aplicação.

Na verdade, qualquer aplicação pode se beneficiar de um RTOS, pois um sistema operacional permite abstrair melhor um problema, permitindo que o programador concentre-se numa abordagem de programação de mais alto nível. Isso porque a abordagem multitarefa permite que uma aplicação seja dividida em partes menores (as threads) que podem funcionar de forma praticamente independente. Uma aplicação poderia ter uma thread cuidando da varredura de um teclado, outra cuidando de um display e a as telas de usuário, outra fazendo o processamento dos dados adquiridos de sensores e assim por diante.

Num produto como o Synergy, o RTOS (ThreadX) está perfeitamente integrado (e totalmente testado) com os drivers dos periféricos internos do chip, some-se a isso o middleware que inclui pilhas de comunicação, sistema de arquivos, gerenciador de interface gráfica de usuário, etc, e você terá uma redução substancial no tempo de desenvolvimento.

Além de facilitar o desenvolvimento, outro aspecto importante que frequentemente é relegado a um plano inferior é a manutenção do código.  As aplicações embarcadas tendem a aumentar em complexidade com o passar do tempo, seja pela introdução de novas funcionalidades ou a integração de novas tecnologias. Em qualquer caso, uma aplicação utilizando RTOS será substancialmente mais fácil de manter que uma aplicação bare metal (monotarefa).

Hello World Multitarefa

Vamos iniciar nossas experiências com o ThreadX criando um programa com um processo único para piscar um LED na placa SK-S7G2. Primeiramente crie um projeto no E2 Studio (o ambiente Eclipse da Renesas). O Thiago Lima postou um artigo no portal Embarcados mostrando como criar e depurar um projeto no E2 Studio. A diferença aqui é que vamos criar um projeto utilizando o ThreadX e o SSP versão 1.2.1.

Figura 1
Figura 2
Figura 3

Uma vez criado o projeto, o E2 Studio deverá apresentar uma tela como a da figura 4. Caso ela não seja apresentada, dê um duplo clique sobre o arquivo configuration.xml e selecione a perspectiva Synergy Configuration.

Figura 4

Conforme a figura 5, observe que a aba “Threads” (disponível na perspectiva Synergy Configuration) irá apresentar duas threads. A primeira (HAL/Common) contém a inicialização dos drivers básicos do sistema. Embora esteja listada com uma thread, a HAL/Common não é realmente uma thread pois não permanece em execução e não ocupa tempo de CPU. A segunda thread (Blinky thread) é uma thread de usuário criada pelo E2 Studio. Ela contém o código exemplo que iremos alterar em seguida. É possível adicionar ou deletar threads clicando-se nos botões + e X na caixa de threads e ao clicar sobre uma das threads é possível verificar as propriedades da mesma na janela properties (no canto inferior esquerdo da tela). Na janela de propriedades podemos verificar e alterar o nome da thread, o tamanho da sua pilha de memória, a prioridade, inicialização e atribuição de fatia de tempo (time slice). Por hora vamos manter as configurações originais.

Figura 5

Substitua o conteúdo do arquivo blinky_thread_entry.c (encontrado dentro da pasta src no projeto dentro do Project Explorer) pelo código a seguir:

#include "blinky_thread.h"

void blinky_thread_entry(void) {
    /* LED type structure */
    bsp_leds_t leds;
    /* Get LED information for this board */
    R_BSP_LedsGet(&leds);
    while (1) {
        g_ioport.p_api->pinWrite(leds.p_leds[0], IOPORT_LEVEL_LOW);
        tx_thread_sleep(100);
        g_ioport.p_api->pinWrite(leds.p_leds[0], IOPORT_LEVEL_HIGH);
        tx_thread_sleep(100);
    }
}

Neste momento, se compilarmos o programa e fizermos o debug na placa SK-S7G2, veremos o LED1 (verde) piscar em uma frequência de 0,5Hz. Isto porque a função tx_thread_sleep(100) faz com que a thread seja desativada por 100 ticks do RTOS. Por padrão, o ThreadX está configurado para operar a 100 ticks por segundo, ou seja, cada tick equivale a 10ms.

Mas o quê acontece quando a thread está desativada? Tecnicamente ela é retirada da lista de tarefas prontas para executar (READY) e um timer interno do RTOS é iniciado. Como nós não temos outras threads na nossa aplicação, o RTOS irá simplesmente deixar transcorrer o tempo até que o timer da thread expire. Quando este timer expira, a thread é movida para a fila de threads prontas e será executada caso nenhuma thread mais prioritária esteja pronta (quanto menor o nível de prioridade da thread, mais prioritária é a mesma). No presente caso, como a thread é única, ela será executada imediatamente após o timer expirar!

Adicionando uma Segunda Thread

Que tal então criarmos uma segunda thread e começarmos realmente a experimentar o que é multiprocessamento? Volte para a tela de configuração do Synergy (figura 5) e clique no botão + em Threads para criar uma nova thread. Nas propriedades da thread defina o símbolo como red_thread e o nome da thread como “Red Thread” (sem aspas).

Após ter alterado as propriedades da nova thread, clique no botão “Generate Project Content” para que o E2Studio gere (regenere na verdade) o código para o projeto (e inclua a nova thread que acabamos de criar). Após gerado o código, veremos que o E2 Studio vai adicionar um novo arquivo na pasta src: red_thread_entry.c. Este arquivo irá conter o código da nossa segunda thread. Abra o arquivo e inclua o código da função conforme abaixo:

void red_thread_entry(void) {
    /* LED type structure */
    bsp_leds_t leds;
    /* Get LED information for this board */
    R_BSP_LedsGet(&leds);
    while (1) {
        g_ioport.p_api->pinWrite(leds.p_leds[1], IOPORT_LEVEL_LOW);
        R_BSP_SoftwareDelay(1000,BSP_DELAY_UNITS_MILLISECONDS);
        g_ioport.p_api->pinWrite(leds.p_leds[1], IOPORT_LEVEL_HIGH);
        R_BSP_SoftwareDelay(1000,BSP_DELAY_UNITS_MILLISECONDS);
    }
}

Lendo o código podemos perceber que nesta thread vamos fazer piscar o led vermelho (o segundo led da estrutura leds lida do BSP) numa frequência de 0,5Hz (1000ms ligado e 1000ms desligado), da mesma forma que a primeira thread. A única diferença é que neste caso estamos utilizando a função de atraso por software R_BSP_SoftwareDelay().

Figura 6

O teste do programa (mostrado na figura 6) demonstra que, apesar das duas threads iniciarem simultaneamente e possuírem atrasos idênticos para os tempos ligado/desligado dos leds, pouco tempo após o início da execução os leds estão piscando de forma totalmente dessincronizada. Você consegue entender porque?

A resposta é que a primeira thread baseia-se tick do kernel do RTOS (controlado por uma interrupção) para determinar o tempo ligado/desligado do led verde, ao passo que a segunda thread utiliza uma rotina de atraso por software. Desta forma, cada vez que a segunda thread é interrompida por um tick do RTOS (ou seja, 100 vezes por segundo), a temporização da mesma é interrompida e somente é retomada quando o RTOS retoma a execução da thread! Na prática a duração da rotina de atraso na segunda thread acaba sendo maior que o realmente desejado. E mais: quanto mais threads estiverem rodando, pior será o impacto na temporização por software!

Isso não ocorre na primeira thread, já que a mesma baseia sua temporização no tick do RTOS. Para verificarmos essa hipótese, vamos substituir as chamadas a R_BSP_SoftwareDelay() na segunda thread por tx_thread_sleep(100) como na primeira thread.

Figura 7

Interessante não? Agora os leds piscam exatamente na mesma frequência e em perfeita sincronia! Fica aqui uma primeira lição acerca da transição do paradigma monotarefa para multitarefa: temporizações por software não são confiáveis num RTOS e devem ser evitadas! Prefira sempre utilizar funções de temporização do kernel que além de serem imunes às trocas de contexto, também permitem melhor utilizar a CPU (já que normalmente estas funções removem a tarefa da fila e permitem que o kernel dedique a sua atenção as outras threads ativas).

No próximo artigo desta série vamos conhecer um pouco mais sobre o kernel do ThreadX e como configurá-lo utilizando o E2 Studio da Renesas, até lá!

Referências

One thought on “Synergy e ThreadX: introdução a multitarefa

Leave a Reply