Robótica com C — Bloco 2
Nos projetos do Bloco 3, você vai controlar LEDs, relays e portões pelo celular via módulo Bluetooth HC-05. Por dentro, o HC-05 é transparente — ele recebe dados do celular e os entrega ao Arduino exatamente como se fossem dados da porta Serial. Letras, números, comandos: chegam pelo mesmo Serial.read() que você já conhece da Aula 41b.
Isso significa que tudo que você aprender aqui sobre controle por Serial funciona identicamente com Bluetooth, sem modificar uma linha de código. A única diferença é o canal físico — cabo USB vs rádio de 2,4 GHz.
A abstração que o HC-05 oferece: o módulo Bluetooth faz o papel de um "cabo USB sem fio". Do ponto de vista do código Arduino, não existe diferença entre receber "L" pelo Monitor Serial do computador e receber "L" de um app no celular via Bluetooth. O protocolo UART por baixo é o mesmo.
| Aspecto | Serial USB (esta aula) | Bluetooth HC-05 (Bloco 3) |
|---|---|---|
| Função de leitura | Serial.read() |
Serial.read() — idêntico |
| Verificar dados | Serial.available() |
Serial.available() — idêntico |
| Enviar resposta | Serial.print() |
Serial.print() — idêntico |
| Origem dos dados | Monitor Serial do computador | App no celular (ex: Serial Bluetooth Terminal) |
| Alcance | Comprimento do cabo USB | Até ~10 metros |
| Hardware extra | Nenhum | Módulo HC-05 (~R$20) |
Você viu Serial.read() brevemente na Aula 41b. Aqui vai a estrutura completa que será usada em todos os projetos com controle remoto:
// Esqueleto universal de controle por Serial/Bluetooth void setup() { Serial.begin(9600); } void loop() { // 1. Verificar se chegou algum dado if (Serial.available() > 0) { // 2. Ler um byte (caractere) char cmd = Serial.read(); // 3. Processar o comando processarComando(cmd); } // 4. Resto do loop() continua livre // (ler sensores, atualizar display, etc.) }
Por que verificar available() antes de read()? Se o buffer estiver vazio e você chamar Serial.read() direto, ele retorna -1 — e seu código vai processar um "comando" inválido a cada iteração do loop, causando comportamento errático. Sempre verifique antes de ler. É a mesma disciplina de verificar fscanf() em C.
O protocolo mais simples e direto: cada letra representa uma ação. Digitar "L" liga, "D" desliga, "R" reseta. Usado nos projetos de LED RGB, lâmpada e campainha do Bloco 3:
/* * Controle de 3 LEDs por comandos de caractere * R = vermelho, G = verde, B = azul * Maiúscula = liga, minúscula = desliga * X = apaga todos */ const int PINO_R = 9; const int PINO_G = 10; const int PINO_B = 11; void setup() { pinMode(PINO_R, OUTPUT); pinMode(PINO_G, OUTPUT); pinMode(PINO_B, OUTPUT); Serial.begin(9600); Serial.println("Comandos: R/r G/g B/b X"); } void processarComando(char cmd) { switch (cmd) { case 'R': digitalWrite(PINO_R, HIGH); Serial.println("Vermelho LIGADO"); break; case 'r': digitalWrite(PINO_R, LOW); Serial.println("Vermelho DESLIGADO"); break; case 'G': digitalWrite(PINO_G, HIGH); Serial.println("Verde LIGADO"); break; case 'g': digitalWrite(PINO_G, LOW); Serial.println("Verde DESLIGADO"); break; case 'B': digitalWrite(PINO_B, HIGH); Serial.println("Azul LIGADO"); break; case 'b': digitalWrite(PINO_B, LOW); Serial.println("Azul DESLIGADO"); break; case 'X': digitalWrite(PINO_R, LOW); digitalWrite(PINO_G, LOW); digitalWrite(PINO_B, LOW); Serial.println("Todos DESLIGADOS"); break; case '\n': case '\r': break; // ignora quebras de linha default: Serial.print("Comando desconhecido: "); Serial.println(cmd); } } void loop() { if (Serial.available() > 0) { processarComando(Serial.read()); } }
Comandos: R/r G/g B/b X
[digitou R] Vermelho LIGADO
[digitou G] Verde LIGADO
[digitou r] Vermelho DESLIGADO
[digitou X] Todos DESLIGADOS
[digitou Z] Comando desconhecido: Z
Sempre trate o default: no ambiente real com Bluetooth, o app pode enviar caracteres inesperados — quebras de linha, bytes de controle, erros de digitação. O default com feedback evita que o Arduino fique em silêncio quando recebe algo inesperado, o que dificulta muito a depuração.
Às vezes um caractere não é suficiente — você quer enviar um ângulo (0–180), um brilho (0–255) ou uma velocidade (0–100). O padrão é enviar o número como texto e converter com Serial.parseInt():
/* * Controle de brilho de LED por valor numérico * Digite um número de 0 a 255 e pressione Enter * Ex: "128" → LED em 50% de brilho */ const int PINO_LED = 9; // pino PWM (~) void setup() { pinMode(PINO_LED, OUTPUT); Serial.begin(9600); Serial.setTimeout(100); // timeout para parseInt() — ms Serial.println("Digite 0-255 e pressione Enter:"); } void loop() { if (Serial.available() > 0) { int brilho = Serial.parseInt(); // lê número inteiro do buffer // constrain() limita o valor ao intervalo válido brilho = constrain(brilho, 0, 255); analogWrite(PINO_LED, brilho); Serial.print("Brilho definido: "); Serial.print(brilho); Serial.print(" ("); Serial.print(map(brilho, 0, 255, 0, 100)); Serial.println("%)"); } }
Digite 0-255 e pressione Enter:
[digitou 128] Brilho definido: 128 (50%)
[digitou 255] Brilho definido: 255 (100%)
[digitou 0] Brilho definido: 0 (0%)
constrain(valor, min, max): função nativa do Arduino que limita um valor a um intervalo — equivalente a if (v < min) v = min; if (v > max) v = max; em uma linha. Indispensável para proteger componentes de valores fora do range esperado.
Quando o projeto tem múltiplos dispositivos controláveis — como no Painel de Comandos do Bloco 3 — um único caractere não é suficiente para identificar o alvo e o valor. O padrão com prefixo resolve isso: cada mensagem começa com uma letra que identifica o dispositivo, seguida do valor.
// Protocolo: LETRA + NÚMERO + '\n' // Exemplos: // "R128\n" → LED vermelho com brilho 128 // "S90\n" → Servo no ângulo 90° // "F1\n" → Ventilador ligado (1) ou desligado (0) // Formato recebido como string: ex "S90" #include <Servo.h> const int PINO_LED = 9; Servo servo; void setup() { pinMode(PINO_LED, OUTPUT); servo.attach(6); Serial.begin(9600); Serial.println("Protocolo: R0-255 | S0-180 | F0/1"); } void processarMensagem(String msg) { if (msg.length() < 2) return; // mensagem inválida char dispositivo = msg[0]; // primeira letra int valor = msg.substring(1).toInt(); // resto como número switch (dispositivo) { case 'R': valor = constrain(valor, 0, 255); analogWrite(PINO_LED, valor); Serial.print("LED vermelho: "); Serial.println(valor); break; case 'S': valor = constrain(valor, 0, 180); servo.write(valor); Serial.print("Servo: "); Serial.print(valor); Serial.println("°"); break; case 'F': digitalWrite(PINO_LED, valor ? HIGH : LOW); Serial.println(valor ? "Ventilador LIGADO" : "Ventilador DESLIGADO"); break; default: Serial.print("Dispositivo desconhecido: "); Serial.println(dispositivo); } } void loop() { if (Serial.available() > 0) { String msg = Serial.readStringUntil('\n'); // lê até Enter msg.trim(); // remove espaços e \r processarMensagem(msg); } }
Protocolo: R0-255 | S0-180 | F0/1
[enviou "R200"] LED vermelho: 200
[enviou "S45"] Servo: 45°
[enviou "F1"] Ventilador LIGADO
[enviou "F0"] Ventilador DESLIGADO
String com S maiúsculo: neste padrão usamos a classe String do Arduino para facilitar o parsing com substring() e toInt(). Lembre do aviso da Aula 41a — em projetos longos, prefira char[] com atoi() para evitar fragmentação de heap. Para os projetos do Bloco 3, onde os comandos são processados pontualmente, String é aceitável.
O Bluetooth não é só entrada — você pode enviar dados de sensores de volta ao celular. O app exibe temperatura, distância, status — qualquer dado que o Arduino gerar. Basta usar Serial.print() normalmente:
/* * Monitor bidirecional — recebe comandos E envia dados * "T\n" → Arduino responde com a temperatura atual * "L\n" → Arduino liga LED e confirma * "D\n" → Arduino desliga LED e confirma */ const int PINO_LED = 8; const int PINO_LM35 = A0; void setup() { pinMode(PINO_LED, OUTPUT); Serial.begin(9600); Serial.println("Pronto. Comandos: T | L | D"); } float lerTemperatura() { return analogRead(PINO_LM35) * (5.0 / 1023.0) * 100.0; } void loop() { // Envia temperatura automaticamente a cada 5 segundos static unsigned long ultimoEnvio = 0; if (millis() - ultimoEnvio >= 5000) { ultimoEnvio = millis(); Serial.print("TEMP:"); Serial.println(lerTemperatura(), 1); } // Responde a comandos recebidos if (Serial.available() > 0) { String cmd = Serial.readStringUntil('\n'); cmd.trim(); if (cmd == "T") { Serial.print("TEMP:"); Serial.println(lerTemperatura(), 1); } else if (cmd == "L") { digitalWrite(PINO_LED, HIGH); Serial.println("LED:1"); } else if (cmd == "D") { digitalWrite(PINO_LED, LOW); Serial.println("LED:0"); } else { Serial.println("ERR:cmd_desconhecido"); } } }
Pronto. Comandos: T | L | D
TEMP:23.4
[enviou "L"] LED:1
[enviou "T"] TEMP:24.1
TEMP:24.1
[enviou "D"] LED:0
Protocolo chave:valor — prefixar as respostas com um identificador ("TEMP:", "LED:", "ERR:") é uma boa prática pois permite que o app parse as mensagens facilmente. Aplicativos como o Serial Bluetooth Terminal (Android) permitem configurar macros para enviar comandos com um toque e exibir respostas em tempo real.
Quando o Bloco 3 chegar, a única mudança no código será a porta Serial usada. O Arduino Uno tem apenas uma UART de hardware (Serial), que é compartilhada com o USB. Para usar o HC-05 sem perder o debug pelo cabo, use SoftwareSerial — uma UART por software em pinos digitais quaisquer:
// Estrutura que você vai usar no Bloco 3: // HC-05 TX → pino 10 (RX do Arduino) // HC-05 RX → pino 11 (TX do Arduino) via divisor de tensão! #include <SoftwareSerial.h> SoftwareSerial bluetooth(10, 11); // RX, TX void setup() { Serial.begin(9600); // debug no computador bluetooth.begin(9600); // comunicação com o HC-05 } void loop() { // Lê do Bluetooth e replica no Serial (debug) if (bluetooth.available()) { char c = bluetooth.read(); Serial.print("BT recebeu: "); Serial.println(c); processarComando(c); // mesma função dos exemplos anteriores! } // Lê do Serial e replica no Bluetooth (teste) if (Serial.available()) { bluetooth.write(Serial.read()); } }
Divisor de tensão no RX do HC-05: o Arduino envia 5V no TX, mas o HC-05 aceita apenas 3,3V no seu pino RX. Conectar direto pode danificar o módulo. Use um divisor de tensão simples: R1=1kΩ entre o TX do Arduino e o RX do HC-05, R2=2kΩ entre o RX do HC-05 e o GND. Isso reduz os 5V para ~3,3V. Essa ligação está detalhada na aula do Projeto 02 do Bloco 3.
O que muda do código desta aula para o Bloco 3: basicamente nada. Troque Serial.read() por bluetooth.read() e Serial.print() por bluetooth.print() — ou mantenha Serial se usar um Arduino Mega que tem múltiplas UARTs hardware. A lógica de processamento de comandos (switch, processarComando()) é copiada literalmente.
| Conceito | C puro (arquivo/stdin) | Arduino (Serial/Bluetooth) |
|---|---|---|
| Ler caractere | getchar() / fgetc(stdin) |
Serial.read() |
| Verificar disponibilidade | feof() / select() |
Serial.available() > 0 |
| Ler string até delimitador | fgets(buf, n, stdin) |
Serial.readStringUntil('\n') |
| Converter string para int | atoi(buf) |
Serial.parseInt() ou String.toInt() |
| Processar comandos | switch(cmd) |
switch(cmd) — idêntico |
| Limitar valor a intervalo | if/else manual | constrain(v, min, max) |
| Bluetooth vs USB | Não aplicável | Troca Serial por bluetooth — mesma API |
Teste o que você aprendeu sobre controle por Serial e a base do Bluetooth: