Pular para o conteúdo principal

Aula de Laboratório — Bits, Bytes e Operadores no ESP32

Plataforma: ESP32 · Ambiente: PlatformIO · Framework: Arduino


1. Objetivos de aprendizagem​

Ao final desta aula, o aluno será capaz de:

  • Manipular bits individuais de uma variável usando bit(), bitSet(), bitClear(), bitRead() e bitWrite().
  • Separar e combinar bytes de um valor de 16 bits com highByte() e lowByte().
  • Usar operadores de comparação (==, !=, <, >, <=, >=) para tomar decisões.
  • Aplicar operadores bitwise (&, |, ^, ~, <<, >>) para criar máscaras e manipular registradores.
  • Combinar condições lógicas com operadores booleanos (&&, ||, !).
  • Entender a diferença crucial entre operadores bitwise (&, |, ~) e booleanos (&&, ||, !).
Pontos de commit

Ao longo da aula, os cartões "Ponto de commit" indicam quando salvar seu progresso. Cada commit corresponde a uma tarefa avaliada automaticamente — use exatamente a mensagem sugerida (o botão copia o comando pronto).


2. Material necessário​

  • 1 × placa ESP32 (ex.: ESP32 DevKit v1)
  • 1 × cabo USB
  • (Opcional) 8 × LEDs + 8 × resistores de 220 Ω + protoboard
  • VS Code com a extensão PlatformIO IDE instalada

3. Setup do projeto no PlatformIO​

Crie um novo projeto e configure o arquivo platformio.ini:

platformio.ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200

Toda a aula usa a Serial Monitor (115200 baud) para observar os resultados. Abra com o botão de "plug" na barra do PlatformIO ou com Ctrl+Alt+S.

Função auxiliar (cole no topo de cada exercício)​

Para visualizar bits, usaremos esta função que imprime um byte em binário com zeros à esquerda:

void printBinByte(uint8_t valor) {
for (int i = 7; i >= 0; i--) {
Serial.print(bitRead(valor, i)); // lê do bit 7 (MSB) ao bit 0 (LSB)
}
}
Conceito

Um byte tem 8 bits, numerados de 0 (LSB — bit menos significativo, à direita) até 7 (MSB — bit mais significativo, à esquerda). Exemplo: em 0b10000001, o bit 0 vale 1 e o bit 7 vale 1; todos os outros valem 0.


Por que quase todo o código fica no setup()?

No Arduino/ESP32, setup() roda uma vez no boot e loop() roda repetidamente logo depois, para sempre. Nos experimentos das Partes 1 a 4, o objetivo é demonstrar um conceito e imprimir o resultado uma única vez para você comparar com a saída esperada — por isso o código fica no setup() e o loop() fica vazio (void loop() {}). Se estivesse no loop(), a mesma saída se repetiria sem parar, rolando na Serial Monitor.

O projeto integrador (seção 5) faz o contrário de propósito: a lógica fica no loop(), porque ele precisa reagir continuamente aos comandos que chegam pela serial. Regra prática: execução única / demonstração → setup(); comportamento contínuo / que reage a entradas ou tempo → loop().


Parte 1 — Funções de Bits and Bytes​

1.1 Teoria rápida​

FunçãoO que fazRetorna / efeito
bit(n)Calcula o valor do bit na posição n2^n (ex.: bit(3) → 8)
bitRead(x, n)Lê o bit n de x0 ou 1
bitSet(x, n)Coloca o bit n de x em 1modifica x
bitClear(x, n)Coloca o bit n de x em 0modifica x
bitWrite(x, n, b)Escreve o valor b (0/1) no bit n de xmodifica x
highByte(w)Byte alto de um valor de 16 bitsuint8_t
lowByte(w)Byte baixo de um valor de 16 bitsuint8_t

1.2 Experimento — manipulando bits​

#include <Arduino.h>

void printBinByte(uint8_t valor) {
for (int i = 7; i >= 0; i--) Serial.print(bitRead(valor, i));
}

void setup() {
Serial.begin(115200);
delay(1000);

uint8_t reg = 0b00000000; // registrador de 8 bits, começa zerado

Serial.print("Inicial: "); printBinByte(reg); Serial.println();

// bit(n) => valor 2^n
Serial.print("bit(0)="); Serial.print(bit(0));
Serial.print(" bit(3)="); Serial.print(bit(3));
Serial.print(" bit(7)="); Serial.println(bit(7));

bitSet(reg, 0); // liga o bit 0
bitSet(reg, 7); // liga o bit 7
Serial.print("Apos set: "); printBinByte(reg); Serial.println();

bitWrite(reg, 3, 1); // escreve 1 no bit 3
Serial.print("Write b3: "); printBinByte(reg); Serial.println();

bitClear(reg, 0); // desliga o bit 0
Serial.print("Clear b0: "); printBinByte(reg); Serial.println();

// bitRead retorna 0 ou 1
Serial.print("Bit 7 vale: "); Serial.println(bitRead(reg, 7));
Serial.print("Bit 0 vale: "); Serial.println(bitRead(reg, 0));
}

void loop() {}

Antes de rodar, preveja a saída e depois compare. Saída esperada:

Inicial: 00000000
bit(0)=1 bit(3)=8 bit(7)=128
Apos set: 10000001
Write b3: 10001001
Clear b0: 10001000
Bit 7 vale: 1
Bit 0 vale: 0
Ponto de commit · T1 (5 pts) #

manipula bits com bitSet, bitClear, bitWrite e bitRead

git add . && git commit -m "T1: manipula bits com bitSet, bitClear, bitWrite e bitRead" && git push

1.3 Experimento — highByte() e lowByte()​

Muitos sensores I²C/SPI devolvem um valor de 16 bits em dois bytes separados. highByte()/lowByte() separam, e o bit shift recombina.

#include <Arduino.h>

void setup() {
Serial.begin(115200);
delay(1000);

uint16_t leitura = 0xABCD; // 43981 em decimal

uint8_t hi = highByte(leitura); // 0xAB
uint8_t lo = lowByte(leitura); // 0xCD

Serial.print("Valor 16 bits: 0x"); Serial.println(leitura, HEX);
Serial.print("High byte: 0x"); Serial.println(hi, HEX);
Serial.print("Low byte: 0x"); Serial.println(lo, HEX);

// Recombinar: sobe o byte alto 8 posições e junta com OR o byte baixo
uint16_t recombinado = (hi << 8) | lo;
Serial.print("Recombinado: 0x"); Serial.println(recombinado, HEX);
}

void loop() {}

Saída esperada:

Valor 16 bits: 0xABCD
High byte: 0xAB
Low byte: 0xCD
Recombinado: 0xABCD
Ponto de commit · T2 (5 pts) #

separa e recombina bytes com highByte, lowByte e shift

git add . && git commit -m "T2: separa e recombina bytes com highByte, lowByte e shift" && git push

Parte 2 — Operadores de Comparação​

2.1 Teoria rápida​

Todos retornam um booleano (true/false, ou seja, 1/0).

OperadorSignificado
==igual a
!=diferente de
>maior que
>=maior ou igual a
<menor que
<=menor ou igual a
Erro clássico

= é atribuição, == é comparação. if (x = 5) sempre é verdadeiro (atribui 5 e testa 5 ≠ 0). O correto é if (x == 5).

2.2 Experimento​

#include <Arduino.h>

void setup() {
Serial.begin(115200);
delay(1000);

int leituraADC = 2048; // simula uma leitura do ADC (0..4095)
int limiar = 2000;

Serial.print("Leitura: "); Serial.println(leituraADC);
Serial.print("== 2048? "); Serial.println(leituraADC == 2048);
Serial.print("!= 2000? "); Serial.println(leituraADC != limiar);
Serial.print("> limiar? "); Serial.println(leituraADC > limiar);
Serial.print(">= 2048? "); Serial.println(leituraADC >= 2048);
Serial.print("< 4095? "); Serial.println(leituraADC < 4095);
Serial.print("<= 2000? "); Serial.println(leituraADC <= limiar);

if (leituraADC > limiar) {
Serial.println("Acao: acima do limiar!");
} else {
Serial.println("Acao: abaixo ou igual ao limiar.");
}
}

void loop() {}

Saída esperada (lembre: 1 = verdadeiro, 0 = falso):

Leitura: 2048
== 2048? 1
!= 2000? 1
> limiar? 1
>= 2048? 1
< 4095? 1
<= 2000? 0
Acao: acima do limiar!
Ponto de commit · T3 (5 pts) #

usa operadores de comparação para decidir

git add . && git commit -m "T3: usa operadores de comparação para decidir" && git push

Parte 3 — Operadores Bitwise​

3.1 Tabelas-verdade (bit a bit)​

A | ~A
--+----
0 | 1
1 | 0
A B | A&B A|B A^B
----+-------------
0 0 | 0 0 0
0 1 | 0 1 1
1 0 | 0 1 1
1 1 | 1 1 0
OperadorNomeUso típico
&ANDtestar/limpar bits (máscara)
|ORligar bits
^XORinverter (toggle) bits
~NOTinverter todos os bits
<<shift leftmultiplica por 2 / posiciona máscara
>>shift rightdivide por 2 / alinha bits

3.2 Experimento — máscaras com AND, OR, XOR​

#include <Arduino.h>

void printBinByte(uint8_t v) {
for (int i = 7; i >= 0; i--) Serial.print(bitRead(v, i));
}

void setup() {
Serial.begin(115200);
delay(1000);

uint8_t reg = 0b10110010;
uint8_t mask = 0b00001111; // isola os 4 bits baixos

Serial.print("reg = "); printBinByte(reg); Serial.println();
Serial.print("mask = "); printBinByte(mask); Serial.println();

Serial.print("reg & mask = "); printBinByte(reg & mask); Serial.println(); // isola bits baixos
Serial.print("reg | mask = "); printBinByte(reg | mask); Serial.println(); // liga bits baixos
Serial.print("reg ^ mask = "); printBinByte(reg ^ mask); Serial.println(); // inverte bits baixos

// ~ inverte TODOS os bits (cuidado: cast para 8 bits!)
Serial.print("~reg = "); printBinByte((uint8_t)~reg); Serial.println();
}

void loop() {}

Saída esperada:

reg = 10110010
mask = 00001111
reg & mask = 00000010
reg | mask = 10111111
reg ^ mask = 10111101
~reg = 01001101
Cuidado com o NOT (~) no ESP32

Um int tem 32 bits. ~reg gera 0xFFFFFF4D. Por isso fazemos o cast (uint8_t) para ver apenas os 8 bits que interessam.

Ponto de commit · T4 (5 pts) #

aplica máscaras com AND, OR e XOR

git add . && git commit -m "T4: aplica máscaras com AND, OR e XOR" && git push

3.3 Experimento — bit shift​

#include <Arduino.h>

void printBinByte(uint8_t v) {
for (int i = 7; i >= 0; i--) Serial.print(bitRead(v, i));
}

void setup() {
Serial.begin(115200);
delay(1000);

uint8_t x = 1; // 00000001

for (int i = 0; i < 8; i++) {
Serial.print("1 << "); Serial.print(i); Serial.print(" = ");
printBinByte(x << i);
Serial.print(" (decimal "); Serial.print(x << i); Serial.println(")");
}

uint8_t y = 0b10000000; // 128
Serial.print("128 >> 3 = "); printBinByte(y >> 3);
Serial.print(" (decimal "); Serial.print(y >> 3); Serial.println(")");
}

void loop() {}
dica

1 << n é exatamente o que bit(n) faz. Deslocar à esquerda multiplica por 2; à direita, divide por 2 (inteiro).

Ponto de commit · T5 (5 pts) #

usa deslocamento de bits (shift left e right)

git add . && git commit -m "T5: usa deslocamento de bits (shift left e right)" && git push

Parte 4 — Operadores Booleanos​

4.1 Teoria rápida​

Operam sobre condições inteiras (verdadeiro/falso), não bit a bit.

OperadorNomeResultado
&&E lógicotrue só se ambas condições forem verdadeiras
||OU lógicotrue se pelo menos uma for verdadeira
!NÃO lógicoinverte o valor lógico

4.2 A diferença que mais confunde: & vs &&​

#include <Arduino.h>

void setup() {
Serial.begin(115200);
delay(1000);

int a = 2; // 00000010
int b = 1; // 00000001

Serial.print("a & b (bitwise) = "); Serial.println(a & b); // 0 -> nao ha bit em comum
Serial.print("a && b (logico) = "); Serial.println(a && b); // 1 -> ambos sao != 0

Serial.print("a | b (bitwise) = "); Serial.println(a | b); // 3
Serial.print("a || b (logico) = "); Serial.println(a || b); // 1
}

void loop() {}

Saída esperada:

a & b (bitwise) = 0
a && b (logico) = 1
a | b (bitwise) = 3
a || b (logico) = 1
Regra de ouro

Use &&, ||, ! em condições de if/while. Use &, |, ~, ^ para manipular bits. Trocar um pelo outro compila, mas produz bugs silenciosos.

Ponto de commit · T6 (5 pts) #

distingue operadores bitwise dos lógicos

git add . && git commit -m "T6: distingue operadores bitwise dos lógicos" && git push

4.3 Experimento — combinando condições​

#include <Arduino.h>

void setup() {
Serial.begin(115200);
delay(1000);

int temperatura = 30; // °C
bool botaoPressionado = true;
int umidade = 80; // %

// Liga o ventilador se estiver quente E o botao estiver pressionado
if (temperatura > 28 && botaoPressionado) {
Serial.println("Ventilador LIGADO");
}

// Alerta se muito quente OU muito umido
if (temperatura > 35 || umidade > 70) {
Serial.println("ALERTA: ambiente desconfortavel");
}

// NOT: age se o botao NAO estiver pressionado
if (!botaoPressionado) {
Serial.println("Botao solto");
} else {
Serial.println("Botao ainda pressionado");
}
}

void loop() {}
Ponto de commit · T7 (5 pts) #

combina condições com operadores lógicos

git add . && git commit -m "T7: combina condições com operadores lógicos" && git push

5. Projeto integrador — "Registrador de 8 status"​

Junta tudo: vamos representar 8 sensores/atuadores em um único byte. Cada bit é um dispositivo. Usamos funções de bit para ligar/desligar, máscaras bitwise para consultar grupos e operadores lógicos para tomar decisões.

#include <Arduino.h>

// Posicao de cada dispositivo dentro do byte de status
#define LED_VERDE 0
#define LED_VERMELHO 1
#define MOTOR 2
#define BUZZER 3
#define SENSOR_PORTA 4
#define SENSOR_LUZ 5
#define SENSOR_TEMP 6
#define ALARME 7

uint8_t status = 0b00000000;

void printBinByte(uint8_t v) {
for (int i = 7; i >= 0; i--) Serial.print(bitRead(v, i));
}

void mostrarStatus() {
Serial.print("STATUS = "); printBinByte(status);
Serial.print(" (0x"); Serial.print(status, HEX); Serial.println(")");
}

void setup() {
Serial.begin(115200);
delay(1000);

// 1) Liga alguns dispositivos usando funcoes de bit
bitSet(status, LED_VERDE);
bitSet(status, SENSOR_PORTA);
bitSet(status, SENSOR_TEMP);
mostrarStatus(); // 01010001

// 2) Consulta um bit especifico (comparacao)
if (bitRead(status, SENSOR_PORTA) == 1) {
Serial.println("-> Porta aberta detectada!");
}

// 3) Mascara: existe ALGUM sensor ativo? (bits 4..7)
uint8_t maskSensores = 0b11110000;
if ((status & maskSensores) != 0) {
Serial.println("-> Pelo menos um sensor ativo.");
}

// 4) Logica combinada: dispara alarme se porta aberta E temperatura ativa
if (bitRead(status, SENSOR_PORTA) && bitRead(status, SENSOR_TEMP)) {
bitSet(status, ALARME);
Serial.println("-> ALARME acionado (porta + temperatura).");
}
mostrarStatus(); // 11010001

// 5) XOR para alternar (toggle) o LED verde 3 vezes
for (int i = 0; i < 3; i++) {
status ^= (1 << LED_VERDE); // inverte o bit 0
Serial.print("Toggle LED verde -> "); mostrarStatus();
}

// 6) Limpa todos os sensores de uma vez com AND + NOT da mascara
status &= ~maskSensores;
Serial.print("Sensores limpos -> "); mostrarStatus();
}

void loop() {}
Extensão com hardware real

Troque cada bitSet/bitClear por digitalWrite(pino, HIGH/LOW) ligando 8 LEDs. Configure os pinos em setup() com pinMode(pino, OUTPUT) e faça a saída dos LEDs espelhar o byte status, bit a bit.

Ponto de commit · T8 (15 pts) #

projeto integrador: registrador de 8 status

git add . && git commit -m "T8: projeto integrador: registrador de 8 status" && git push

6. Desafios​

Tente resolver sem olhar o gabarito. Faça o commit de cada desafio conforme concluir.

D1. Escreva uma função bool ehPar(uint8_t n) que use apenas um operador bitwise (dica: o bit 0) para decidir se n é par.

Ponto de commit · T9 (10 pts) #

desafio D1: função ehPar usando bitwise

git add . && git commit -m "T9: desafio D1: função ehPar usando bitwise" && git push

D2. Dado uint16_t valor = 0x1234;, imprima os dois bytes separados usando highByte()/lowByte() e depois recombine sem usar essas funções (só shift e máscara).

Ponto de commit · T10 (10 pts) #

desafio D2: separa e recombina 0x1234 sem highByte/lowByte

git add . && git commit -m "T10: desafio D2: separa e recombina 0x1234 sem highByte/lowByte" && git push

D3. Escreva void trocaBits(uint8_t &x, int i, int j) que troca os bits nas posições i e j de x. Teste com 0b10000001, trocando bits 0 e 6.

Ponto de commit · T11 (15 pts) #

desafio D3: função trocaBits(x, i, j)

git add . && git commit -m "T11: desafio D3: função trocaBits(x, i, j)" && git push

D4. Você tem uint8_t flags. Escreva uma condição if que seja verdadeira somente quando o bit 2 estiver ligado E o bit 5 estiver desligado.

Ponto de commit · T12 (15 pts) #

desafio D4: condição bit 2 ligado e bit 5 desligado

git add . && git commit -m "T12: desafio D4: condição bit 2 ligado e bit 5 desligado" && git push

D5. Explique com suas palavras por que if (x = 3) é quase sempre um bug e o que o compilador do PlatformIO normalmente avisa.


7. Gabarito​

Spoiler

Só abra depois de tentar os desafios por conta própria.

Ver soluções

D1.

bool ehPar(uint8_t n) {
return (n & 1) == 0; // se o bit 0 e 0, o numero e par
}

D2.

uint16_t valor = 0x1234;
uint8_t hi = highByte(valor); // 0x12
uint8_t lo = lowByte(valor); // 0x34
uint16_t rec = ((uint16_t)hi << 8) | lo; // 0x1234

D3.

void trocaBits(uint8_t &x, int i, int j) {
bool bi = bitRead(x, i);
bool bj = bitRead(x, j);
bitWrite(x, i, bj);
bitWrite(x, j, bi);
}
// 0b10000001, trocar bits 0 e 6 -> 0b11000000

D4.

if (bitRead(flags, 2) == 1 && bitRead(flags, 5) == 0) {
// ...
}
// Alternativa com mascara:
// if ((flags & 0b00100100) == 0b00000100) { ... }

D5. = atribui o valor 3 a x e a expressão inteira vale 3, que é diferente de zero, logo o if é sempre verdadeiro — a intenção provavelmente era ==. O compilador (GCC, usado pelo PlatformIO) geralmente emite o aviso "suggest parentheses around assignment used as truth value".


8. Checklist de encerramento​

  • Compilei e rodei todos os experimentos (Partes 1 a 4).
  • Previ a saída antes de rodar e comparei.
  • Rodei o projeto integrador e entendi cada passo.
  • Sei explicar a diferença entre &/&& e |/||.
  • Resolvi ao menos os desafios D1 a D4.
  • Fiz o commit de cada ponto marcado (T1 a T12).

Fim da aula.