Funções e arranjo de bancada — Arduino e IoT — semana 4 do 1o trimestre

Informatica · Conteudo · publicado em 02/10/2026
Semana 4 · Funções e arranjo de bancada — Material de Apoio Arduino

Semana 4 de 13· 1o trimestre · 01/02 a 30/04

Funções e arranjo de bancada

Parametros, retorno e o arranjo: por que subir um arranjo de protoboard e a primeira coisa a aprender.

Aula 1 — Declarar, chamar e passar parametro

Objetivos

  • Declarar uma função com tipo de retorno, nome e parâmetros, e chamá-la pelo nome.
  • Escolher entre função que devolve valor e função void conforme o que o loop precisa fazer com o resultado.
  • Explicar passagem por valor: a função trabalha com uma cópia e a variável de fora não muda.
  • Resolver a ordem de declaração com protótipo de função, que é o que o JavaScript não exige.

Material

  • 1 ESP32 DevKit V1 por aluno, com o cabo USB
  • 1 protoboard de 830 pontos e 1 jogo de jumpers por aluno
  • 2 a 5 LEDs de 3,3 V e 2 a 5 resistores de 330 Ω por aluno
  • 1 módulo de alimentação MB102 por dupla
  • Folha com as duas formas de declarar função impressa, uma por dupla

Conceitos

A assinatura, na ordem certa

float voltsDoAdc(int leituraCrua) {
  return leituraCrua * 3.3 / 4095.0;
}

Quatro partes, nesta ordem: float é o tipo que sai, voltsDoAdc é o nome, (int leituraCrua) é a lista do que a função precisa. E a assinatura inteira vem com o ponto e vírgula quando é só declaração.

O nome da função é um verbo com o que ela faz: voltsDoAdc devolve volts, piscar pisca. fazerAlgo não diz nada e é o primeiro sinal de que a função está mal pensada.

void é uma resposta

void no lugar do tipo de retorno significa "esta função não devolve nada". Ela faz o serviço e pronto:

void piscar(int pino, int tempo) {
  digitalWrite(pino, HIGH);
  delay(tempo);
  digitalWrite(pino, LOW);
  delay(tempo);
}

A chamada é piscar(2, 200) e não x = piscar(2, 200), porque não há x para receber. Quando o aluno escreve x = piscar(...), o erro de compilação diz que não se pode converter void em int — e essa mensagem ensina exatamente a diferença.

Parâmetro é cópia

Quando você chama voltsDoAdc(2048), a função não enxerga o seu 2048. Ela recebe uma cópia num lugar da memória que só pertence a ela. Se a função mexer nesse número, o número de fora continua igual.

Para mexer de verdade na variável do chamador é preciso passar o endereço, e é o assunto da aula 2 do dia de hoje.

O protótipo e a ordem das linhas

Em JavaScript, chamar uma função antes de ela aparecer no arquivo funciona. Em C++ não: o compilador lê o arquivo de cima para baixo, e na linha da chamada ele ainda não sabe que a função existe.

A solução é o protótipo — a assinatura sozinha, terminada em ponto e vírgula, antes do setup:

void mostrarRegra(int passo, float valor);   // promessa

Depois do loop, a definição completa vem sem ponto e vírgula. O protótipo é uma promessa feita ao compilador: "essa função existe, e é assim". Manter protótipos de todas as funções no topo é regra do curso, e o dia 13, na revisão final, cobra isso.

Atividade

Montagem:

  • Resistor de 330 Ω entre o GPIO2 e um lado do LED.
  • O outro lado do LED na trilha + da protoboard.
  • Jumper macho-fêmea levando o GPIO2 ao resistor.

O lado longo do LED é o anodo e vai para o positivo.

  1. Grave a resolução e confirme que o serial mostra as três linhas da tabela passo | tensao e que o LED pisca.
  2. Escreva a assinatura de uma função que receba dois inteiros e devolva o maior deles, sem executar. Diga o que você colocou em cada uma das quatro partes.
  3. Escreva a função do item 2 de verdade, com o corpo, e chame-a com os números 7 e 12. O número que aparece no serial é o esperado?
  4. Escreva uma função void que receba um pino e escreva HIGH nele, e outra que receba o mesmo pino e escreva LOW. Chame as duas em sequência e confirme que o LED acende e apaga.
  5. Mova a definição de voltsDoAdc para depois do loop, sem escrever o protótipo, e grave. Leia a mensagem de erro do compilador e escreva no caderno o que ela está dizendo.
  6. Escreva no caderno o que aconteceria com a sua variável de fora se, dentro da função voltsDoAdc, você fizesse leituraCrua = 0 antes de calcular. Depois teste isso no código e veja se a linha da tabela muda.

Nota: 10 pontos. Critério de fim: a função do item 3 devolve o número certo e a do item 4 acende e apaga o LED.

Resolucao

// Aula 1 do dia 4: declarar, chamar e passar parâmetro.
//
// Função é a peça que o aluno precisa para o projeto final: o código do
// projeto tem projeto de estação meteorológica e uma biblioteca própria,
// e as duas coisas se apoiam em funções com parâmetro e retorno.

#include <Arduino.h>

const int PINO_LED = 2;
const int TOTAL_DE_PASSOS = 3;

// Protótipo: avisa ao compilador que a função existe antes de ela aparecer
// no arquivo. Sem isso, chamar `acender` antes do próprio código de `acender`
// dá erro de compilação. É a diferença entre JavaScript e C++.
void mostrarRegra(int passo, float valor);
float voltsDoAdc(int leituraCrua);
void piscar(int pino, int tempo);

void setup() {
  pinMode(PINO_LED, OUTPUT);

  Serial.begin(115200);
  Serial.println();
  Serial.println("=== dia 4, aula 1: função, parâmetro e retorno ===");
  Serial.println();
  Serial.println("mes | tensao");
  Serial.println("----+-------");

  // Um laço, uma chamada. O valor muda a cada volta, a função não muda.
  for (int passo = 1; passo <= TOTAL_DE_PASSOS; passo++) {
    int leitura = passo * 1365;   // 4095 dividido em 3 partes
    mostrarRegra(passo, voltsDoAdc(leitura));
  }

  Serial.println();
  Serial.println("Função sem retorno, só com efeito:");
  piscar(2, 150);
}

void mostrarRegra(int passo, float valor) {
  Serial.print(passo);
  Serial.print("   |   ");
  Serial.print(valor);
  Serial.println(" V");
}

// Parâmetro é cópia: os 3.3 volts desta função não têm nada a ver com a
// constante global do topo. Mudar `valor` aqui dentro não muda em lugar
// nenhum do programa. É a passagem por valor.
float voltsDoAdc(int leituraCrua) {
  return leituraCrua * 3.3 / 4095.0;
}

// `void` no lugar do tipo de retorno significa: não devolve nada. A função
// faz o serviço e pronto. É o caso do piscar.
void piscar(int pino, int tempo) {
  digitalWrite(pino, HIGH);
  delay(tempo);
  digitalWrite(pino, LOW);
  delay(tempo);
}

// A função que devolve valor entra na conta e o resultado é usado; a que não
// devolve nada é chamada sozinha, e o compilador deixa claro que ali não tem
// resultado nenhum para esperar. As duas no mesmo `loop`, para a turma ver as
// duas formas lado a lado.
void loop() {
  piscar(PINO_LED, 200);
  Serial.print("tensao do passo 1: ");
  Serial.println(voltsDoAdc(1365));
  Serial.println("---");
  delay(600);
}

Por que assim e não de outro jeito. O setup chama a mesma função três vezes com números diferentes. Se a tabela estivesse escrita com três chamadas de Serial.print repetidas, o aluno aprenderia que imprimir uma tabela é copiar e colar — e no dia 13, com o display OLED, a tabela tem quatro linhas e cinco colunas.

A função voltsDoAdc recebe int e devolve float. Esse desenho é o que o projeto final exige: o analogRead devolve inteiro, a conta de volts não é inteira, e a função é o lugar onde a conversão mora. Um aluno que fizer a conversão espalhada em cinco linhas vai repetir o mesmo erro de divisão em todos os lugares.

Os protótipos estão no topo, antes do setup, e as definições depois do loop. Essa ordem não é estética: é a que permite o setup chamar mostrarRegra sem o compilador reclamar.

O item 6 da atividade é o motivo de a passagem por valor estar aqui e não na aula 2 do dia. A função não consegue mexer na sua variável — e o aluno descobre isso testando, não ouvindo.

Criterios de correcao

CritérioPontos
Protótipos de todas as funções antes do setup2 pontos
Função do item 3 escrita, executada e devolvendo o maior número3 pontos
Funções void do item 4 acendendo e apagando o LED3 pontos
Item 5 com a mensagem de erro lida e explicada2 pontos

Erros comuns

ErroComo apareceCorrecao
Chamar a função antes de declararErro de compilacao dizendo que a função não foi declarada"Em C++ o compilador le de cima para baixo. Escreva o prototipo antes do setup."
x = piscar(...) em função void"cannot convert void to int""A função não devolve nada, então não tem o que atribuir. Chame ela sozinha."
Passar o valor errado na chamadaFunção devolve número estranho sem reclamar"Cheque a ordem dos parametros: primeiro o pino, depois o tempo. A função não sabe a ordem."
Declarar o parametro com o valorvoid piscar(int pino = 2)"Parametro e um nome que a função preenche quando chamada. O valor vem na chamada."
Achar que a função altera a variável de foraItem 6 com "o número fica", e o código mostra que não"Você passou uma copia. Para mexer na sua variável, precisa passar o endereco — isso e a aula 2 de hoje."
Usar pino como nome globalVariável global que o aluno acha que a função enxerga"Variável dentro da função e local: ela so existe la dentro. Nome igual em outro lugar são duas coisas."
Nome de função sem verboint processar(int valor) com nome que não diz o que faz"Nomeia com o que ela faz: voltsDoAdc, piscar, somarCiclo."

Desafio extra

Escreva três funções que devolvam três coisas ao mesmo tempo: o maior número, o menor número e a média de uma lista de cinco inteiros — e chame as três sem repetir o laço. Se não for possível devolver três valores com um return só, descreva em duas linhas qual é o caminho e implemente-o com uma função que devolve um número por chamada. Diga, das duas formas, qual delas se parece mais com o que o JavaScript faz.

>

A resolucao, compilada

// Aula 1 do dia 4: declarar, chamar e passar parâmetro.
//
// Função é a peça que o aluno precisa para o projeto final: o código do
// projeto tem projeto de estação meteorológica e uma biblioteca própria,
// e as duas coisas se apoiam em funções com parâmetro e retorno.

#include <Arduino.h>

const int PINO_LED = 2;
const int TOTAL_DE_PASSOS = 3;

// Protótipo: avisa ao compilador que a função existe antes de ela aparecer
// no arquivo. Sem isso, chamar `acender` antes do próprio código de `acender`
// dá erro de compilação. É a diferença entre JavaScript e C++.
void mostrarRegra(int passo, float valor);
float voltsDoAdc(int leituraCrua);
void piscar(int pino, int tempo);

void setup() {
  pinMode(PINO_LED, OUTPUT);

  Serial.begin(115200);
  Serial.println();
  Serial.println("=== dia 4, aula 1: função, parâmetro e retorno ===");
  Serial.println();
  Serial.println("mes | tensao");
  Serial.println("----+-------");

  // Um laço, uma chamada. O valor muda a cada volta, a função não muda.
  for (int passo = 1; passo <= TOTAL_DE_PASSOS; passo++) {
    int leitura = passo * 1365;   // 4095 dividido em 3 partes
    mostrarRegra(passo, voltsDoAdc(leitura));
  }

  Serial.println();
  Serial.println("Função sem retorno, só com efeito:");
  piscar(2, 150);
}

void mostrarRegra(int passo, float valor) {
  Serial.print(passo);
  Serial.print("   |   ");
  Serial.print(valor);
  Serial.println(" V");
}

// Parâmetro é cópia: os 3.3 volts desta função não têm nada a ver com a
// constante global do topo. Mudar `valor` aqui dentro não muda em lugar
// nenhum do programa. É a passagem por valor.
float voltsDoAdc(int leituraCrua) {
  return leituraCrua * 3.3 / 4095.0;
}

// `void` no lugar do tipo de retorno significa: não devolve nada. A função
// faz o serviço e pronto. É o caso do piscar.
void piscar(int pino, int tempo) {
  digitalWrite(pino, HIGH);
  delay(tempo);
  digitalWrite(pino, LOW);
  delay(tempo);
}

// A função que devolve valor entra na conta e o resultado é usado; a que não
// devolve nada é chamada sozinha, e o compilador deixa claro que ali não tem
// resultado nenhum para esperar. As duas no mesmo `loop`, para a turma ver as
// duas formas lado a lado.
void loop() {
  piscar(PINO_LED, 200);
  Serial.print("tensao do passo 1: ");
  Serial.println(voltsDoAdc(1365));
  Serial.println("---");
  delay(600);
}

Sem saída de compilação gravada. Rode python3 validar.py -t 1 dia04 aula1.

Aula 2 — Funções com referência: as funções de arranjo do kit

Objetivos

  • Passar uma variável por referência e ver a variável de fora mudar depois da chamada.
  • Dizer por que passagem por valor não serve quando a função precisa acumular um contador.
  • Refatorar um arranjo de bancada com dois LEDs usando uma única função, em vez de duas cópias.
  • Reconhecer o caso em que a referência não é necessária, e deixar o return no lugar.

Material

  • 1 ESP32 DevKit V1 por aluno, com o cabo USB
  • 1 protoboard de 830 pontos e 1 jogo de jumpers por aluno
  • 4 LEDs de 3,3 V e 4 resistores de 330 Ω por aluno
  • 1 módulo de alimentação MB102 por dupla
  • Folha com os quatro efeitos de LED do arranjo impressa, uma por dupla

Conceitos

Passagem por valor: a cópia

Quando você chama somarCopia(total, 5), a função recebe uma cópia do número. Ela soma o que quiser na cópia; a sua variável não existe lá dentro.

Isso é bom na maioria dos casos: a função não bagunça o que você já tem. O problema é quando a função existe justamente para acumular — o contador do arranjo, o total de leituras, o número de eventos. Uma função que devolve o valor resolve; uma função que soma no acumulador resolve melhor quando ela precisa mexer em vários lugares de uma vez.

Passagem por referência: o endereço

void somarCiclo(int& contador, int quantidade) {
  contador += quantidade;
}

O & grudado no tipo, int&, diz que o parâmetro é o endereço da variável, não uma cópia. O que a função somar aqui, soma ali.

O símbolo tem dois nomes, e o aluno precisa saber qual é qual: & na declaração do parâmetro é referência; & na chamada é o endereço de. Os dois se usam juntos:

somarCiclo(ciclos, 1);   // chamada: passa o valor normal
somarCiclo(&total, 2);   // se a funcao pedir o endereco, e este sinal

O defeito silencioso da referência: se você esquecer o & na declaração do parâmetro, o programa compila, roda e não faz nada. A função soma uma cópia e devolve, e a sua variável fica parada. Esse é o mesmo tipo de bug do pinMode faltando, e vale o mesmo cuidado.

Quando a referência é o exagero

Nem toda função precisa de referência. Se a função só calcula e devolve um resultado, o return é mais curto e mais claro:

int maior(int a, int b) { return a > b ? a : b; }

A regra do curso: função que calcula usa return; função que acumula ou mexe em pinos usa referência ou pino como parâmetro. Deixar claro isso evita o código cheio de & onde ninguém precisa de &.

Refatorar é o trabalho de verdade

O arranjo da aula tem dois LEDs e quatro funções. A versão ingênua escreve oito linhas de digitalWrite e delay. A versão com função escreve quatro chamadas.

O ganho não é só o tamanho: quando a montagem do dia 7 tiver três LEDs, a função não muda. Sem a refatoração, o aluno copia o bloco, apaga um número e reza para não esquecer nada. Com a função, ele escreve uma linha nova. Esse é o motivo de o dia 4 existir antes de o primeiro sensor.

Atividade

Montagem:

  • Resistor de 330 Ω entre o GPIO2 e um lado do LED A.
  • Resistor de 330 Ω entre o GPIO4 e um lado do LED B.
  • O lado livre de cada LED na trilha + da protoboard.
  • Jumper macho-fêmea levando GPIO2 e GPIO4 aos respectivos resistores.

Os lados longos dos dois LEDs são os ânodos e vão para o positivo.

  1. Grave a resolução e confirme que os dois LEDs piscam em tempos diferentes e que a coluna de acertos sobe.
  2. Prova de que passou por valor: escreva no caderno o valor de totalLocal depois da chamada de somarCopia. Ele mudou? Justifique.
  3. Apague o & do parâmetro de somarCiclo, grave e observe o serial. Escreva o que aconteceu com o contador ciclos e explique por que o programa não deu erro.
  4. Volte a colocar o &, grave e confirme que o contador subiu. Registre os dois resultados lado a lado.
  5. Escreva uma função void inverter(int& led) que inverta o estado de um pino lendo o endereço, e chame-a para os dois pinos no seu loop. Confirme que cada LED inverte no seu ritmo.
  6. Refatore o loop para que o arranjo inteiro use apenas chamadas de função, sem nenhum digitalWrite escrito diretamente. Diga quantas linhas de loop sobraram e quanto elas eram antes.

Nota: 10 pontos. Critério de fim: o arranjo roda, a prova do item 3 está no caderno e o loop do item 6 não tem digitalWrite direto.

Resolucao

// Aula 2 do dia 4: funções com referência, as funções de arranjo do kit.
//
// Passagem por valor copia o número; passagem por referência entrega o
// endereço da variável. É a diferença entre uma função que "faz o serviço"
// e uma função que "muda o seu estado" — e o arranjo de bancada é o caso
// onde isso aparece o tempo todo: piscar dois LEDs com a mesma função.

#include <Arduino.h>

const int PIN_LED_A = 2;
const int PIN_LED_B = 4;
const int TEMPO_ACESO = 300;
const int TEMPO_APAGADO = 150;
const int TOTAL_DE_CICLOS = 4;

int ciclos = 0;
int totalDeAcertos = 0;

// Passagem por REFERÊNCIA: o `int&` diz que o parâmetro é o próprio
// endereço. O que a função mexer aqui, muda lá na variável do chamador.
// Note que o tipo é `int&`, com o & grudado — não é `int &` nem `&int`.
void somarCiclo(int& contador, int quantidade);
void piscar(int pino, int tempo);
void alternar(int pino);

// Passagem por VALOR, para contraste: os 5 que chegam aqui são uma cópia.
// A função pode somar à vontade que a variável de fora nem fica sabendo.
int somarCopia(int valor, int quantidade) {
  return valor + quantidade;
}

void setup() {
  pinMode(PIN_LED_A, OUTPUT);
  pinMode(PIN_LED_B, OUTPUT);

  Serial.begin(115200);
  Serial.println();
  Serial.println("=== dia 4, aula 2: passagem por referência ===");
  Serial.println();
  Serial.println("ciclos | acertos");

  int totalLocal = 0;
  int porValor = somarCopia(totalLocal, 5);
  Serial.print("por valor: o argumento era ");
  Serial.print(totalLocal);
  Serial.print(" e a funcao devolveu ");
  Serial.println(porValor);
  Serial.println("  (totalLocal continua 0: a funcao recebeu uma copia)");
  Serial.println();
}

void loop() {
  // A mesma função, chamada em dois pinos. Uma vez com o LED A, uma com o B.
  piscar(PIN_LED_A, TEMPO_ACESO);
  piscar(PIN_LED_B, TEMPO_APAGADO);
  alternar(PIN_LED_A);
  alternar(PIN_LED_B);

  // Por referência: `ciclos` muda de verdade depois da chamada.
  somarCiclo(ciclos, 1);

  Serial.print(ciclos);
  Serial.print("   |   ");
  Serial.println(totalDeAcertos);

  // Referência também serve para devolver mais de um valor de uma vez.
  // Se usasse retorno, teria que devolver um arranjo ou inventar um tipo.
  int antes = totalDeAcertos;
  somarCiclo(totalDeAcertos, 2);
  Serial.print("somarCiclo mudou a variavel de fora: ");
  Serial.print(antes);
  Serial.print(" -> ");
  Serial.println(totalDeAcertos);

  delay(TEMPO_APAGADO * 2);
}

// `int& contador` — sem o &, esta função devolveria uma cópia alterada, e o
// `ciclos` do `loop` ficaria parado no 1 para sempre. É o defeito silencioso
// da referência: o código compila, roda, e só não faz nada.
void somarCiclo(int& contador, int quantidade) {
  contador += quantidade;
}

void piscar(int pino, int tempo) {
  digitalWrite(pino, HIGH);
  delay(tempo);
  digitalWrite(pino, LOW);
  delay(tempo);
}

// `bool` no lugar de `void` seria o jeito de devolver o estado novo. Aqui
// não precisa: `alternar` só precisa inverter. O `loop` é que não pode ler
// o pino logo depois, porque precisa do valor novo — e por isso a chamada
// para o LED B vem logo em seguida.
void alternar(int pino) {
  digitalWrite(pino, digitalRead(pino) == HIGH ? LOW : HIGH);
}

Por que assim e não de outro jeito. O sketch tem as duas funções lado a lado de propósito: somarCopia por valor e somarCiclo por referência, com o mesmo nome e quase o mesmo corpo. O aluno vê a única diferença — o & — e o efeito muda completamente. Se fossem funções separadas, com nomes separados, a comparação não existiria.

A prova da passagem por valor está no setup: totalLocal vale 0, somarCopia devolve 5, e o serial mostra as duas coisas em linhas separadas. Sem essa prova escrita, metade da turma sai da aula achando que a função mexeu na variável.

O alternar não usa referência, e isso é proposital. Ele recebe o valor do pino e escreve nele — é o pino que importa, não o estado de uma variável do loop. A aula mostra que referência não é um enfeite para colocar em toda função: quando o que você quer mudar é um pino, o parâmetro comum resolve e o código fica mais curto.

O delay(TEMPO_APAGADO * 2) no fim do loop é a única espera do arquivo. Ele existe para o arranjo aparecer devagar na tela do professor; a lógica do padrão de LEDs não depende dele e o aluno pode removê-lo sem quebrar a sequência.

Criterios de correcao

CritérioPontos
int& declarado no parâmetro e o contador mudando de verdade3 pontos
Prova escrita do item 3: o que acontece sem o &3 pontos
Função inverter escrita e os dois LEDs invertendo2 pontos
loop do item 6 sem digitalWrite direto2 pontos

Erros comuns

ErroComo apareceCorrecao
Esquecer o & na declaração do parametroContador parado, programa compila e roda"Você passou uma copia de novo. Com o & no tipo, eu recebo o endereco da sua variável."
Usar & na chamada e não na declaracaoErro de compilacao sobre referência invalida"O & vai na declaracao do parametro, grudado no tipo: int& contador. Na chamada passa o valor normal."
Achar que referência copia o valorAluno escreve int& n = outroNumero; esperando copia"Referência e o endereco. A variável ligada nela e a mesma, não uma copia."
Passar número solto onde a função pede variávelErro de compilacao de tipo"A função pede um endereco de int. So uma variável int serve."
Fazer referência em função que so calculaCódigo cheio de & sem ganho nenhum"Se a função so calcula, devolve com return. Referência e para acumular ou mexer em pinos."
Colocar o & longe do tipoint &contador com o & solto da declaracao"Grude o sinal no tipo: int& contador. E o que o compilador le."
Refatorar e apagar a função antigaSketch so funciona se a ordem das chamadas for exata"A função nova precisa estar definida e prototypada. Se apagou o prototipo, o compilador reclama na chamada."

Desafio extra

Escreva uma função void registrarLeitura(int& total, int& maior, int& menor, int valor) que receba quatro referências e atualize as três de uma vez: some o valor no total, e guarde o maior e o menor. Chame-a em um laço de dez leituras de analogRead e imprima no serial as três estatísticas ao final. Depois troque o laço por um for de 1 a 100 e explique no caderno por que a função continua funcionando sem nenhuma alteração.

>

A resolucao, compilada

// Aula 2 do dia 4: funções com referência, as funções de arranjo do kit.
//
// Passagem por valor copia o número; passagem por referência entrega o
// endereço da variável. É a diferença entre uma função que "faz o serviço"
// e uma função que "muda o seu estado" — e o arranjo de bancada é o caso
// onde isso aparece o tempo todo: piscar dois LEDs com a mesma função.

#include <Arduino.h>

const int PIN_LED_A = 2;
const int PIN_LED_B = 4;
const int TEMPO_ACESO = 300;
const int TEMPO_APAGADO = 150;
const int TOTAL_DE_CICLOS = 4;

int ciclos = 0;
int totalDeAcertos = 0;

// Passagem por REFERÊNCIA: o `int&` diz que o parâmetro é o próprio
// endereço. O que a função mexer aqui, muda lá na variável do chamador.
// Note que o tipo é `int&`, com o & grudado — não é `int &` nem `&int`.
void somarCiclo(int& contador, int quantidade);
void piscar(int pino, int tempo);
void alternar(int pino);

// Passagem por VALOR, para contraste: os 5 que chegam aqui são uma cópia.
// A função pode somar à vontade que a variável de fora nem fica sabendo.
int somarCopia(int valor, int quantidade) {
  return valor + quantidade;
}

void setup() {
  pinMode(PIN_LED_A, OUTPUT);
  pinMode(PIN_LED_B, OUTPUT);

  Serial.begin(115200);
  Serial.println();
  Serial.println("=== dia 4, aula 2: passagem por referência ===");
  Serial.println();
  Serial.println("ciclos | acertos");

  int totalLocal = 0;
  int porValor = somarCopia(totalLocal, 5);
  Serial.print("por valor: o argumento era ");
  Serial.print(totalLocal);
  Serial.print(" e a funcao devolveu ");
  Serial.println(porValor);
  Serial.println("  (totalLocal continua 0: a funcao recebeu uma copia)");
  Serial.println();
}

void loop() {
  // A mesma função, chamada em dois pinos. Uma vez com o LED A, uma com o B.
  piscar(PIN_LED_A, TEMPO_ACESO);
  piscar(PIN_LED_B, TEMPO_APAGADO);
  alternar(PIN_LED_A);
  alternar(PIN_LED_B);

  // Por referência: `ciclos` muda de verdade depois da chamada.
  somarCiclo(ciclos, 1);

  Serial.print(ciclos);
  Serial.print("   |   ");
  Serial.println(totalDeAcertos);

  // Referência também serve para devolver mais de um valor de uma vez.
  // Se usasse retorno, teria que devolver um arranjo ou inventar um tipo.
  int antes = totalDeAcertos;
  somarCiclo(totalDeAcertos, 2);
  Serial.print("somarCiclo mudou a variavel de fora: ");
  Serial.print(antes);
  Serial.print(" -> ");
  Serial.println(totalDeAcertos);

  delay(TEMPO_APAGADO * 2);
}

// `int& contador` — sem o &, esta função devolveria uma cópia alterada, e o
// `ciclos` do `loop` ficaria parado no 1 para sempre. É o defeito silencioso
// da referência: o código compila, roda, e só não faz nada.
void somarCiclo(int& contador, int quantidade) {
  contador += quantidade;
}

void piscar(int pino, int tempo) {
  digitalWrite(pino, HIGH);
  delay(tempo);
  digitalWrite(pino, LOW);
  delay(tempo);
}

// `bool` no lugar de `void` seria o jeito de devolver o estado novo. Aqui
// não precisa: `alternar` só precisa inverter. O `loop` é que não pode ler
// o pino logo depois, porque precisa do valor novo — e por isso a chamada
// para o LED B vem logo em seguida.
void alternar(int pino) {
  digitalWrite(pino, digitalRead(pino) == HIGH ? LOW : HIGH);
}

Sem saída de compilação gravada. Rode python3 validar.py -t 1 dia04 aula2.