Tutorial · Iniciante · 17 min de leitura
Máquinas de estados finitas para o comportamento de robôs
Projete uma máquina de estados finita com transições explícitas, tempos limite, recuperação, atualizações não bloqueantes e regras de segurança testáveis.
Os programas de robôs costumam começar como um laço cheio de instruções if: siga a linha, a menos que um obstáculo esteja próximo; vire até que a passagem fique livre; pare quando a bateria estiver baixa; e tente se recuperar quando o tempo limite de um sensor expira. À medida que os comportamentos crescem, a mesma condição pode gerar comandos contraditórios.
Uma máquina de estados finita (FSM) torna explícito o comportamento ativo. A cada instante, o robô está em um único estado; eventos e condições de transição determinam quando ele muda para outro. O padrão é pequeno o suficiente para um Arduino e estruturado o suficiente para ser testado.
Separe estado, evento e ação
Um estado descreve o que o robô faz ao longo do tempo: FOLLOW_LINE, AVOID, SEARCH ou FAULT. Um evento é algo observado: um obstáculo entra na zona de parada, a linha é perdida, um tempo limite expira ou o operador pressiona o botão de parada. Uma ação é a saída: definir as velocidades das rodas, limpar um integrador ou emitir um alerta.
Evite criar estados para fatos momentâneos. OBSTACLE_DETECTED costuma ser um evento ou uma condição; AVOIDING_OBSTACLE é um estado porque persiste enquanto o robô executa um comportamento.
Comece com uma tabela de transições antes de escrever o código:
| Estado atual | Condição ou evento | Próximo estado | Ação de entrada |
|---|---|---|---|
| Idle | Início solicitado e sensores válidos | Follow | Redefinir o controlador |
| Follow | Obstáculo dentro do limiar de parada | Avoid | Parar o avanço |
| Follow | Linha ausente por 200 ms | Search | Registrar o último lado |
| Avoid | Caminho livre por 500 ms | Follow | Redefinir o controlador |
| Qualquer estado ativo | Tempo limite do sensor excedido | Fault | Parar os motores |
| Fault | Redefinição pelo operador e condições de saúde válidas | Idle | Limpar a falha |
A tabela revela casos ausentes e torna visível a prioridade da segurança.
Mantenha o laço de atualização não bloqueante
Um estado deve realizar uma atualização pequena e retornar. Um delay() de dois segundos dentro de AVOID impede que o programa veja uma solicitação de parada ou uma falha de sensor durante esses dois segundos.
enum class State { Idle, Follow, Avoid, Search, Fault };
State state = State::Idle;
unsigned long enteredAt = 0;
void transitionTo(State next) {
state = next;
enteredAt = millis();
}
void loop() {
Sensors s = readSensors();
if (!s.healthy && state != State::Fault) transitionTo(State::Fault);
switch (state) {
case State::Idle: updateIdle(s); break;
case State::Follow: updateFollow(s); break;
case State::Avoid: updateAvoid(s); break;
case State::Search: updateSearch(s); break;
case State::Fault: updateFault(s); break;
}
}
Cada atualização lê o instantâneo de sensores já capturado e escreve um comando coerente para os atuadores. Isso impede que estados diferentes leiam valores mutáveis em momentos diferentes dentro do mesmo ciclo.
Defina fronteiras claras para as transições
Escolha uma única regra de responsabilidade. Em um projeto embarcado simples, cada estado pode solicitar suas próprias transições de saída, enquanto uma verificação de segurança global pode substituir qualquer estado ativo. Essa regra deixa explícito quem tem autoridade para iniciar cada mudança de estado.
void updateFollow(const Sensors& s) {
if (s.obstacleMm < stopThresholdMm) {
setWheels(0, 0);
transitionTo(State::Avoid);
return;
}
if (s.lineMissingForMs > 200) {
transitionTo(State::Search);
return;
}
followLine(s.lineError);
}
Retorne imediatamente após uma transição para que o estado antigo não também possa comandar os atuadores. Ao entrar em um estado de controle, redefina o histórico que não deve permanecer de um comportamento para o outro, como o termo integral de um PID ou a amostra anterior do termo derivativo.
Use ações de saída com parcimônia. A maior parte da limpeza pode acontecer na função centralizada transitionTo() ou no caminho de entrada do próximo estado. Funções separadas onEntry, onUpdate e onExit só se tornam úteis quando vários estados realmente precisam delas.
Use tempos limite e histerese como condições
Eventos físicos são ruidosos. Se AVOID sai no instante em que a distância ultrapassa o mesmo limiar que provocou sua entrada, o ruído de medição pode provocar alternâncias rápidas entre estados. Use dois limiares ou um tempo de persistência: entre abaixo de 250 mm, mas saia somente depois que a distância permanecer acima de 350 mm por 500 ms.
Todo estado que espera progresso precisa de um tempo limite. Se o robô entra em SEARCH mas nunca encontra a linha, não deve girar indefinidamente. Faça uma transição para FAULT ou para uma recuperação limitada após um intervalo testado.
Baseie os temporizadores no tempo desde a entrada ou desde o último progresso. Use uma subtração como now - enteredAt >= timeout, que continua correta quando o temporizador sem sinal sofre transbordo, desde que os tipos sejam consistentes.
Dê prioridade às transições de segurança
Defina o que pode interromper tudo: parada de emergência, dados inválidos de um sensor crítico, falha do driver de motores ou expiração do watchdog. Avalie essas condições antes das transições específicas de cada comportamento e torne explícita a saída segura.
Um estado FAULT deve permanecer travado quando a recuperação automática puder reiniciar um movimento perigoso. Exigir uma redefinição do operador depois que a causa desapareça costuma ser mais seguro do que voltar diretamente para FOLLOW.
Não permita que duas máquinas de estado independentes escrevam os mesmos comandos de motor sem uma regra explícita de arbitragem. Se navegação e gerenciamento da bateria afetarem o movimento, um único responsável deve selecionar o comando final e aplicar os limites de segurança.
Teste transições sem operar motores
Mova as decisões para funções que aceitem um instantâneo de sensores e um instante de tempo e, em seguida, retornem um próximo estado ou comando. Forneça uma sequência de instantâneos sintéticos:
- Linha válida e caminho livre mantêm o robô em
FOLLOW. - Uma única leitura próxima e ruidosa não dispara uma transição se a persistência for exigida.
- Um obstáculo próximo persistente leva a
AVOIDe comanda a parada primeiro. - Um caminho livre mantido pelo intervalo de saída retorna a
FOLLOW. - Um tempo limite do sensor leva a
FAULTa partir de qualquer estado ativo.
Registre as mudanças de estado como old → new, o evento que as disparou e o horário. Registros contínuos por laço escondem as transições no ruído; registros de transição explicam por que o comportamento mudou.
Saiba quando uma FSM deixa de ser o modelo mais simples
Uma FSM funciona bem quando o robô tem um número modesto de modos mutuamente exclusivos. Ela fica difícil quando os estados se multiplicam para representar combinações como “navegar enquanto monitora a bateria enquanto tenta a localização novamente”. A quantidade de transições pode crescer mais rápido do que os próprios comportamentos.
Árvores de comportamento componem ações reutilizáveis com estruturas de sequência, alternativa e recuperação, e é por isso que sistemas de navegação como o Nav2 as usam para tarefas complexas. Não introduza uma estrutura de árvore de comportamento para quatro estados. Avance quando a tabela de transições ficar realmente difícil de revisar ou quando os comportamentos precisarem de reutilização hierárquica.
Diagnose falhas comuns de projeto
- O robô ignora a parada enquanto gira: um estado contém um laço ou atraso bloqueante.
- Os motores recebem dois comandos por ciclo: o código de transição continua sendo executado depois de mudar o estado.
- A máquina alterna rapidamente entre estados: condições ruidosas não têm histerese ou persistência.
- A recuperação nunca termina: o estado não tem condição de progresso nem tempo limite.
- Uma falha reinicia o movimento imediatamente: falhas de segurança não ficam travadas ou as condições de redefinição são fracas demais.
- Os testes exigem o robô inteiro: decisões e acesso ao hardware estão misturados, em vez de usar instantâneos de sensores e saídas de comando.
O simulador de desvio de obstáculos oferece uma sequência útil de comportamento para modelar: deslocar-se, contornar uma ameaça, chegar ou parar quando não houver movimento seguro restante. O simulador de solução de labirintos mostra outra separação entre sensores, atualização do mapa, planejamento e movimento de uma célula. O robô mini sumo é o teste mais rigoroso da regra de segurança travada acima: seu reflexo de borda precisa ter precedência sobre todos os outros estados, a partir de qualquer estado, ou o robô sai para fora da arena.
Explore o grafo
Faz parte destas montagens
Projetos e caminhos de aprendizagem que incluem este tutorial.
- ProjetoConstrua um carro-robô para celular via Bluetooth ou Wi-Fi
- ProjetoConstrua um robô de coleta e colocação que sabe quando falhou
- ProjetoConstrua um robô mini sumo: preensão, cunha e detecção de borda
- ProjetoConstrua um robô ROS 2 que vê e segue até um marcador
- Caminho de aprendizagemNavegação reativa: ensina um robô a desviar de obstáculos
- Caminho de aprendizagemPreensão e efetuadores finais: como realmente pegar o objeto
Leitura complementar