Como usar o monitor serial do Arduino Deixe um comentário

O que é comunicação serial? O que é monitor serial? Para que serve? Como utilizar? Nesse artigo, iremos explorar a ferramenta monitor serial do Arduino.

Comunicação Serial

Para falarmos de monitor serial no Arduino, primeiro é fundamental termos um embasamento teórico sobre o conceito de comunicação serial, que permite o uso do monitor.

Esse tipo de comunicação tem como objetivo transferir bits em série por um barramento; a grosso modo, isso significa que valores binários, zeros e uns, serão transmitidos um a um, ou seja, sequencialmente ou um atrás do outro.

Mas como isso é feito na prática?

Basicamente, é utilizada variação de tensão para representar zeros e uns. Isso significa que uma tensão alta representa valor um e tensão baixa, zero. Então um gráfico de envio de dados em função do tempo teria um sinal como o seguinte.

Gráfico de envio de dados em função do tempo
Gráfico de envio de dados em função do tempo

(Vale dizer que foram omitidos os start bit e o stop bit desse gráfico, mas basicamente o start bit informa o início da leitura e o stop bit, o final.)

Quando transmitimos 8 bits, iremos compor um byte e cada byte representa um valor decimal. Veja a seguinte tabela.

Valor decimal Byte correspondente
0 00000000
1 00000001
2 00000010
3 00000011
4 00000100
5 00000101
255 11111111

Veja que conforme a sequência de bits que compõem o byte, teremos um número. E por que isso funciona dessa forma? Porque no final das contas, tudo que o computador entende são zeros e uns. Perceba ainda que o número final de um byte é 255, porque é o maior número possível que conseguimos montar com 8 bits.

Legal, então agora, por mais que ainda não sabemos como fazer isso na prática com o Arduino, sabemos que podemos transmitir números binários que correspondem a números decimais. Mas se nós quiséssemos transferir caracteres alfanuméricos, ou seja, além de números, quiséssemos enviar letras, símbolos populares e etc?

Para isso, precisamos ter como principal ferramenta a tabela ASCII.

Tabela ASCII

Essa tabela basicamente relaciona vários tipos de valores alfanuméricos e o que nos interessa é saber que cada um dos valores decimais, que temos derivados dos bytes, é interpretado pelo computador como um caractere também. Então veja as seguintes tabelas que são recortes da ASCII.

Decimal Caracter
65 A
66 B
67 C
68 D

 

Decimal Caracter
97 a
98 b
99 c
100 d

 

Decimal Caractere
48 0
49 1
50 2
51 3

Caso queira conhecer a própria tabela, confira na íntegra.

Analisando essas abstrações, algumas coisas chamam a atenção. Primeiro que os caracteres ‘A’ e ‘a’, por mais que sejam a mesma letra, são símbolos diferentes e também diferem no valor que os codifica. Em segundo lugar, há uma diferença entre um o valor decimal que codifica um caractere e um algarismo.

Por exemplo, os caracteres dos números 0, 1, 2, 3, 4, 5, …, 9 são representações dos algarismos, ou seja, quando tratamos um numeral como caractere, estamos preocupados apenas com a simbologia do número, pois não nos convém manipular um caractere da mesma forma que lidamos com um número, fazendo soma, subtração e outras aplicações matemáticas. Dessa forma, se quiséssemos transmitir o caractere ‘3’, por exemplo, estaríamos, por baixo dos panos, referenciando o decimal 51.

Com isso, entendemos o que é comunicação serial e que a utilizamos para transferir bytes que decodificados resultam em valores decimais que por sua vez o computador consegue interpretar como caracteres especiais.

Se essa seção soar confusa, fique tranquilo que na prática fica muito mais fácil absorver esses conceitos.

Monitor serial

Agora que sabemos como funciona a comunicação serial e como caracteres se relacionam baseados na tabela ASCII, podemos utilizar na prática esses conceito com o uso do monitor serial.

O monitor serial é uma ferramenta presente na IDE do Arduino e por ela você pode acompanhar qualquer impressão ou transferência de dados via serial. Podemos abri-la das seguintes maneiras:

  • Acessando o ícone superior direito (destacado em vermelho):
Captura de tela demostrando a primeira maneira de como abrir o monitor serial
Abrindo o monitor serial pelo botão “Monitor Serial”
  • Abrindo o menu “Ferramentas” e então “Monitor serial”:
Captura de tela mostrando como abrir o monitor serial atrave´s do menu da IDE
Abrindo o monitor serial através do menu da IDE

Por exemplo, em um projeto com um LED piscando, podemos configurar avisos ao usuário, pessoa que faz uso do programa, para informá-lo quando o LED acende e quando o LED apaga. O local por onde o usuário verá os avisos será o monitor serial.

Se você já teve um pouco de contato com programação Arduino, provavelmente já viu alguma das funções utilizadas para interagir com o monitor serial: Serial.begin(), Serial.println(), Serial.available(), entre outras. Agora, se nunca ouviu falar sobre, é interessante você já conhecer na prática como elas funcionam.

Funções principais

Para contextualizar essa exposição, vamos levar em consideração o projeto super básico para piscar um LED que falamos, onde temos o seguinte esquemático:

Esquemático do projeto piscar um LED
Esquemático do projeto “Piscar um LED”

Além do esquemático, temos o seguinte programa:

#define pinoLED 7

void setup(){
  pinMode(pinoLED, OUTPUT);
  
}

void loop(){

  digitalWrite(pinoLED, HIGH);
  delay(1000); 

  digitalWrite(pinoLED, LOW);
  delay(1000);
  
}

Basicamente, configuramos o pino do LED que estamos utilizando como uma saída. usamos digitalWrite() para acender e apagar o LED e delay() utilizamos para fazer uma espera entre a instrução anterior e consecutiva. Caso você tenha dúvidas como esse projeto funciona ou queira uma explicação mais detalhada, confira essa aula da MakerHero sobre piscar um LED.

Agora, queremos utilizar o monitor serial do Arduino para dar avisos ao usuário. Para fazer isso, primeiro precisamos inicializar a comunicação serial no código, com o comando seguinte comando dentro do setup.

Serial.begin();

Essa função begin recebe como parâmetro a velocidade de transmissão de bits, mais conhecido como baud rate. Há alguns valores padrões que normalmente são utilizados e diferentes componentes podem necessitar de diferentes taxas de transmissão. No nosso caso, o valor 9600 é adequado.

Serial.begin(9600);

Com o serial configurado, estamos livres para usar suas funções. Seguindo o escopo que definimos para o projeto, colocamos um aviso que o LED acendeu e outro que ele apagou no loop junto às funções digitalWrite() e delay().

Faço isso utilizando a função Serial.println() que irá imprimir algo, assim como sugere “print” e irá pular uma linha, e o que indica esse pulo de linha é o “ln”. O parâmetro dessa função é a frase que quero colocar como aviso entre aspas.

O código com as novas informações ficou assim:

#define pinoLED 7

void setup(){
Serial.begin(9600);
pinMode(pinoLED, OUTPUT);

}

void loop(){

digitalWrite(pinoLED, HIGH);
Serial.println("LED está aceso.");
delay(1000);

digitalWrite(pinoLED, LOW);
Serial.println("LED está apagado.");
delay(1000);

}

Quando rodarmos nosso programa, basta abrir o monitor serial e teremos os avisos aparecendo de acordo com o comportamento do LED. Perceba que nós mesmos definimos quando a mensagem aparece e o que ela está dizendo. Então, se o aviso vai fazer sentido ou não, depende de você que está escrevendo-o.

Para continuar nossa exposição sobre o monitor serial, vou fazer um outro exemplo que nos mostrará que podemos imprimir outros dados além de um texto como parâmetro.

Temos o seguinte projeto feito, um controle básico sobre um servo motor. O que eu quero fazer é acompanhar o ângulo do servo. Caso você não saiba como utilizar um servo, confira nosso artigo de como utilizar um servo motor.

O esquemático será o seguinte:

Esquemático Arduino Uno + Servo
Esquemático Arduino Uno + Servo

E o código (confira-o atentamente, por mais que seja uma cópia do código da aula endereçada acima, há algumas mudanças em relação ao original).

#include <Servo.h>
 
#define SERVO 3 // Porta Digital 3 PWM
 
Servo s; // Variável Servo
int pos; // Posição Servo
 
void setup (){
  s.attach(SERVO);
  Serial.begin(9600);
  s.write(0); // Inicia motor posição zero
}
 
void loop(){
  for(pos = 0; pos <= 90; pos+=30){
    s.write(pos);
    delay(1000);
  }
  
  delay(500);
  
  for(pos = 90; pos >= 0; pos-=30){
    s.write(pos);
    delay(1000);
  }

}

Para inicializar o serial, fazemos o mesmo procedimento já feito anteriormente com a função begin(). Agora, podemos gerar mensagens que informem o valor do ângulo do servo. Fazemos isso dessa forma:

#include <Servo.h>
 
#define SERVO 3 // Porta Digital 3 PWM
 
Servo s; // Variável Servo
int pos; // Posição Servo
 
void setup (){
  s.attach(SERVO);
  Serial.begin(9600);
  s.write(0); // Inicia motor posição zero
}
 
void loop(){
  for(pos = 0; pos <= 90; pos+=30){
    s.write(pos);
    Serial.print("Angulo: ");
    Serial.println(pos);
    delay(1000);
  }
  
  delay(500);
  
  for(pos = 90; pos >= 0; pos-=30){
    s.write(pos);
    Serial.print("Angulo: ");
    Serial.println(pos);
    delay(1000);
  }

}

O que estamos fazendo é fazer um print() que imprime a mensagem “Angulo: ”, perceba a ausência do ln na função faz com que nenhuma linha seja pulada, para que no próximo print, a informação impressa fique ao lado de “Angulo: “.

Com isso, o próximo print, o println(pos), apresenta o ln ao lado, pois nesse momento convém que a linha seja pulada ao final da impressão e como parâmetro, passaremos a própria variável pos. Assim, a cada println, o valor da variável pos será colocado numericamente no monitor.

Agora que sabemos que podemos colocar outros valores além de Strings, as populares frases, vamos voltar no primeiro exemplo.

Uma mudança que poderíamos fazer para deixar o meu aviso mais dinâmico é o seguinte: invés de só escrever a mensagem “LED está aceso.” ou “LED está apagado.”, poderíamos colocar o seguinte.

Serial.println(digitalRead(pinoLED));

Assim, iremos imprimir o valor da leitura digital do pino que o LED está conectado. No primeiro print do loop, como acabamos de colocar o nível lógico do pino em HIGH, utilizando a função digitalWrite, sabemos que a leitura digitalRead a ser feita do pinoLED será 1, que é equivalente à HIGH.

Já no segundo print, colocamos o nível lógico do pino 7 em baixo, logo, o print irá imprimir o valor zero, que é equivalente à LOW.

O código fica assim:

#define pinoLED 7

void setup(){
  Serial.begin(9600);
  pinMode(pinoLED, OUTPUT);
  
}

void loop(){

  digitalWrite(pinoLED, HIGH);
  Serial.println(digitalRead(pinoLED));
  delay(1000); 

  digitalWrite(pinoLED, LOW);
  Serial.println(digitalRead(pinoLED));
  delay(1000);
  
}

E os prints no monitor serial ficam assim:

Monitor serial em funcionamento
Monitor serial em funcionamento

É possível formatar isso de uma melhor forma para quem estiver lendo ter maior clareza e esclarecimento do que são esses valores 1 e 0 aparecendo no monitor, fica de desafio para você implementar uma mensagem no monitor melhor formatada.

Para finalizar as funções principais da comunicação serial, temos ainda como ler valores enviados via comunicação serial, seja um envio de um outro microcontrolador, ou uma inserção manual de dados no serial.

Esses dados são enviados para um buffer, que é uma área da memória do Arduino reservada para armazenamento desses dados. A partir disso, podemos acessar esses dados e manipularmos da forma que desejarmos.

Para fazer isso, precisamos de uma conexão com Arduino para termos acesso ao monitor serial. Além disso, no setup temos apenas a configuração da taxa de transmissão empregada.

void setup(){
    Serial.begin(9600);
}

Agora no loop, teremos que utilizar as funções Serial.read() e Serial.available(). Esta irá retornar quantos bytes estão disponíveis para a leitura e aquela serve para ler efetivamente os bytes em ordem de chegada tal qual uma fila.

O fluxo principal aqui deve ser o seguinte: se a função Serial.available retornar um número maior que zero, significa que existem caracteres no buffer para serem lidos, e a partir disso, podemos fazer leituras desses caracteres.

Para primeiro exemplo, iremos apenas ler os dados e imprimí-los logo em seguida. Seguindo essa linha de raciocínio teremos o seguinte.

void loop() {
    if(Serial.available()>0){
        char c;
        c = Serial.read();
        Serial.print(“Você escreveu “);
        Serial.println(c);
        delay(150);
    }
}

Dessa forma, se tivermos caracteres para serem lidos, entramos no if, criamos uma variável do tipo char que pode receber caracteres, ou seja, símbolos alfanuméricos e comandos, depois fazemos dois prints diferentes onde o primeiro serve apenas para imprimir “Você escreveu” e segundo para imprimir o valor da variável e pular uma linha.

Se rodarmos nosso código, e escrever a letra ‘a’ no monitor serial, teremos o seguinte.

Monitor serial em funcionamento
Monitor serial em funcionamento

Isso pode ser estranho a primeiro momento, porque a primeira linha impressa realmente era esperada. Escrevemos a letra ‘a’, esse caractere foi lido e atribuído a uma variável, essa variável foi printada. Mas porque a mensagem “Você escreveu” apareceu na linha abaixo novamente e com um espaço em branco?

O motivo disso na verdade é bem simples: quando escrevemos ‘a’ e apertamos enter, estamos enviando dois caracteres via serial, o ‘a’ e ‘\n’, sendo este o “pula linha”. Ou seja, quando apertamos enter, além do caractere ou conjuntos de caracteres que escrevemos, estaremos enviando uma instrução de pular linha que é entendida pelo Arduino como um caractere.

E na linguagem do Arduino, que é uma abstração da linguagem C, ‘\n’ é dito um caractere não imprimível, ou seja, ele não possui um representação visual, servindo apenas para indicar a quebra de linha. Por isso que a mensagem “Você escreveu” aparece novamente, pois após a primeira leitura de ‘a’, realmente ainda tem um caractere para ser lido que é ‘\n’. Este caractere, então, é lido, sua função é executada, mas nada é mostrado para o usuário.

Há outros caracteres além de ‘\n’ que são não imprimíveis e servem para executar alguma ação. Eles podem ser vistos na própria tabela ASCII.

Ainda nessa questão do caractere não imprimível, poderíamos mexer no código para excluir essa impressão. Bastaria fazer o condicional if para só imprimirmos o char c quando o valor que c está armazenando é diferente de ‘\n’.

void loop() {
    if(Serial.available()>0){
        char c;
        c = Serial.read();
        if(c != ‘\n’){
	Serial.print(“Você escreveu “);
            Serial.println(c);
        }
        
        delay(150);
    }
}

Com esse código podemos inclusive imprimir uma mensagem e cada caractere será impresso linha a linha. Confira esse caso onde foi escrito ARDUINO.

Monitor serial em funcionamento
Monitor serial do Arduino em funcionamento

Mas se quiséssemos poder imprimir uma frase invés de um valores independentes?

Para isso, trabalharemos com Strings, que é um tipo de dado que pode armazenar vários caracteres em conjunto. A grande vantagem, nesse caso, de usar Strings é que podemos concatenar valores.

Concatenar é uma operação em que somamos duas Strings, String e um Char, etc. Por exemplo, se quiséssemos somar “Eu gosto de “ com “cães” e se você pensasse intuitivamente que o resultado fosse algo como “Eu gosto de cães”, significa que você já sabe o que é uma concatenação de strings.

Vejamos um exemplo abaixo.

void setup(){
  Serial.begin(9600);

  String frase1 = "Arduino é uma placa que possui um";
  String frase2 = "microcontrolador";

  Serial.println(frase1 + " " + frase2);
  
}

void loop() {
}

O que aparecerá no monitor serial será o seguinte:

Texto escrito no monitor serial Arduino
“Arduino é uma placa que possui microcontrolador”

Perceba que foram concatenadas duas Strings e um caractere nulo ou vazio no meio delas para termos um espaço entre as frases.

Sabendo isso, agora podemos montar uma frase e imprimí-la no monitor. O que estaremos fazendo na prática é criar uma variável String que inicialmente não terá nenhum valor em si, mas com concatenações sucessivas irá guardando caractere a caractere, que serão escritos e sobrescritos em um Char c. Confira o código abaixo.

void loop() {

  if(Serial.available()>0){

    String frase = "";
    
    while(Serial.available()>0){
      char c;
      c = Serial.read();
      if(c != '\n'){
        frase += c;
        Serial.println(frase);
      }
      delay(100);
    }

    Serial.println(frase);
}

Então, o if serve apenas para o programa perceber a chegada de algum dado no buffer. Consequentemente, o while serve para as concatenações acontecerem enquanto ainda há algum dado no buffer.

A concatenação é feita com frase += c que é a exata mesma coisa que frase = frase + c, ou seja, frase é concatenado de c e esse valor é atribuído para frase. Então em cada iteração do while teríamos o comportamento da seguinte tabela para a inserção da palavra “ATMega” no monitor:

frase c frase + c
A A
A T AT
AT M ATM
ATM e ATMe
ATMe g ATMeg
ATMeg a ATMega

Perceba que frase recebe caractere por caractere até formar a palavra inteira que inserimos.

Mas afinal, por que utilizar a comunicação serial e o monitor?

Como já vimos, o monitor serial do Arduino é muito útil para poder imprimir valores desejados e também enviar valores para o programa.

Com isso, podemos utilizar a ferramenta para depurar o nosso código, que significa reduzir os defeitos em um programa e conseguir acompanhar o que nosso programa está fazendo. Quando desenvolvemos um projeto é muito comum encontrarmos dois tipos de erros: os sintáticos e os semânticos.

Os sintáticos são aqueles referentes à nossa escrita, como por exemplo, esquecer um ponto e vírgula no final de uma linha, escrever errado alguma palavra reservada, entre outras. Já os semânticos são aqueles que o compilador não irá apontar para nós, o programa só não irá reproduzir o código da forma que imaginamos, pois programamos de forma equivocada.

Para este problema, torna-se muito útil colocar vários prints do serial durante o código para recebermos alertas informando onde está sendo feita a leitura e/ou acompanhar o valor de uma variável em específico.

Além disso, é possível utilizar uma outra ferramenta serial que pode gerar gráficos a partir de valores sendo impressos via print serial. Essa se chama plotter serial. Basta nós configurarmos um valor para ser impresso continuamente via comunicação serial, que ao abrir o plotter, a IDE do Arduino estará fazendo a construção de um gráfico conforme os prints. Confira como ficaria com o exemplo do servo que já trabalhamos.

Abrindo o Plotter Serial: Ferramentas -> Plotter Serial
Abrindo o Plotter Serial: Ferramentas -> Plotter Serial
Grafíco mostrando o valor da variável "pos" pelo tempo decorrido
Valor da variável “pos” pelo tempo decorrido

Finalização

E aí, maker! Gostou? Chegamos ao fim do nosso artigo e espero que você tenha gostado e entendido como utilizar o monitor serial da melhor maneira possível.

Qualquer dúvida que tiver, não hesite em mandar aqui nos comentários abaixo. E caso tenha gostado do conteúdo mais aprofundado, tenha ficado com dúvidas no uso de variáveis, laços de repetição como o While, condicionais como o If, te convido para adquirir o mais novo curso de programação C/C++ para Arduino da MakerHero.

Faça um bom proveito de seus novos conhecimentos e até mais!

Faça seu comentário

Acesse sua conta e participe