30. Arduino Multicomando

Passamos a usar uma abordagem multicomando a partir do projeto Sistema Multipropósito para Monitoramento e Controle de Processos em Laboratório - SisProLab, visando usar a placa Arduino como uma plataforma adequada para diferentes projeto com a incorporação de novos comandos para os novos projetos, sem modificação dos comandos existentes.

30.1. Comando setPosition

Implementamos o comando setPosition com a sintaxe: [comando];[id_valve];[angle];[id_transaction].

  • [comando]: setPosition

  • [id_valve]: V1, V2, V3 ... Vn - identifica a válvula que vai receber o comando (a válvula deve estar mapeada no Arduino com o respectivo pino ao qual está conectada

  • [angle]: ângulo de giro do servomotor

  • [id_transaction]: um identificador qualquer que é retornado pelo Arduino permitir o mapeamento de comandos gerados pela interface de controle

Exemplos de comando:

  • setPosition;V1;45;125 - comando para o servomotor da válvula V1 se deslocar para o ângulo de 45°. E retornar 125 como ID da transação

  • setPosition;V2;125;200 - comando para o servomotor da válvula V2 se deslocar para o ângulo de 125°. E retornar 200 como o ID da transação.

Após o comando o Arduino retorna uma mensagem com a seguinte sintaxe: ACK;[id_valve];[angle];[id_transaction].

A mensagem ACK confirma a execução do comando e informa para o solicitante que o comando foi executado.

Nota

O uso das mensagens de retorno ACK/NACK permite estabelecer um mecanismo de checagem para verificar se os comandos são reconhecidos e possuemm a sintaxe correta.

Mas até esse momento não implementamos esse recurso de forma completa.

A seguir o código do Arduino com a função setPosition que recebe os argumentos que identicam o servomotor, o ângulo para o deslocamento e um identificador de comando que pode ser usado pela interface de controle que está solicitando o comando.

  /*
  24/02/2021
  Adapted from:
  Example of processing incoming serial data without blocking.
  Author:   Nick Gammon
  (http://www.gammon.com.au/forum/?id=11425)
  Commands for monitoring capacitive currents
*/

//Available pins
//Digital
//D2, D3 (PWM), D4, D5(PWM), D6(PWM), D7, D8, D9(PWM), D10(PWM), D11(PWM) e D12
//Analogic
//A0, A1, A2, A3, A4 e A5

#include <Servo.h>

const unsigned int MAX_INPUT = 40;
char *cmd;
char *transaction_id;
char *id_valve;
char *value;
int pos; //Position valve

Servo servo_valve_1; // Servo variable of a valve
Servo servo_valve_2; // Servo variable of a valve
Servo servo_pump_1; // Servo variable of a pump

void setup() {
  // put your setup code here, to run once:

  servo_valve_1.attach(9); //Servo of valve 1 connected to pin 9
  
  // servo_valve_1.write(0); //Set the position of servo for valve 1 to 0 degree

  servo_valve_2.attach(10); //Servo of valve 2 connected to pin 10

  // servo_valve_2.write(0); //Set the position of servo for valve 2 to 0 degree
  

  Serial.begin(9600);
  
  Serial.flush();

 
}

void processIncomingByte (const byte inByte) {
  static char input_line[MAX_INPUT];
  static unsigned int input_pos = 0;

  switch (inByte)
    {

    case '\n':   // end of text
      input_line[input_pos] = 0;  // terminating null byte
      
      // terminator reached! process input_line here ...
      process_data (input_line);
      
      // reset buffer for next time
      input_pos = 0;  
      break;

    case '\r':   // discard carriage return
      break;

    default:
      // keep adding if not full ... allow for terminating null byte
      if (input_pos < (MAX_INPUT - 1))
        input_line[input_pos++] = inByte;
      break;   
      

    }  // end of switch
    
  }// end of processIncomingByte  


//Process_data to process incoming serial data after a terminator received

void process_data (char *data) {

  cmd = strtok(data, ";");

  if ( (strcmp(cmd, "checkConnection") == 0) || (strcmp(cmd, "checkconnection") == 0) ) {

        transaction_id = strtok(NULL, ";");
       
        checkConnection( transaction_id );

  } else if ( (strcmp(cmd, "setPosition") == 0) || (strcmp(cmd, "setposition") == 0) ) {
      
      id_valve = strtok(NULL, ";");
      value = strtok(NULL, ";");
      transaction_id = strtok(NULL, ";");
      
      setPosition(id_valve, value, transaction_id); 

  } else {

        Serial.print("#unknown command: ");
        Serial.println(cmd);

  } // end of if

}

//Send message "ACK" to to confirm the connection with the Arduino

  void checkConnection( char *transaction_id ) {

  
    Serial.print("ACK;");
    Serial.println(transaction_id);

  }

  void setPosition(char *id_valve, char *val, char *transaction_id) { 

    if ((id_valve[0] == 'V') || (id_valve[0] == 'v')) {
      
        byte id = strtol(id_valve+1, NULL, 10);

        byte position = atoi(val);

        Serial.print("ACK;");
        Serial.print("V");
        Serial.print(id);
        Serial.print(";");
        Serial.print(position);
        Serial.print(";");
        Serial.println(transaction_id);
        

    switch (id) 
      {
      case 1:
              servo_valve_1.write(position);
              break; 
      case 2:
              servo_valve_2.write(position);
              break;
      }
    }
    
  }

void loop() {
  // put your main code here, to run repeatedly:

  // if serial data available, process it
  while (Serial.available () > 0)
    processIncomingByte (Serial.read());
    
    
  // do other stuff here like testing digital input (button presses) ...


}

Nota

Os comandos servo_valve_1.write(0) e servo_valve_2.write(0) da seção setup foram comentados (\\) para evitar a sua execução e o reposicionamento inadequado toda vêz que a porta serial for aberta. Pois observamos que os comandos da seção setup são executados sempre que a porta serial é aberta com o comando open do Tcl.

30.2. Comando help

Um comando útil que retorna a versão e todos os comandos implementados no Arduino com a respectiva sintaxe, permitindo identificar o firmware gravado.

Foi implementado adicionando o teste if:

  ...
  } else if ( (strcmp(cmd, "HELP") == 0) || (strcmp(cmd, "help") == 0) ) {

         help();
  
  } else if (
  ...	 

E a função help:

  //Send the name of commands and the syntax

  void help( ) {

    Serial.println("arduino_multicom Version 00");
    Serial.println("setPosition;[id_valve];[angle];[id_transaction]");
    Serial.println("checkConnection;[id_transaction]");
    
  }

Mais tarde incluímos informações sobre os pinos usados para conectar os servomotores para controle das válvulas e bomba de seringa:

  void help( ) {

    Serial.println("arduino_multicom Version 00");
    Serial.println("Valve 1 connected to pin: ");
    Serial.println("Valve 2 connected to pin: ");
    Serial.println("Pump 1 connected to pin: ");
    Serial.println("Pump 2 connected to pin: ");
    Serial.println("setPosition;[id_valve];[angle];[id_transaction]");
    Serial.println("checkConnection;[id_transaction]");
    }

Download do arquivo arduino_multicom_00.ino.

30.3. Comandos startPump e stopPump para acionamento da bomba de seringa controlada por servomotor

O comando setPosition é adequado para o acionamento dos servomotores que controlam as válvulas (Válvula de 3 vias Controlada por Servomotor), pois com o comando servo_valve_1.write(position); o servomotor se desloca com velocidade máxima para a nova posição definida pela variável position.

Dessa forma o comando não interrompe significativamente a execução de outros comandos que precisam ser executados simultâneamente, tais como a leitura de um detector.

No entanto, o controle de um servo que aciona o movimento do êmbolo de uma bomba de seringa precisa ser feito de forma lenta tanto para carregar como para descarregar a seringa.

Atenção

Pois movimentos bruscos no êmbolo da seringa pode gerar uma pressão suficiente para soltar as mangueiras e causar vazamentos. Ou mesmo acidentes se um líquido corrosivo for lançado no rosto de alguém!

E além disso, pode ser necessário executar outras ações simultâneamente como, por exemplo, leituras de detectores.

A placa Arduino não possui recursos de Multitarefa, mas podemos contornar essa limitação executando os diferentes comandos intercalados em um loop temporizado utilizando o comando millis() conforme o tutorial: Multi-tasking the Arduino - Part 1.

Para isso vamos implementar os códigos para os seguintes comandos:

  • startPump;id_pump;set_point;interval;id_transaction

  • stopPump;id_pump;id_transation

Pensamos inicialmente no diagrama de fluxo da figura 356.

Figura 356. Diagrama de fluxo para o controle temporizado de diferentes comandos pelo Arduino (Fonte: https://tinyurl.com/bdh8w3sj)

Diagrama de fluxo para o controle temporizado de diferentes comandos pelo Arduino (Fonte: https://tinyurl.com/bdh8w3sj)

Essa lógica foi implementada com o uso de testes if no loop principal que utilizam as variáveis status_pump_1 e status_pump_2 como semáforos para indicar se alguma bomba está em operação e faz a chamada da função moveServoPump() passando como argumento o ID da respectiva bomba.

  if (status_pump_1 == 1) {
    moveServoPump(1);
  }

  if (status_pump_2 == 1) {
    moveServoPump(2);
  }

Os semáforos status_pump_1 e status_pump_2 são variáveis globais modificadas dentro da função startPump() que recebe como argumentos: id_pump;set_point;interval;id_transaction.

Tivemos que usar o recurso de armazenamento na EEPROM, seguindo o tutorial Arduino EEPROM Explained - Remember Last LED State, para armazenar a posição do servo no final do último ciclo, mesmo após desligamento do Arduino, para evitar movimentos bruscos do êmbolo da seringa ao religar o sistema.

Atenção

Mas existe um limite de 100.000 operações de escrita na memória EEPROM do Arduino (pelo menos do UNO). Por isso, para não reduzir o tempo de vida da placa o salvamento é feito apenas no final de um ciclo de bombeio.

Se a gravação na EEPROM fosse feita a cada incremento na posição do servo teríamos, em média, uns 100 incrementos em cada ciclo de bombeio, e portanto teria 100 operações de escrita na EEPROM para cada ciclo.

Dessa forma só poderíamos registrar ~1000 ciclos de bombeio. Para um sistema de análise química que funcionando 24 horas por dia, fazendo apenas 1 análise por hora, teríamos 720 ciclos de bombeio em apenas em 1 mês.

Ou seja, essa estratégia só iria funcionar pouco mais de 1 mês.

Vamos futuramente rever essa estratégia de uso da EEPROM para evitar o sucateamento programado das placas Arduino.

Isso poderia ser feito incluindo a posição atual como mais um argumento do comando startPump: startPump;id_pump;current_position;set_point;interval;id_transaction.

Ver também: EEPROM Questions on the 100,000 write limit.

Para permitir a associação das mensagens de retorno do Arduino com os comandos solicitantes, incluímos no final de todas as mensagens de retorno o id_transaction.

Por exemplo: ACK;START_PUMP;id_transaction, ACK;END_PUMP;id_transaction, ACK;STOP_PUMP;id_transaction

Download do arquivo arduino_multicom_01.ino.

30.4. Comandos startFlowMeter e stopFlowMeter para ativar e desativar um Medidor de Vazão de Gases.

Esta seção documenta as modificações que foram feitas para inserir os comandos necessários para o projeto de um medidor de vazão volumétrica de gases que está documentado na seção Open Automation for Water.

Este sensor consiste de um Airlock no qual foi instalado um LED-IR e um Fototransístor-IR. A a vazão de um gás qualquer pode então ser medida pela duração dos pulsos gerados pela passagem das bolhas do gás entre o feixe de IR.

A passagem de uma bolha de gás gera uma variação na intensidade de IR que incide sobre o fototransístor o qual gera um pulso que pode ser registrado pelo pino digital do Arduino.

30.5. Links úteis: