Partilhar: Classroom Teams WhatsApp
aulify · Sebenta
UC · Unidade de Competência · UC00670

Efetuar a programação de microcontroladores (UC00670)

Arduino, registos, GPIO, sensores, atuadores, interrupções, protocolos e depuração
50h · 4.5 pontos crédito Curso: T. Sist. Comp. Redes, T. Eletrónica e mais 2 cursos ↗ Referencial oficial SNQ
Índice

Há um pacote premium desta unidade: um PowerPoint editável de 100 ou mais diapositivos, um manual em PDF e dois testes com correção. Esta sebenta continua gratuita.

Introdução

Esta sebenta ensina a programar microcontroladores e a ligar o código ao mundo físico: ler um botão, medir uma temperatura, acender um LED, mover um servo, ligar uma bomba de rega. O microcontrolador está dentro de quase tudo o que tem eletrónica: máquinas de lavar, comandos de garagem, contadores de energia, impressoras, automóveis, quadros de automação industrial. Quem instala, repara ou programa estes equipamentos precisa de saber ler um esquema, ler um programa e perceber onde o sinal elétrico se transforma em decisão.

Usamos o Arduino UNO (microcontrolador ATmega328P) como plataforma de referência, porque tem documentação abundante, simuladores gratuitos e um ambiente de desenvolvimento simples. Tudo o que aprendes (entradas, saídas, temporização, interrupções, protocolos, depuração) transfere-se para ESP32, STM32, Raspberry Pi Pico e outros: muda a folha de características, não a lógica.

O percurso segue as três realizações do referencial desta unidade:

  1. Escrever as instruções em código de programação de microcontroladores (capítulos 1 a 6).
  2. Interligar dispositivos externos com o microcontrolador: botões, sensores, motores, relés (capítulos 7 a 12).
  3. Ensaiar e depurar o programa e o funcionamento do circuito (capítulos 13 e 14).

Como estudar. Em cada capítulo há código que podes copiar para o simulador Wokwi ou Tinkercad Circuits e correr sem hardware. Lê o código, prevê o que vai acontecer, só depois corre. Quando a previsão falhar, aprendeste alguma coisa.

Segurança. Nesta unidade trabalhas com baixa tensão (5 V e 3,3 V). Qualquer ligação à rede de 230 V, por exemplo comandar uma lâmpada através de um relé, só se faz com o circuito sem tensão, com módulos preparados para essa tensão e, na prática profissional, por quem tem habilitação para instalações elétricas. Nas aulas, ensaia a carga com uma lâmpada de baixa tensão ou um LED.

1. O microcontrolador: fundamentos e plataformas

1.1 Microcontrolador e microprocessador

Um microprocessador (µP) é só a unidade central de processamento: precisa de memória RAM, de armazenamento e de circuitos de entrada e saída montados à sua volta. É o que existe num computador pessoal. Um microcontrolador (µC) é um computador completo num único circuito integrado: CPU, memória de programa, memória de dados, temporizadores, conversor analógico-digital, portas de comunicação e pinos de entrada e saída.

Microcontrolador (µC) Microprocessador (µP)
Integra CPU, RAM, Flash e periféricos num único chip Apenas CPU; RAM, armazenamento e I/O são externos
Tarefa dedicada (controlar um sistema) Uso geral (PC, servidor, telemóvel)
Baixo consumo, baixo custo, arranque imediato Alto desempenho, mais consumo, arranque lento
Corre um programa (firmware), muitas vezes sem sistema operativo Corre um sistema operativo com muitos programas
Arduino, ESP32, PIC, STM32, RP2040 Intel, AMD e os ARM dos computadores

O programa de um microcontrolador chama-se firmware: fica gravado na memória Flash, sobrevive a cortes de energia e começa a correr assim que o circuito é alimentado. Não há ecrã, teclado nem ficheiros por omissão; o programador tem de decidir como o sistema comunica com o exterior.

1.2 Famílias e plataformas

Plataforma Núcleo Lógica Pontos fortes
Arduino UNO R3 (ATmega328P) AVR 8 bits, 16 MHz 5 V Didática, ecossistema enorme, robusta
Arduino Mega 2560 (ATmega2560) AVR 8 bits, 16 MHz 5 V Muitos pinos e mais memória (256 KB de Flash, 8 KB de SRAM)
ESP32 Xtensa de 32 bits, até 240 MHz 3,3 V Wi-Fi e Bluetooth integrados: IoT
STM32 ARM Cortex-M de 32 bits 3,3 V Uso profissional, muitos periféricos
Raspberry Pi Pico (RP2040) 2 núcleos ARM Cortex-M0+ 3,3 V Barata, programável em C/C++ e MicroPython
PIC (Microchip) 8 a 32 bits 3,3 V ou 5 V Muito usada na indústria

Os valores exatos (frequência, memória, corrente por pino) estão na folha de características (datasheet) de cada componente e variam de versão para versão. Antes de ligar qualquer coisa, confirma-os no documento do fabricante.

1.3 Como escolher um microcontrolador

A escolha parte dos requisitos, não da moda. Perguntas a fazer, por ordem:

  1. Quantas entradas e saídas são precisas, e de que tipo (digitais, analógicas, PWM)?
  2. Que comunicação é necessária (série, I²C, SPI, Wi-Fi, Bluetooth)?
  3. Qual a tensão dos sensores e atuadores? Misturar 5 V com 3,3 V exige adaptação de níveis.
  4. Quanta memória de programa e de dados? Processar imagens ou áudio não cabe em 2 KB de SRAM.
  5. Quanto consome e como é alimentado (rede, pilha, bateria solar)?
  6. Quanto custa e quanto tempo se demora a desenvolver (bibliotecas, apoio, ferramentas)?

Exemplo resolvido · Escolher a placa para três projetos

Projeto Requisitos Escolha Justificação
Termóstato de sala com ecrã 1 sensor, 1 relé, 1 ecrã I²C Arduino UNO Poucos pinos, tudo a 5 V, simples
Sonda de humidade do solo que envia dados para a nuvem 1 sensor, Wi-Fi, bateria ESP32 Wi-Fi integrado, modos de baixo consumo
Painel com 40 LED e 12 botões 52 pinos Arduino Mega 2560 Número de pinos que o UNO não tem

1.4 Especificar as funções e os requisitos do sistema

Antes de escrever uma linha de código, escreve o que o sistema tem de fazer. Uma especificação útil tem três partes: entradas (o que o sistema lê), saídas (o que comanda) e regras (a ligação entre elas, com números).

Exemplo para uma rega automática:

Uma regra sem número ("regar quando estiver seco") não se programa. "Abaixo de 30 %" já se programa e já se testa.

2. Arquitetura interna e memória

2.1 Diagrama de blocos do ATmega328P

O diagrama de blocos mostra o que existe dentro do chip e como se liga. Simplificado:

              +-----------------------------------------------+
  Flash       |   Contador de programa  -->  Descodificador   |
 (programa)-->|                                  |            |
  32 KB       |        32 registos         +-----v-----+      |
              |        R0 a R31  <-------> |    ALU    |      |
  SRAM  <---->|            |               +-----------+      |
  2 KB        |       Registo de estado SREG                  |
  EEPROM      +------------+----------------------------------+
  1 KB                     | barramento de dados (8 bits)
        +---------+--------+---------+----------+-----------+
        | Portas  | Timers | ADC     | UART     | I2C / SPI |
        | B, C, D | 0,1,2  | 10 bits | USART0   | TWI / SPI |
        +---------+--------+---------+----------+-----------+
              |        |        |         |           |
             pinos   PWM    entradas    TX/RX     SDA/SCL

Pontos a reter:

2.2 Unidade lógica e aritmética (ALU)

A ALU realiza as operações aritméticas (soma, subtração) e lógicas (E, OU, OU exclusivo, negação, deslocamentos) sobre os registos. Cada operação atualiza o registo de estado SREG, cujos bits (flags) permitem à CPU tomar decisões.

Bit Nome Significado
I Interrupt enable Interrupções globais ligadas
T Bit copy storage Bit auxiliar
H Half carry Transporte do bit 3 para o bit 4
S Sign N exclusivo-OU V
V Overflow Excesso em complemento para dois
N Negative Resultado negativo
Z Zero Resultado igual a zero
C Carry Transporte (ou borrow)

Exemplo resolvido · Somar dois valores de 8 bits e ver as flags

Somar 200 com 100 num registo de 8 bits (0 a 255):

  1. 200 em binário: 1100 1000. 100 em binário: 0110 0100.
  2. Soma bit a bit: 1 0010 1100, ou seja, 9 bits.
  3. O registo guarda só os 8 bits menos significativos: 0010 1100 = 44.
  4. O nono bit foi para a flag C (carry = 1).

Conclusão: o resultado guardado é 44, não 300, e a flag C avisa que houve excesso. Num programa em C, um uint8_t com 200 + 100 também dá 44. É por isso que se escolhe o tipo de dados com cuidado (capítulo 6).

2.3 Memória de programa e memória de dados

O ATmega328P tem três memórias com funções diferentes:

Memória Tamanho Volátil? Para que serve
Flash 32 KB (cerca de 0,5 KB ocupados pelo carregador de arranque) Não Guarda o programa (e constantes marcadas com PROGMEM)
SRAM 2 KB Sim Variáveis, pilha e memória dinâmica durante a execução
EEPROM 1 KB Não Dados que têm de sobreviver a desligar (calibrações, contadores)

A EEPROM tem um número limitado de escritas (a folha de características indica cerca de 100 000 ciclos por célula), por isso nunca se escreve nela dentro de um ciclo que repete milhares de vezes por segundo.

A SRAM é a memória mais escassa e a que mais engana. Divide-se em três zonas: variáveis globais e estáticas (tamanho fixo), heap (memória dinâmica, cresce para cima) e pilha (stack, guarda chamadas de funções e variáveis locais, cresce para baixo). Se a pilha e o heap se encontram, o programa comporta-se de forma imprevisível, sem mensagem de erro.

Mapa do espaço de dados (ATmega328P):

Endereço Conteúdo
0x0000 a 0x001F 32 registos de uso geral
0x0020 a 0x005F 64 registos de I/O (PORTx, DDRx, PINx, SREG...)
0x0060 a 0x00FF 160 registos de I/O alargados (timers, ADC, UART...)
0x0100 a 0x08FF 2048 bytes de SRAM

Exemplo resolvido · Ler o relatório de compilação

Depois de compilar, o IDE mostra algo como:

O sketch usa 4120 bytes (12%) do espaço de armazenamento de programa. O máximo é 32256 bytes.
As variáveis globais usam 410 bytes (20%) de memória dinâmica, deixando 1638 bytes para variáveis locais. O máximo é 2048 bytes.

Leitura: 4120 de 32256 são 12,8 %, por isso o IDE mostra 12 %. Há muita Flash livre. Das variáveis globais, 410 de 2048 são 20 %, restam 1638 bytes para a pilha e o heap. Se a segunda percentagem passar de cerca de 75 %, o risco de falta de SRAM é real: reduzir String, usar F() para textos e arrays com o menor tipo possível.

(Os números variam com o programa e a versão do IDE; o que importa é saber interpretar as duas linhas.)

2.4 Pinagem do Arduino UNO

O UNO tem 14 pinos digitais (D0 a D13) e 6 entradas analógicas (A0 a A5, que também funcionam como digitais). Cada pino tem várias funções:

Pino Função(ões) especial(is)
D0 (RX), D1 (TX) Porta série (partilhada com a ligação USB: evitar nestes pinos durante o carregamento)
D2, D3 Interrupções externas INT0 e INT1
D3, D5, D6, D9, D10, D11 Saídas PWM
D10 a D13 SPI: SS, MOSI, MISO, SCK
A4, A5 I²C: SDA e SCL
D13 LED integrado na placa

Cada pino pertence a uma porta do microcontrolador: D0 a D7 formam a porta D, D8 a D13 a porta B, A0 a A5 a porta C. Esta correspondência é essencial para a manipulação direta de registos (capítulo 3).

Limites elétricos (valores típicos; confirmar na folha de características do ATmega328P e na documentação da placa):

ESP32 e restantes placas de 3,3 V. Os pinos não toleram 5 V. Ligar a saída de um módulo de 5 V diretamente a um pino de 3,3 V pode destruir o pino. Usa um divisor resistivo (por exemplo 1 kΩ em série e 2 kΩ para a massa: 5 V × 2/3 ≈ 3,3 V) ou um conversor de níveis.

3. Conjunto de instruções e modos de endereçamento

3.1 Linguagens: máquina, assembly e C

O processador só executa código máquina. O assembly dá nomes legíveis (mnemónicas) a essas instruções; o compilador avr-gcc traduz o C/C++ do sketch para código máquina. Conhecer o assembly serve para perceber o que o processador faz e porque é que certas linhas de C são lentas ou ocupam muita memória.

3.2 Tipos de instruções

O conjunto de instruções dos AVR é do tipo RISC (conjunto reduzido, cerca de 130 instruções, quase todas num ou dois ciclos). Agrupam-se em:

Tipo Exemplos O que fazem
Transferência de dados LDI, MOV, LD, ST, IN, OUT, PUSH, POP Movem dados entre registos, memória e I/O
Aritméticas ADD, ADC, SUB, INC, DEC, MUL Somam, subtraem, incrementam
Lógicas AND, OR, EOR, COM, LSL, LSR Operações bit a bit e deslocamentos
Operações sobre bits SBI, CBI, SBRS, SBRC Põem a 1/0 ou testam um bit
Comparação e salto CP, CPI, BREQ, BRNE, RJMP, RCALL, RET Comparam e alteram o fluxo do programa
Controlo NOP, SEI, CLI, SLEEP Ativam interrupções, adormecem a CPU

3.3 Modos de endereçamento

O modo de endereçamento diz onde a instrução vai buscar ou guardar o operando.

Modo Descrição Exemplo
Registo direto O operando está num registo MOV R16, R17
Imediato O valor vem na própria instrução LDI R16, 0x05
Direto à memória de dados O endereço vem na instrução LDS R16, 0x0100
Indireto O endereço está num par de registos (X, Y, Z) LD R16, X
Indireto com pré-decremento ou pós-incremento O ponteiro atualiza-se sozinho LD R16, X+
Indireto com deslocamento Ponteiro mais um deslocamento fixo LDD R16, Y+5
Memória de programa Lê constantes da Flash LPM R16, Z
Relativo Salto em relação ao contador de programa RJMP etiqueta

Exemplo resolvido · Somar 5 e 3 em assembly AVR

        LDI  R16, 5      ; R16 = 5        (imediato)
        LDI  R17, 3      ; R17 = 3
        ADD  R16, R17    ; R16 = R16 + R17 = 8 (registo direto)

Resultado: R16 = 8, flag Z = 0 (não deu zero), flag C = 0 (não houve excesso).

3.4 Registos de entrada e saída: DDR, PORT e PIN

Cada porta tem três registos de 8 bits, um bit por pino:

Registo Função
DDRx Direção: 1 = saída, 0 = entrada
PORTx Saída: nível do pino (se saída). Se entrada: 1 liga a resistência de pull-up
PINx Leitura do nível atual do pino

O que pinMode, digitalWrite e digitalRead fazem por baixo é escrever e ler estes registos. O pino 13 do UNO é o bit 5 da porta B (PB5).

// Equivalente a pinMode(13, OUTPUT); digitalWrite(13, HIGH);
void setup() {
  DDRB  |= (1 << DDB5);    // PB5 como saída
  PORTB |= (1 << PORTB5);  // PB5 a 1 (HIGH)
}

void loop() {
  PORTB ^= (1 << PORTB5);  // inverte PB5
  delay(500);
}

3.5 Operações bit a bit

Para mexer num bit sem alterar os outros usam-se máscaras:

Objetivo Expressão Exemplo (bit 5)
Pôr a 1 reg \|= (1 << n) PORTB \|= (1 << 5)
Pôr a 0 reg &= ~(1 << n) PORTB &= ~(1 << 5)
Inverter reg ^= (1 << n) PORTB ^= (1 << 5)
Testar reg & (1 << n) PINB & (1 << 5)

Exemplo resolvido · Pôr a 1 os bits 2 e 5 de uma porta

Estado inicial de PORTB = 0b0100 0001 (bits 0 e 6 a 1).

  1. Máscara: (1 << 2) | (1 << 5) = 0b0000 0100 | 0b0010 0000 = 0b0010 0100.
  2. PORTB |= máscara: 0100 0001 OU 0010 0100 = 0110 0101.

Resultado 0b0110 0101 = 0x65: os bits 0 e 6 mantiveram-se, os bits 2 e 5 ficaram a 1.

Para limpar o bit 6 depois: PORTB &= ~(1 << 6): ~(0100 0000) = 1011 1111; 0110 0101 E 1011 1111 = 0010 0101.

Em hexadecimal, cada algarismo representa 4 bits: 0b1011 0100 = 0xB4 = 180 (128 + 32 + 16 + 4).

4. Ambiente de desenvolvimento e configuração

4.1 O que é um IDE

Um ambiente de desenvolvimento integrado (IDE) reúne num só programa o editor de código, o compilador, o carregador (uploader) e as ferramentas de monitorização. Para Arduino:

Ferramenta Características
Arduino IDE 2 Oficial, simples, editor com realce, gestor de placas e bibliotecas, monitor série
PlatformIO (extensão do VS Code) Profissional, vários microcontroladores, controlo de versões, depuração, ficheiro platformio.ini
Simuladores online (Wokwi, Tinkercad Circuits) Corre o código num microcontrolador virtual, sem hardware

4.2 Instalar e configurar a plataforma, passo a passo

  1. Instalar o IDE a partir do site oficial e, se for outra placa (ESP32, RP2040), o suporte da placa. O UNO já vem com o IDE; as restantes instalam-se no Gestor de Placas.
  2. Ligar a placa por USB. Se o sistema não a reconhecer, instalar o driver do conversor USB-série (nos clones costuma ser o CH340).
  3. Escolher a placa (Ferramentas > Placa) e a porta (Ferramentas > Porta). Estas duas escolhas erradas explicam a maioria dos "não carrega".
  4. Testar com o exemplo Blink (Ficheiro > Exemplos > 01.Basics > Blink): compilar, carregar, ver o LED a piscar.
  5. Abrir o Monitor Série e definir a velocidade (baud rate) igual à do Serial.begin.

Procedimento de configuração é um procedimento. Em contexto profissional, regista a versão do IDE, a versão do pacote da placa e as versões das bibliotecas usadas. Um projeto que compila hoje pode deixar de compilar daqui a um ano se as versões mudarem. No PlatformIO isto fica escrito no platformio.ini.

4.3 O ciclo de desenvolvimento

escrever o código
      |
      v
compilar (verificar) --erro--> corrigir erros de sintaxe
      |
      v
carregar (upload via USB)
      |
      v
observar (LED, Monitor Série, multímetro)
      |
      +--comportamento errado--> depurar --> voltar a escrever

Compilar traduz o código C/C++ em código máquina e deteta erros de sintaxe. Carregar envia o código máquina para a Flash através do carregador de arranque (bootloader) que já está no chip. Cada carregamento apaga o programa anterior.

4.4 Instalar e usar bibliotecas

Uma biblioteca é código já escrito por outros para uma tarefa (ler um DHT22, comandar um servo, falar com um ecrã). Poupa tempo e erros, mas cada biblioteca ocupa memória e tem versões.

Formas de instalar no Arduino IDE:

  1. Gestor de Bibliotecas (Sketch > Incluir Biblioteca > Gerir Bibliotecas): pesquisar, escolher a versão, instalar. Recomendado.
  2. Ficheiro .zip (Sketch > Incluir Biblioteca > Adicionar biblioteca .ZIP).
  3. No PlatformIO: acrescentar a linha lib_deps = ... ao platformio.ini.

Depois usa-se com #include <Nome.h> no início do sketch. Bibliotecas que vêm com o IDE: Servo, Wire (I²C), SPI, EEPROM, SoftwareSerial, LiquidCrystal, Stepper.

#include <Servo.h>   // biblioteca instalada com o IDE
Servo braco;         // objeto da biblioteca

Verifica sempre a compatibilidade. Uma biblioteca escrita para AVR pode não funcionar em ESP32 por usar registos específicos do ATmega328P. Lê a documentação da biblioteca e as placas suportadas antes de a instalar.

4.5 Selecionar o ambiente

Para os primeiros passos e sem hardware, o Wokwi ou o Tinkercad Circuits chegam. Numa aula com Arduino UNO, o Arduino IDE 2. Num projeto com várias placas, repositório Git e depuração, o PlatformIO. A linguagem é determinada pelo microcontrolador: C/C++ é a escolha comum para Arduino; MicroPython é possível em RP2040 e ESP32 (mais fácil de escrever, mais lento e mais pesado em memória).

5. Fluxogramas: simbologia e técnicas de elaboração

5.1 Para que servem

Um fluxograma representa a lógica de um programa por símbolos ligados por setas. Desenha-se antes de programar: obriga a pensar em todos os caminhos possíveis, e é a melhor forma de explicar a um colega (ou a um cliente) o que o sistema faz.

5.2 Simbologia

Os símbolos seguem a norma internacional para fluxogramas (ISO 5807).

Símbolo Forma Uso
Terminal Oval Início e fim
Processo Retângulo Uma ação: atribuição, cálculo, comando de um atuador
Decisão Losango Pergunta com resposta sim/não (duas saídas)
Entrada/saída Paralelogramo Ler um sensor, mostrar um valor
Subrotina Retângulo com barras laterais Chamada de uma função
Conector Círculo pequeno Continuação noutro ponto ou página
Seta Linha com ponta Sentido do fluxo

5.3 Regras de elaboração

  1. Um só ponto de início; o fluxo corre de cima para baixo e da esquerda para a direita.
  2. Cada decisão tem duas saídas rotuladas (Sim/Não).
  3. Os ciclos fecham-se: a seta volta a um ponto anterior.
  4. Um passo por símbolo; texto curto, no infinitivo ou como pergunta.
  5. Em programas de microcontrolador o loop() é um ciclo infinito: o fluxograma do programa principal não tem "Fim", tem um retorno ao início.

Exemplo resolvido · Fluxograma de um termóstato com histerese

Requisito: ventoinha liga acima de 26 °C e só desliga abaixo de 24 °C.

        ( Início )
            |
   [ Configurar pinos e Serial ]
            |
            v
   +------->[ Ler temperatura t ]
   |        |
   |        v
   |    < t > 26 ? >--Sim--> [ Ligar ventoinha ]--+
   |        |                                     |
   |       Não                                    |
   |        v                                     |
   |    < t < 24 ? >--Sim--> [ Desligar ventoinha ]
   |        |                         |           |
   |       Não                        |           |
   |        v                         v           v
   |    [ Mostrar t no Monitor Série ]<-----------+
   |        |
   |        v
   +----[ Esperar 2 s (sem bloquear) ]

A zona entre 24 °C e 26 °C não altera nada: a ventoinha mantém o estado anterior. Isto é a histerese e evita que o relé comute dezenas de vezes por minuto quando a temperatura oscila à volta do limiar.

5.4 Do fluxograma ao código

Cada decisão do fluxograma é um if, cada ciclo um while ou for, cada subrotina uma função void nome() { ... } e cada símbolo de entrada/saída uma chamada como analogRead ou Serial.println.

6. Estrutura de um programa: funções, variáveis, tipos e operadores

6.1 Anatomia de um sketch

Um programa Arduino tem duas funções obrigatórias, chamadas pelo sistema, e zero ou mais funções do programador.

#include <Servo.h>               // 1. bibliotecas

const int PINO_LED = 13;         // 2. constantes e variáveis globais
int contador = 0;

void piscar(int vezes);          // 3. protótipos (opcional)

void setup() {                   // 4. corre UMA vez
  pinMode(PINO_LED, OUTPUT);
  Serial.begin(9600);
}

void loop() {                    // 5. repete para sempre
  piscar(3);
  delay(2000);
}

void piscar(int vezes) {         // 6. funções do programador
  for (int i = 0; i < vezes; i++) {
    digitalWrite(PINO_LED, HIGH);
    delay(200);
    digitalWrite(PINO_LED, LOW);
    delay(200);
  }
}

6.2 Tipos de dados

Em AVR (Arduino UNO) os tamanhos são:

Tipo Bytes Gama Uso típico
bool 1 true ou false Estados
byte / uint8_t 1 0 a 255 Valores de PWM, registos
char / int8_t 1 -128 a 127 Carateres, pequenos valores
int / int16_t 2 -32 768 a 32 767 Contadores, leituras do ADC
unsigned int / uint16_t 2 0 a 65 535 Valores positivos
long / int32_t 4 cerca de -2,1 mil milhões a +2,1 mil milhões Cálculos intermédios
unsigned long / uint32_t 4 0 a 4 294 967 295 millis(), micros()
float 4 cerca de 7 algarismos significativos Valores com vírgula

No ATmega328P double tem o mesmo tamanho que float (4 bytes). Noutras placas (ESP32) é diferente: o tamanho de int também muda (4 bytes em ARM e ESP32). Para código portável usa os tipos de tamanho fixo (uint8_t, int16_t, uint32_t).

Exemplo resolvido · Excesso (overflow) de um int

Cálculo: converter uma leitura do LM35 (10 mV/°C, referência de 5 V, ADC de 10 bits) para graus Celsius (parte inteira). Fórmula: T(°C) = leitura × 5 / 1024 × 100 = leitura × 500 / 1024.

Versão errada:

int leitura = 66;
int graus = leitura * 500 / 1024;   // ERRADO
  1. leitura * 500 = 66 × 500 = 33 000.
  2. 33 000 passa o máximo de um int (32 767) e o resultado "dá a volta": 33 000 - 65 536 = -32 536.
  3. -32 536 / 1024 = -31 (divisão inteira). A temperatura real seria 32 °C.

Versão certa, com um tipo maior:

int leitura = 66;
long graus = (long)leitura * 500 / 1024;   // 33000 / 1024 = 32

Conversão explícita para long antes da multiplicação. O erro mais frequente é converter o resultado e não o operando. Para obter décimas de grau (322, ou seja 32,2 °C) a fórmula passa a (long)leitura * 5000 / 1024.

Exemplo resolvido · Divisão inteira

int a = 5, b = 2;
float r1 = a / b;          // 2.0  (divisão inteira feita ANTES da conversão)
float r2 = (float)a / b;   // 2.5
float r3 = 5 / 2.0;        // 2.5  (um dos operandos já é decimal)

Em float volts = leitura * 5 / 1023; com leitura inteiro, o cálculo faz-se em inteiros e perde a parte decimal. Escrever leitura * 5.0 / 1023.0.

6.3 Variáveis: âmbito e duração

Tipo de variável Onde se declara Vive Notas
Global Fora de qualquer função Todo o programa Ocupa SRAM sempre; partilhada
Local Dentro de uma função Só durante a função Perde o valor ao terminar
static local Dentro de uma função, com static Todo o programa Mantém o valor entre chamadas
const Qualquer Constante Não ocupa SRAM se o compilador a substituir
volatile Qualquer Variável que muda "por fora" (interrupção) Impede o compilador de a guardar em registo (capítulo 11)

Convenção útil: constantes em MAIÚSCULAS (PINO_LED) e const int em vez de #define.

6.4 Operadores

Família Operadores
Aritméticos + - * / %
Comparação == != < > <= >=
Lógicos && \|\| !
Bit a bit & \| ^ ~ << >>
Composição += -= *= /= \|= &= ^= e ++ --

Armadilha clássica: = atribui, == compara. if (x = 5) atribui 5 a x e a condição é sempre verdadeira. Os compiladores avisam; lê os avisos.

6.5 Estruturas de controlo

// Decisão
if (t > 26.0) {
  ligar();
} else if (t < 24.0) {
  desligar();
}

// Seleção múltipla
switch (estado) {
  case 0: /* repouso */   break;
  case 1: /* armado */    break;
  default: /* inválido */ break;
}

// Ciclos
for (int i = 0; i < 5; i++) { /* 5 vezes */ }
while (digitalRead(BTN) == HIGH) { /* enquanto não premido */ }

Atenção ao while e ao delay() dentro do loop(): um while que espera por um botão bloqueia o programa inteiro. Outras tarefas (ler outro sensor, atualizar um ecrã) ficam paradas. Voltamos a isto nos capítulos 7 e 11.

6.6 Funções e modularidade

Uma função agrupa instruções com um nome, recebe parâmetros e pode devolver um valor.

const int LED_ALERTA = 12;               // LED de aviso de calor

float lerTemperatura() {                 // devolve °C
  int leitura = analogRead(A0);
  float tensao = leitura * 5.0 / 1024.0;
  return tensao * 100.0;                  // LM35: 10 mV/°C
}

void aplicarControlo(float t) {          // não devolve nada
  digitalWrite(LED_ALERTA, t > 30.0 ? HIGH : LOW);   // LED de aviso de calor
}

Regra de ouro: uma função, uma responsabilidade. lerTemperatura só lê; aplicarControlo só decide; loop só orquestra. Código assim lê-se, testa-se por partes e reutiliza-se.

6.7 Bibliotecas e funções do sistema

Funções que usarás todos os dias, sem #include:

Função Para que serve
pinMode(p, modo) Configura o pino como INPUT, OUTPUT ou INPUT_PULLUP
digitalRead(p) / digitalWrite(p, v) Lê e escreve um pino digital
analogRead(p) / analogWrite(p, v) Lê o ADC e escreve PWM
delay(ms) / delayMicroseconds(us) Bloqueia o programa
millis() / micros() Tempo desde o arranque
map(v, a, b, c, d) Converte um valor de um intervalo para outro
constrain(v, min, max) Limita um valor
Serial.print, Serial.println Escrever no Monitor Série

7. Entradas e saídas digitais

7.1 Saída digital e dimensionamento do LED

pinMode(9, OUTPUT);
digitalWrite(9, HIGH);   // pino a 5 V
digitalWrite(9, LOW);    // pino a 0 V

Um LED precisa de uma resistência em série que limite a corrente. Com Vcc = 5 V, tensão direta do LED (Vf) e corrente pretendida (I):

R = (Vcc - Vf) / I

Exemplo resolvido · Resistência para um LED vermelho

Dados: Vcc = 5 V, Vf ≈ 2,0 V (valor típico; confirmar na folha de características), I = 15 mA.

  1. R = (5 - 2,0) / 0,015 = 200 Ω.
  2. Valor normalizado imediatamente acima: 220 Ω.
  3. Corrente real: I = 3 / 220 = 13,6 mA.
  4. Potência dissipada na resistência: P = R × I² = 220 × 0,0136² = 0,041 W, muito abaixo de 0,25 W.

Sem resistência, o LED tenta puxar uma corrente muito acima do limite do pino e a vida útil de ambos fica em risco.

7.2 Entrada digital, pull-up e pull-down

Um pino de entrada sem nada ligado flutua: lê 0 ou 1 ao acaso, captando ruído. É preciso definir o estado de repouso.

Configuração Esquema Repouso Premido
Pull-up Botão entre o pino e GND; resistência para Vcc HIGH LOW
Pull-down Botão entre o pino e Vcc; resistência para GND LOW HIGH

No Arduino, INPUT_PULLUP liga uma resistência interna (da ordem de dezenas de kΩ, ver folha de características) entre o pino e Vcc. Não é preciso componente externo:

pinMode(2, INPUT_PULLUP);
bool premido = (digitalRead(2) == LOW);   // lógica invertida

A lógica fica invertida: premido = LOW. Habitua-te a escrever == LOW para "premido" quando usas pull-up.

7.3 Ressalto (debounce)

Os contactos mecânicos "saltam" durante alguns milissegundos ao fechar. O microcontrolador, muito mais rápido, lê vários toques onde houve um.

Sinal real do botão:   HIGH ____    __ _   ________ LOW
                                \__/  \_| |_/
                                  ressalto (poucos ms)

Debounce por software, bloqueante (simples, só para exemplos):

if (digitalRead(BTN) == LOW) {
  delay(20);                          // espera que estabilize
  if (digitalRead(BTN) == LOW) { /* toque válido */ }
}

Debounce não bloqueante, com millis():

const int BTN = 2;
const unsigned long ATRASO = 30;      // ms
int ultimoLido = HIGH;
int estavel = HIGH;
unsigned long tMudanca = 0;

bool premiu() {                       // true uma vez por toque
  int lido = digitalRead(BTN);
  if (lido != ultimoLido) {           // mudou: reinicia a contagem
    tMudanca = millis();
    ultimoLido = lido;
  }
  if (millis() - tMudanca > ATRASO && lido != estavel) {
    estavel = lido;
    if (estavel == LOW) return true;  // flanco descendente estável
  }
  return false;
}

Também se resolve por hardware com um condensador (filtro RC) em paralelo com o botão.

7.4 Detetar o flanco e fazer um interruptor de toque

Um botão que alterna o LED a cada toque tem de reagir à mudança, não ao nível.

Exemplo resolvido · Botão que liga e desliga um LED

Requisito: cada toque inverte o estado do LED (pino 9); botão no pino 2 com pull-up.

const int LED = 9;
const int BTN = 2;
bool ledLigado = false;
int anterior = HIGH;

void setup() {
  pinMode(LED, OUTPUT);
  pinMode(BTN, INPUT_PULLUP);
}

void loop() {
  int atual = digitalRead(BTN);
  if (anterior == HIGH && atual == LOW) {   // flanco descendente
    ledLigado = !ledLigado;
    digitalWrite(LED, ledLigado);
    delay(30);                               // debounce simples
  }
  anterior = atual;
}

Passo a passo: com o botão em repouso, anterior e atual são HIGH, nada acontece. No instante em que atual passa a LOW com anterior ainda HIGH, há um flanco: inverte-se o estado uma só vez. Enquanto o botão se mantém premido, anterior passa a LOW e a condição deixa de se verificar. Sem a variável anterior, o LED piscaria centenas de vezes por segundo enquanto o dedo estivesse no botão.

8. Entradas analógicas, ADC e PWM

8.1 O conversor analógico-digital

O mundo físico é analógico (temperatura, luz, posição). O ADC converte uma tensão numa contagem. O ADC do ATmega328P tem 10 bits: 2¹⁰ = 1024 níveis (0 a 1023) entre 0 V e a tensão de referência.

leitura = tensão / Vref × 1024 (arredondado para baixo)

tensão = leitura × Vref / 1024

Por convenção simples usa-se frequentemente 1023; a diferença (menos de 0,1 %) é desprezável na maioria dos casos.

Referência (analogReference) Valor Uso
DEFAULT 5 V (tensão de alimentação) Predefinição
INTERNAL 1,1 V interna (valor típico; a folha de características dá 1,0 a 1,2 V) Sensores de sinal pequeno (LM35): mais resolução
EXTERNAL Tensão no pino AREF Referência precisa externa

Resolução é a menor variação distinguível: Vref / 1024. Com 5 V: 4,88 mV. Com 1,1 V: 1,07 mV.

Exemplo resolvido · Resolução de um LM35 com duas referências

O LM35 dá 10 mV por °C.

Conclusão: a mesma medida ganha cerca de 4,5 vezes mais resolução usando a referência de 1,1 V, à custa de só medir até 1,1 V (110 °C no cálculo; confirma na folha de características a gama da variante do LM35 usada).

Os 1,1 V são um valor típico: a folha de características do ATmega328P dá 1,0 a 1,2 V. Por isso os 232 níveis e os 0,107 °C são valores nominais. Para medidas rigorosas mede-se o valor real da referência e calibra-se.

O ADC demora cerca de 104 µs por conversão na configuração predefinida (13 ciclos do relógio do ADC de 125 kHz), cerca de 9600 leituras por segundo. É rápido para a maioria das aplicações, mas não para áudio.

8.2 Ruído e média

O valor do ADC "treme" um ou dois níveis por ruído elétrico. Tratamento comum: média de várias leituras (somar num long para evitar overflow e dividir por n). Com 8 leituras o ruído aleatório reduz-se aproximadamente na raiz quadrada de 8.

8.3 A função map e os seus limites

map(valor, deMin, deMax, paraMin, paraMax) faz uma proporção linear com números inteiros:

map(512, 0, 1023, 0, 255) = (512 - 0) × (255 - 0) / (1023 - 0) + 0 = 127 (divisão inteira).

map não limita o resultado. Se o valor sair do intervalo de origem, sai também do intervalo de destino. Junta-lhe constrain(v, 0, 255) quando a entrada pode exceder o esperado.

8.4 PWM: saída "analógica"

O microcontrolador não tem saídas verdadeiramente analógicas. O PWM (modulação por largura de impulso) liga e desliga o pino muito depressa; a fração do tempo em que fica ligado chama-se duty cycle (ciclo de trabalho).

Duty de 25 % dá uma média de 1,25 V; de 50 %, 2,5 V; de 75 %, 3,75 V.

analogWrite(pino, valor) aceita 0 a 255: duty = valor / 255.

Exemplo resolvido · Potenciómetro a regular o brilho de um LED

Potenciómetro em A0, LED com resistência de 220 Ω no pino 6.

const int POT = A0;
const int LED = 6;                       // pino PWM

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

void loop() {
  int leitura = analogRead(POT);               // 0 a 1023
  int brilho  = map(leitura, 0, 1023, 0, 255); // 0 a 255
  analogWrite(LED, brilho);
}

Verificação com três posições do potenciómetro:

Posição leitura brilho Duty
Mínimo 0 0 0 %
Meio 512 127 49,8 %
Máximo 1023 255 100 %

O olho humano não vê linear: a 50 % de duty o LED parece bastante mais brilhante do que "metade". Para comandos de iluminação convém uma curva (por exemplo, quadrática) em vez de uma reta.

PWM serve para brilho de LED, velocidade de motores DC (através de driver), posição de servos e sinais de áudio simples. Não alimenta cargas diretamente: o pino continua limitado a poucos miliamperes (capítulo 10).

9. Interligação de sensores

Um sensor converte uma grandeza física num sinal elétrico. O microcontrolador lê-o de uma de três formas: digital (HIGH/LOW), analógica (via ADC) ou por protocolo (I²C, SPI, 1-Wire, dados temporizados).

9.1 Divisor de tensão: a base dos sensores resistivos

Sensores resistivos (LDR, NTC, potenciómetro) não dão tensão: variam a resistência. O ADC lê tensão, por isso monta-se um divisor de tensão com uma resistência fixa.

Vcc (5 V)
   |
 [ LDR ]  resistência variável
   |
   +-----------> A0 (ADC)
   |
 [ 10 kΩ ]  resistência fixa
   |
  GND

Vout = Vcc × Rfixa / (Rfixa + Rsensor)

Exemplo resolvido · LDR em divisor com 10 kΩ

Valores típicos de um LDR: cerca de 1 kΩ com muita luz e 100 kΩ às escuras (confirmar no componente usado).

Condição R do LDR Vout analogRead
Muita luz 1 kΩ 5 × 10 / (10 + 1) = 4,55 V cerca de 931
Luz normal 10 kΩ 5 × 10 / 20 = 2,50 V 512
Escuro 100 kΩ 5 × 10 / 110 = 0,45 V cerca de 93

Nesta montagem mais luz = valor mais alto. Se trocares a posição do LDR e da resistência fixa, inverte-se: mais luz = valor mais baixo. Define o limiar de acender uma luz exterior pelo valor lido, por exemplo if (analogRead(A0) < 200) para "escuro".

9.2 Temperatura: LM35, NTC e DHT22

LM35 (analógico, linear): 10 mV por °C. Cálculo simples e com boa precisão.

float lerLM35() {
  int leitura = analogRead(A0);
  return leitura * 5.0 / 1024.0 * 100.0;   // °C
}

NTC (resistência que diminui com a temperatura, não linear). Monta-se num divisor e converte-se com a equação beta:

1/T = 1/T0 + (1/B) × ln(R/R0) (T em kelvin)

Exemplo resolvido · NTC de 10 kΩ (B = 3950) num divisor com 10 kΩ

NTC ligado a Vcc, resistência fixa de 10 kΩ a GND, saída em A0. Leitura obtida: 754.

  1. Resistência do NTC: R = Rfixa × (1024 / leitura - 1) = 10 000 × (1024 / 754 - 1) = 10 000 × 0,358 = 3580 Ω.
  2. Equação beta, com T0 = 298,15 K (25 °C) e R0 = 10 000 Ω: 1/T = 1/298,15 + ln(3580 / 10 000) / 3950 ln(0,358) = -1,0275; -1,0275 / 3950 = -0,000260; 1/298,15 = 0,003354 1/T = 0,003094, logo T = 323,2 K.
  3. T em °C: 323,2 - 273,15 = 50,0 °C.

Em código:

float lerNTC() {
  const float R0 = 10000.0, B = 3950.0, T0 = 298.15, RF = 10000.0;
  int leitura = analogRead(A0);
  if (leitura <= 0 || leitura >= 1023) return NAN;   // 0 daria divisão por zero; 0 e 1023 indicam NTC solto ou em curto
  float r = RF * (1024.0 / leitura - 1.0);
  float tK = 1.0 / (1.0 / T0 + log(r / R0) / B);
  return tK - 273.15;
}

O chamador testa o resultado antes de decidir:

float t = lerNTC();
if (isnan(t)) {
  Serial.println("NTC com falha");   // e a carga fica no estado seguro
} else {
  controlar(t);
}

Os valores R0 e B vêm da folha de características do NTC usado; tolerâncias de 1 a 5 % mudam o resultado.

DHT11 e DHT22 (digitais, temperatura e humidade): protocolo temporizado de um só fio, tratado por uma biblioteca (por exemplo a DHT sensor library). Lê-se no máximo cada poucos segundos (consultar a folha de características: o DHT22 tem cadência de leitura lenta).

#include <DHT.h>
DHT dht(4, DHT22);                  // pino 4

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

void loop() {
  float h = dht.readHumidity();
  float t = dht.readTemperature();
  if (isnan(h) || isnan(t)) {       // leitura inválida
    Serial.println("Falha de leitura");
  } else {
    Serial.print(t); Serial.print(" C  "); Serial.print(h); Serial.println(" %");
  }
  delay(2000);
}

Testar sempre isnan: um sensor desligado devolve "não é número" e, se o código não o trata, o relé comanda-se com lixo.

9.3 Distância: HC-SR04

O módulo ultrassónico envia um pulso de som e mede o tempo até ao eco.

 TRIG: pulso de 10 µs
 ECHO: fica HIGH durante o tempo de ida e volta do som

Distância = tempo × velocidade do som / 2 (o som vai e volta). A cerca de 20 °C o som viaja a 343 m/s = 0,0343 cm/µs.

Exemplo resolvido · Calcular a distância a partir do eco

Medimos duracao = 5830 µs.

  1. Distância total percorrida: 5830 × 0,0343 = 199,97 cm.
  2. Dividir por dois (ida e volta): 199,97 / 2 = 100 cm.
const int TRIG = 9, ECHO = 10;

void setup() {
  pinMode(TRIG, OUTPUT);
  pinMode(ECHO, INPUT);
  Serial.begin(9600);
}

float lerDistanciaCm() {
  digitalWrite(TRIG, LOW);  delayMicroseconds(2);
  digitalWrite(TRIG, HIGH); delayMicroseconds(10);
  digitalWrite(TRIG, LOW);
  unsigned long duracao = pulseIn(ECHO, HIGH, 30000UL);  // timeout 30 ms
  if (duracao == 0) return -1.0;                          // sem eco
  return duracao * 0.0343 / 2.0;
}

void loop() {
  Serial.println(lerDistanciaCm());
  delay(100);
}

O argumento de timeout de pulseIn (30 000 µs) evita que o programa fique preso a esperar um eco que não chega. O módulo tem alcance limitado (consultar a folha de características do módulo); superfícies muito inclinadas ou macias devolvem leituras erradas.

9.4 Movimento, gás e outros

O PIR dá HIGH ao detetar movimento e precisa de tempo de estabilização depois de ligado. Os fins de curso e as barreiras óticas são digitais (usar pull-up e debounce). O MQ-x (gás) é analógico, requer aquecimento e calibração e não substitui um detetor certificado. Os sensores de pressão e de humidade do solo são analógicos: ver a gama e a tensão na folha de características e calibrar com dois pontos.

9.5 Calibrar um sensor

Um valor lido só é útil se for calibrado: relacionado com a grandeza real.

  1. Recolhe dois pontos de referência conhecidos (por exemplo, sensor de humidade do solo seco e dentro de água).
  2. Regista o valor lido em cada um (seco = 820, agua = 350).
  3. Converte a leitura em percentagem: perc = map(leitura, seco, agua, 0, 100) e limita com constrain(perc, 0, 100).
  4. Compara com um instrumento fiável e anota o desvio.

Mostra primeiro o valor bruto do ADC no Monitor Série e só depois converte: se o bruto já está errado, o problema é de ligação, não de fórmula.

10. Atuadores e ligação de potência

10.1 O limite do pino

Um pino do ATmega328P aguenta, de forma recomendada, cerca de 20 mA (máximo absoluto 40 mA, ver folha de características). Isso chega para um LED com resistência; não chega para um motor, um relé nu, uma fita de LED ou um altifalante. A regra é simples: o pino comanda, não alimenta. A potência vem de uma fonte própria, interrompida por um componente que o pino sabe acionar.

Ordens de grandeza: um LED com resistência pede 10 a 20 mA (ligação direta); uma bobina de relé, dezenas de mA (transístor ou módulo de relé); um motor DC pequeno, centenas de mA (transístor ou ponte H); um motor de passo, de centenas de mA a vários A (driver dedicado); fitas de LED, bombas e ventoinhas de potência, amperes (MOSFET ou relé com fonte própria).

10.2 Transístor como interruptor

Um transístor NPN (de pequeno sinal) comanda uma carga maior: o pino alimenta a base através de uma resistência, o transístor deixa passar a corrente da carga pelo coletor para o emissor (ligado à massa).

        +V (fonte da carga)
          |
        [CARGA]  --- díodo em antiparalelo se for indutiva
          |
          C
pino --[Rb]-- B   NPN
          E
          |
         GND  (comum com o do Arduino)

Exemplo resolvido · Resistência de base para comandar um relé de 5 V

Dados: bobina do relé com 70 mA (valor de exemplo: confirmar na folha de características do relé), saída do pino a 5 V, queda base-emissor Vbe ≈ 0,7 V, ganho do transístor hFE mínimo dado pela folha de características do transístor usado (confirmar; aqui assumimos 100).

  1. Para garantir saturação usa-se um ganho "forçado" de cerca de 10: Ib = Ic / 10 = 70 / 10 = 7 mA.
  2. Rb = (Vpino - Vbe) / Ib = (5 - 0,7) / 0,007 = 614 Ω.
  3. Valor normalizado: 560 Ω. Ib real = 4,3 / 560 = 7,7 mA, abaixo dos 20 mA recomendados para o pino.

Se Rb fosse demasiado alta (por exemplo 10 kΩ, Ib = 0,43 mA), o transístor não saturava, aquecia e o relé não fechava bem.

10.3 Cargas indutivas e díodo de roda livre

Motores, relés e eletroímãs são indutivos. Ao cortar a corrente, a bobina gera um pico de tensão muito superior à alimentação, que pode destruir o transístor e o microcontrolador. A solução é um díodo de roda livre (flyback) em antiparalelo com a bobina (cátodo para o positivo), por exemplo um 1N4007 ou um 1N4148 consoante a corrente. Os módulos de relé já trazem este díodo; ao montar a mão, é obrigatório.

10.4 Alimentação e massa comum

  1. A carga tem fonte própria dimensionada para a sua corrente máxima (motores com arranque exigem bem mais do que a corrente nominal).
  2. As massas (GND) são ligadas entre si: a do Arduino e a da fonte da carga. Sem massa comum o sinal de comando não tem referência e o circuito não funciona.
  3. Não alimentar motores pelo pino de 5 V da placa USB: o porto USB e o regulador têm pouca margem.
  4. Quedas de tensão por motores a arrancar podem reiniciar o microcontrolador: separar as alimentações ou acrescentar um condensador de desacoplamento junto à carga.

10.5 LED, buzzer e relé

LED: digitalWrite para ligar/desligar, analogWrite para o brilho.

Buzzer: o ativo tem oscilador interno e liga-se com digitalWrite; o passivo recebe uma frequência com tone().

tone(8, 440);       // nota Lá (440 Hz) no pino 8
delay(500);
noTone(8);

Relé: é um interruptor comandado por eletroíman, isolado galvanicamente do circuito de comando. Os módulos habituais têm um transístor e um díodo integrados e, muitas vezes, lógica de ativação invertida (LOW liga). Lê a documentação do módulo.

const int RELE = 7;

void setup() {
  digitalWrite(RELE, HIGH);   // nível seguro primeiro (módulo invertido: HIGH = desligado)
  pinMode(RELE, OUTPUT);      // só depois passa a saída
}

Escreve o nível seguro antes de declarar o pino como saída, para o relé não dar um clique de arranque.

Corrente alternada. Os contactos do relé têm tensão e corrente máximas escritas no próprio componente; nunca as ultrapasses. Montagens a 230 V exigem caixa isolada, ligações protegidas e habilitação adequada. Nas aulas, usa a carga de baixa tensão.

10.6 Servomotor

Um servo posiciona-se num ângulo (0 a 180° nos modelos comuns). Recebe um sinal PWM de 50 Hz (período de 20 ms) em que a largura do impulso codifica a posição: cerca de 1 ms numa extremidade e 2 ms na outra. A biblioteca Servo esconde isto.

#include <Servo.h>
Servo s;

void setup() { s.attach(9); }

void loop() {
  s.write(0);    delay(1000);
  s.write(90);   delay(1000);
  s.write(180);  delay(1000);
}

Por omissão, a biblioteca mapeia 0° a 544 µs e 180° a 2400 µs. Para 90° o impulso é 544 + (2400 - 544) / 2 = 1472 µs. Um servo pode consumir centenas de mA ao mover-se: dá-lhe fonte própria, com massa comum.

10.7 Motor DC e ponte H

O motor DC roda com velocidade proporcional à tensão média e inverte o sentido se a polaridade inverter. A ponte H (L298N, L293D, TB6612, entre outros) permite as duas coisas: dois sinais de direção (IN1, IN2) e um sinal PWM de velocidade (ENA).

IN1 IN2 Motor
LOW LOW Parado
HIGH LOW Sentido 1
LOW HIGH Sentido 2
HIGH HIGH Travagem (em muitos drivers)
const int IN1 = 4, IN2 = 5, ENA = 6;      // ENA é pino PWM

void motor(int velocidade) {              // -255 a +255
  digitalWrite(IN1, velocidade > 0);
  digitalWrite(IN2, velocidade < 0);
  analogWrite(ENA, abs(velocidade));
}

void setup() {
  pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT); pinMode(ENA, OUTPUT);
}

void loop() {
  motor(150);  delay(2000);               // avança a ~59 % de duty
  motor(0);    delay(500);
  motor(-150); delay(2000);               // recua
  motor(0);    delay(500);
}

Mudar de sentido de repente é violento para o motor e para o driver: pára primeiro, espera um pouco, inverte.

10.8 Motor de passo

O motor de passo roda em passos discretos e mantém a posição sem sensor. Um motor de 1,8° por passo dá 360 / 1,8 = 200 passos por volta. Os drivers (por exemplo A4988) simplificam o comando para dois sinais: STEP (cada impulso é um passo) e DIR (sentido); suportam micropasso (por exemplo 1/16, o que dá 3200 passos por volta).

Segurança com o driver A4988. O motor de passo não se alimenta pelo Arduino: o driver tem uma alimentação própria para o motor (a tensão admissível está na folha de características do driver) e a alimentação lógica vem do Arduino. Regula o limite de corrente do driver para a corrente do motor usado, segundo a folha de características do driver e do motor, antes de o ligar. As massas do Arduino e da fonte do motor ficam ligadas (massa comum). Nunca ligues nem desligues o motor com o driver alimentado: o pico de tensão pode destruir o driver, e uma ligação solta com a fonte ligada é um risco para ti. Corta a alimentação do motor, liga ou desliga, e só depois voltas a alimentar. Trabalha sempre com a fonte de baixa tensão do motor, nunca com a rede de 230 V.

Exemplo resolvido · Velocidade de um motor de passo

Pretende-se 60 rotações por minuto num motor de 200 passos por volta, em passo completo.

  1. Rotações por segundo: 60 / 60 = 1 volta/s.
  2. Passos por segundo: 200 × 1 = 200 passos/s.
  3. Intervalo entre passos: 1 / 200 = 5 ms.
const int STEP = 3, DIR = 2;

void setup() { pinMode(STEP, OUTPUT); pinMode(DIR, OUTPUT); }

void loop() {
  digitalWrite(DIR, HIGH);               // sentido 1
  for (int i = 0; i < 200; i++) {        // uma volta
    digitalWrite(STEP, HIGH); delayMicroseconds(10);
    digitalWrite(STEP, LOW);  delay(5);  // 5 ms entre passos
  }
  delay(1000);
}

Com micropasso 1/16 seriam 3200 passos por volta e um intervalo de 312 µs para a mesma velocidade.

11. Interrupções e temporização

11.1 Sondagem contra interrupção

Há duas formas de reagir a um evento (um botão, um pulso de um sensor):

loop(): ...instrução... ----[evento!]----> ISR() executa -> volta ao loop() no mesmo ponto

11.2 Interrupções externas

No UNO, os pinos 2 (INT0) e 3 (INT1) geram interrupções por hardware. Modos de disparo:

Modo Dispara quando
LOW O pino está a LOW
CHANGE O nível muda
RISING Passa de LOW a HIGH
FALLING Passa de HIGH a LOW
const int BTN = 2;
volatile unsigned int toques = 0;     // volatile: alterada na ISR

void contar() {                       // ISR: curta e sem delay()
  toques++;
}

void setup() {
  pinMode(BTN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(BTN), contar, FALLING);
  Serial.begin(9600);
}

void loop() {
  noInterrupts();                     // leitura atómica de 16 bits
  unsigned int copia = toques;
  interrupts();
  Serial.println(copia);
  delay(500);
}

11.3 Regras de ouro das ISR

  1. Curtas e rápidas: altera uma variável, ativa uma bandeira (flag) e sai. O trabalho pesado faz-se no loop().
  2. Sem delay(); o millis() não avança dentro da ISR; Serial.print dentro da ISR é má ideia (usa interrupções e pode bloquear).
  3. Variáveis partilhadas com o loop() são volatile: dizem ao compilador que o valor pode mudar a qualquer momento e que tem de o ler da memória sempre.
  4. Variáveis maiores do que 8 bits (como int e long) são lidas em vários passos; uma interrupção a meio dá um valor corrompido. A leitura faz-se com as interrupções desativadas por instantes (noInterrupts() / interrupts()).
  5. O botão com ressalto dispara a ISR várias vezes: faz debounce comparando millis() com o instante do último evento válido.

11.4 millis() em vez de delay()

delay(5000) pára tudo durante 5 s: botões, sensores, comunicação. A alternativa é guardar o instante da última execução e comparar com o tempo atual.

const unsigned long INTERVALO = 1000;   // ms
unsigned long ultimo = 0;

void loop() {
  unsigned long agora = millis();
  if (agora - ultimo >= INTERVALO) {
    ultimo = agora;
    // tarefa periódica
  }
  // aqui continuam a correr as outras tarefas
}

millis() devolve milissegundos desde o arranque num unsigned long e volta a zero ao fim de cerca de 49,7 dias (2³² ms). A subtração agora - ultimo funciona na mesma, porque a aritmética sem sinal "dá a volta" igualmente: se ultimo = 4 294 967 000 e agora = 200, então agora - ultimo = 200 - 4 294 967 000 + 4 294 967 296 = 496 ms. A comparação agora >= ultimo + INTERVALO falharia neste caso. Usa sempre a subtração.

Exemplo resolvido · Duas tarefas a ritmos diferentes sem bloquear

LED a piscar a 500 ms e leitura de temperatura a cada 2 s.

const int LED = 9;
unsigned long tLed = 0, tTemp = 0;
bool estadoLed = false;

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

void loop() {
  unsigned long agora = millis();

  if (agora - tLed >= 500) {          // tarefa 1
    tLed = agora;
    estadoLed = !estadoLed;
    digitalWrite(LED, estadoLed);
  }
  if (agora - tTemp >= 2000) {        // tarefa 2
    tTemp = agora;
    float t = analogRead(A0) * 5.0 / 1024.0 * 100.0;
    Serial.println(t);
  }
}

Com delay() seria impossível: ou piscava, ou lia. Este padrão é a base de qualquer programa com várias funções.

11.5 Temporizadores (timers)

O ATmega328P tem três temporizadores: Timer0 (8 bits), Timer1 (16 bits) e Timer2 (8 bits). Cada um conta impulsos do relógio divididos por um prescaler e pode gerar interrupções ao atingir um valor. O Timer0 e o Timer1 usam o prescaler 1, 8, 64, 256 ou 1024; o Timer2 tem ainda 32 e 128 (ver a folha de características). O Timer0 está reservado ao Arduino: transborda cada 1,024 ms (16 MHz / 64 / 256 = 976,56 Hz) e é daí que sai o millis().

Para gerar uma interrupção periódica exata usa-se o modo CTC (Clear Timer on Compare Match): o contador reinicia ao atingir o valor de comparação OCR1A.

OCR1A = f_clk / (prescaler × f_desejada) - 1

Exemplo resolvido · Interrupção de Timer1 uma vez por segundo

Dados: f_clk = 16 MHz, prescaler 256, f_desejada = 1 Hz.

  1. Frequência de contagem: 16 000 000 / 256 = 62 500 Hz.
  2. OCR1A = 62 500 / 1 - 1 = 62 499 (cabe em 16 bits, máximo 65 535).
volatile bool passouSegundo = false;

ISR(TIMER1_COMPA_vect) {              // vetor de interrupção do Timer1
  passouSegundo = true;
}

void setup() {
  Serial.begin(9600);
  noInterrupts();
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;                         // contador a zero
  OCR1A  = 62499;                     // valor de comparação
  TCCR1B |= (1 << WGM12);             // modo CTC
  TCCR1B |= (1 << CS12);              // prescaler 256
  TIMSK1 |= (1 << OCIE1A);            // ativa a interrupção
  interrupts();
}

void loop() {
  if (passouSegundo) {
    passouSegundo = false;
    Serial.println("1 s");
  }
}

A ISR só ativa uma bandeira; o Serial.println está no loop().

12. Comunicação série e protocolos

12.1 UART e o Monitor Série

A UART é uma comunicação assíncrona com dois fios (TX e RX) e uma velocidade combinada entre as duas partes (baud rate). A trama habitual 8N1 tem 1 bit de início, 8 bits de dados, sem paridade e 1 bit de paragem, ou seja, 10 bits por byte.

 repouso (HIGH)  start  d0 d1 d2 d3 d4 d5 d6 d7  stop  repouso
   ______________   _   _____________________________  ______
                  \_/                                  

Exemplo resolvido · Quanto demora a enviar uma linha a 9600 baud

A mensagem Valor: 1023 seguida de fim de linha (\r\n) tem 11 + 2 = 13 carateres = 13 bytes.

  1. Bits por byte na trama 8N1: 10.
  2. Bits totais: 13 × 10 = 130.
  3. Tempo: 130 / 9600 = 13,5 ms.

Se o loop() enviar isto a cada 10 ms, a porta não consegue acompanhar e o programa fica parado à espera. Subir a velocidade (115200) ou enviar menos dados resolve.

Funções principais:

Serial.begin(9600);                  // no setup
Serial.print("T = ");  Serial.println(25.4, 1);   // 1 casa decimal
if (Serial.available() > 0) {        // há bytes recebidos?
  char c = Serial.read();            // lê um byte
}

A velocidade do Monitor Série tem de ser igual à do Serial.begin; se vires carateres estranhos, é quase sempre isto.

// Comandar um LED a partir do teclado: 'a' acende, 'p' apaga
void setup() { pinMode(13, OUTPUT); Serial.begin(9600); }

void loop() {
  if (Serial.available() > 0) {
    char c = Serial.read();
    if (c == 'a') digitalWrite(13, HIGH);
    if (c == 'p') digitalWrite(13, LOW);
  }
}

12.2 I²C

O I²C usa dois fios: SDA (dados) e SCL (relógio), com resistências de pull-up (tipicamente alguns kΩ; muitos módulos já as trazem). Há um mestre (o microcontrolador) e vários escravos, cada um com um endereço de 7 bits. No UNO: SDA = A4, SCL = A5.

Endereços habituais (podem variar com o módulo e a ligação dos pinos de endereço):

Dispositivo Endereço comum
Ecrã LCD com adaptador PCF8574 0x27 ou 0x3F
Relógio DS3231 0x68
Sensor BME280 0x76 ou 0x77

Dois dispositivos com o mesmo endereço no mesmo barramento colidem. O programa de varrimento (scanner) descobre os que estão presentes:

#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(9600);
  for (byte addr = 1; addr < 127; addr++) {
    Wire.beginTransmission(addr);
    if (Wire.endTransmission() == 0) {      // 0 = o dispositivo respondeu
      Serial.print("Encontrado em 0x");
      Serial.println(addr, HEX);
    }
  }
}

void loop() {}

12.3 SPI

O SPI é um barramento síncrono e rápido, com quatro fios: MOSI (mestre para escravo), MISO (escravo para mestre), SCK (relógio) e CS/SS (seleção do escravo, um por dispositivo). Usa-se em cartões SD, ecrãs TFT, módulos de rádio. No UNO: MOSI = 11, MISO = 12, SCK = 13, SS = 10.

12.4 1-Wire

Um único fio de dados e vários dispositivos, cada um com identificador próprio. Típico do sensor DS18B20, com pull-up indicado na folha de características.

12.5 Comparação dos protocolos

Protocolo Fios Velocidade Dispositivos Uso típico
UART 2 (TX, RX) Média Ponto a ponto Depuração, GPS, módulos
I²C 2 (SDA, SCL) Média Muitos (endereços) Sensores, ecrãs, RTC
SPI 4 + 1 CS por escravo Alta Poucos SD, TFT, rádio
1-Wire 1 Baixa Muitos Temperatura DS18B20

12.6 Wi-Fi e Internet das Coisas

O ESP32 e o ESP8266 têm Wi-Fi integrado. Um nó IoT típico lê um sensor, liga-se à rede e envia os dados para um servidor, por HTTP (pedido e resposta) ou MQTT (publicação e subscrição de mensagens por tópicos, leve e adequada a sensores).

[sensor] -> [ESP32] --Wi-Fi--> [broker MQTT] --> [painel / base de dados]

Cuidados mínimos de segurança: nunca deixar palavras-passe escritas no código partilhado, usar ligação cifrada sempre que o serviço a permita, atualizar o firmware e não expor o dispositivo à Internet sem necessidade.

13. Ensaio e depuração

13.1 Os três tipos de erro

Tipo Quando aparece Sintoma Exemplo
Sintático Ao compilar O IDE recusa e indica linha e mensagem Falta de ;, nome não declarado
Lógico Ao correr Compila e corre, mas faz a coisa errada = em vez de ==; limiar trocado
De tempo de execução Ao correr, por vezes só depois de horas Reinícios, valores corrompidos, ficar "preso" Overflow, falta de SRAM, delay que bloqueia, ISR demorada

Exemplo resolvido · Encontrar e corrigir os três erros

Código com erros propositados (não compila):

const int LED = 13;
int contador = 0

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

void loop() {
  if (contador = 10) {
    digitalWrite(LED, HIGH);
  }
  contador++;
  delay(1000);
}
  1. Erro sintático: falta ; em int contador = 0. O compilador indica o erro na linha seguinte (expected ',' or ';' before 'void'), porque só percebe que falta quando encontra a palavra seguinte. A linha reportada pode estar depois do erro real.
  2. Erro lógico: contador = 10 atribui 10 e é sempre verdadeiro. O LED acende logo na primeira volta e o contador nunca chega a ser incrementado de forma útil. Corrigir para contador == 10.
  3. Erro de execução: depois de corrigir o erro lógico, surge o terceiro. O contador é um int e nunca pára de crescer. Ao fim de 32 767 voltas (cerca de 9 horas, uma volta por segundo) passa a -32 768 e a contagem deixa de fazer sentido. Para um comportamento "depois de 10 s, acender e ficar assim", a contagem deve parar em 10 (if (contador < 10) contador++;).

13.2 Depuração com o Monitor Série

Num microcontrolador não há ecrã nem consola, por isso a ferramenta mais comum é escrever valores no Monitor Série (printf debugging).

  1. Imprime os valores brutos (leitura do ADC, estado do pino), não apenas os calculados.
  2. Imprime marcas de fluxo ("entrei na função X", "ramo Y") para confirmar por onde o programa passa.
  3. Usa o Serial Plotter para ver sinais ao longo do tempo (ruído, oscilação, resposta a um degrau).
  4. Etiqueta tudo: Serial.print("T="); Serial.println(t);.
  5. Remove ou desativa as mensagens no fim; ocupam tempo e SRAM.

13.3 Depuradores e pontos de pausa

Um depurador (debugger) controla a execução do programa no próprio microcontrolador. Permite:

Um depurador de hardware é um aparelho que liga o PC ao microcontrolador por uma interface de depuração: SWD ou JTAG nos ARM (por exemplo um ST-LINK para STM32), debugWIRE ou JTAG em alguns AVR. O ATmega328P do UNO suporta debugWIRE, mas a placa não traz a ligação preparada: exige uma sonda externa (por exemplo um Atmel-ICE) e a alteração de um fuse. Muitos ESP32 e Raspberry Pi Pico dispõem de depuração por JTAG ou SWD com uma sonda de baixo custo. O Arduino IDE 2 e o PlatformIO integram o depurador nas placas que o suportam. Consulta a documentação da placa antes de comprar a sonda.

13.4 Depuração do circuito

Muitos "erros de programa" são erros de ligação. Material: multímetro e, quando disponível, osciloscópio.

Sintoma Verificar
Nada acontece Alimentação (5 V na placa?), cabo USB de dados, porta e placa escolhidas
Botão lê sempre o mesmo valor Pull-up, ligação a GND, pino certo
Leitura analógica sempre 0 ou 1023 Sensor ligado ao pino certo, divisor montado, massa comum
Leitura analógica a tremer Fios longos, falta de condensador, alimentação instável
Motor não gira / placa reinicia Fonte própria, corrente suficiente, díodo de roda livre, massa comum

Método: isola. Testa um periférico de cada vez com um programa mínimo; só juntas quando cada um funciona. Muda uma coisa de cada vez e regista o resultado.

13.5 Plano de ensaios

Um ensaio é uma verificação com resultado previsto. Antes de entregar um sistema, escreve a tabela:

N.º Condição de teste Resultado esperado Resultado obtido Aprovado
1 Temperatura a 20 °C Relé desligado
2 Temperatura sobe a 27 °C Relé liga
3 Temperatura desce a 25 °C Relé continua ligado (histerese)
4 Temperatura desce a 23 °C Relé desliga
5 Sensor desligado Relé desliga e mostra erro

Os casos de fronteira (limiar exato, sensor desligado, arranque com a carga já ligada) são os que mais revelam erros.

14. Projeto integrado: ler, decidir, atuar

14.1 O padrão sensor, decisão, atuador

Quase toda a automação segue o ciclo ler, decidir, atuar (e monitorizar):

 +--------+      +-----------+      +----------+
 | SENSOR | ---> | DECISÃO   | ---> | ATUADOR  |
 +--------+      | (programa)|      +----------+
      ^          +-----------+            |
      |                |                  |
      +------ mundo físico <--------------+
                       |
                  Monitor Série / ecrã / nuvem

O mundo físico fecha o ciclo: o atuador altera o ambiente, o sensor volta a medi-lo. Um termóstato, uma rega automática, um alarme e uma estufa são variações deste esquema.

14.2 Histerese

Um controlo com um único limiar (liga a 26 °C, desliga a 26 °C) faz o atuador comutar rapidamente quando a medida oscila à volta do valor. A solução é a histerese: dois limiares.

const int RELE = 7;                      // pino que comanda o módulo de relé
const int RELE_ATIVO = LOW;              // módulo ativo a LOW (o mais comum); se for ativo a HIGH, trocar para HIGH
const float LIGA = 26.0, DESLIGA = 24.0;
bool ventoinha = false;

void setup() {
  digitalWrite(RELE, !RELE_ATIVO);       // nível seguro (desligado) antes de ser saída
  pinMode(RELE, OUTPUT);
}

void controlar(float t) {
  if (!ventoinha && t > LIGA)    ventoinha = true;
  if (ventoinha  && t < DESLIGA) ventoinha = false;
  digitalWrite(RELE, ventoinha ? RELE_ATIVO : !RELE_ATIVO);
}

Lê a documentação do módulo para saber se liga com LOW ou com HIGH e ajusta RELE_ATIVO.

14.3 Máquina de estados

Quando o sistema tem modos (repouso, armado, disparado), uma máquina de estados finita (FSM) torna o código claro: o sistema está sempre num estado, e transições bem definidas levam a outro.

              botão                 PIR deteta
 DESARMADO ----------> ARMADO ---------------> DISPARADO
     ^                   ^                          |
     |     botão         |     botão (desativa)     |
     +-------------------+--------------------------+

Exemplo resolvido · Alarme com PIR, buzzer e botão

Requisitos: botão (pino 2, pull-up) alterna entre desarmado e armado; em modo armado, o PIR (pino 4) dispara o buzzer (pino 8); o botão desliga o alarme.

enum Estado { DESARMADO, ARMADO, DISPARADO };
Estado estado = DESARMADO;

const int BTN = 2, PIR = 4, BUZZER = 8, LED = 13;
int btnAnterior = HIGH;

void setup() {
  pinMode(BTN, INPUT_PULLUP);
  pinMode(PIR, INPUT);
  pinMode(BUZZER, OUTPUT);
  pinMode(LED, OUTPUT);
  Serial.begin(9600);
}

bool botaoPremido() {                    // flanco descendente
  int atual = digitalRead(BTN);
  bool premiu = (btnAnterior == HIGH && atual == LOW);
  btnAnterior = atual;
  if (premiu) delay(30);                 // debounce simples
  return premiu;
}

void loop() {
  bool botao = botaoPremido();

  switch (estado) {
    case DESARMADO:
      digitalWrite(LED, LOW);
      noTone(BUZZER);
      if (botao) { estado = ARMADO; Serial.println("ARMADO"); }
      break;
    case ARMADO:
      digitalWrite(LED, HIGH);
      if (botao) { estado = DESARMADO; Serial.println("DESARMADO"); }
      else if (digitalRead(PIR) == HIGH) {
        estado = DISPARADO; Serial.println("DISPARADO");
      }
      break;
    case DISPARADO:
      tone(BUZZER, 1000);
      if (botao) { estado = DESARMADO; Serial.println("DESARMADO"); }
      break;
  }
}

Cada case contém: o que fazer neste estado, e a condição de saída. Para acrescentar um novo estado (por exemplo ATRASO de saída) escreve-se um case novo, sem mexer nos outros.

14.4 Guardar dados: EEPROM

Para que um valor sobreviva a um corte de energia (calibração, contador, estado), usa-se a EEPROM. A biblioteca EEPROM tem read, write, update, put e get. Prefere update/put, que só escrevem se o valor mudou e poupam ciclos de escrita.

#include <EEPROM.h>

int limiar;

void setup() {
  EEPROM.get(0, limiar);               // lê 2 bytes a partir do endereço 0
  if (limiar < 0 || limiar > 1023) {   // primeira vez: valor inválido
    limiar = 512;
    EEPROM.put(0, limiar);
  }
}

14.5 Estruturar o projeto

Num projeto com mais de 100 linhas: um cabeçalho (descrição, autor, data, versão); constantes nomeadas para pinos e limiares; funções pequenas (lerSensor, decidir, comandar, mostrar); um loop() curto que só as chama; texto na Flash com F("..."); esquema elétrico atualizado (Fritzing); registo dos ensaios; e Git desde o primeiro dia.

Erros comuns

Glossário

Síntese

Um microcontrolador é um computador completo num chip: CPU, Flash, SRAM, EEPROM e periféricos ligados por registos. O programa começa com especificação e fluxograma, escreve-se em C/C++ com setup() e loop(), com atenção aos tipos (overflow, divisão inteira) e ao âmbito das variáveis.

As entradas digitais usam pull-up e debounce; as analógicas usam o ADC (10 bits, tensão = leitura × Vref / 1024); a saída "analógica" é PWM. Sensores resistivos leem-se com divisor de tensão e calibram-se. Atuadores de potência nunca vão ao pino: transístor, driver ou relé, fonte própria, massa comum e díodo de roda livre.

Para responder depressa sem bloquear, usam-se millis(), interrupções (ISR curta, volatile) e temporizadores. Os dispositivos comunicam por UART, I²C, SPI ou Wi-Fi. A depuração combina Monitor Série, simulador, multímetro e, quando existe, depurador de hardware com pontos de pausa. O padrão final é sempre ler, decidir, atuar, com plano de ensaios.

Exercícios resolvidos

1. O ADC de 10 bits do UNO devolve 768 com Vref = 5 V. Qual a tensão?

Resolução: V = 768 × 5 / 1024 = 3,75 V. Em 768/1024 = 75 % da escala, 75 % de 5 V = 3,75 V.

2. Um botão ligado ao pino 2 com INPUT_PULLUP está solto. Qual o valor lido e porquê?

Resolução: HIGH. A resistência interna puxa o pino para Vcc; só ao premir o botão o pino liga a GND e lê LOW.

3. Calcula a resistência em série para um LED verde (Vf = 2,1 V, 10 mA) a 5 V.

Resolução: R = (5 - 2,1) / 0,010 = 290 Ω. Valor normalizado acima: 330 Ω (corrente real 8,8 mA). Escolher o valor imediatamente acima garante que a corrente não excede a pretendida.

4. analogWrite(9, 64) produz que duty cycle e que tensão média?

Resolução: duty = 64 / 255 = 25,1 %. Tensão média = 0,251 × 5 V = 1,25 V.

5. Um HC-SR04 devolve pulseIn = 2915 µs. Qual a distância?

Resolução: 2915 × 0,0343 / 2 = 49,99, ou seja 50 cm.

6. Prescaler 64 e OCR1A = 24 999 no Timer1 em modo CTC (16 MHz). Qual a frequência da interrupção?

Resolução: frequência de contagem = 16 000 000 / 64 = 250 000 Hz. f = 250 000 / (24 999 + 1) = 10 Hz (uma interrupção de 100 em 100 ms).

7. Porque pode um sistema com delay(2000) perder o toque num botão? Como se corrige?

Resolução: durante o delay a CPU não lê o pino. Corrige-se com millis() para a tarefa periódica e leitura do botão em cada volta do loop(), ou com interrupção no pino 2 ou 3.

8. Escreve a máscara para pôr a 1 os bits 3 e 5 de PORTD sem alterar os outros.

Resolução: PORTD |= (1 << 3) | (1 << 5); A máscara é 0b0010 1000 = 0x28. Os restantes bits mantêm-se porque OU com 0 não altera. Escolhem-se bits que não são os 0 e 1, que correspondem aos pinos RX e TX da porta série.

Apêndices

A · Cheatsheet Arduino

pinMode(p, INPUT | OUTPUT | INPUT_PULLUP);
digitalRead(p);  digitalWrite(p, HIGH | LOW);
analogRead(A0);  analogWrite(p, 0..255);
Serial.begin(9600); Serial.print(x); Serial.println(x);
millis(); micros(); delay(ms);
tone(p, freq); noTone(p);
attachInterrupt(digitalPinToInterrupt(2), isr, FALLING);
map(v, a, b, c, d); constrain(v, min, max);
#include <Servo.h>  <Wire.h>  <SPI.h>  <EEPROM.h>

B · Fórmulas

Grandeza Fórmula
Tensão do ADC V = leitura × Vref / 1024
Resistência em série de um LED R = (Vcc - Vf) / I
Divisor de tensão Vout = Vcc × R2 / (R1 + R2)
Duty cycle PWM valor / 255
OCR em modo CTC f_clk / (prescaler × f) - 1
Distância por eco tempo × 0,0343 / 2 (cm, µs)

C · Recursos