Arduino 3: Introdução à programação

Para fazer qualquer circuito com o Arduino, é preciso primeiro programá-lo. Este é o objetivo desta seção onde introduziremos, de forma prática, sua linguagem de programação.

Para que esta introdução seja curta, objetiva e mais simples possível, omitiremos detalhes e abordaremos conceitos de maneira incompleta e às vezes até imprecisa, para focar apenas no essencial necessário para entender os programas que serão construídos.

Estrutura básica de um sketch

Um programa Arduino é chamado de Sketch. Ele consiste de uma sequência de instruções escritas em uma linguagem chamada Processing. Para ser usado o programa depois precisa ser compilado (traduzido para linguagem de máquina) e transferido para o Arduino. Um sketch é também um arquivo de texto que pode ser gravado no computador, e possui a extensão .ino. O menor sketch contém no mínimo a seguinte estrutura (que não faz nada):

void setup() {

}

void loop() {

}

Se você está usando um Arduino pela primeira vez, e não sabe que programa está em sua memória, é uma boa prática transferir o programa acima para ele. Isto garante que ele não executará nenhuma tarefa que possa danificá-lo.

A estrutura acima possui dois blocos, que podemos chamar de bloco setup() e bloco loop(). A instrução void setup() define o bloco setup(), e a instrução void loop() define o bloco loop(). Essas instruções são chamadas automaticamente quando o Arduino estiver executando, e todas as instruções que forem digitadas entre as chaves { } serão executadas.

No programa acima, as chaves estão vazias, portanto quando o Arduino chamar setup() e loop(), ele não vai fazer nada.

Os blocos setup() e loop() funcionam de forma distinta. O bloco setup() é chamado uma vez só, portanto ele deve conter instruções que serão executadas uma única vez. Já o bloco loop() é chamado eternamente, e deve conter instruções que repetem para sempre (até que o Arduino seja desligado ou reiniciado).

Normalmente dentro de setup() serão colocadas instruções de configuração (por exemplo, especificar a função que um determinado pino irá assumir – se entrada ou saída). Em loop() ficam as instruções que efetivamente programam o Arduino, por exemplo, mandar nível lógico alto (5V) para pino 4, esperar meio segundo, e depois mandar nível lógico baixo (0V), e repetir isto sem parar.

Antes de programar qualquer coisa, vamos testar a transferência de programas para o Arduino.

Digite o programa acima. Para transferir para o Arduino, clique no ícone   (ou selecione o menu Sketch/Upload). Se houver erro, ele aparecerá na caixinha de status na parte inferior do programa, e a transferência não acontecerá. Um erro comum é esquecer de selecionar o modelo de Arduino e sua porta de comunicação (veja a seção 5.5.3). Verifique também se não esqueceu de fechar alguma chave, ou se digitou algo diferente do que foi listado. Os comandos precisam ser escritos exatamente como acima (letras maiúsculas e minúsculas são consideradas diferentes na linguagem do Arduino (ex: escrever LOOP() ou Loop() é um erro).

Sintaxe das instruções

As instruções usadas dentro dos blocos setup() e loop() têm uma sintaxe bem definida. Existem vários tipos. Usaremos principalmente instruções de uma linha. Essas instruções sempre terminam em ponto-e-vírgula e podem ser classificadas como comandos (que mandam o Arduino fazer alguma coisa) ou expressões (que calculam valores, guardam dados, etc.).

Comandos

Comandos são formalmente chamados de funções ou métodos. Eles fazem parte de uma biblioteca que define seus nomes e parâmetros. Os parâmetros são valores passados entre parênteses, às vezes entre aspas, e separados por vírgulas depois do nome da função. Há comandos que não têm parâmetros (apenas os parênteses vazios). Por exemplo, o comando:

delay(500);

manda o Arduino esperar meio segundo (500 milissegundos). Há apenas um parâmetro que é o número de milissegundos a esperar. Este outro comando:

analogWrite(9, 128);

manda o Arduino produzir no seu pino digital 9 um sinal analógico de nível 128 (o nível varia de 0 a 255, e corresponde a valores médios (simulados) de 0 a 5V produzidos com PWM). A instrução, portanto, produz um pulso ligado 50% do tempo que resulta em um valor médio de 2,5V no pino 9.

Observe que as instruções terminam sempre em ponto-e-vírgula. Observe também que o “W” em analogWrite é maiúsculo (e assim deve ser escrito).

Expressões e variáveis

Pode-se escrever um programa apenas com comandos, mas alguns comandos retornam resultados que precisam ser processados. O processamento é feito através de expressões. Existem vários tipos de expressões: aritméticas, lógicas, etc. Expressões frequentemente são formadas por operações. Por exemplo, esta é uma expressão contendo uma operação de soma e uma operação de atribuição:

numero = 3 + 4;

O Arduino irá somar 3 com 4 e guardar o resultado na variável numero. O sinal de = é usado para fazer uma operação de atribuição, isto é, copiar um valor (o resultado da expressão) para uma área da memória associada à variável. Variáveis são palavras usadas para identificar, guardar e referenciar dados. Elas só guardam dados de um tipo de dados específico. A variável acima precisa declarar o tipo de dados que pode armazenar, antes que seja usada. A declaração é também uma expressão. Então algum lugar antes da linha acima, deve haver algo como:

int numero;

declarando que a variável numero é do tipo int. A palavra int significa inteiro, e é usada para declarar variáveis que só aceitam valores inteiros. Não seria possível, por exemplo, guardar um 3.14 na variável numero. Para isto ela teria que ser declarada como float, que é o nome usado para variáveis com parte decimal. Observe que a declaração da variável também termina em ponto-e-vírgula.

Nos programas em Arduino que faremos nesta introdução, declararemos apenas variáveis do tipo int e float. Muitas vezes, a declaração e a atribuição ocorrem na mesma linha, por exemplo:

int pino = 6;
float valor = 3.14;

Depois de declarada uma variável, provavelmente vamos querer usá-la depois. O uso de uma variável pode ser, por exemplo, a inclusão do valor que ela contém em alguma outra expressão ou comando:

float raio = 9.5;
float area = valor * raio * raio;
int tempo = 1000;
delay( tempo );

A última linha acima é um comando que está usando a variável tempo, que contém o valor inteiro 1000, que é passado como parâmetro da função delay().

Alguns comandos produzem um valor, que geralmente é resultado do processamento executado por eles. Esse valor geralmente é guardado em uma variável. Por exemplo:

int duracao = analogRead(2);

O comando analogRead(2) é executado, e seu resultado é copiado (via operação de atribuição) para a variável duracao. Depois, este valor pode ser usado em outro comando, por exemplo:

delay(duracao);

A instrução analogRead(2) produz um valor (entre 0 e 1023) resultante da leitura do nível da tensão no pino A2. Por exemplo, se houver um potenciômetro com os pinos externos ligados entre 0 e 5V, e o pino do meio estiver ligado no A2 do Arduino, e este potenciômetro estiver com o seletor posicionado exatamente no meio, o valor recebido por analogRead(2) será 1024/2 ou 512.

Nomes de variáveis

Variáveis não podem ter qualquer nome. Não crie nomes com acentos, hífens, pontos. O ideal é usar nomes curtos e explicativos. Se você quer criar uma variável com mais de uma palavra, você pode distinguir as palavras usando maiúsculas, por exemplo:

int numeroDoPino = 6;

ou sublinhados:

int NUMERO_DO_PINO = 6;

Variáveis também não podem usar certas palavras, que são reservadas. Exemplos são as palavras int e float, que têm significado especial para o Arduino. É fácil saber quando uma palavra é reservada, pois ela aparece com uma cor diferente (azulada) no IDE.

Comentários

Nem tudo o que está escrito em um sketch é enviado para o Arduino. Para que os programas sejam mais fáceis de entender pelos humanos que irão lê-lo, é comum que tenham comentários. Os comentários são texto ignorado pelo compilador (mecanismo da IDE que traduz o programa para linguagem de máquina) e devem ser usados para explicar trechos do programa, ou incluir instruções de como usá-los. Há dois tipos: comentários de linha e comentários de bloco.

Comentários de linha geralmente aparecem antes de instruções, ou logo depois do ponto-e-vírgula, na mesma linha que a instrução. Tudo o que aparece depois do // é considerado um comentário. Por exemplo:

int PINO_DO_LED = 13;   // este é o pino do LED interno

Outra forma de escrever comentários no programa é usar comentários de bloco, que consiste em incluir o texto de uma ou mais linhas entre /* e */. Use esse tipo de comentário se o que você pretende escrever tem muitas linhas:

/*
Este programa faz um LED piscar.
Ligue o Anodo do LED no pino 6.
Ligue o Catodo em um resistor de 470 ohms, ligado a GND.
*/
void setup() { ... }

Comentários também são usados para temporariamente ignorar um trecho de código (que você não quer que execute, mas não quer apagar do sketch):

void loop() {
    // delay(500);
    delay(1000);   // a linha anterior será ignorada
}

Isto é suficiente como uma introdução à linguagem do Arduino. Com o que vimos até aqui já é possível fazer um primeiro programa para piscar um LED.

Experimento 24 – Piscando um LED

Material necessário:

  • Arduino Nano + cabo USB + computador
  • Um LED de qualquer cor
  • Resistor de 220 Ω
  • Protoboard, fios e jumpers

Para piscar um LED, é preciso liga-lo em uma saída que alterne entre dois níveis lógicos: alto e baixo, com um intervalo entre eles. Como precisamos de apenas dois estados, podemos usar um pino de saída digital. Há 14 deles. Podemos usar qualquer um. Vamos escolher o pino digital 8, identificado na placa do Arduino Nano com a indicação D8.

O nível lógico ALTO no Arduino é sempre 5 volts. Precisamos de um resistor para limitar a corrente do LED e temos informações suficientes para calcular seu valor. Se for um LED vermelho, com 2V de queda de tensão:

R = (5V – 2V) / 0,02 A = 150 ohms.

Não temos 150 ohms no kit, mas podemos usar 220 ohms que consome um pouco menos corrente, ou até mesmo arriscar um valor menor (100 ohms) já que ele não vai ficar ligado muito tempo. Podemos também usar um resistor de 100 ohms e trocar o LED vermelho por um de 3V (azul, rosa ou branco).

Antes de montar qualquer circuito, sempre desligue o Arduino do computador (desconecte o cabo USB). Monte o circuito abaixo, verifique as conexões, e depois ligue novamente o Arduino ao computador.

A porta USB é quem irá fornecer corrente para o circuito. Se houver um problema no seu circuito e ele tentar puxar corrente demais da porta USB, o computador desligará o acesso e desligará o Arduino (você terá que remover o cabo e reinserir novamente, depois de corrigir o problema).

O resistor ligado ao catodo do LED pode ser conectado a qualquer um dos dois pinos GND disponíveis no Arduino Nano (há um de cada lado). Internamente eles estão ligados entre si.

Agora vamos escrever um programa para piscar o LED. Abra um novo sketch (ícone ou Menu File/New) e preencha os blocos setup() e loop() com as seguintes instruções:

void setup()
    pinMode(8, OUTPUT);     // declara pino 8 como uma saída
}
void loop() {
    digitalWrite(8, HIGH);
    delay(500);
    digitalWrite(8, LOW);
    delay(500);
}

Clique no ícone para transferir o programa para o Arduino. Em alguns segundos o LED deverá começar a piscar ficando meio segundo aceso e meio segundo apagado. Experimente mudar o valor de delay() para que ele pisque mais rápido, ou que fique mais tempo aceso que apagado.

Explicaremos os comandos usados no programa nas seções a seguir.

Pinos digitais e estados HIGH e LOW

Um pino digital permite a entrada e saída de valores digitais correspondentes aos níveis lógicos ligado (5V), ou ALTO, e desligado (0V), ou BAIXO. Esses dois estados são representados na linguagem do Arduino pelas palavras reservadas HIGH (sempre em maiúsculas) e LOW (idem). No contexto do circuito, correspondem aos valores de tensão 5V e GND (0V).

(Na verdade HIGH e LOW são variáveis pré-definidas que, no contexto do programa, contém os números inteiros 1 e 0, respectivamente.)

Um componente de dois terminais ligado a um pino de saída deve ter o seu outro terminal ligado a uma referência de tensão: o pino GND (negativo) ou o pino 5V (positivo). Haverá corrente se houver diferença de potencial entre o pino de saída e a referência. Isto significa que, para que haja corrente fluindo por um componente, se ele estiver conectado a um pino que é acionado pelo valor HIGH, o outro terminal deve estar conectado a GND (é assim que o LED está configurado no nosso exemplo). Se o componente for acionado pelo valor LOW, o outro terminal deve estar conectado a 5V.

É importante observar a polaridade do componente e posicioná-lo de acordo, e também limitar a corrente. Um pino e saída do Arduino não suporta mais que 40 mA. Ligar um pino de saída diretamente a 5V ou GND sem resistor limitador gera uma corrente muito alta que poderá queimá-lo quando houver uma diferença de potencial no pino.

A instrução pinMode()

Normalmente os pinos operam como entrada. Para usar um pino como saída digital é preciso executar uma instrução para declará-lo explicitamente. Isto normalmente é feito dentro do bloco setup() através da instrução pinMode(número-do-pino, função) (observe o “M” maiúsculo da instrução). Os dois parâmetros informam respectivamente o número do pino e o tipo de função que ele vai exercer (OUTPUT, para a função saída):

void setup()
    pinMode(8, OUTPUT);     // pino 8* é uma saída
}

* Na placa do Arduino Nano os pinos digitais são identificados pelo prefixo D (D0, D1, D2, D3, etc.) e os analógicos pelo prefixo A (A0, A1, A2, A3, etc.) No programa, apenas os números dos pinos digitais são usados (ex: 0, 1, 2, 3, etc.) Os pinos analógicos podem ser identificados com ou sem prefixo nos comandos que aceitam entradas analógicas.

Saída digital

Para produzir uma saída digital em níveis lógicos (HIGH/5V ou LOW/0V, sem valores intermediários) usa-se a instrução digitalWrite(número-do-pino, nível-lógico), dentro de setup() (para rodar apenas uma vez) ou loop() (para rodar repetidas vezes). O número do pino precisa ter sido previamente declarado como OUTPUT através da instrução pinMode().

Por exemplo:

digitalWrite(8, HIGH); // aplica o valor HIGH (5 volts) no pino 8

O comando acima transfere 5V (HIGH) para a saída digital D8. Se no pino D8 houver um LED (alimentado entre o pino 8 e GND), ele irá receber 5V, e acender.

Para o LED piscar é preciso fazer o pino 8 ter valor HIGH, depois esperar algum tempo (mantendo o pino neste estado) e em seguida fazer o pino 8 ter valor LOW, esperar mais algum tempo (em que o LED ficará apagado) e repetir a sequência. A repetição acontece automaticamente para instruções digitadas dentro do bloco loop(), portanto para piscar o LED serão necessárias apenas quatro instruções:

void loop() {
    digitalWrite(8, HIGH);        // aplica 5V no LED+resistor
    delay(500);           // mantém em 5V por 0,5 segundos
    digitalWrite(8, LOW);   // aplica 0V no LED+resistor
    delay(500);             // mantém em 0V por 0,5 segundos
}
// repete tudo ad infinitum

Variáveis globais e #define

Quando se tem um programa que usa apenas um ou dois pinos, é fácil lembrar o que está conectado a cada um, mas se muitos pinos estiverem sendo usados o programa pode tornar-se difícil de ler e entender. E se for necessário mudar um componente para outro pino? O número teria que ser alterado em todos os lugares onde foi digitado. Uma solução para este problema é declarar uma variável para identificar o pino:

void loop() {
    int PINO_DO_LED = 8;    // Declarando variável PINO_DO_LED contendo 8
    digitalWrite(PINO_DO_LED, HIGH); // o primeiro parâmetro recebe 8
    delay(500);
    digitalWrite(PINO_DO_LED, LOW);
    delay(500);
}

Variáveis globais

Uma variável declarada dentro das chaves { … } de um bloco (ex: setup() ou loop()) é acessível apenas dentro daquele mesmo bloco. Quando o bloco terminar, ela não poderá mais ser usada (causará erro no programa). Mas às vezes criamos uma variável exatamente para poder usá-la em blocos diferentes. Por isto é comum que a declaração de algumas variáveis ocorra fora dos blocos loop() e setup(). Variáveis declaradas fora dos blocos são chamadas de variáveis globais, porque elas podem ser usadas em qualquer um dos dois blocos.

No exemplo abaixo, criamos uma variável global para guardar o pino do LED:.

int LED = 8; // declarada fora de setup() ou loop() – é global

void setup()
    pinMode(LED, OUTPUT);    // reconhecida dentro de setup() – recebe 8
}

void loop() {
    digitalWrite(LED, HIGH); // reconhecida dentro de loop() – recebe 8
    delay(500);
    digitalWrite(LED, LOW);
    delay(500);
}

Outra forma de declarar uma variável global para um pino

Você encontrará alguns programas que declaram variáveis usando o comando #define antes dos blocos setup() e loop(). Por exemplo:

#define LED 8

A sintaxe é diferente de uma declaração de variável comum. Não existe o sinal de igual (=) e nem ponto-e-vírgula (não pode ter ponto-e-vírgula). Na prática o resultado é o mesmo. Usar esta forma ou a outra é uma questão de estilo. Não vai alterar o funcionamento do programa. Mesmo que você escolha usar apenas a outra forma, é importante reconhecer essa sintaxe, pois muitos programadores preferem usar #define em vez de declarar variáveis globais.

As declarações #define geralmente aparecem no início do programa. Elas não podem aparecer dentro dos blocos setup() ou loop().

Alteração 24.1 – Usando variáveis

Altere o programa do último experimento substituindo o número do pino por uma variável, e faça o upload novamente. Veja que o funcionamento não muda. Agora mude a posição do LED para o pino 7 no protoboard. Ele não pisca mais, mas você pode abrir o programa, fazer apenas uma alteração (mudando a variável LED para 7) e transferi-lo novamente, que ele voltará a funcionar.