Análise de código com motor de passo, estou tentando entender o Arduino.

Sou programador mas é meu primeiro contato com o Arduino, estou tentando ajudar no projeto de faculdade do meu irmão. Ainda não entendi bem como o Arduino funciona dentro do loop() dele, qual a velocidade de processamento desse loop, algumas coisas andam ocorrendo aqui e me parecem estranhas, erros de hardware e não de software, preciso de ajuda para entender o que pode estar ocorrendo de errado. Vamos lá.

Tratasse de um projeto de automação residencial onde será controlado o portão da garagem e algumas luzes da casa. Esta sendo usado o Arduino Uno e no portão um motor de passo.

O código abaixo funciona com o portão do projeto, porém ocorre que quando eu desligo a função controlandoCasa() no loop, o código funciona bem e o portão responde a todos os comandos para Abrir, Fechar e Parar, mostrando as mensagens de debug na tela. Mas quando ligo a função controlandoCasa(), o portão apenas executa o modo Fechar e não consegue fazer os outros comandos enviados. O que me intriga aqui é o fato do Arduino não estar debugando direito as ações do programa quando rodado com todas as funções, do portão e da casa. Por exemplo, eu envio o comando 3 para parar o motor quando ele esta andando, o motor faz uma pausa e depois retorna, porém no debug do aplicativo ele não emite a execução da linha de código do comando Parar, que deveria mostrar o texto “parado” na tela. Tenho a impressão de que o código roda mais lento e o Arduino não consegue processar os envios dos comandos.

#include <Stepper.h>
 
// Configuração de pinos dos equipamentos ligados
#define lampada1          2
#define lampada2          3
#define lampada3          4
#define lampada4          5
#define ventilador        6
#define limiteAbertura    7 // switch que limita a abertura do portão
#define limiteFechamento  8 // switch que limita o fechamento do portão

int acaoPortao = 0; // registro da ação escolhida pelo aplicativo
int acaoCasa = 0; // registro da ação escolhida pelo aplicativo
int passosDoMotor = 100; // número de passos que o motor suport

// Conectando pinos do motor à biblioteca
Stepper portao(passosDoMotor, 10, 12, 11, 13); // sequência de pinos 1, 3, 2, 4

void setup() {

  // Executando setups
  setupPortao();
  setupCasa();

  // Inicia a porta serial
  Serial.begin(9600);
  
}

void loop() {
  
  // Executando controladores
  controlandoPortao();
  controlandoCasa();
  
}

// Setup do portão
void setupPortao() {
  
  portao.setSpeed(350); // velocidade do portão
  
  pinMode(limiteAbertura, INPUT); // switch da abertura
  pinMode(limiteFechamento, INPUT); // switch do fechamento

}

// Setup dos equipamentos da casa
void setupCasa() {

  // Lista de equipamentos
  pinMode(lampada1, OUTPUT);
  pinMode(lampada2, OUTPUT);
  pinMode(lampada3, OUTPUT);
  pinMode(lampada4, OUTPUT);
  pinMode(ventilador, OUTPUT);

  // Iniciando todos como desligado
  digitalWrite(lampada1, LOW);
  digitalWrite(lampada2, LOW);
  digitalWrite(lampada3, LOW);
  digitalWrite(lampada4, LOW);
  digitalWrite(ventilador, LOW);
  
}

/*
 * Controlando ações do portão
 * Ações de valor 1 a 9 correspondem aos eventos do portão
 * O funcionamento segue o ciclo do loop(), em cada passada é checado os switches
 * para saber se deve continuar ou não com os steps() do motor.
 * Ações: 1 = Abrir, 2 = Fechar, 3 = Parar
 */
void controlandoPortao() {

  // Verificando entrada dos botões do aplicativo
  if (Serial.available() > 0) {
    if (Serial.parseInt() >= 1 && Serial.parseInt() <= 9)
      acaoPortao = Serial.parseInt();
  }

  // Botões do aplicativo
  switch (acaoPortao) {
  
    // Abrindo portão
    case 1:
      if (digitalRead(limiteAbertura) == LOW) {
        portao.step(-100);
        Serial.println("abrindo");
      } else {
        acaoPortao = 0;
        Serial.println("limite de abertura alcançado");
      }
      break;
      
    // Fechando portão
    case 2:
      if (digitalRead(limiteFechamento) == LOW) {
        portao.step(100);
        Serial.println("fechando");
      } else {
        acaoPortao = 0;
        Serial.println("limite de fechamento alcançado");
      }
      break;
      
    // Parando portão
    case 3:
      acaoPortao = 0;
      Serial.println("parado");
      break;
      
  }
  
}

/* 
 * Controlando equipamentos da casa
 * Ações a partir do valor 11 correspondem aos eventos dentro da casa
 */
void controlandoCasa() {

  // Verificando entrada dos botões do aplicativo
  if (Serial.available() > 0) {
    acaoCasa = Serial.parseInt();
  } else {
    return;
  }

  // Botões do aplicativo
  switch (acaoCasa) {

    // Lâmpada 1
    case 11: interruptor(lampada1); break;

    // Lâmpada 2
    case 12: interruptor(lampada2); break;

    // Lâmpada 3
    case 13: interruptor(lampada3); break;

    // Lâmpada 4
    case 14: interruptor(lampada4); break;

    // Ventilador
    case 15: interruptor(ventilador); break;
      
  }

}

// Função liga/desliga para equipamentos simples
void interruptor(int equipamento) {
  
  if (digitalRead(equipamento) == LOW) {
    digitalWrite(equipamento, HIGH);
  } else {
    digitalWrite(equipamento, LOW);
  }

}

Um detalhe que descobri agora, a biblioteca Stepper ao executar a função step(), interrompe o processamento do programa até que se finalize a passagem completa do motor. Qual seria a forma ideal para que o programa continuasse sendo executado enquanto o motor esta executando seus passos em paralelo? Acredito que por conta desta interrupção os comandos enviados ao arduino não são processados a tempo.

https://www.arduino.cc/en/Reference/StepperStep

O comportamento mantém-se se trocar a ordem da chamada das funções dentro da função loop()? A mim parece-me que se diz:

quando eu desligo a função controlandoCasa() no loop, o código funciona bem

o problema será software e não hardware e estará relacionado com a função controlandoCasa() ou com alguma interferência entre esta e a função controlandoPortao(). Porque é que é que vai duas vezes à porta série dentro da função loop() ? Não podia ir só uma? E se arranjar forma de apenas processar a porta série 1 vez o problema mantém-se?

Acho que descobriste o teu problema...

Para fazer o que pretendes, eu olharia a implementar uma função que corre com base no tempo (pesquisa por Timer1 interrupt) e programaria ela para interromper o programa de x em x tempo para processar os passos em vez de bloquear o programa.

Falhando isso podes dar instruções mais pequenas de step() dando assim hipótese de processar código entre passos. Não é processamento paralelo, mas dará a sensação disso.

Quanto ao teu problema de não aceitar comandos, parece-me óbvio porquê... não podes ler da porta série em dois sítios diferentes. Imagina que tu mandas um comando para acender uma lampada, mas a função que é chamada logo a seguir é a controlandoPortao()... o que vai acontecer é que o comando é ignorado.

Assim sendo eu criaria uma função para tratar da porta série em separado dessas funções e mediante o comando recebido, executarias uma função para cada um dos comandos.

bubulindo: Acho que descobriste o teu problema...

Para fazer o que pretendes, eu olharia a implementar uma função que corre com base no tempo (pesquisa por Timer1 interrupt) e programaria ela para interromper o programa de x em x tempo para processar os passos em vez de bloquear o programa.

Falhando isso podes dar instruções mais pequenas de step() dando assim hipótese de processar código entre passos. Não é processamento paralelo, mas dará a sensação disso.

Quanto ao teu problema de não aceitar comandos, parece-me óbvio porquê... não podes ler da porta série em dois sítios diferentes. Imagina que tu mandas um comando para acender uma lampada, mas a função que é chamada logo a seguir é a controlandoPortao()... o que vai acontecer é que o comando é ignorado.

Assim sendo eu criaria uma função para tratar da porta série em separado dessas funções e mediante o comando recebido, executarias uma função para cada um dos comandos.

A função controlandoPortao() sozinha funciona bem. Sendo assim eu digo que o único problema é mesmo a porta série.

O problema é exatamente este, até li o script da biblioteca Stepper para entender o que ela fazia. O arduino fica ocupado com o programa enquanto o motor esta executando seus passos, isso impede que eu leia o Serial para executar alguma outra ação solicitada pelo aplicativo como a de parar o motor.

O programa até funcionou quando usei apenas o script do portão, mesmo assim a lógica realmente esta errada por conta deste detalhe de funcionamento do motor de passos. Para que funcionasse corretamente, eu deveria ter alguma peça de hardware adicional que controlasse o motor e na programação eu apenas solicitaria a ligação ou desligamento do motor, deixando que o hardware cuidasse de controlar os passos do motor. Desta forma o programa ficaria livre para atender as solicitação de comandos via Serial.

edir:
O problema é exatamente este, até li o script da biblioteca Stepper para entender o que ela fazia. O arduino fica ocupado com o programa enquanto o motor esta executando seus passos, isso impede que eu leia o Serial para executar alguma outra ação solicitada pelo aplicativo como a de parar o motor.

O programa até funcionou quando usei apenas o script do portão, mesmo assim a lógica realmente esta errada por conta deste detalhe de funcionamento do motor de passos. Para que funcionasse corretamente, eu deveria ter alguma peça de hardware adicional que controlasse o motor e na programação eu apenas solicitaria a ligação ou desligamento do motor, deixando que o hardware cuidasse de controlar os passos do motor. Desta forma o programa ficaria livre para atender as solicitação de comandos via Serial.

Admitindo que é esse o problema, a porta série quando o stepper está em funcionamento, apenas fica ocupada quando envia dados de depuração. A depuração pode ser feita de outra forma, por exemplo com LEDs.
Por outro lado há várias bibliotecas para controlar steppers. Haverá algum que não bloqueie o programa?
Por outro lado ainda, a porta série pode funcionar através de interrupção (não sei se é este o caso) em que após a mensagem se colocada no buffer, não é necessário nenhum controlo do programa para que a mensagem seja enviada.
Há várias dúvidas que estão no ar. É necessário fazer testes para ver o que realmente se passa e como se pode corrigir.

Resolvido!

Deixei de usar a biblioteca Stepper e optamos por colocar o Easy Driver para controlar o motor diretamente. Agora os passos do motor ocorrem dentro do loop() e o programa não fica impedido de checar o Serial. Se tiver curiosidade segue o código de teste abaixo que fiz.

#define motorDirecao      9
#define motorPassos       10

void setup() {

  pinMode(motorDirecao, OUTPUT);
  pinMode(motorPassos, OUTPUT);
  Serial.begin(9600);
  
}

void loop() {

  acoes(); 
  motor();
  
}

void acoes() {

  if (Serial.available()) {
    Serial.print("Recebendo ação ");
    Serial.println(Serial.parseInt());
  }
  
}

void motor() {

  digitalWrite(motorPassos, HIGH);
  digitalWrite(motorPassos, LOW);
  delayMicroseconds(250);

}

Experimentaste o que eu disse acerca de dares vários passos em vez de todos os passos duma vez?

Efectivamente, este código faz exactamente o que a biblioteca Stepper faz já que o código bloqueia na mesma.