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.