Portas I/O

Nesta página serão apresentadas as funções presentes na biblioteca Arduino destinadas à configuração e uso de portas digitais de entrada e/ou saída. Serão igualmente abordadas formas de implementar temporizações de forma bloqueante e não bloqueante. Em seguida iremos ver como se pode aceder diretamente aos Special Function Registers (SFRs) do microcontrolador PIC32. Por fim, explica-se como se podem adicionar ao projeto outras bibliotecas.

Projeto blink

Comecemos por analisar o código (main.cpp) usado no tutorial sobre a instalação e uso do PlatformIO, e que faz piscar um dos LEDs da placa ChipKIT Uno32.

#include <Arduino.h>

#define LED 13

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

void loop() {
  digitalWrite(LED, HIGH);
  delay(1000);
  digitalWrite(LED, LOW);
  delay(1000);
}

De notar que aparentemente este programa em C não apresenta qualquer função main. Isto não é verdade pois dentro da biblioteca Arduino.h é possível chegar ao seguinte código:

int main(void)
{
	init();

	setup();

	while (1)
	{
		_scheduleTask();
		loop();
	}
	return 0;
}

Entre outra coisas, esta função main começa por chamar a função setup entrando depois num ciclo infinito onde chama repetidamente a função loop, bastando-nos pois definir no nosso código essas duas funções.

No nosso exemplo, em setup vamos definir que o pino onde está ligado o LED da placa ChipKIT é uma saída (OUTPUT), recorrendo para isso à função pinMode.

Em loop vamos piscar o LED, colocando nesse pino, ora o valor lógico HIGH (ou 1 ou TRUE ou true), ora o valor lógico LOW (ou 0 ou FALSE ou false), recorrendo à função digitalWrite. Para que a troca desses valores lógicos se realize a uma cadência mais baixa, são introduzidos atrasos de 1 segundo (1000 milissegundos) através da função delay.

De notar que o LED que pretendemos controlar é o LED4 que tal como a seguinte imagem ilustra é o pino 13, tal como está indicado no #define respetivo.

chipkituno32_pinout

Um código alternativo seria o seguinte:

#include <Arduino.h>

#define LED4 13

bool LED4state = LOW;

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

void loop() {
  digitalWrite(LED4, LED4state = !LED4state);
  delay(1000);
}

Aqui foi definido o estado inicial do LED4 (LOW) numa variável que é depois negada de 1 em 1 segundo.

Projeto butled

Vamos agora criar um outro projeto que trocará o estado dum LED externo à placa (ligado no pino 7) quando se prime um botão ligado no pino 6.

Comece por montar na breadboard o seguinte circuito, com base no pcb fornecido e que já contém 3 LEDs e 2 botões. Basicamente bastará realizar as 4 ligações com a placa chipKIT indicadas a vermelho (GND, 3V3, PIN6 e PIN7):

butled

O ficheiro main.cpp seria por exemplo o seguinte:

#include <Arduino.h>

#define LED 7
#define BUT 6

bool lastBUT = HIGH;
bool LEDstate = LOW;

void setup() {
  pinMode(LED, OUTPUT);
  digitalWrite(LED, LEDstate);
  pinMode(BUT, INPUT);
}

void loop() {
  bool but = digitalRead(BUT);
  if ((but == LOW) && (lastBUT == HIGH))
    digitalWrite(LED, LEDstate = !LEDstate);
  lastBUT = but;
}

Aqui é usada a função digitalRead para obter o valor lógico presente no pino onde está ligado o botão que foi previamente definido como sendo uma entrada (INPUT). Note que no circuito acima apresentado, quando se prime o botão esse valor lógico é LOW e não HIGH.

Bouncing do botão

Embora os botões presentes no módulo não padeçam muito deste problema, é preciso estar a tento à possibilidade de ocorrência do fenómeno de “bouncing”. Isto acontece se a passagem entre níveis lógicos quando se carrega/liberta um botão não se efetuar com uma transição único mas com várias provocando-se assim efeitos indesejáveis. Com efeito os botões sendo componentes mecânicos podem nos momentos de fecho ou abertura dos contactos provocar essas flutuações como a seguinte imagem ilustra:

bouncing

Uma forma de resolvermos o problema em hardware será o que adicionar um pequeno condensador em paralelo com o botão que “filtre” essas oscilações.

De realçar que este condensador já está incluído no primeiro dos botões presentes no módulo, e poderá estar ou não (depende das ligações efetuadas) no segundo botão.

Por exemplo, o problema do “bouncing” poderá ocorrer caso as ligações do PIN6 sejam feitas ao segundo botão  da seguinte forma:

bounce_test

Situação que poderá ser corrigida ligando-se os ponto C e Bu.

Projeto mix

Tentemos agora juntar num único programa as duas funcionalidades acima descritas, i.e. fazer piscar o LED4 da placa e simultaneamente trocar o LED externo de cada vez que se carrega no botão.

Aparentemente bastaria juntar os dois programas:

#include <Arduino.h>

#define LED4 13
#define LED 7
#define BUT 6

bool LED4state = LOW;
bool lastBUT = HIGH;
bool LEDstate = LOW;

void setup() {
  pinMode(LED4, OUTPUT);
  pinMode(LED, OUTPUT);
  digitalWrite(LED, LEDstate);
  pinMode(BUT, INPUT);
}

void loop() {
  digitalWrite(LED4, LED4state = !LED4state);
  delay(1000);

  bool but = digitalRead(BUT);
  if ((but == LOW) && (lastBUT == HIGH))
    digitalWrite(LED, LEDstate = !LEDstate);
  lastBUT = but;
}

Testando contudo o que resulta deste código, leva-nos a concluir que não faz exatamente o pretendido muito por culpa da função delay durante a qual o programa fica bloqueado não sendo possível detetar alterações no estado do botão.

Para se resolver o problema teremos que implementar o atraso de 1 segundo de uma forma “não bloqueante”, e portanto sem recorrer à função delay. Para isso vamos usar a função millis que retorna o número de milissegundos decorridos desde o início do programa.

#include <Arduino.h>

#define LED4 13
#define LED 7
#define BUT 6

bool LED4state = LOW;
bool lastBUT = HIGH;
bool LEDstate = LOW;

unsigned long last;

void setup() {
  pinMode(LED4, OUTPUT);
  pinMode(LED, OUTPUT);
  digitalWrite(LED, LEDstate);
  pinMode(BUT, INPUT);
  
  last = millis();
}

void loop() {
  if (millis() - last > 1000) {
	digitalWrite(LED4, LED4state = !LED4state);
    last += 1000;
  } 

  bool but = digitalRead(BUT);
  if ((but == LOW) && (lastBUT == HIGH))
    digitalWrite(LED, LEDstate = !LEDstate);
  lastBUT = but;
}

Desta feita, o programa já funcionará como pretendido pois já não é feito qualquer bloqueio dentro da função loop. Para provar isso vamos gerar um impulso no pino 5 (a definir como saída) a cada execução da função loop.

#include <Arduino.h>

#define LED4 13
#define LED 7
#define BUT 6

bool LED4state = LOW;
bool lastBUT = HIGH;
bool LEDstate = LOW;

unsigned long last;

void setup() {
  pinMode(5, OUTPUT);
  pinMode(LED4, OUTPUT);
  pinMode(LED, OUTPUT);
  digitalWrite(LED, LEDstate);
  pinMode(BUT, INPUT);

  last = millis();
}

void loop() {
  digitalWrite(5, HIGH);
  digitalWrite(5, LOW);
  
  if (millis() - last > 1000) {
	digitalWrite(LED4, LED4state = !LED4state);
    last += 1000;
  }

  bool but = digitalRead(BUT);
  if ((but == LOW) && (lastBUT == HIGH))
    digitalWrite(LED, LEDstate = !LEDstate);
  lastBUT = but;
}

Observado esse sinal no osciloscópio é possível obter o seguinte:

tick_screenshot

Ou seja, neste exemplo a função loop, e portanto também o teste do botão, são executados a cada 5,260μs!

SFRs

A utilização das funções incluídas na biblioteca Arduino permite a criação de programa de uma forma fácil mas também apresenta algumas desvantagens:

  • a ocupação da memória de programa (flash) é em geral maior
  • o desempenho é normalmente pior, i.e. os tempos de execução são maiores
  • certas funcionalidades do PIC32 podem ainda não estar incluídas na biblioteca

Embora o tamanho da memória flash do PIC32 seja suficientemente grande para o primeiro ponto não ser muito crítico, e o tipo de programa que iremos desenvolver não apresentarem em geral grandes apertos temporais, a não existência de funções na biblioteca Arduino que cubram algumas das funcionalidades do PIC32 poderá ser um forte incentivo ao acesso direto ao recursos de hardware do microcontrolador. Esse acesso faz-se através de registos especiais, os chamados Special Function Registers (SFR).

Projeto semaforo

Pretende-se desenvolver um programa que controle um semáforo para automóveis situado junto a uma passadeira para peões.

semaforo

O ciclo normal de funcionamento será de 9 segundos em verde (LEDG) , 1 segundo em amarelo (LEDY) e 5 segundos em vermelho (LEDR).

Caso seja premido o botão dos peões (B1) o tempo de verde deverá ser imediatamente terminado desde que já tenham decorrido 4 segundos. Se esse tempo ainda não decorreu a passagem para amarelo deverá ocorrer só após esses 4 segundos de verde.
O botão B2 servirá para a central de controlo de tráfego iniciar/terminar remotamente o modo “intermitente”. Neste modo o semáforo deverá ter o amarelo a piscar ciclicamente (1 segundo ligado, 1 segundo desligado).

Note: Os tempos referidos em seguida embora sejam irrealistas, tornam mais rápido o teste do programa!

Monte o circuito envolvendo os 3 LEDs e 2 botões acima mencionados e que deverão ser ligados a outros tantos pinos da placa. Use para isso o pcb fornecido e que já contém os LEDs e botões necessários:

ledbut

Desenvolva a seguir o código que implemente a funcionalidade acima descrita e teste-o.


Bibliotecas

Porque o uso de botões e a implementação de tarefas a executar em intervalos regulares é bastante usual, vamos agora ver como se poderiam usar bibliotecas para facilitar estas necessidades.

Em concreto usaremos as seguintes bilbiotecas:

OneButton

A adição duma biblioteca (por exemplo a OneButton) faz-se:

  1. Abrir a janela PIO Home
  2. Selecionando a aba Libraries
  3. Escrever o nome da biblioteca (OneButton)
  4. Iniciando a procura
  5. Selecionando a bilbiotexa de entre as que são seguridas

Isto dá a cesso a uma página com variada informação sobre a biblioteca, incluindo na aba Instalation as linhas que se têm que incluir no ficheiro platformio.ini:

Essa biblioteca não ficará localizada dentro da pasta lib mas sim na pasta .pio\libdeps:

O programa Mix recorrendo agora à biblioteca OneButton ficaria assim:

#include <Arduino.h>
#include <OneButton.h>

#define LED4 13
#define LED 7
#define BUT 6

OneButton button(BUT, true); // includes pinMode on BUT, true for "active LOW"

void callback() {
  static int LEDstate = LOW;

  digitalWrite(LED, LEDstate = !LEDstate);
}

bool LED4state = false;
unsigned long last;

void setup() {
  pinMode(LED4, OUTPUT);
  digitalWrite(LED4, LOW);

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

  button.attachClick(callback);

  last = millis();
}

void loop() {
  if (millis() - last > 1000) {
    digitalWrite(LED4, LED4state = !LED4state);
    last += 1000;
  }

  button.tick();
}

TaskScheduler

Para fazer piscar o LED da placa (LED4) a cada segundo de uma outra forma vamos agora incluir a bilbioteca TaskScheduler:

Como a aba de instalação sugere, basta adicionar à secção lib_deps do platformio.ini a linha correspondente a esta nova biblioteca:

lib_deps =
  mathertel/OneButton
  arkhipenko/TaskScheduler

O programa anterior recorrendo agora também a esta biblioteca ficaria assim:

#include <Arduino.h>
#include <OneButton.h>
#include <TaskScheduler.h>

#define LED4 13
#define LED 7
#define BUT 6

OneButton button(BUT, true);

void callback() {
  static int LEDstate = LOW;

  digitalWrite(LED, LEDstate = !LEDstate);
}

void t1Callback() {
  static bool LED4state = false;

  digitalWrite(LED4, LED4state = !LED4state);
}

Task t1(1000, TASK_FOREVER, &t1Callback);
Scheduler runner;

void setup() {
  pinMode(LED4, OUTPUT);
  digitalWrite(LED4, LOW);

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

  button.attachClick(callback);

  runner.init();
  runner.addTask(t1);
  t1.enable();
}

void loop() {
  runner.execute();

  button.tick();
}

Sugere-se a leitura da documentação de ambas as bibliotecas pois existem outras funcionalidades interessantes e que não foram aqui exploradas!

O procedimento descrito nestas últimas secções para integração de novas bibliotecas pode ser replicado em outros casos facilitando assim o uso de funcionalidades que não são fornecidas pelo pacote original de bibliotecas Arduino.

Existe ainda a possibilidade de desenvolvermos as nossas próprias bilbiotecas devendo os respetivos ficheiros .c(pp) e .h ser colocados na pasta lib do projeto.