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.
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.
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) ...
}
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.
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.
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.
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)

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.
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.
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.