Máquina de estado

Máquina de estado

Este artigo visa explicar os conceitos e algumas aplicações práticas de uma máquina de estado.

Pré-requisitos

Para uma boa compreensão deste artigo, existem os seguintes pré-requisitos de conhecimento em linguagem C:

  • Uso de switch/case;
  • Construção e uso de funções;
  • Construção e uso de macros / defines.

Máquina de estado: o que é?

No desenvolvimento de software (seja ele embarcado ou não), frequentemente nos deparamos com situações em que é necessário que uma certa seqüência de comandos / ações / dados seja obedecida para o software poder agir / tomar uma decisão. Um exemplo disto seria a recepção de dados via um canal serial, onde é necessário que o buffer tenha sido recebido no formato correto antes de seu tratamento. Para resolver estas questões de forma computacionalmente satisfatória, existe o conceito de máquina de estado.

Uma máquina de estado se fundamenta, como o próprio nome diz, em direcionar o funcionamento de um software em um número finito de estados, sendo cada um desses estados uma situação relevante do sistema. É possível avançar, recuar ou permanecer em um estado, e o mecanismo deste fluxo da máquina de estado é definido pelo desenvolvedor e sua complexidade é tão grande quanto a complexidade do sistema desenvolvido.

Logo, utilizar máquina de estado é direcionar o funcionamento de um software, fazendo com que uma ordem de execução seja obedecida. Ah, é muito importante ressaltar: somente um estado é executado por vez (não é correto, nem possível, que uma máquina de estado esteja em dois estados simultaneamente).

Parece complexo, não? Nada como um código-exemplo para esclarecer

No geral, conclui-se que a máquina de estado tem como fundamento assegurar uma seqüência de execução de um programa. Ok, máquina de estado realmente é um recurso poderoso, mas quais seriam as formas de implementá-la em linguagem C?

Primeiramente, uma máquina de estado deve, obrigatoriamente, ter um estado inicial. Afinal, se é necessário que uma sequência de execução seja obedecida, deve haver um começo.

Para implementar uma máquina de estado em linguagem C há duas formas: via switch/case e via ponteiro de função. Ambas as formas são válidas, porém a necessidade / problema a ser resolvido vai dar pistas de qual delas é a melhor solução. Veremos em detalhes cada uma delas.

a) Via switch/case:

Trata-se da forma mais utilizada para implementar máquinas de estado, pois trata-se da forma mais simples e intuitiva para isso. Nela, há uma variável de controle (responsável por armazenar o estado atual) e cada case corresponde a um estado diferente. É importante ressaltar que a variável de controle de um switch/case deve, obrigatoriamente, ser do tipo caractere ou numérico sem ponto flutuante (long ou int).

Para maior facilidade e rapidez de leitura e compreensão de código, costuma-se ter os estados representados em macros/defines. Assim, pode-se ter uma palavra ou frase representando um estado (numérico, devido ao tipo da variável de controle do switch/case). Isto facilita muito a leitura e, em tempo de compilação, esta representação é substituída pelos números correspondentes, não exigindo então esforço computacional adicional nenhum em tempo de execução.

Segue abaixo um exemplo de aplicação desta máquina de estado. Neste exemplo, é criada uma máquina de estado que busca que o usuário digite a sequência “abcd” no teclado. Ao fazer a sequência correta, a mensagem “OK” é exibida (mecanismo semelhante a sistemas que aceitam comandos textuais, como comandos AT, por exemplo). Se qualquer letra for digitada fora da ordem desejada, volta-se ao estado inicial (aguardar a letra ‘a’) novamente. O programa foi elaborado no Dev-Cpp versão 4.9.9.2. 

Importante: Pelo fato da variável de controle da máquina de estado (ou switch/case) ser fundamental no andamento dos estados, esta deve ser global. Logo, deve-se ter cuidado redobrado com a integridade desta variável (nada deverá modificar seu valor, caso contrário o funcionamento do software será seriamente comprometido).

b) Via ponteiro de função

Nesta forma de se fazer máquina de estado, cada estado será representado por uma função, sendo que estas são chamadas através de uma atribuição de ponteiro de função (que aponta sempre para a função/estado que deve ser executado no momento). Ou seja, ao invés de se utilizar uma variável de controle para se orientar pelos estados, utiliza-se um ponteiro de função e chamadas do mesmo.

As vantagens deste tipo de metodologia de máquina de estado são:

  • chamada da execução da máquina em apenas uma linha de código;
  • maior modularização e;
  • por fim, mas não menos importante, contribui para um código mais limpo (fácil leitura).

Vejamos o mesmo exemplo anterior utilizando uma máquina de estado por ponteiro de função: 

Este tipo de máquina de estado, pela clareza de código, é muito utilizado em sistemas embarcados.

Boas práticas, dicas e truques

Vimos que máquinas de estado são recursos muito bons em se tratando de organização e garantia de funcionamento de software. Porém, há algumas boas práticas, dicas e truques no seu uso: 

  1. Em máquina de estado via switch/case,  uma boa prática é a variável de controle ser volatile (sobretudo quando se fala de sistemas embarcados compilados com alto grau de otimização). Não se deve correr risco algum na atualização desta variável;
  2. Ainda em máquina de estado via switch/case, quando aplicada em sistemas complexos (com muitos estados), é uma boa prática colocar um estado default (o default do switch/case). Isso é uma boa prática pois, caso haja o corrompimento da variável de controle (ou até mesmo um bug no software quanto ao gerenciamento desta variável), o software poderá se restabelecer (e não ficar “perdido em um estado” por muito tempo ou pra sempre);
  3. Em máquinas de estado via ponteiro de função, deve-se priorizar uso de variáveis locais. Devido ao fato de o número de estados / funções poder ser grande, se as variáveis forem declaradas como globais será alocada desnecessariamente uma quantidade significativa de memória RAM, algo ruim em sistemas embarcados com microcontroladores com pouca memória desse tipo. Aliás, evite sempre muitas variáveis globais, isso só gera dor de cabeça!;
  4. E o mais importante: quando for planejar sua máquina de estado, seja SIMPLES! Evite ao máximo utilizar numerosos estados e/ou máquinas de estado a perder de vista. Em software embarcado, quanto mais simples for para entender e debugar, melhor.

Conclusão

Um software, seja embarcado ou não, necessita muitas vezes que uma determinada ordem de acontecimentos seja obedecida, e aí entra o conceito de máquina de estado. Trata-se de um recurso muito poderoso e que garante o correto funcionamento de softwares simples ou complexos.

Referências

  1. Post do Sérgio Prado sobre máquinas de estado: http://sergioprado.org/maquina-de-estados-em-c/
  2. Conteúdo sobre ponteiros de função: http://www.dca.fee.unicamp.br/cursos/EA876/apostila/HTML/node144.html
  3. Figura destacada obtida de: http://ces22.wdfiles.com/local–files/relas%3Alab3-francisco-germano/MotorFoguete2.jpg
  4. Para mais detalhes sobre máquinas de estados finitos (FSM), acesse o site: http://users.ece.utexas.edu/~valvano/Volume1/E-Book/C10_FiniteStateMachines.htm
Website | Veja + conteúdo

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

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

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

Comentários:
Notificações
Notificar
guest
11 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Leandro Poloni Dantas
09/09/2015 19:30

Muito bom seu artigo Pedro.
Vou indicá-lo para meus alunos.

Pedro Henrique Bertoleti
phfbertoleti
Reply to  Leandro Poloni Dantas
22/09/2015 10:39

Leandro, muito obrigado!

Lenilson Barreto
Lenilson Amaral Barreto
11/08/2015 12:05

Nos bons tempo do assemble, eu gerava uma interrupção e a partir deste sincronismo, executava uma função após outra, enquanto esperava a assimilação da informação de um lcd, agia em outra rotina.

Euripedes Filho
Euripedes Rocha Filho
08/08/2015 09:02

Muito bom artigo!
Uma coisa que eu faço é nunca realizar a decisão de estado em conjunto com as ações executadas pelos estados. Com isso a lógica de transição fica concentrada em um único local e é aplicado o princípio de responsabilidade única que leva a um código um pouco mais robusto, além de permitir manutenção e modificações na lógica de transição e na execução de estados sem a necessidade de modificar muitos pontos do código.

Pedro Henrique Bertoleti
phfbertoleti
Reply to  Euripedes Rocha Filho
08/08/2015 20:29

Boa!

Rafael Dias
Rafael Dias
07/08/2015 11:05

Muito bom.

Máquinas de estado são mecanismos que podem deixar o seu sistema paralelizado sem que ele seja “paralelo”. Um bom conhecimento em FSM é o que diferencia o desenvolvedor.

Já vi muito sistema bare metal com muita responsividade e que leva o usuário a pensar que lá temos um rtos operando, mas na verdade é um sistema bare metal com inúmeras máquinas de estados, cada uma responsável por um aspecto do produto.

Muito legal.

Pedro Henrique Bertoleti
phfbertoleti
Reply to  Rafael Dias
07/08/2015 17:17

Rafael, muito obrigado!

Realmente, em sistemas bare metal, utilizar bem FSM é vital para bom desempenho. Também já vi vários casos como os que citou.

Helton Moraes
Helton Moraes
07/08/2015 09:40

Caso o ambiente tenha opção para linguagens orientadas a objeto (C++, provavalmente), existe o “State Design Pattern”, onde cada estado é representado por uma sub-classe que implementa os event-handlers de forma diferente (incluindo as ações apropriadas e a troca para o estado seguinte) através de polimorfismo:
https://sourcemaking.com/design_patterns/state

Vinicius may
Vinicius may
13/01/2017 09:33

A variavel de controle do estado pode ser também do tipo CHAR.

Mateus Oliveira
Mateus Oliveira
14/12/2016 18:59

Muito bom! Nunca implementei fsm com esse metodo de funcao. Mas ja implementei de outra forma que nao foi abordada, onde nada maos eh do que uma versao hibrida. Pra ficar mais modularizado, crio uma look up table com as entradas de cada estado, sendo, um ponteiro pra funcao a ser executada ao entrar, durante, e aosair de cada estado. Sendo null caso nao seja necessario. E cada funcao nao altera a variavel de estado, mas retorn o valor para o proximo (e recebe como parametro o estado anterior) Utilizo tanto para receber dados fprmatados quanto controlar chamadas de menus… Leia mais »

Eder Andrade
Eder
21/08/2016 12:48

Boa!

Ponteiro de função é um ótimo recurso 🙂

Talvez você goste:

Séries

Menu