Eletrônica Por Equipe DevBras

Capa do artigo: Programa no PC falando com o Arduino pela serial: porta, velocidade e o reset escondido

Programa no PC falando com o Arduino pela serial: porta, velocidade e o reset escondido

Faça um programa no computador conversar com o Arduino pela USB: mesma velocidade dos dois lados, porta certa, espera pelo reset da placa e timeout na leitura.

Você escreveu um programa no computador com um botão "Ligar LED", o Arduino tem um sketch que lê a serial, e nada acontece. Pelo monitor serial da IDE, a letra funciona. Pelo seu programa, não. Na maioria das vezes, o programa manda o comando cedo demais: abrir a porta reinicia o Uno, e os primeiros bytes chegam enquanto a placa ainda está acordando. Quando não é isso, é velocidade diferente nos dois lados, a porta errada ou o monitor serial da IDE ainda segurando a porta.

A resposta curta: use a mesma velocidade (9600, por exemplo) no Serial.begin e no programa, abra a porta que apareceu quando a placa foi conectada, feche o monitor serial da IDE, espere a placa avisar que está pronta antes de mandar o primeiro comando e use timeout em toda leitura.

Por que a placa não responde ao primeiro comando

A serial é um fio de bytes com um ritmo combinado. Não há negociação: se um lado fala a 9600 bits por segundo e o outro escuta a 115200, os bits são amostrados no tempo errado e chega lixo ou nada.

O reset é menos óbvio. No Uno e em placas parecidas, abrir a porta serial no computador aciona um sinal que reinicia o microcontrolador. É assim que a IDE consegue gravar o programa sem você apertar o botão de reset. O efeito colateral é que, ao abrir a porta no seu programa, a placa reinicia, passa pelo bootloader e só depois de algo como um ou dois segundos chega ao setup(). O comando enviado nesse intervalo se perde.

Placas com USB nativo, como o Leonardo, não reiniciam ao abrir a porta. Nelas, a mensagem de "pronto" do setup() pode ter saído antes do seu programa conectar. O protocolo abaixo trata os dois casos.

O protocolo mínimo: pronto, comando, resposta

O exemplo que vai atravessar o texto: um script em Python, no notebook, que liga e desliga o LED do pino 13. O protocolo tem três regras: a placa anuncia PRONTO ao iniciar e sempre que receber ?; cada comando é uma letra; toda letra recebe uma linha de resposta.

O sketch do Arduino

const int PINO_LED = 13;

void setup() {
  pinMode(PINO_LED, OUTPUT);
  Serial.begin(9600);
  Serial.println("PRONTO");
}

void loop() {
  if (Serial.available() > 0) {
    char c = Serial.read();

    if (c == 'A') {
      digitalWrite(PINO_LED, HIGH);
      Serial.println("OK A");
    } else if (c == 'a') {
      digitalWrite(PINO_LED, LOW);
      Serial.println("OK a");
    } else if (c == '?') {
      Serial.println("PRONTO");
    } else if (c >= ' ') {
      // Ignora quebras de linha; avisa sobre qualquer outro caractere.
      Serial.print("ERRO ");
      Serial.println(c);
    }
  }
}

O programa no computador, em Python

Ele usa a biblioteca pyserial (pip install pyserial). A porta vem como argumento: no Windows, algo como COM3; no Linux, /dev/ttyACM0 ou /dev/ttyUSB0; no macOS, um nome começando com /dev/cu..

import sys
import time

import serial
from serial.tools import list_ports

BAUD = 9600


def ler_linha(ser):
    return ser.readline().decode('utf-8', errors='replace').strip()


def esperar_pronto(ser, limite_s=6.0):
    fim = time.monotonic() + limite_s
    while time.monotonic() < fim:
        linha = ler_linha(ser)
        if linha == 'PRONTO':
            return True
        if linha == '':
            # Nada em 1 s: talvez a placa já tenha iniciado antes de conectarmos.
            ser.write(b'?')
    return False


def main():
    if len(sys.argv) < 2:
        print('uso: python led.py PORTA')
        print('portas encontradas:')
        for p in list_ports.comports():
            print('  ', p.device, '-', p.description)
        return 1

    porta = sys.argv[1]
    try:
        with serial.Serial(porta, BAUD, timeout=1) as ser:
            if not esperar_pronto(ser):
                print('A placa não disse PRONTO. Confira velocidade, porta e sketch.')
                return 2
            for comando in (b'A', b'a'):
                ser.write(comando)
                resposta = ler_linha(ser)
                print('enviei', comando.decode(), '-> recebi', resposta or '(nada em 1 s)')
                time.sleep(1)
    except serial.SerialException as erro:
        print('Não consegui usar a porta', porta, '-', erro)
        return 3
    return 0


if __name__ == '__main__':
    sys.exit(main())

Rodando com a placa certa, a saída esperada é:

enviei A -> recebi OK A
enviei a -> recebi OK a

O timeout=1 no construtor faz o readline() desistir depois de um segundo. Sem ele, uma placa travada ou desconectada deixa o seu programa esperando para sempre. É a mesma lição de colocar timeout antes que a espera vire infinita, só que num cabo USB.

Como achar a porta e o cabo certos

  1. Liste as portas sem a placa, rodando o script sem argumentos.
  2. Conecte a placa e liste de novo. A porta nova é a sua. Não escolha a primeira da lista: no Windows, COM1 costuma existir sem ter nada a ver com o Arduino.
  3. Se nenhuma porta nova aparece, troque o cabo. Muitos cabos USB vendidos com carregadores só têm os fios de energia. A placa acende e o computador não vê nada.
  4. Clones com conversor USB-serial diferente podem precisar de driver em alguns sistemas. A descrição da porta na listagem ajuda a identificar o chip.
  5. No Linux, permissão negada costuma significar que o seu usuário não está no grupo que pode usar portas seriais (em Debian e Ubuntu, dialout). Adicione e faça login de novo.

Uma porta, um programa

Uma porta serial só aceita um programa aberto por vez. Se o monitor serial da IDE está aberto, o seu script recebe erro de acesso negado ou porta ocupada. O contrário também vale: com o script rodando, a IDE não consegue gravar o sketch. Feche um antes de abrir o outro.

Quando o comando precisa levar um número

Uma letra por comando acaba rápido. Para mandar um valor, como o brilho de um LED, o caminho mais fácil de depurar é texto em linha: o computador envia B128 seguido de quebra de linha, e o Arduino junta os caracteres até a quebra antes de interpretar. Não processe byte a byte achando que a linha inteira já chegou; a serial entrega aos pedaços, no ritmo dela. Texto também tem a vantagem de poder ser testado à mão no monitor serial, sem o seu programa no meio.

A mesma ideia de canal serial vale sem fio. Um módulo Bluetooth clássico é uma serial com o cabo trocado por rádio, como em Bluetooth HC-05 no Arduino.

Erros comuns

  • Velocidades diferentes nos dois lados: chega lixo ou nada, e o código parece certo nos dois programas.
  • Mandar comando logo após abrir a porta: a placa está reiniciando e o primeiro comando some.
  • Ler sem timeout: se a placa trava ou o cabo sai, o programa congela junto.
  • Monitor serial da IDE aberto ao mesmo tempo: o seu programa não consegue abrir a porta.
  • Cabo só de carga: a placa liga, mas nenhuma porta aparece no computador.
  • Protocolo sem resposta: o computador nunca sabe se o comando chegou, e você depura no escuro.

Checklist

  • Mesma velocidade no Serial.begin e no programa do computador.
  • A porta usada é a que apareceu quando a placa foi conectada.
  • O cabo é de dados, testado com a IDE.
  • Monitor serial da IDE fechado enquanto o programa roda.
  • O programa espera PRONTO antes do primeiro comando.
  • Toda leitura tem timeout.
  • Cada comando recebe uma resposta, inclusive para caractere desconhecido.

Rode o script com a sua porta agora. A placa disse PRONTO, e a letra A voltou como OK A? Se a primeira linha nunca chega, o problema está na porta ou na velocidade, não no comando.

Avalie este artigo

Referência: http://waihung.net/arduino-controlled-by-visual-basic-program/