Partilhar: WhatsApp
aulify · Sebenta
UC · Unidade de Competência · UC00671

Sebenta · Desenvolver e executar sistemas baseados em microcontrolador (UC00671)

Arquitetura, programação em C/C++, sensores, atuadores, comunicação série e interface de utilizador
50h · 4.5 pontos crédito Curso: T. Eletrónica ↗ Referencial oficial SNQ
Índice

Introdução

Esta sebenta cobre o desenvolvimento completo de sistemas baseados em microcontrolador: desde a arquitetura interna do chip até um sistema a funcionar, com sensores a ler o mundo real, atuadores a responder, comunicação com outros dispositivos e uma interface para o utilizador interagir.

Um microcontrolador é um circuito integrado que junta, num só chip, processador, memória e periféricos de entrada/saída. Está dentro de eletrodomésticos, automóveis, máquinas industriais e equipamento médico: é o "cérebro" invisível de quase todos os produtos eletrónicos modernos. O percurso desta UC segue o de um profissional de eletrónica e automação: entender o hardware → programar em C/C++ → ler entradas → controlar saídas → comunicar → dar uma interface ao utilizador → construir e testar o circuito real.

Objetivos de aprendizagem (referencial): desenvolver um programa para leitura de entradas digitais e analógicas, controlo de saídas e comunicação série; implementar um sistema de controlo com sensores e atuadores baseado em microcontrolador; e desenvolver uma interface de utilizador para interação com esse sistema. No final, deves conseguir escolher, programar, montar e testar um sistema destes de forma autónoma, respeitando as normas de segurança e proteção ambiental.

1. Arquitetura de um microcontrolador

Um microcontrolador organiza-se em blocos internos que trabalham em conjunto:

Barramentos e registos

A CPU comunica com a memória e os periféricos através de barramentos (bus): conjuntos de linhas elétricas que transportam dados, endereços e sinais de controlo. O barramento de dados transporta a informação; o barramento de endereços identifica onde ler ou escrever.

Internamente, cada periférico é controlado através de registos: pequenas células de memória mapeadas em endereços específicos. Programar um microcontrolador é, no fundo, ler e escrever nestes registos, mesmo quando usamos funções de alto nível como digitalWrite(), que por baixo escreve um bit num registo de porta (ex.: PORTB).

Exemplo resolvido · identificar os blocos

Um sistema lê um sensor de temperatura, guarda o valor máximo do dia numa memória que sobrevive a cortes de energia, e acende um LED se ultrapassar um limite.

Resolução: o ADC (periférico) lê o sensor; a RAM guarda a leitura atual e as variáveis de cálculo durante a execução; a EEPROM guarda o valor máximo do dia, porque tem de sobreviver a um corte de energia; o GPIO de saída controla o LED. O programa que faz tudo isto está gravado na Flash.

2. Ferramentas de desenvolvimento e critérios de seleção

O fluxo de ferramentas

O ciclo de desenvolvimento embebido é: escrever → compilar → carregar (upload/flash) → executar → depurar. Ao contrário de um programa de computador, não há sistema operativo por trás na maioria dos casos: o programa corre isolado, diretamente sobre o hardware.

Critérios de seleção de um microcontrolador

Escolher o microcontrolador certo é uma aptidão central desta UC. Os critérios a pesar:

Critério Porquê importa
Nº de pinos I/O tem de chegar para todos os sensores/atuadores do projeto
Memória (Flash/RAM) o programa e as variáveis têm de caber
Velocidade de relógio tarefas em tempo real exigem mais
Periféricos integrados ADC, PWM, UART/SPI/I2C evitam hardware extra
Consumo de energia crítico em sistemas alimentados a bateria
Custo e disponibilidade viabilidade de um projeto em série
Ecossistema (IDE, bibliotecas, comunidade) reduz o tempo de desenvolvimento

Nenhuma seleção se faz sem consultar a documentação técnica do fabricante: o datasheet (especificações elétricas, pinout, limites absolutos de tensão e corrente), as application notes (exemplos de circuitos recomendados) e o reference manual (descrição detalhada de cada periférico e dos seus registos).

Exemplo resolvido · escolher entre dois microcontroladores

Um projeto é um sensor de temperatura a bateria, que acorda de hora a hora, envia uma leitura por Bluetooth e volta a "dormir". Compara um microcontrolador de 8 bits simples (baixo consumo, sem rádio) com um de 32 bits com Bluetooth integrado.

Resolução: o critério decisivo aqui é o consumo e a necessidade de comunicação sem fios integrada. Um microcontrolador de 32 bits com Bluetooth Low Energy (BLE) integrado evita um módulo externo e simplifica o circuito, e muitos destes chips têm modos de baixíssimo consumo em repouso. A escolha certa depende do datasheet: comparar a corrente em modo sleep de ambos antes de decidir, não assumir que "mais simples = menos consumo".

3. Conversores A/D e D/A

O mundo real é analógico (temperatura, luz, pressão, tensão contínua variável); o microcontrolador só processa valores digitais.

Fórmula de conversão

tensão = leitura_ADC × (Vref / (2^n − 1))

onde Vref é a tensão de referência do ADC (normalmente a tensão de alimentação, ex.: 5 V ou 3,3 V) e n é a resolução em bits.

Exemplo resolvido · converter uma leitura

Um ADC de 10 bits, referência de 5 V, devolve o valor leitura = 614.

Resolução: tensão = 614 × (5 / 1023) ≈ 3,00 V. Se o sensor ligado converte 10 mV por grau Celsius com 0 V a 0°C, a temperatura seria 3,00 / 0,01 = 300°C · um valor irrealista, sinal de que o sensor teria outra escala; o método (leitura → tensão → grandeza física, usando sempre a fórmula do datasheet do sensor) é o que importa fixar.

4. Linguagem C/C++ para microcontroladores

Estrutura de um programa

#include <biblioteca.h>

void setup() {
  // corre uma vez: configurar pinos, periféricos
}

void loop() {
  // corre em ciclo infinito: a lógica do programa
}

O setup() trata da inicialização (direção dos pinos, velocidade da UART, etc.) e corre uma única vez. O loop() é o "coração" do programa: repete indefinidamente enquanto houver alimentação. Sem sistema operativo por trás, não há "sair do programa".

Tipos de dados

Em sistemas com pouca memória, o tipo de dados certo poupa recursos escassos:

Tipo Tamanho típico Uso recomendado
bool 1 bit (armazenado em 1 byte) flags, estados on/off
uint8_t 8 bits (0 a 255) contadores pequenos, bytes de comunicação
int / int16_t 16 bits valores com sinal, cálculos gerais
float 32 bits valores decimais, usar com moderação

Usar float em excesso num microcontrolador pequeno pode esgotar memória e tornar o programa lento. Preferir inteiros sempre que a precisão permitir.

Loops, condicionais e funções

void verificarTemperatura() {
  int leitura = analogRead(SENSOR);
  if (leitura > LIMITE) {
    ligarVentoinha();
  } else {
    desligarVentoinha();
  }
}

Manipulação de bits e portas

Trabalhar diretamente com bits de um registo é necessário quando não há função de alto nível disponível, ou quando se quer controlo fino e eficiente:

PORTB |= (1 << 3);    // liga o bit 3, mantém os restantes
PORTB &= ~(1 << 3);   // desliga o bit 3
PORTB ^= (1 << 3);    // inverte o bit 3

Exemplo resolvido · ligar um bit sem apagar os outros

PORTB está atualmente em 00000101 (bits 0 e 2 ligados). Pretende-se ligar também o bit 4, sem alterar os restantes.

Resolução: usar PORTB |= (1 << 4);. A máscara (1 << 4) é 00010000; com OR, só o bit 4 é forçado a 1, os restantes bits mantêm o valor que já tinham. O resultado é 00010101. Se em vez disso se escrevesse PORTB = (1 << 4);, o resultado seria 00010000, apagando os bits 0 e 2 por engano.

Assembly para casos de uso específicos

O Assembly é a linguagem mais próxima do hardware: cada instrução corresponde diretamente a uma operação da CPU. Usa-se sobretudo em:

Na prática profissional, programa-se maioritariamente em C/C++ e recorre-se a Assembly apenas em blocos isolados e bem justificados, ou para compreender o código gerado pelo compilador ao depurar um problema de baixo nível.

5. Entradas digitais e analógicas

Entradas digitais e o problema do debouncing

Uma entrada digital só tem dois estados: HIGH ou LOW. Um botão liga o pino a Vcc ou GND consoante é premido; uma resistência de pull-up ou pull-down garante um nível estável quando o botão não está premido.

pinMode(BOTAO, INPUT_PULLUP);
if (digitalRead(BOTAO) == LOW) { /* premido */ }

Um botão mecânico não muda de estado de forma limpa: os contactos "ressaltam" (bounce) durante alguns milissegundos, gerando várias transições falsas que, sem tratamento, seriam lidas como vários cliques.

if (digitalRead(BOTAO) == LOW) {
  delay(30);
  if (digitalRead(BOTAO) == LOW) { /* clique confirmado */ }
}

Entradas analógicas: sensores

Sensores de temperatura, luz (LDR) ou pressão entregam uma tensão proporcional à grandeza medida, lida pelo ADC (ver secção 3).

Exemplo resolvido · debouncing com contador

Sem debounce, um contador de cliques incrementa 3 a 5 vezes por cada clique real. Corrige-se o código.

Resolução: antes: if (digitalRead(BOTAO)==LOW) { cliques++; } dentro do loop(), sem espera. Depois: adicionar uma espera de confirmação e só contar a transição de HIGH para LOW uma vez. c if (digitalRead(BOTAO) == LOW && estadoAnterior == HIGH) { delay(30); if (digitalRead(BOTAO) == LOW) cliques++; } estadoAnterior = digitalRead(BOTAO); Guardar o estadoAnterior evita contar o mesmo clique repetidamente enquanto o botão continua premido.

6. Saídas: LEDs, displays, motores e relés

LEDs e indicadores

pinMode(LED, OUTPUT);
digitalWrite(LED, HIGH);

Um LED precisa sempre de uma resistência limitadora de corrente em série, para não exceder a corrente máxima do pino nem a do LED.

Displays

Display Características Comunicação típica
LED de 7 segmentos mostra dígitos pinos diretos ou driver dedicado
LCD alfanumérico (16x2) mostra texto paralelo ou I2C
OLED pequeno, nítido I2C ou SPI
lcd.begin(16, 2);
lcd.print("Temp: 23.5 C");

PWM para motores e brilho

O PWM (Pulse Width Modulation) simula uma saída "analógica" ligando/desligando o pino muito rapidamente, variando a percentagem de tempo ligado (duty cycle).

analogWrite(MOTOR, 128);  // ~50% duty cycle, de 0 a 255

Motores exigem normalmente um driver de motor (ponte H) entre o microcontrolador e o motor, porque o pino não fornece corrente suficiente.

Relés

Um relé permite controlar cargas de maior tensão e corrente (ex.: 230 V AC) com um sinal digital de baixa potência. O microcontrolador ativa uma bobina de baixa potência através de um transístor ou driver dedicado; esta fecha um contacto que liga a carga real. Um díodo de roda livre (flyback), em paralelo com a bobina, protege o circuito da tensão inversa gerada quando esta desliga.

Exemplo resolvido · duty cycle e velocidade

Um motor DC deve rodar a aproximadamente 70% da velocidade máxima, usando analogWrite() de 0 a 255.

Resolução: duty cycle = 70% de 255 = 0,70 × 255 ≈ 178. Instrução: analogWrite(MOTOR, 178);. Nota: a relação entre duty cycle e velocidade real não é sempre perfeitamente linear (atrito, carga do motor), mas é uma boa aproximação de projeto.

7. Comunicação série: UART, SPI e I2C

UART

A UART transmite dados bit a bit, em série, sem relógio partilhado. Ambos os lados têm de usar a mesma velocidade (baud rate), ex.: 9600 ou 115200 bps, e usa 2 fios: TX e RX, cruzados entre os dois dispositivos.

Serial.begin(9600);
Serial.println("Sistema iniciado");

Cada byte segue uma trama: bit de start, bits de dados, bit de paridade (opcional), bit(s) de stop. É ideal para ligações ponto a ponto (microcontrolador ↔ computador, ↔ módulo GPS/Bluetooth) e é a principal ferramenta de diagnóstico durante o desenvolvimento.

SPI

O SPI (Serial Peripheral Interface) é síncrono, com 4 linhas: SCK (relógio), MOSI (mestre→escravo), MISO (escravo→mestre) e SS/CS (seleção do escravo). Um único mestre controla vários escravos, cada um com a sua linha CS dedicada. É rápido, ao custo de mais fios.

I2C

O I2C (Inter-Integrated Circuit) é síncrono, com apenas 2 fios partilhados: SDA (dados) e SCL (relógio). Cada dispositivo tem um endereço único, permitindo dezenas de sensores/módulos partilharem a mesma linha. Requer resistências de pull-up em SDA e SCL.

Protocolo Fios Velocidade Nº de dispositivos
UART 2 (TX/RX) média 2 (ponto a ponto)
SPI 4 ou mais alta vários (1 CS cada)
I2C 2 (SDA/SCL) média/baixa vários (endereço único)

Exemplo resolvido · escolher o protocolo certo

Um projeto tem 6 sensores I2C compatíveis e apenas 4 pinos digitais livres no microcontrolador escolhido.

Resolução: I2C. Com apenas 2 fios partilhados (SDA e SCL), todos os 6 sensores podem ligar-se ao mesmo barramento, desde que cada um tenha um endereço diferente (ou configurável por pino/resistência). Usar SPI exigiria uma linha CS dedicada por sensor, o que não caberia nos 4 pinos livres.

8. Interrupções

Uma interrupção suspende temporariamente o programa principal para executar de imediato uma rotina prioritária, a ISR (Interrupt Service Routine), voltando depois ao ponto onde estava.

attachInterrupt(digitalPinToInterrupt(PINO), rotinaISR, FALLING);

void rotinaISR() {
  flagEvento = true;
}

Uma interrupção é a alternativa a "verificar constantemente" (polling) uma entrada no loop(), o que desperdiça tempo de processamento e pode falhar eventos rápidos. Boa prática: a ISR deve ser curta, apenas marcar uma variável (flag, declarada volatile); o processamento pesado faz-se no loop() principal.

Exemplo resolvido · decidir polling vs interrupção

Um sistema tem um botão de paragem de emergência e um sensor de temperatura lido a cada segundo.

Resolução: o botão de emergência justifica interrupção externa: a paragem tem de ser imediata, independentemente do que o programa esteja a fazer. O sensor de temperatura, lido periodicamente e sem urgência de milissegundos, pode continuar em polling dentro do loop(), ou usar um timer interno para marcar quando é altura de ler de novo.

9. Sensores, atuadores e controlo dinâmico

Um sistema de controlo típico segue o ciclo ler sensor → decidir → atuar, repetido continuamente:

void loop() {
  int temperatura = lerSensor();
  if (temperatura > LIMITE) {
    ligarVentoinha();
  } else {
    desligarVentoinha();
  }
}

Resposta dinâmica: histerese

Um limiar único faz o atuador oscilar rapidamente (ligar/desligar) quando a leitura ronda o valor de corte. A histerese usa dois limiares diferentes: liga acima de um valor, desliga abaixo de outro (mais baixo), criando uma "zona morta" no meio.

if (temperatura > LIMITE_LIGAR) ventoinhaLigada = true;
if (temperatura < LIMITE_DESLIGAR) ventoinhaLigada = false;

Outras técnicas de resposta dinâmica

Exemplo resolvido · histerese num termóstato

Um termóstato deve ligar o aquecimento abaixo de 18°C e desligá-lo só acima de 20°C, evitando ligar/desligar constantemente perto de um único valor.

Resolução: c if (temperatura < 18.0) aquecimentoLigado = true; if (temperatura > 20.0) aquecimentoLigado = false; Entre 18°C e 20°C, o estado mantém-se como estava (não há decisão nova), evitando oscilações rápidas junto de um único limiar. É exatamente o comportamento de um termóstato doméstico real.

10. Interface de utilizador e comunicação sem fios

Interfaces locais

Comunicação sem fios

O microcontrolador expõe dados e comandos (ex.: um pequeno servidor web, ou mensagens por Bluetooth) que uma app de smartphone ou computador lê e apresenta de forma amigável ao utilizador final.

Exemplo resolvido · escolher entre Bluetooth e Wi-Fi

Um sensor de porta de garagem, a bateria, deve avisar o telemóvel do dono quando alguém a abre, apenas quando ele está perto de casa.

Resolução: Bluetooth Low Energy (BLE). O alcance limitado não é problema (o aviso só interessa quando o dono está perto), e o consumo baixíssimo do BLE prolonga muito a duração da bateria face a Wi-Fi, que exige mais energia para se manter ligado à rede.

Erros comuns

Glossário

Síntese

Um sistema baseado em microcontrolador constrói-se sempre pela mesma sequência: arquitetura e escolha do chip (CPU, memória, periféricos, critérios de seleção) → programação (C/C++, estruturas de controlo, manipulação de bits) → entradas (digitais com debounce, analógicas com ADC) → saídas (LEDs, displays, PWM, motores, relés) → comunicação (UART, SPI, I2C, interrupções, sem fios) → circuito de suporte e PCBmetodologia de projeto e testes (requisitos → simulação → montagem → validação com multímetro e osciloscópio). Domina este fluxo e serás capaz de desenvolver, com autonomia, qualquer sistema deste tipo, dos mais simples aos mais complexos.

Exercícios resolvidos

1. Um sistema tem de acender um LED sempre que um sensor de luz (LDR) detetar escuridão, mas não deve piscar quando a luz está no limiar. Que técnica se aplica e como?

Resolução: aplica-se histerese: definir um limiar de "ligar" (ex.: leitura ADC abaixo de 300) e um limiar de "desligar" diferente e mais alto (ex.: acima de 350). Entre os dois valores, o estado do LED mantém-se, evitando que ele pisque quando a leitura oscila perto de um único limiar.

2. Porque é que PORTB = 0b00000001; pode ser perigoso num sistema onde outros bits de PORTB já controlam outras saídas?

Resolução: porque a atribuição = substitui todo o registo, apagando o estado de todos os outros bits (outras saídas que possam estar ligadas). O correto é usar PORTB |= 0b00000001; para ligar só o bit pretendido sem afetar os restantes.

3. Um botão está a gerar múltiplos cliques por cada pressão física. Qual é a causa e como se resolve por software?

Resolução: a causa é o ressalto (bounce) dos contactos mecânicos do botão, que gera várias transições elétricas numa única pressão. Resolve-se com debouncing por software: depois de detetar uma transição, esperar um pequeno intervalo (20-50 ms) e confirmar que o estado se mantém antes de aceitar o clique como válido.

4. Um projeto precisa de ligar 8 sensores a um microcontrolador que só tem 3 pinos digitais livres. Que protocolo de comunicação resolve o problema, e porquê?

Resolução: I2C, porque usa apenas 2 fios partilhados (SDA e SCL) para comunicar com todos os dispositivos, desde que cada um tenha um endereço único no barramento. Com apenas 3 pinos livres, nem UART (2 dispositivos apenas) nem SPI (exige uma linha CS por dispositivo) resolveriam o problema com tantos sensores.

5. Porque é que uma rotina de interrupção (ISR) não deve conter um delay() longo?

Resolução: porque, enquanto a ISR está a correr, o programa principal (e frequentemente outras interrupções) fica bloqueado. Um delay() longo numa ISR atrasa toda a resposta do sistema a outros eventos, incluindo eventos urgentes. A boa prática é a ISR ser curta, apenas marcar uma variável volatile, e deixar o processamento pesado para o loop() principal.

6. Antes de montar fisicamente um circuito com microcontrolador, que passo da metodologia de projeto não deve ser saltado, e que instrumento confirma a montagem depois?

Resolução: não deve ser saltada a simulação em software de desenho e simulação de circuitos, que apanha erros lógicos e de ligação antes de gastar componentes reais. Depois de montado, o multímetro confirma a tensão de alimentação correta antes de ligar o microcontrolador, e o osciloscópio confirma a forma de sinais dinâmicos como PWM ou comunicação série.