Exemplo de aplicação com TickAttack

TickAttack
Este post faz parte da série TickAttack - gerenciador de tarefas para ARM Cortex-M0. Leia também os outros posts da série:

O TickAttack, abordado no artigo deste link, consiste em um gerenciador de tarefas simples, porém eficaz. Com ele, é possível projetar sistemas embarcados diversos, extraindo o máximo de processamento da plataforma em que está rodando.

 

Nessa linha de raciocínio, este post mostra um exemplo de aplicação utilizando o TickAttack e a placa de desenvolvimento STM32F072RBDISCOVERY. O exemplo em questão trata-se do controle de quatro GPIOs (configurados como saída) e leitura de um ADC.

 

 

Requisitos

 

Para reproduzir este projeto da forma com menos adaptações possíveis, é necessário:

  • Baixar e instalar a IDE e compilador MDK Keil 5. Apesar de normalmente ser uma IDE e compiladores pagos, para desenvolvimento em microcontroladores STM32F0 (ou seja, os ARM Cortex-M0 da ST) o uso é gratuito (inclusive para uso comercial). Portanto, pode baixar e instalar sem medo.
    As instruções de como baixar, instalar e ativar a licença free da ST são encontradas aqui.
    A única ressalva é que não há versões para Linux.
  • Clonar (ou baixar) o repositório do projeto original do TickAttack (para acessar o repositório, clique aqui).
  • Ter em mãos a placa de desenvolvimento STM32F072RBDISCOVERY.

 

 

Abrindo o projeto

 

Após instalar a IDE e compilador utilizando as instruções do link fornecido, abra o MDK Keil 5. Com ele aberto, vá em "Project > Open Project". Na pasta em que descompactou o projeto TickAttack / clonou o repositório, procure pelo diretório "MDK ARM". Dentro dele, estará o arquivo de projeto (GerenciadorTarefas_ARMCortexM0.uvprojx). Como exemplo, no meu caso, o caminho completo foi: 

C:\Projetos\TickAttack-master\Projeto_TickAttack_STM32F072RB\MDK-ARM

 

Após a abertura, o  MDK Keil 5 ficará conforme a figura 1.

 

Aplicação com TickAttack - Projeto aberto do MSK Keil 5
Figura 1 - Projeto aberto do MDK Keil 5

 

 

Desenvolvendo as tarefas

 

Conforme descrito no artigo do TickAttack, na abordagem de desenvolvimento de um sistema embarcado utilizando um sistema operacional de tempo real (RTOS) ou também no próprio TickAttack, cada funcionalidade do sistema em si pode ser uma tarefa. Ou seja, o exemplo aqui mostrado será composto de cinco tarefas distintas, sendo:

  1. Tarefa 1: controle de um dos GPIOs
  2. Tarefa 2: controle de um dos GPIOs
  3. Tarefa 3: controle de um dos GPIOs
  4. Tarefa 4: controle de um dos GPIOs
  5. Tarefa 5: leitura do ADC

 

Conforme também explicado no artigo do TickAttack, as tarefas são definidas e implementadas no arquivo do próprio Kernel cooperativo, ou seja, no arquivo GerenciadorTarefas.c

 

Sendo assim, abra-o e verifique o trecho do código em que as tarefas começas a ser definidas. Observe a figura 2.

 

Início das funções das tarefas
Figura 2 - Início das funções das tarefas

 

O projeto original já está preparado para o que precisamos fazer (ou seja, controlar 4 GPIOs e ler o ADC).

Portanto, vamos fazer com que as quatro primeiras tarefas façam o Toggle (invertam o estado) de seu GPIO correspondente utilizando macros já definidas no código, assim como fazer a leitura do ADC para uma variável destinada a isso, que já consta nos dados compartilhados (veja o arquivo DadosCompartilhados.h do projeto).

 

Atentar que, para usar o ADC, é preciso utilizar a variável de Handle de ADC hadc. Como ela é pertencente ao HAL da ST, será necessário fazer uma referência a ela como extern. Portanto, sua declaração no arquivo GerenciadorTarefas.c ficará assim:

 

extern ADC_HandleTypeDef hadc;

 

Desta forma, o arquivo GerenciadorTarefas.c completo ficará da seguinte forma:

 

//Módulo: Gerenciador de tarefas
//Descrição: Módulo responsável por fazer o gerenciamento de todas as tarefas do firmware. 
//           A execução das tarefas é orientada à tempozização, compondo um kernel cooperativo.

//include guard
#define DEF_GERENCIADORTAREFAS

//includes
#include "GerenciadorTarefas.h"
#include "stm32f0xx_hal.h"
#include "DadosCompartilhados.h"

//defines 
#define TOOGLE_LED3      HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_6)
#define TOOGLE_LED4      HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_7)
#define TOOGLE_LED5      HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_8)
#define TOOGLE_LED6      HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_9)

//typedefs

//variáveis locais
extern ADC_HandleTypeDef hadc;

//variaveis externas

//prototypes locais
void Tarefa1(void);
void Tarefa2(void);
void Tarefa3(void);
void Tarefa4(void);
void Tarefa5(void);
unsigned long ObtemTick(void);
unsigned long TempoGastoTarefa(unsigned long TickInicialTarefa, unsigned long TickFinalTarefa);

//implementações:

//Função: inicialização do gerenciador de tarefas
//Descrição: faz a inicialização do gerenciador de tarefas: escaloma as tarefas
//           e suas respectivas temporizações. Além disso, configura o timeout 
//           da execução de tarefas também.
//           IMPORTANTE: todas as tarefas devem ter parÂmetros e retorno do tipo void 
//Parâmetros: nenhum
//Retorno: nenhum
void IniciaGerenciadorTarefas(void)
{
    //Inicialização dos ponteiros de funções (tarefas)
    SetupGerenciadorDeTarefas.TarefasAgendadas[INDEX_TAREFA_1] = Tarefa1;
    SetupGerenciadorDeTarefas.TarefasAgendadas[INDEX_TAREFA_2] = Tarefa2;
    SetupGerenciadorDeTarefas.TarefasAgendadas[INDEX_TAREFA_3] = Tarefa3;
    SetupGerenciadorDeTarefas.TarefasAgendadas[INDEX_TAREFA_4] = Tarefa4;
    SetupGerenciadorDeTarefas.TarefasAgendadas[INDEX_TAREFA_5] = Tarefa5;
	
    //Inicialização dos tempos de execução de cada tarefa
    SetupGerenciadorDeTarefas.TempoDeExecucaoTarefas[INDEX_TAREFA_1] = TEMPO_PARA_EXECUTAR_TAREFA1;
    SetupGerenciadorDeTarefas.TempoDeExecucaoTarefas[INDEX_TAREFA_2] = TEMPO_PARA_EXECUTAR_TAREFA2;
    SetupGerenciadorDeTarefas.TempoDeExecucaoTarefas[INDEX_TAREFA_3] = TEMPO_PARA_EXECUTAR_TAREFA3;
    SetupGerenciadorDeTarefas.TempoDeExecucaoTarefas[INDEX_TAREFA_4] = TEMPO_PARA_EXECUTAR_TAREFA4;
    SetupGerenciadorDeTarefas.TempoDeExecucaoTarefas[INDEX_TAREFA_5] = TEMPO_PARA_EXECUTAR_TAREFA5;
	
	
    //Inicializa tempo restante para cada tarefa executar
    SetupGerenciadorDeTarefas.TempoParaExecutarTarefas[INDEX_TAREFA_1] = TEMPO_PARA_EXECUTAR_TAREFA1;
    SetupGerenciadorDeTarefas.TempoParaExecutarTarefas[INDEX_TAREFA_2] = TEMPO_PARA_EXECUTAR_TAREFA2;
    SetupGerenciadorDeTarefas.TempoParaExecutarTarefas[INDEX_TAREFA_3] = TEMPO_PARA_EXECUTAR_TAREFA3;	
    SetupGerenciadorDeTarefas.TempoParaExecutarTarefas[INDEX_TAREFA_4] = TEMPO_PARA_EXECUTAR_TAREFA4;	
    SetupGerenciadorDeTarefas.TempoParaExecutarTarefas[INDEX_TAREFA_5] = TEMPO_PARA_EXECUTAR_TAREFA5;	
  
    //nenhuma tarefa está em execução no momento
    SetupGerenciadorDeTarefas.TimeoutDaTarefa = NAO;
}

//Função: Execução de tarefa
//Descrição: verifica se deve executar alguma tarefa. Em caso positivo, a execução é feita
//Parâmetros: nenhum
//Retorno: nenhum
void ExecutaTarefa(void)
{
    long i;
    unsigned long TickInicialTarefa;
    unsigned long TickFinalTarefa;
	
    for (i=0; i<NUMERO_DE_TAREFAS; i++)
    {
        //verifica se está na hora de executar alguma tarefa
	if ((SetupGerenciadorDeTarefas.TarefasAgendadas[i] != 0) && (SetupGerenciadorDeTarefas.TempoParaExecutarTarefas[i] == 0))
	{
	    //obtem o valor do tick   
	    TickInicialTarefa = ObtemTick();
				 
	    //executa a tarefa
	    SetupGerenciadorDeTarefas.HaTarefaEmExecucao = SIM;
            SetupGerenciadorDeTarefas.TimeoutDaTarefa = TIMEOUT_DE_TAREFA;
            SetupGerenciadorDeTarefas.TarefasAgendadas[i]();  //executa a tarefa agendada
            SetupGerenciadorDeTarefas.HaTarefaEmExecucao = NAO;
	    SetupGerenciadorDeTarefas.TempoParaExecutarTarefas[i] = SetupGerenciadorDeTarefas.TempoDeExecucaoTarefas[i];  //reagendamento da tarefa
				 
	    //contabiliza tempo de execução da tarefa (em ms)
	    TickFinalTarefa = ObtemTick();
	    SetupGerenciadorDeTarefas.TempoGastoPorTarefa[i] = TempoGastoTarefa(TickInicialTarefa,TickFinalTarefa);
	}
    }
}

/*
* As tarefas e número de tarefas aqui colocadas são arbitrários. 
* Estes devem ser modificados para o uso desejado.
*/

//Função: tarefa 1
//Descrição: contém rotinas da tarefa 1
//Parâmetros: nenhum
//Retorno: nenhum
void Tarefa1(void)
{
		TOOGLE_LED3;
}

//Função: tarefa 2
//Descrição: contém rotinas da tarefa 2
//Parâmetros: nenhum
//Retorno: nenhum
void Tarefa2(void)
{
		TOOGLE_LED4;
}

//Função: tarefa 3
//Descrição: contém rotinas da tarefa 3
//Parâmetros: nenhum
//Retorno: nenhum
void Tarefa3(void)
{
		TOOGLE_LED5;
}

//Função: tarefa 4
//Descrição: contém rotinas da tarefa 4
//Parâmetros: nenhum
//Retorno: nenhum
void Tarefa4(void)
{
		TOOGLE_LED6;
}

//Função: tarefa 5
//Descrição: contém rotinas da tarefa 5
//Parâmetros: nenhum
//Retorno: nenhum
void Tarefa5(void)
{
		HAL_ADC_Start(&hadc);	
	  HAL_ADC_PollForConversion(&hadc, 10);	
		SetLeituraADC(HAL_ADC_GetValue(&hadc));
}  

//Função: obtem valor atual do tick
//Parâmetros: nenhum
//Retorno: valor atual do tick
unsigned long ObtemTick(void)
{
    return HAL_GetTick();
}

//Função: calcula tempo gasto numa tarefa
//Parâmetros: tempos inicial e final (em ms) de uma tarefa
//Retorno: tempo gasto numa tarefa (em ms)
unsigned long TempoGastoTarefa(unsigned long TickInicialTarefa, unsigned long TickFinalTarefa)
{
    if (TickFinalTarefa > TickInicialTarefa)
        return  (TickFinalTarefa-TickInicialTarefa);
    else
        return  (0xFFFFFFFF-TickInicialTarefa) + TickFinalTarefa  + 1;  
}

 

Importante:

  1. A leitura do ADC está sendo simplesmente carregada numa variável (da área de dados compartilhados). Portanto, você conseguirá ver o valor da leitura do ADC somente se colocá-la no Watch de variáveis enquanto roda o firmware em modo Debug na placa pelo MDK Keil 5. 
  2. As tarefas foram definidas como exemplo. Você é livre para implementar as tarefas que quiser.

 

 

Definição de quando cada tarefa deverá executar

 

Para definir a periodicidade da tarefas, ou seja, de quanto em quanto tempo ela deve executar, o arquivo GerenciadorTarefas.h deve receber atenção. Observe nele o seguinte trecho:

 

//tempos de execução de tarefas arbitrários. Deve-se modificar para o uso ao qual for destinado.
#define TEMPO_PARA_EXECUTAR_TAREFA1    500    //ms
#define TEMPO_PARA_EXECUTAR_TAREFA2    500    //ms
#define TEMPO_PARA_EXECUTAR_TAREFA3    500    //ms
#define TEMPO_PARA_EXECUTAR_TAREFA4    500    //ms
#define TEMPO_PARA_EXECUTAR_TAREFA5    500    //ms

 

Cada um destas definições compreende a periodicidade da tarefa correspondente. Portanto, se quiser alterar a periodicidade de uma tarefa, basta alterar estes valores para os desejados. No exemplo informado, cada tarefa será executada a cada 500 milissegundo.

 

Observação: conforme dito no artigo do TickAttack, cada Tick do SysTick é de 1 milissegundo. Isso significa dizer que a temporização de uma tarefa deve ser maior ou igual a 1 milissegundo.

 

Em suma, utilize as temporizações que desejar, respeitando o limite do Tick do SysTick.

 

 

Compilando e rodando o software

 

Para compilar e rodar o software na placa pelo MDK Keil 5, siga o procedimento abaixo:

  1. Aperte a tecla F7 (ou vá em "Project > Build Target") para dar Build no código. Esta etapa pode demorar alguns minutos, sobretudo se se tratar da primeira compilação do projeto.
  2. Feita o processo de Build, conecte a placa ao computador através do cabo USB.
  3. Carregue o firmware na placa indo em "Flash > Download"
  4. Após o carregamento, entre em modo Debug indo em "Debug > Start/Stop Debug Session"
  5. Após o procedimento de entrada no modo debug, aperte a tecla F5 e o software irá rodar na placa. Observe os LEDs piscando na temporização que você definiu.
    Você pode colocar breakpoints e ver o valor das variáveis que quiser (em tempo real) através do Watch também.

 

 

Indo além

 

Para ir além e manipular outros periféricos, recomendo instalar o software de geração de HAL da ST, o STM32CubeMX. Nele, basta abrir o arquivo correspondente (GerenciadorTarefas_ARMCortexM0, já contido no repositório do projeto) e alterar/usar como quiser a periferia do microcontrolador.

 

 

Sugestões de aplicações

 

O exemplo aqui retratado, com pouquíssimas modificações, poderia ser aplicado ao controle e monitoramento de um robô, sendo os GPIOs responsáveis pelos acionamento de motores (através de interfaces de potência ou pontes-H, por exemplo) e a leitura do ADC ser a leitura do nível do bateria do robô (com a devida circuitaria para isso, claro), por exemplo.

Outros artigos da série

<< TickAttack: um gerenciador de tarefas simples para um ARM Cortex-M0
Este post faz da série TickAttack - gerenciador de tarefas para ARM Cortex-M0. Leia também os outros posts da série:
NEWSLETTER

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Obrigado! Sua inscrição foi um sucesso.

Ops, algo deu errado. Por favor tente novamente.

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Pedro Bertoleti
Sou engenheiro eletricista formado pela Faculdade de Engenharia de Guaratinguetá (FEG - UNESP) e trabalho com Android embarcado em Campinas-SP. Curioso e viciado em tecnologia, sempre busco me aprimorar na área de sistemas embarcados (modalidades bare-metal, RTOS, Linux embarcado e Android embarcado).Para mais informações, acesse minha página no Facebook:https://www.facebook.com/pbertoleti

Deixe um comentário

avatar
 
  Notificações  
Notificar