Prévia do material em texto
Periféricos externos
Descrição geral e acesso aos periféricos externos mais importantes dos microcontroladores, Programação
para realização de sistemas completos de monitoração e controle com a plataforma Arduino e com
microcontroladores da família PIC.
Prof. Marcos Santana Farias
1. Itens iniciais
Propósito
Acionar e coletar dados de dispositivos externos aos microcontroladores, complementares aos sistemas
embarcados nas aplicações de monitoração e controle, é importante para a formação do projetista desses
sistemas que programa as interfaces de entrada e saída para integrar as funções não atendidas pelos
periféricos internos, mas que são essenciais no desenvolvimento do projeto.
Preparação
Antes de iniciar o conteúdo deste tema, tenha instalado o compilador CCS, o ambiente MPLAB e o simulador
PICSimLab, além de fazer um cadastro no simulador Tinkercad.
Identificar as funções de programação de protocolos de transmissão de dados em microcontroladores
para a comunicação com dispositivos externos.
Descrever sensores e atuadores para a programação de sistemas de controle embarcados.
Identificar as funções para a programação de mostradores na criação de interfaces com o usuário.
Reconhecer a importância da programação de interrupções para o recebimento otimizado de dados de
dispositivos externos ao microcontrolador.
Introdução
Sempre que uma determinada aplicação precisar de algum periférico diferente dos fornecidos dentro do
microcontrolador, eles podem ser adicionados posteriormente. Esses periféricos são chamados de periféricos
externos, pois, evidentemente, são externos ao chip do microcontrolador, diferentemente dos que já estão no
chip, que são chamados de periféricos internos.
Dessa forma, quando não é possível encontrar um microcontrolador que tenha todos os periféricos
necessários internamente, pode-se comprar o periférico separadamente, soldá-lo na mesma placa de circuito
impresso e fazê-lo se comunicar com o microcontrolador.
Neste conteúdo, interagiremos com os periféricos externos mais comuns, que são utilizados para estabelecer
interfaces com os usuários e para criar um sistema completo de controle.
Para isso, usaremos protocolos de comunicação disponibilizados em periféricos internos do microcontrolador,
tais como I2C, UART e SPI. Utilizaremos também a técnica de interrupção para o atendimento eficiente aos
periféricos externos. Dessa forma, será possível usar as instruções em C para se comunicar e atuar em
periféricos externos no desenvolvimento de projetos de sistemas embarcados, com exemplos para as
plataformas Arduino e PIC de microcontroladores.
•
•
•
•
1. Funções de programação de protocolos de transmissão de dados em microcontroladores para a
comunicação com dispositivos externos
Transmissão de dados
A transmissão de dados é um modo de transmitir dados digitais ou analógicos em um meio de comunicação
para um ou mais dispositivos.
Permite a transmissão e a comunicação de dispositivos em diferentes ambientes:
Ponto a ponto.
Ponto a multiponto.
Multiponto a multiponto.
A transmissão de dados pode ser analógica ou digital, mas é principalmente destinada ao envio e recebimento
de dados digitais. Como tal, ela também é conhecida como transmissão digital ou comunicação digital.
Assim, ela atua quando um dispositivo visa transmitir um conjunto de informações para um ou vários
dispositivos destinatários.
Os dados digitais vêm do dispositivo de origem na forma de fluxos de bits digitais. Esses fluxos de dados são
então colocados em um meio de comunicação, que pode ser paralelo ou serial, permitindo, portanto, que eles
sejam entregues para o dispositivo de destino.
Transmissão paralela
Dentro de uma placa de circuito impresso com microcontroladores, as distâncias entre as diferentes
subunidades são curtas. Assim, poderia ser considerada uma prática comum a transferência de dados entre
essas subunidades utilizando um fio separado para transportar cada bit de dados.
Os dados, nesse caso, são trocados por meio de um modo de transferência paralela. Esse modo de operação
resulta em atrasos mínimos na transferência de cada palavra, pois todos os bits de dados são transmitidos
simultaneamente, sendo que, para transmitir n bits, são utilizados n fios ou linhas. Dessa forma, cada bit tem
sua própria linha e todos os n bits de um grupo são transmitidos em um pulso de clock de um dispositivo para
outro.
Uma desvantagem desse tipo de transmissão, no entanto, decorre do fato de mais linhas paralelas implicarem
em chips maiores, com mais portas, ocupando mais espaço. Em função disso, o uso de periféricos externos
com portas paralelas para comunicação tem se tornado menos usual. Em geral, somente alguns conversores
analógicos-digitais de alta velocidade têm mantido o uso de portas paralelas. Os microcontroladores usam as
suas portas de entrada e saída digitais, normalmente duas ou três de 8 bits cada, para se comunicarem com
periféricos externos de interface paralela.
Transmissão serial
•
•
•
A transmissão serial possui a vantagem principal de utilizar poucos fios ou linhas para a transmissão. Os
dados, neste caso, são transmitidos por meio de um único bit por vez, usando um intervalo de tempo fixo para
cada bit. Assim, todos os bits de dados são transmitidos em uma única linha de maneira serial, com apenas
um único bit enviado com cada pulso de clock.
Então, para um byte (8 bits), como 10011010, ser enviado da origem para o destino, ele pode ser transmitido a
partir do bit menos significativo (0), seguido pelos bits seguintes até o mais significativo (1), tudo através de
uma única linha de comunicação.
Existem dois tipos de transmissão serial – assíncrona e síncrona – e ambas usam sincronização de bits. A
sincronização de bits é uma função necessária para se determinar quando o início e o fim da transmissão de
dados ocorrem. Dessa forma, ela ajuda o receptor a saber quando os dados começam e terminam durante
uma transmissão. Portanto, a sincronização de bits fornece controle de temporização.
Transmissão assíncrona
A transmissão assíncrona envia apenas um caractere (byte) por vez. A sincronização de bits entre os
dois dispositivos é possível usando o bit de início e o bit de parada. O primeiro indica o início dos
dados, isto é, alerta o receptor para a chegada de um novo grupo de bits. Portanto, um bit de início,
geralmente 0, deve ser adicionado no começo de cada byte. Já o bit de parada indica o fim dos
dados, ou seja, permite que o receptor saiba que o byte está concluído. O bit de parada é,
geralmente, de valor 1. A adição de bits ao início e ao final aumenta o número de bits de dados. Logo,
mais largura de banda é consumida na transmissão assíncrona.
Transmissão síncrona
A transmissão síncrona não usa bits de início e parada. Nesse método, o fluxo de bits pode ser
combinado em quadros maiores que podem conter vários bytes, ou seja, não há intervalo entre os
vários bytes no fluxo de dados. Na ausência de bits de início e parada, a sincronização de bits é
estabelecida entre o emissor e o receptor por um bit de temporização, um pino adicional de clock que
sincroniza a transmissão de cada bit. Logo, para receber os dados sem erros, o receptor e o emissor
operam na mesma frequência de clock. Como vários bytes podem ser colocados na transmissão sem
qualquer espaço entre eles, é responsabilidade do receptor separar o fluxo de bits em bytes para
reconstruir as informações originais.
Protocolo serial I2C
O I2C, abreviatura de Inter-Integrated Circuit (Circuito Inter integrado), é um protocolo de barramento serial de
curta distância desenvolvido pela Philips Semiconductor para melhorar a comunicação entre o núcleo
controlador e vários outros chips envolvidos em torno do núcleo. Ele foi criado para reduzir os custos de
fabricação de produtos eletrônicos.
1
Antes do I2C
Antes do I2C as comunicações chip a chip utilizavam diversos fios em uma interface paralela, muitas
vezes exigindo chips periféricos com mais#use delay(clock=4MHz)
#INT_EXT
void int_externa0() {
output_toggle(PIN_D1);
}
void main () {
enable_interrupts(GLOBAL);
enable_interrupts(INT_EXT);
while(true) {
output_toggle(PIN_D0);
delay_ms(1000);
}
}
Verifica-se que o clique no botão de interrupção 0, botão RBO/INT na placa 4, irá alternar imediatamente entre
acesso e apagado o LED D1, não importando se o programa esteja na função de atraso (delay) que acende e
apaga o LED D0.
Trocando a instrução enable_interrupts(INT_EXT) por enable_interrupts(INT_EXT_H2L) ou
enable_interrupts(INT_EXT_L2H) pode-se controlar se a interrupção ocorre na borda de descida (H2L) ou
subida (L2H), respectivamente.
Para habilitar e usar outras interrupções externas, por exemplo, a interrupção 1, basta usar as diretivas:
c
#INT_EXT1
void externa1() {
………………;
}
E também
c
enable_interrupts(GLOBAL);
enable_interrupts(INT_EXT1);
Interrupção externa no Arduino
O Arduino Uno usa os pinos 2 e 3 como pinos de interrupção externa. Na montagem da Figura 14, o pino 2
permanece em nível alto, indo a nível baixo quando se aperta o botão.
Figura 14 – Montagem no simulador Tinkercad.
Essa transição de nível alto para baixo é capturada pelo programa a seguir utilizando a interrupção externa
com a função attachInterrupt() que chama a ISR ledchange.
cpp
#include
#include
#include
// OLED display endereço
#define OLED_ADDR 0x3C
Adafruit_SSD1306 display(-1);
void setup() {
// Inicializa e limpa o display
display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR);
display.clearDisplay();
display.display();
// Escreve uma linha de texto
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(27,30);
display.print("Ola!");
// Atualiza o display
display.display();
}
void loop() {
//
}
Empregando interrupções
Neste vídeo, falaremos sobre as interrupções.
Conteúdo interativo
Acesse a versão digital para assistir ao vídeo.
Verificando o aprendizado
Questão 1
A contagem de pulsos por segundo deve ser usada para se saber a taxa de radiação medida por um detector
de radiação. Quantas interrupções são necessárias para contar os pulsos por segundo que chegam a um pino
de um microcontrolador?
A
Duas interrupções externas
B
Uma interrupção externa e uma interna
C
Duas interrupções internas e uma externa
D
Nenhuma interrupção
E
Uma interrupção interna
A alternativa B está correta.
Uma interrupção externa permite chamar uma rotina de interrupção que incremente um contador a cada
pulso percebido no pino externo. Uma interrupção interna por temporizador permite que a cada intervalo de
um segundo uma rotina informe quantos pulsos foram contados e indique a taxa de radiação.
Questão 2
O que o programa para o compilador CCS C a seguir faz em uma placa com PIC?
plain-text
#include
#fuses XT, NOWDT, NOMCLR
#use delay(clock=4MHz)
Int16 c = 0;
#INT_EXT
void int_externa0() {
c++;
}
void main () {
enable_interrupts(GLOBAL);
enable_interrupts(INT_EXT_L2H);
while(true) {
}
}
A
Incrementa a variável c toda vez que o pino da interrupção externa 0 muda de estado baixo para alto.
B
Incrementa a variável c toda vez que o pino da interrupção externa 0 muda de estado alto para baixo.
C
Incrementa a variável c toda vez que o pino da interrupção externa 0 muda de estado.
D
Incrementa a variável c a cada segundo.
E
Incrementa a variável c a cada segundo se o pino da interrupção externa 0 muda de estado.
A alternativa A está correta.
As aplicações possuem motores DC com acionadores de movimento e, portanto, a modulação por largura
de pulso, PWM, pode ser usada nessas aplicações.
5. Conclusão
Considerações finais
Neste tema, discutimos os tipos mais comuns de periféricos externos aos microcontroladores e como acioná-
los usando protocolos de comunicação, bem como receber acionamento destes por interrupção.
Também exemplificamos a programação e a comunicação com periféricos externos em microcontroladores da
família PIC e da plataforma Arduino, mostrando como testar esses códigos em simuladores.
Agora você tem condições de projetar sistemas que empreguem periféricos externos conectados aos
microcontroladores.
Podcast
Para encerrar, ouça sobre o tema Periféricos Externos.
Conteúdo interativo
Acesse a versão digital para ouvir o áudio.
Explore +
Para saber mais sobre os assuntos tratados neste tema, leia os artigos:
Automação de tanques para aquicultura, de Kevin Manoel Guimarães e Daniel Lohmann.
Construção de um protótipo de um braço robótico microcontrolado com interface de comunicação com
um microcomputador, de Leandro Almeida Santos e Enrique Peter Rivas Padilla.
Referências
MONK, S. Programação com Arduino: começando com sketches. 1. ed. Porto Alegre: Bookman, 2017.
OLIVEIRA, A. S. de; ANDRADE, F. S. de. Sistemas embarcados: hardware e firmware na prática. 1. ed. São
Paulo: Érica, 2010.
PECKOL, J. K. Embedded Systems: a contemporary design tool. 1 ed. Nova Jersey, EUA: Wiley, 2019.
ZANCO, W. da S. Microcontroladores PIC18 com Linguagem C: uma abordagem prática e objetiva. São Paulo:
Érica, 2010.
•
•
Periféricos externos
1. Itens iniciais
Propósito
Preparação
Introdução
1. Funções de programação de protocolos de transmissão de dados em microcontroladores para a comunicação com dispositivos externos
Transmissão de dados
Transmissão paralela
Transmissão serial
Transmissão assíncrona
Transmissão síncrona
Protocolo serial I2C
Antes do I2C
Após o I2C
Comentário
Enviando uma mensagem
Ouvindo uma mensagem
Funções para protocolo I2C no Arduino
Código para o I2C mestre
Código para o I2C escravo
Funções para PIC
Protocolo UART
UART e RS232
Comentário
Funções para uso de porta serial em PIC
printf
putc
if(kbhit())
getc()
Funções para uso de porta serial em Arduino
Serial.print()
Exemplo
Exemplo
Serial.available()
Serial.read()
readStringUntil()
Protocolo SPI
Comentário
Simulando a transmissão de dados
Conteúdo interativo
Verificando o aprendizado
2. Sensores e atuadores para a programação de sistemas de controle embarcados
Conceito de sensores e atuadores
Exemplo
Tipos de sensores quanto ao sinal gerado
Exemplo
Utilização de sensores digitais
Alimentação do sensor
Saída do sensor
Utilização de sensores analógicos
Sensor de temperatura com Arduino
Exemplo
Sensor de ultrassônico com Arduino
Utilização de atuadores
Comentário
Empregando sensores e atuadores
Conteúdo interativo
Verificando o aprendizado
Questão 2
3. Funções para a programação de mostradores na criação de interfaces com o usuário
Display de caracteres e gráfico
LCD (Liquid Crystal Display – Display de Cristal Líquido)
Comentário
Interface de LCD 16×2 com microcontrolador
Atenção
LiquidCrystal()
begin()
setCursor()
clear()
Display de 7 segmentos
Exemplo
Código para usar display de 7 segmentos no PIC
Displays gráficos
Exemplo
Display OLED
Atenção
Código para usar um OLED no Arduino
Empregando displays
Conteúdo interativo
Verificando o aprendizado
O trecho de código a seguir foi usado para escrever em um LCD de 16 caracteres (colunas) por linha em duas linhas, ligado a uma placa Arduino. Onde será escrito o dígito 5?lcd.setCursor(0, 1);lcd.print(“125”);
4. Programação de interrupções para o recebimento otimizado de dados de dispositivos externos ao microcontrolador
Interrupções
Mecanismo de interrupção
Atenção
Curiosidade
Atenção
Aplicações de interrupções
Interrupção
Condições importantes
Eventos críticos
Condição de erro
Controle do tempo
Tipos e formas de interrupções externas
Atenção
Sinalizadores e prioridade de interrupção
Começo da rotina
Final da rotina
Exemplo
Cuidados no uso de interrupção
Recomendação
Programação de interrupção externa
Interrupção externa no PIC
Interrupção externa no Arduino
Empregando interrupções
Conteúdo interativo
Verificandoo aprendizado
Questão 2
O que o programa para o compilador CCS C a seguir faz em uma placa com PIC?
5. Conclusão
Considerações finais
Podcast
Conteúdo interativo
Explore +
Referênciasde 20 pinos usados para endereçamento, seleção,
controle e transferência de dados entre os chips.
2 Após o I2C
O I2C realiza comunicações chip a chip usando apenas dois fios em uma interface serial, permitindo
que eles se comuniquem com menos pinos.
Os dois fios usados no I2C são chamados de clock (SCL) e dados (SDA). Esses dois fios carregam
endereçamento, seleção, controle e dados, um bit de cada vez.
O fio SDA carrega os dados, enquanto o fio SCL sincroniza o emissor e o receptor durante a transferência;
portanto, o I2C é de transmissão síncrona.
Comentário
Chips que usam o barramento I2C podem realizar a mesma função que suas contrapartes de interface
paralela maiores, mas com muito menos pinos. Isso reduz muito o tamanho e o custo desses chips.
Os dispositivos I2C são classificados como mestre (master) ou escravo (slave), sendo o conjunto de
dispositivos classificados como barramento I2C. Um dispositivo que inicia uma mensagem é chamado de
mestre, enquanto outro que responde a uma mensagem é chamado de escravo.
Um dispositivo pode ser somente mestre, somente escravo ou alternar entre mestre e escravo,
conforme a necessidade do aplicativo.
O barramento I2C, como mostrado na Figura 1, pode conectar muitos chips, como conversores (ADC e DAC),
microcontroladores (µC) e outros em apenas dois fios. Cada dispositivo escravo I2C tem seu próprio endereço
exclusivo.
Enviando uma mensagem
Quando um mestre envia uma mensagem, inclui
o endereço do escravo no início da mensagem.
Ouvindo uma mensagem
Todos os dispositivos no barramento ouvem a
mensagem, mas apenas o escravo que
reconhece o seu próprio endereço participa da
transferência.
Figura 1 – Barramento I2C.
O I2C também oferece suporte a vários dispositivos mestre (master) no barramento ao mesmo tempo, um
recurso poderoso que otimiza o uso deste último, reduzindo ao mínimo o tráfego de mensagens.
Para oferecer suporte a vários mestres, o I2C deve resolver conflitos de sinal quando dois ou mais dispositivos
mestres tentarem falar no barramento ao mesmo tempo.
Esse feito, chamado de detecção de perda de arbitragem de barramento, permite que um mestre detecte
quando seus sinais de barramento estão em conflito com os de outro. Um mestre que assim os detecta
encerra seu uso do barramento, permitindo que a mensagem gerada por outro mestre cruze o barramento
ileso.
Aplicações iniciais para I2C incluíam controle de volume e contraste em rádios e televisores.
O I2C se expandiu para incluir uma ampla gama de aplicativos.
Atualmente, o I2C pode ser encontrado em uma variedade de sistemas computacionais, industriais, de
entretenimento, médicos e militares, com potencial de crescimento quase ilimitado.
Funções para protocolo I2C no Arduino
O ambiente de desenvolvimento da plataforma Arduino utiliza biblioteca Wire para implementar comunicação
por protocolo I2C. No Arduino Uno, os pinos para I2C são A4 (SDA) e A5 (SCL). Um exemplo de comunicação
entre duas placas Arduino Uno pode ser montado como mostrado na figura 2, realizada no simulador
Tinkercad. Nesta montagem, os pinos A4, A5 e GND de cada placa são ligados entre si.
Figura 2 – Montagem para comunicação I2C entre duas placas Arduino Uno.
Na placa Arduino da esquerda, na Figura 2, um botão (push button) é ligado entre os pinos 12 e GND. Na placa
da direita, um LED é montado entre os pinos 13 e GND. No programa, ao se pressionar o botão na placa
mestre (Arduino Uno da esquerda), o LED acende na placa escravo (Arduino Uno da direita). Os códigos para a
placa mestre e para a placa escravo são mostrados mais adiante.
Observe bem o código para verificar como a transmissão é configurada com o escravo definindo seu
endereço como 5 no setup(). Quando o botão é pressionado, a placa da esquerda emite um caractere H. Caso
contrário, ela emite um caractere L. Assim, quando recebe o caractere, a placa escravo apaga ou acende o
LED em função do caractere recebido.
•
•
•
Código para o I2C mestre
cpp
#include
void setup() {
Wire.begin();
pinMode(12, INPUT_PULLUP);
}
void loop() {
// Endereço do Escravo é 5
if (digitalRead(12) == LOW) {
Wire.beginTransmission(5);
Wire.write('H');
Wire.endTransmission();
delay(100);
} else {
Wire.beginTransmission(5);
Wire.write('L');
Wire.endTransmission();
delay(100);
}
}
Código para o I2C escravo
cpp
#include
void setup() {
// Endereço é 5
Wire.begin(5);
Wire.onReceive(react);
pinMode(13,OUTPUT);
digitalWrite(13,LOW);
}
void loop() {
}
void react(int n) {
while (Wire.available()) {
char c = Wire.read();
if (c=='H') {
digitalWrite(13,HIGH);
} else {
digitalWrite(13,LOW);
}
}
}
Funções para PIC
O compilador CCS oferece suporte para o I2C baseado em hardware e um dispositivo I2C mestre baseado em
software.
As funções principais da biblioteca I2C para PIC são:
i2c_start() Emite um comando de partida quando no modo mestre I2C.
i2c_write(data) Envia um único byte pela interface I2C.
i2c_read() Lê um byte da I2C.
i2c_stop() Emite um comando de parada quando no modo mestre I2C.
i2c_poll() Retorna um TRUE se o hardware recebeu um byte no buffer.
Marcos Santana Farias.
Protocolo UART
UART (Universal Asynchronous Transmitter Receiver – Transmissor Receptor Universal Assíncrono), é o
protocolo mais comum usado para comunicação serial full duplex, ou seja, onde o dispositivo pode transmitir e
receber dados ao mesmo tempo.
Como seu próprio nome sugere, ele foi projetado para realizar comunicação assíncrona, enviando e recebendo
dados de um sistema para outro. Assim, o transmissor e o receptor usam os parâmetros bit de início, bit de
parada e uma temporização para sincronizar uns com os outros.
Dessa forma, como o protocolo não tem sinal de sincronismo, um parâmetro muito importante é o Baud Rate,
que especifica a velocidade de recepção e envio, sendo que os dois dispositivos devem utilizar a mesma taxa.
A unidade do Baud Rate é bits por segundo (bps).
As taxas de comunicação assíncrona mais comumente usadas são 9600, 33600 e 115200 bps.
Em geral, um pino denominado RX é usado para representar o pino receptor de uma comunicação serial e TX
representa o transmissor em microcontroladores. O TX deve ser ligado no RX, ou seja, transmissor enviando
para o receptor.
UART e RS232
O UART é um protocolo para transferir os dados em série de um nó para outro. Logo, especialmente em
comunicação serial, o protocolo é obrigatório, obrigando-nos a informar ao sistema quando começarmos a
transmitir dados, quantos bits existem e como a transferência deles terminará. Todas essas regras estão
incluídas no protocolo.
Protocolo
Um protocolo é conjunto de regras adotadas por todas as partes que participam na comunicação, a fim
de obter uma comunicação livre de erros.
Entretanto, quando queremos exibir nossos dados no computador, devemos transferi-los através da porta
COM. Nesse caso, temos o conceito de padrões de comunicação, que indica a camada física para transferir os
dados.
•
•
Comentário
Os antigos computadores possuíam portas que seguiam o padrão RS232 para aceitar os dados, que
empregava um conector de nove pinos (DB9).
De acordo com a norma RS232:
A Lógica 1 (nível alto) é aceita entre -3 V a -25 V.
A Lógica 0 (nível baixo) é aceita entre +3 V a +25 V.
O protocolo UART gera dados de acordo com a lógica TTL, nos quais a lógica 1 é aceita entre 2,4 V a 5 V e
lógica 0 entre 0 a 0,4 V.
Portanto, os dados do protocolo UART não podem ser transferidos diretamente na porta RS232 do
computador.
Um driver de linha, ou conversor chamado MAX232, é usado para converter níveis TTL em níveis RS232. Para
se comunicar com computadores modernos por UART pode-se usar conversores conhecidos como USB/Serial
UART. Estes criam uma COM virtual que é usada assim como os computadores antigos usavam a RS232.
Da mesma forma, temos outros padrões que são perfeitamente compatíveis com o protocolo UART, como os
padrões RS485 e RS422, muito usados em ambientesindustriais.
Funções para uso de porta serial em PIC
No compilador CCS, temos funções para enviar dados pela porta serial. O código a seguir pode ser usado
para acessar a porta serial na placa 4 (PICGenios) do simulador PICSimLab.
Verifique que o programa envia mensagens e depois fica aguardando caracteres pela porta serial. Quando o
programa recebe um caractere, ele o devolve.
•
•
c
#include
#fuses NOMCLR INTRC_IO
#use delay(clock = 8000000)
#use rs232(uart1, baud = 9600) // Inicializa módulo UART
#include
char message[] = "Microcontrolador PIC18F4550 - exemplo UART" ;
char i, j, textsize;
void main(){
setup_oscillator(OSC_8MHZ);
putc(13); // Vai para a primeira coluna
printf("Ola Mundo!"); // Escreve na UART
delay_ms(5000);
putc(13);
putc(10); // Nova linha
textsize = strlen(message); // Conta No de caracteres
for(j = 0; j3, enviando a letra A com a função Wire.write('A'). A função
Wire.endTransmission() encerra a transmissão com o escravo I2C e libera o barramento para outras
transmissões.
2. Sensores e atuadores para a programação de sistemas de controle embarcados
Conceito de sensores e atuadores
Para que um sistema embarcado baseado em microcontrolador realize um controle, ele precisa ser capaz de
receber dados externos de dispositivos que medem grandezas físicas ou de ler um sinal digital de entrada,
além de poder atuar de alguma forma em dispositivos externos, para acender uma luz, por exemplo.
Em outras palavras, um sistema embarcado para controle deve ser capaz de ler dados de sensores e enviar
dados para atuadores.
E o que são sensores e atuadores?
Transdutor é o termo coletivo utilizado para os sensores, que podem ser acionados para detectar
uma ampla gama de diferentes formas de energia; e os atuadores, que podem ser usados para
alternar tensões ou correntes, “atuando” em um sistema.
Um sensor monitora as condições ambientais, tais como níveis de fluido, temperaturas, vibrações ou tensão. À
medida que essas condições ambientais mudam, elas são percebidas por um sensor, que emite um sinal
elétrico proporcional à condição. Logo, um sensor é um dispositivo que detecta e responde a algum tipo de
entrada do ambiente físico.
A entrada específica pode ser luz, calor, movimento, umidade, pressão ou vários outros de um grande
número de fenômenos ambientais.
A saída é um sinal elétrico que, quando recebido por um microcontrolador, pode ser processado e
convertido para exibição legível no local do sensor ou transmitido eletronicamente por uma rede para
leitura ou processamento posterior.
O sinal elétrico fornecido pelo sensor também pode ser usado pelo microcontrolador para estabelecer um
controle. Em função do processamento do sinal, o código embarcado pode enviar um sinal a um atuador de
um sistema e este realizar um movimento, ou seja, ele recebe um sinal elétrico e o combina com uma fonte de
energia para criar movimento físico.
Um atuador pode ser:
pneumático
hidráulico
elétrico
térmico
•
•
•
•
•
•
magnético
Exemplo
Um pulso elétrico pode conduzir o funcionamento de um motor dentro de um sistema de controle.
Os atuadores estão presentes em quase todas
as máquinas ao nosso redor, desde sistemas de
controle de acesso eletrônico simples, como o
vibrador dos telefones celulares, até
eletrodomésticos, veículos, dispositivos
industriais e robôs. Exemplos comuns de
atuadores incluem motores elétricos, motores
de passo, macacos hidráulicos, dentre outros.
Tipos de sensores quanto
ao sinal gerado
Sensores analógicos estão presentes nos mais diversos equipamentos e sistemas. Eles produzem um sinal de
saída contínuo ou uma tensão que é geralmente proporcional à quantidade sendo medida.
Quantidades físicas como temperatura, velocidade, pressão, deslocamento, deformação etc. são todas
grandezas analógicas, pois tendem a ser de natureza contínua.
Exemplo
A temperatura de um líquido pode ser medida usando um termopar, que responde continuamente com
variações de tensão às alterações de temperatura, à medida que o líquido é aquecido ou resfriado.
Os sensores digitais produzem uma saída discreta representando um número ou dígito binário, como um nível
lógico “0” ou um nível lógico “1”. Nesse sentido, um simples botão (push button) de uma placa pode ser
considerado um sensor digital, indicando quando o usuário o pressionou.
Vejamos exemplos de utilização de sensores digitais e analógicos usados com microcontroladores.
Utilização de sensores digitais
Um exemplo de sensor digital é o sensor PIR (passive infrared sensor), que permite perceber movimento,
quase sempre usado para detectar se um humano entrou ou saiu da faixa de percepção do sensor.
•
Eles são pequenos, baratos, de baixa potência, fáceis de usar e não se desgastam. Por essa razão, são
comumente encontrados em aparelhos instalados em residências ou empresas.
Os PIRs são basicamente feitos de um sensor piroelétrico que pode detectar níveis de radiação infravermelha
(RI). Todos os objetos emitem alguma radiação de baixo nível, e quanto mais quente algo está, mais radiação é
emitida.
O sensor em um detector de movimento é dividido em duas metades. A razão para isso é que estamos
procurando detectar o movimento (mudança) e não os níveis médios de RI.
As duas metades estão ligadas de modo que se anulam mutuamente. Se uma metade perceber mais ou
menos radiação IR do que a outra, situação esta que é típica de movimento na área de detecção (detecting
area), a saída será alta ou baixa.
A Figura 4 ilustra a atuação do sensor PIR.
Figura 4 – Atuação do sensor PIR.
Sensor PIR com Arduino
A montagem da Figura 5, feita no simulador Tinkercad, mostra um exemplo de teste com o uso de sensores
digitais com microcontroladores. Nesta, um sensor PIR está ligado a uma placa Arduino Uno.
•
Figura 5 – Montagem de sensor PIR com Arduino.
Alimentação do sensor
Os fios vermelho e preto ligados ao sensor PIR
são a alimentação do sensor, retirada do
Arduino.
Saída do sensor
O fio amarelo é a saída do sensor PIR, que
informa nível lógico alto quando percebe
movimento.
O programa a seguir é usado para simular a aquisição de dados do sensor PIR.
cpp
void setup() {
pinMode(2, INPUT); // Pino 2 como entrada
pinMode(13, OUTPUT); // Pino 13 como saída
}
void loop() {
if (digitalRead(2) == HIGH) // Verifica se o sensor PIR está acionado
{
digitalWrite(13, HIGH); // Liga o LED da placa
delay(100); // Espera 100 milissegundos
digitalWrite(13, LOW); // Desliga o LED da placa
delay(100); // Espera 100 milissegundos
}
}
A simulação da percepção de movimento pelo sensor PIR é feita clicando-se no centro do sensor e
movimentando um círculo que aparece em área sombreada na frente deste, como pode ser visto na Figura 5.
Talvez seja necessário diminuir o zoom do campo onde é observada a montagem para se ter acesso à área
sombreada. Pelo código, a percepção de movimento faz com que o LED da placa pisque.
Utilização de sensores analógicos
Sensor de temperatura com Arduino
Quando usamos sensores analógicos precisamos medir, em geral, uma tensão que é proporcional à
quantidade sendo medida. Esse é o caso dos sensores de temperatura.
Exemplo
O LM35 ou o TMP36 são sensores de temperatura linear analógicos. Isso significa que a tensão de saída
é proporcional à temperatura.
A tensão de saída aumenta em 10 mV para cada aumento de 1 grau Celsius na temperatura. O Arduino pode
ler a entrada de 0 a 5 V com suas entradas analógicas ligadas aos conversores analógicos/digitais (ADC). O
Arduino armazena isso como um número de 10 bits (0-1023).
O sensor de temperatura pode ser montado como mostrado na Figura 6.
Figura 6 – Montagem de sensor de temperatura com Arduino.
O código a seguir mostra a aquisição dos dados do sensor e a conversão para temperatura, com envio do
valor em graus Celsius para a porta serial. O código pode ser copiado e editado no Tinkercad.
cpp
char grau = 176; //Valor ASCII de Grau
void setup()
{
pinMode(A0,INPUT);
Serial.begin(9600);
}
void loop()
{
int tmp = analogRead(A0);
float voltage = (tmp * 5.0)/1024; //(5*temp)/1024 para converter em tensão
float milliVolt = voltage * 1000; //para converter em milivolts.
float tmpCel = (milliVolt-500)/10 ; //Para TMP36 sensor. Range(−40°C a +125°C)
Serial.print("Celsius: ");
Serial.print(tmpCel);
Serial.println(grau);
delay(1000);
}
O valor de temperatura, simulado no sensor, pode ser alterado ao se clicar nos ícones do Tinkercad durante a
simulação, alterando o valor como mostrado na Figura 7.
Figura 7 – Alteração de temperatura no simulador.
Sensor de ultrassônico com Arduino
Um sensor muito utilizado em projetos de robótica é o sensor ultrassônico HC-SR04.
Ele usa sonar para determinar a distânciade um objeto, assim como os morcegos.
Oferece excelente detecção de alcance sem contato, com alta precisão e leituras estáveis em um
pacote fácil de usar.
Ele vem completo com módulos transmissores e receptores ultrassônicos.
Veja na Figura 8 o que acontece.
•
•
•
Figura 8 – Princípio de funcionamento do sensor de distância por ultrassom.
O transmissor (pino de trigger) envia um sinal, um som de alta frequência. Quando o sinal encontra um objeto,
ele é refletido e o receptor (pino de eco) o recebe. O tempo entre a transmissão e a recepção do sinal nos
permite calcular a distância até um objeto. Isso é possível porque sabemos a velocidade do som no ar, que é
de aproximadamente 343 m/s.
O sensor de distância do simulador Tinkercad usa o mesmo pino para transmitir e receber o sinal. A Figura 9
mostra uma possível montagem.
Figura 9 – Montagem do sensor de distância no simulador Tinkercad.
A entrada digital 7 da placa é utilizada para realizar a medida de tempo entre o pulso enviado e recebido. Esse
tempo é usado para calcular a distância. O código se encontra a seguir.
cpp
int trigPin = 7; // Trigger
int echoPin = 7; // Eco
long duracao, cm;
void setup() {
Serial.begin (9600);
}
void loop() {
// O sensor é disparado por um pulso alto (HIGH) de 10 ou mais microssegundos.
// Dê um pulso baixo (LOW) curto antes para garantir um pulso alto limpo:
pinMode(trigPin, OUTPUT);
digitalWrite(trigPin, LOW);
delayMicroseconds(5);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
// Lê o sinal do sensor em uma duração de tempo em microssegundos
pinMode(echoPin, INPUT);
duracao = pulseIn(echoPin, HIGH);
// Converte o tempo em uma distância
cm = (duracao/2) / 29.1; // Divide por 29.1
Serial.print(cm);
Serial.print("cm");
Serial.println();
delay(250);
}
Veja que um pulso alto é preciso para disparar o envio do pulso ultrassônico. Para se ler o intervalo de tempo
entre o pulso de envio e retorno é usada a função pulseIn().
Se o valor HIGH é passado para a função (como nesse exemplo), esta última espera o pino ir do estado LOW
para o HIGH, começa a contar o tempo e, então, espera o pino ir para o estado LOW para finalizar essa
contagem, devolvida em microssegundos.
Como queremos saber a distância em centímetros, tivemos que usar a velocidade do som no ar em
centímetros por segundo, 34300 cm/s. Depois, calculamos quantos microssegundos o som leva para
percorrer um centímetro. Para isso, basta resolver a seguinte regra de três:
De acordo com o resultado, são necessários 29.15 µs para o som percorrer um centímetro.
Agora, com o valor da medida de tempo em microssegundos para o som ir e voltar ao sensor (variável
duração), basta dividir esse tempo por 29.15 para descobrir quantos centímetros ele teve que percorrer.
1000000 µs => 34300 cm Xµs => 1cm
Como o som precisa fazer o trajeto de ida e volta, finalizamos com a divisão do resultado por dois para termos
a distância entre o sensor e o objeto.
Utilização de atuadores
Diversos tipos de atuadores podem ser usados em sistemas embarcados com microcontroladores. Além de
motores DC, que podem ser acionados com sinal PWM, há também outros tipos, como os motores de passo e
os servo motores.
A característica de um servo motor é permitir que seja colocado em posições angulares específicas por um
sinal codificado. Servos são usados em aviões controlados por rádio, para posicionar superfícies de controle
como elevadores e lemes em robótica e muitas outras aplicações.
Comentário
Os servos variam em tamanho, desde miniatura para pequenos projetos até tamanhos maiores para fins
industriais.
Dentro da biblioteca do Arduino existe um conjunto de funções que permite o envio de comando para servo
motores pequenos. O código a seguir faz uso dessas funções, que estão na biblioteca servo.h.
Veja que a variável tipo servo que foi criada pode receber o comando write e, como parâmetro, o ângulo para
posicionar o servo motor.
Assim, um loop for para movimentar o servo de 0 a 90 graus, e de 90 a 0, continuamente, é criado com a
variação do ângulo de 1 em 1 grau.
O intervalo de 50 milissegundos após cada alteração em 1 grau permite o movimento contínuo, que leva no
total 9 segundos (180x50 milissegundos) para retornar ao ponto inicial.
A montagem no simulador Tinkercad é mostrada na Figura 10.
cpp
#include
#define SERVO 9 // Porta Digital 9 PWM
Servo s; // Variável tipo Servo
int pos; // Posição Servo
void setup ()
{
s.attach(SERVO);
s.write(0); // Inicia o motor na posição zero
}
void loop()
{
for(pos = 0; pos = 0; pos--)
{
s.write(pos);
delay(50);
}
}
Figura 10 – Montagem de atuador servo motor no simulador Tinkercad.
Empregando sensores e atuadores
Neste vídeo, veremos como utilizar os sensores e atuadores.
Conteúdo interativo
Acesse a versão digital para assistir ao vídeo.
Verificando o aprendizado
Questão 1
Um sensor de temperatura possui uma conversão de tensão para temperatura dada pela fórmula abaixo:
Temperatura em graus Celsius (°C) = [(tensão em mV) - 500] / 10.
Sabendo que um microcontrolador pode ler valores de 0 a 3,3 volts, qual o range de temperatura que poderá
ser medido?
A
-45 °C a 500 °C
B
50 °C a 1000 °C
C
-50 °C a 280 °C
D
0 a 500 °C
E
-45 °C a -450 °C
A alternativa D está correta.
Para tensão de 0 V, a temperatura é igual a (0-500)/10 = -50 °C.
Para tensão de 3,3 V, a temperatura é igual a (3300 – 500)/10 = 280 °C.
Logo, o range varia de -50 °C a 280 °C.
Questão 2
Foi solicitado um projeto, utilizando Arduino, que controla a frequência com que um LED pisca em função da
proximidade de um objeto. Para isso, se utilizou um sensor de distância por ultrassom. Dessa forma, quanto
mais próximo o objeto do sensor, mais rápido o LED deverá piscar. Escolha a opção que completa o código a
seguir de forma correta para conseguir esse efeito no LED do pino 13 do Arduino Uno.
plain-text
int trigPin = 7; // Trigger do sensor de distância
int echoPin = 7; // Eco do sensor de distância
long duracao, mm;
void setup() {
}
void loop() {
pinMode(trigPin, OUTPUT);
digitalWrite(trigPin, LOW);
delayMicroseconds(5);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
// Lê o sinal do sensor em uma duração de tempo em microssegundos
pinMode(echoPin, INPUT);
duracao = pulseIn(echoPin, HIGH);
// Converte o tempo em uma distância em mm (milímetros)
mm = (duracao/2) / 291;
// CÓDIGO para conseguir o efeito desejado
}
A
analogWrite (13, HIGH);
delay (mm);
B
digitalWrite (13, HIGH);
delay (mm);
C
digitalWrite (13, LOW);
delay (mm);
digitalWrite (trigPin, HIGH);
delay (mm);
D
digitalWrite (trigPin, HIGH);
delay (mm);
digitalWrite (trigPin, LOW);
delay (mm);
E
digitalWrite (13, HIGH);
delay (mm);
digitalWrite (13, LOW);
delay (mm);
A alternativa E está correta.
O código acende o LED no pino 13 com a função digitalWrite (13, HIGH). Em seguida, aguarda um intervalo
em milissegundos, com delay (mm). Esse valor em milissegundos (mm) é igual ao valor da distância para o
obstáculo em milímetros, calculada anteriormente. Depois apaga o LED pelo mesmo intervalo de tempo.
Com isso, quanto menor a distância, mais rápido o LED piscará.
3. Funções para a programação de mostradores na criação de interfaces com o usuário
Display de caracteres e gráfico
Caso o objetivo seja que o microcontrolador compartilhe informações ou mostre o que ele está tentando fazer,
é preciso conectar um mostrador de saída ou display.
Um mostrador de saída é algo que fornece uma maneira de mostrar informações do
microcontrolador.
Ou seja, o mostrador permite que o microcontrolador envie informações para ele. Já trabalhamos com outro
mostrador de saída bem simples, chamado LED (Light Emitting Diode), que emite luzquando é programado
para isso.
Começaremos agora a ver mais detalhes para a programação do LCD (Liquid Crystal Display – Display de
Cristal Líquido), de displays de 7 segmentos e de displays gráficos.
LCD (Liquid Crystal Display – Display de Cristal Líquido)
O LCD possui um material que une as propriedades do líquido e dos cristais. Ele tem uma faixa de temperatura
dentro da qual as partículas são essencialmente tão móveis quanto poderiam ser em um líquido, no entanto,
são reunidas em uma forma semelhante a um cristal.
Comentário
O LCD é um mostrador de saída muito mais informativo do que um único LED, pois é um display que
pode mostrar facilmente os caracteres em sua tela. Eles variam em tamanho, preço e configuração, e
podem ter desde algumas linhas até telas grandes. Alguns são até mesmo projetados especificamente
para um único aplicativo, tendo apenas a capacidade de exibir gráficos definidos.
O LCD recebe informações e controle, e os requisitos são restritos para garantir que as informações sejam
enviadas a ele de forma que possa aceitá-las apropriadamente.
Primeiro, há uma discrepância de velocidade entre o LCD e o microcontrolador, pois este último é muito mais
rápido que o primeiro. Portanto, o programa do microcontrolador deve estar totalmente ciente disso e
compensar o tempo que o LCD fica ocupado trabalhando em dados enviados anteriormente.
Os compiladores atuais já conhecem essa necessidade de temporização para acionar LCDs e
automaticamente atuam para que eles recebam os comandos e dados no tempo certo.
Interface de LCD 16×2 com microcontrolador
Um LCD muito popular é o que tem duas linhas e 16 caracteres por linha.
Um LCD desse tipo pode ser operado em dois modos: modo de 4 bits e modo de 8 bits. Ele possui dois
registradores: um de comando e outro de dados.
Além do pino GND e Vcc (alimentação de 5 V), o display possui 8 pinos para envio de dados em paralelo (DB0
a DB7), ajuste de contrastes (VEE), pino de controle para selecionar envio de dados para o registro de
comando ou de dados (RS), leitura ou escrita no registro (RW), pino de habilitação do dispositivo (EN) e
entrada para alimentação da luz de fundo (backlight) nos pinos Led+ e Led-.
Ao conectar as três linhas de seleção e as linhas de dados com o microcontrolador, as mensagens podem ser
exibidas no LCD.
Os caracteres escritos se baseiam na tabela ASCII. Neste LCD, cada caractere é exibido em uma matriz de
5x7 pixels.
A Figura 11 apresenta a pinagem desse módulo. O pino de terra (GND) é o pino 1.
Figura 11 – Pinagem de LCD 16x2.
Atenção
Quando o display LCD não está habilitado, as linhas de dados ficam em um estado de alta impedância.
Isso significa que não interferem na operação do microcontrolador quando o display não é usado.
A linha de controle EN (habilitar) é usada para permitir o envio de dados para o LCD. Uma transição de alto
para baixo nesse pino habilitará o módulo. Quando RS (Seleção de Registrador) é baixo, os dados devem ser
tratados como uma instrução de comando. Quando RS está alto, os dados que estão sendo enviados são
exibidos na tela. Assim, para exibir qualquer caractere na tela, definimos RS alto.
Quando RW (linha de controle de leitura/gravação) está baixa, as informações no barramento de dados estão
sendo gravadas no LCD. Quando RW está alto, o programa está efetivamente lendo o LCD. A linha RW sempre
será baixa.
Funções para uso de LCD de caracteres no PIC
Para programar a escrita em displays LCD de caracteres, é preciso seguir passos de envio de comandos com
temporização adequada, para que o display esteja preparado para receber os dados, o valor ASCII de cada
caractere que se deseja escrever.
Todo display possui um controlador que define que comandos devem ser utilizados. É esse controlador que
designa como devem ser os comandos para operar o LCD.
As funções auxiliam a utilização do LCD, realizam sua inicialização e também a dos passos e temporizações
necessárias para que o controlador tenha tempo de enviar os dados e acender os caracteres no display.
No compilador CCS, as funções lcd_init() e LCD_PUTC (utilizada como parâmetro da função printf) são as
principais usadas para operar o LCD.
O código a seguir pode ser utilizado no LCD da placa 4 (PICGenios) do simulador PICSimLab.
•
c
#include
#fuses HS, NOMCLR, NOWDT
#use delay(clock=20MHz)
#define LCD_DB0 PIN_D0
#define LCD_DB1 PIN_D1
#define LCD_DB2 PIN_D2
#define LCD_DB3 PIN_D3
#define LCD_DB4 PIN_D4
#define LCD_DB5 PIN_D5
#define LCD_DB6 PIN_D6
#define LCD_DB7 PIN_D7
#define LCD_E PIN_E1
#define LCD_RS PIN_E2
#include
void main () {
lcd_init();
printf(LCD_PUTC, "Usando LCD");
delay_ms(1000);
printf(LCD_PUTC, "\nTemp = %f", 23.6);
}
Funções para uso de LCD de caracteres em Arduino
No IDE do Arduino existem muitas funções, dentro da biblioteca LiquidCrystal, para operar um display LCD.
Essa biblioteca permite que uma placa Arduino controle as telas (LCDs) baseadas no chipset Hitachi HD44780
(ou compatível), encontrado na maioria dos LCDs baseados em caracteres.
As principais funções dessa biblioteca são encontradas a seguir.
LiquidCrystal()
Cria uma variável do tipo LiquidCrystal. O display pode ser controlado usando 4 ou 8 linhas de dados. Para
usar 4 bits, omita os números dos pinos de d0 a d3 e deixe essas linhas desconectadas. O pino RW pode ser
ligado ao terra (GND). Nesse caso, omita-o dos parâmetros dessa função.
Sintaxe: os parâmetros a seguir devem ser substituídos pelo número do pino do Arduino ao qual o pino
correspondente do display está ligado.
cpp
LiquidCrystal(rs, enable, d4, d5, d6, d7).
LiquidCrystal(rs, rw, enable, d4, d5, d6, d7).
LiquidCrystal(rs, enable, d0, d1, d2, d3, d4, d5, d6, d7).
LiquidCrystal(rs, rw, enable, d0, d1, d2, d3, d4, d5, d6, d7).
begin()
Inicializa a interface para a tela LCD e especifica as dimensões (largura e altura) da tela. A função begin()
precisa ser chamada antes de qualquer outro comando da biblioteca LCD.
Sintaxe: lcd.begin (colunas, linhas).
•
setCursor()
Posiciona o cursor do LCD, isto é, define a localização na qual o texto subsequente gravado no LCD será
exibido.
Sintaxe: lcd.setCursor (coluna, linha).
clear()
Limpa a tela LCD e posiciona o cursor no canto superior esquerdo.
Sintaxe: lcd.clear().
Uma montagem do display LCD no simulador Tinkercad é mostrada na Figura 12.
Figura 12 – Montagem do display 16x2 no Tinkercad.
O código a seguir mostra um contador sendo apresentado no display.
cpp
/*
Circuito:
* Pino LCD RS ligado ao pino digital 12
* Pino LCD Enable ligado ao pino digital 11
* Pino LCD D4 ligado ao pino digital 5
* Pino LCD D5 ligado ao pino digital 4
* Pino LCD D6 ligado ao pino digital 3
* Pino LCD D7 ligado ao pino digital 2
* Pino LCD R/W ligado ao GND
* Pino LCD GND ligado ao GND
* Pino LCD VCC ligado ao 5V
* Pino LCD VO ligado ao GND
* Pino LED+ ligado ao Resistor 220 ohms
* Resistor 220 Ohms ligado ao 5V
* Pino LED- ligado ao GND
*/
#include
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
void setup() {
lcd.begin(16, 2);
lcd.print("Usando o LCD");
}
void loop() {
lcd.setCursor(0, 1);
// Imprime o número de segundos
delay(1000);
lcd.print(millis() / 1000);
}
A função lcd.setCursor(0, 1) define a posição no LCD onde serão escritos os próximos caracteres. O
parâmetro (0, 1) corresponde à coluna e à linha respectivamente, começando em 0. Assim, nessa instrução o
LCD irá escrever na segunda linha e na primeira coluna.
Display de 7 segmentos
O display de 7 segmentos consiste em sete LEDs organizados de forma retangular, conforme mostrado na
Figura 13.
Cada um dos sete LEDs é chamado de segmento porque, quando iluminado, o segmento faz parte de um
dígito numérico (decimal e hexadecimal) a ser exibido.
Um oitavo LED adicional às vezes é usado dentro do mesmo pacote, permitindo a indicação de um ponto
decimal (DP) quando doisou mais monitores de 7 segmentos são conectados juntos para exibir números
maiores que dez.
Figura 13 – Display de 7 segmentos.
Cada um dos sete LEDs no display recebe um segmento posicional com um de seus pinos de conexão. Esses
pinos LED individuais são rotulados de A até G, representando cada LED individual. Os outros pinos LED são
conectados juntos para formar um pino comum.
Portanto, ao polarizar os pinos apropriados dos segmentos de LED em uma ordem específica, alguns
segmentos estarão acesos e outros não, permitindo que o padrão de caracteres desejado do número seja
gerado na tela. Isso nos permite exibir cada um dos dez dígitos decimais de 0 a 9 na mesma exibição de 7
segmentos.
Dependendo do dígito decimal a ser exibido, um conjunto específico de LEDs é polarizado diretamente.
Exemplo
Para exibir o dígito numérico 0, precisaremos acender seis dos segmentos de LED correspondentes a A,
B, C, D, E e F. Assim, os vários dígitos de 0 a 9 podem ser exibidos usando um display de 7 segmentos.
Código para usar display de 7 segmentos no PIC
Para escrever os dígitos no display de 7 segmentos, usaremos alguns artifícios de programação para facilitar.
Criaremos um vetor com 10 posições, uma para cada dígito.
Cada posição será um número binário de 8 dígitos, um para cada segmento do display (A, B, C, D, E, F,
G e ponto).
Nesse número binário, consideraremos o dígito menos significativo como o segmento A, o seguinte
como B, e assim sucessivamente.
Dessa forma, para acender somente o segmento A, escreveríamos o número binário 0b00000001.
O vetor dígitos[], cujo índice corresponde a um número de 0 a 9, fica assim:
c
digitos[] = {
0b00111111, 0b00000110, 0b01011011, 0b01001111,
0b01100110, 0b01101101, 0b01111100, 0b00000111,
0b01111111, 0b01100111}
A placa 4 do simulador PICSimLab possui 4 displays de 7 segmentos. Eles estão ligados na porta D (RD0 a
RD7 correspondendo de A até ponto) e são multiplexados por quatro chaves transistores, acionadas pelos
pinos RA2, RA3, RA4 e RA5 (entradas DISP1, DISP2, DISP3 e DISP4).
Como são multiplexados, cada display deve ser aceso por vez para apresentar um dígito. Esse intervalo entre
acender um display e outro deve ser rápido o suficiente para dar a ilusão visual de que todos estão acesos ao
mesmo tempo.
O código a seguir permite que se escreva os dígitos 1, 2, 3 e 4 em cada display. Ele pode ser usado para criar
um projeto no MPLAB para o PIC18F4550, compilar e usar o arquivo .hex gerado para rodar o simulador
PICSimLab.
•
•
•
•
c
#include
#fuses XT, NOMCLR, NOWDT
#use delay(clock=4MHz)
int8 digitos[] = {
0b00111111, 0b00000110, 0b01011011, 0b01001111,
0b01100110, 0b01101101, 0b01111100, 0b00000111,
0b01111111, 0b01100111
};
void ligaDisplay(int pino, int numero) {
output_bit (PIN_A3, 0);
output_bit (PIN_A4, 0);
output_bit (PIN_A5, 0);
output_bit (PIN_A2, 0);
output_bit (pino, 1);
output_d(digito[numero]);
delay_ms(25);
}
void main () {
while(true) {
ligaDisplay (PIN_A2, 1);
ligaDisplay (PIN_A3, 2);
ligaDisplay (PIN_A4, 3);
ligaDisplay (PIN_A5, 4);
}
}
Displays gráficos
Um display gráfico é exatamente o que seu nome sugere: um visor matricial capaz de exibir imagens, letras e
números gerados por meio do firmware do microcontrolador. Qualquer aplicativo que precisa exibir mais do
que apenas letras e números pode usar um display gráfico.
Os visores matriciais são identificados por dois conjuntos de números.
Exemplo
É o caso de 128x64. Esse display contém 128 pontos ao longo do eixo X (ou horizontal) e 64 pontos ao
longo do eixo Y (ou vertical).Cada um desses pontos, às vezes chamados de pixel, pode ser ligado e
desligado independentemente um do outro.
O programador faz uso do software para informar a cada ponto quando ligar e desligar, e a tecnologia utilizada
pode ser a do próprio LCD.
As configurações comuns de display LCD gráfico incluem 122x32, 160x160, 240x128 e 320x240.
Outra tecnologia para display gráfico que vem crescendo e se popularizando é a de OLED.
Display OLED
Os displays OLED (Organic Light-Emitting Diode – Diodo Orgânico Emissor de Luz) para uso em monitores de
TV têm uma excelente qualidade de imagem, apresentando cores brilhantes, contraste infinito, taxa de
resposta rápida e ângulos de visão amplos.
O principal componente de um display OLED é o emissor OLED, um material orgânico (à base de carbono) que
emite luz quando a eletricidade é aplicada. A estrutura básica de um OLED é uma camada emissiva
“ensanduichada” entre um cátodo (que injeta elétrons) e um ânodo (que remove elétrons).
Atenção
Os dispositivos OLED modernos usam muito mais camadas para torná-los mais eficientes e duráveis,
mas a funcionalidade básica permanece a mesma.
Os OLEDs são muito sensíveis ao oxigênio e à umidade, portanto, a camada de encapsulamento é crítica.
Mostradores gráficos de OLED para uso em sistemas embarcados podem ser encontrados por baixíssimo
custo no mercado. Os que trabalham com o controlador SSD1306 são um exemplo.
Ele pode se comunicar com o microcontrolador de várias maneiras, incluindo I2C e SPI.
O SPI é geralmente mais rápido do que o I2C, mas requer mais pinos de E/S. Por outro lado, o I2C requer
apenas dois pinos e pode ser compartilhado com outros periféricos I2C. Graças à versatilidade do controlador
SSD1306, o módulo vem em diferentes tamanhos e cores, por exemplo, 128x64 e 128x32, com OLEDs
brancos ou azuis.
E/S
Entrada e Saída
A tensão de operação do controlador SSD1306 é de 1,65 V a 3,3 V, enquanto o painel OLED requer uma tensão
de alimentação de 7 V a 15 V.
Todos esses diferentes requisitos de energia são satisfeitos usando os circuitos internos de carga. Isso torna
possível conectá-lo a um Arduino ou a qualquer microcontrolador de 5 V facilmente, sem usar nenhum
conversor de nível lógico.
Código para usar um OLED no Arduino
Usando a biblioteca Wire para o protocolo I2C e mais uma biblioteca específica para o controlador SSD1306, é
possível acionar um display OLED com o código a seguir no Arduino.
cpp
#include
#include
#include
// OLED display endereço
#define OLED_ADDR 0x3C
Adafruit_SSD1306 display(-1);
void setup() {
// Inicializa e limpa o display
display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR);
display.clearDisplay();
display.display();
// Escreve uma linha de texto
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(27,30);
display.print("Ola!");
// Atualiza o display
display.display();
}
void loop() {
//
}
Empregando displays
Neste vídeo, abordaremos sobre os displyas!
Conteúdo interativo
Acesse a versão digital para assistir ao vídeo.
Verificando o aprendizado
Questão 1
O trecho de código a seguir foi usado para escrever em um LCD de 16 caracteres (colunas)
por linha em duas linhas, ligado a uma placa Arduino. Onde será escrito o dígito 5?
lcd.setCursor(0, 1);
lcd.print(“125”);
A
Na segunda coluna da segunda linha.
B
Na primeira coluna da segunda linha.
C
Na terceira coluna da primeira linha.
D
Na terceira coluna da segunda linha.
E
Na segunda coluna da primeira linha.
A alternativa D está correta.
A função lcd.setCursor(0, 1) indica a posição no LCD onde serão escritos os próximos caracteres. O
parâmetro (0, 1) corresponde à coluna e à linha respectivamente, começando em 0. Assim, nesta instrução
o LCD começará a escrever o número 125 na segunda e na primeira coluna. Logo, o digito 5 será escrito na
terceira coluna da segunda linha.
Questão 2
O trecho de código a seguir para o compilador CCS C foi usado para escrever em um LCD de 16 caracteres
por linha em duas linhas, ligado a um microcontrolador PIC. O que será escrito e em que posição no LCD?
void main () {
lcd_init();
printf(LCD_PUTC, “ LCD 123”);
}
A
Será escrito “ LCD 123” na primeira linha.
B
Será escrito “LCD 123” na primeira linha.C
Será escrito “ LCD 123” na segunda linha.
D
Será escrito “LCD 123” na segunda linha.
E
Será escrito “123” na segunda linha.
A alternativa D está correta.
No CCS C a função printf foi adaptada para enviar caracteres ao LCD quando recebe o parâmetro
LCD_PUTC. Os caracteres são escritos como na função printf já conhecida.
Assim, o comando \n indica pular linha e a frase “LCD 123” é escrita na segunda linha.
4. Programação de interrupções para o recebimento otimizado de dados de dispositivos externos ao
microcontrolador
Interrupções
Considere este cenário:
Precisamos monitorar uma entrada de microcontrolador e responder a essa entrada ativa dentro de
apenas alguns microssegundos, e não em dezenas de milissegundos, como acontecia com os apertos
de botão humanos.
O programa pode estar em qualquer ponto de sua execução quando essa entrada for ativada.
É necessário fazer o equivalente a monitorar esse sinal não cem vezes por segundo, mas centenas de
milhares de vezes por segundo, enquanto são efetuadas todas as outras operações que nosso
programa precisa realizar.
Como podemos responder tão rapidamente e ainda ter tempo para fazer qualquer outro trabalho em nosso
programa?
As interrupções são a resposta.
Mecanismo de interrupção
Uma interrupção é um sinal assíncrono para a atenção do processador que pode ser originado no hardware ou
no software. O mecanismo de interrupção fornece uma maneira de evitar o desperdício de tempo do
processador, livrando-se de rotinas de pesquisa ineficazes em loops fechados.
As interrupções permitem que o processador continue seu trabalho até que o evento que dispara a
interrupção ocorra. Também garante que a CPU receberá um sinal sempre que ocorrer um evento que requeira
sua atenção.
Uma interrupção é, portanto, um sinal para a CPU começar imediatamente a executar um código diferente,
código este que é escrito para responder à causa da interrupção.
Atenção
O advérbio “imediatamente”, utilizado aqui, pode significar, na melhor das hipóteses, “assim que terminar
a instrução atual”.
O tempo entre a geração da solicitação de interrupção e a entrada na rotina que a atende é chamado
de latência de interrupção.
•
•
•
•
E, quanto menor for esse intervalo (latência mais baixa), será sempre melhor.
A CPU se lembrará da localização da próxima instrução que iria executar, armazenando seu endereço em um
registrador ou local de memória, e então pulará diretamente para o código designado pelo programador para
aquela interrupção particular.
Além de salvar o endereço da próxima instrução, muitas vezes também salvará o registrador de status da CPU
e desabilitará outras interrupções. Ela também irá, na maioria dos casos, limpar automaticamente a solicitação
de interrupção que acionou, de forma que uma única solicitação de interrupção não resulte na entrada de uma
rotina várias vezes.
Uma CPU de um microcontrolador é projetada para responder a um número de fontes de interrupção
diferentes (mais de 10, normalmente), e cada fonte pode ter um código específico, escrito pelo usuário, que é
executado quando a interrupção é acionada. O código executado para uma interrupção é chamado de Rotina
de Serviço de Interrupção ou ISR.
Curiosidade
Caso fosse possível assistir à execução do código em câmera lenta, o contador do programa estaria se
movendo de uma instrução para a próxima e, então, quando a interrupção fosse acionada, o contador de
programa iria repentinamente parar em alguma área totalmente diferente do programa, o ponto de
entrada da ISR. Então, quando o ISR estivesse completo, o contador de programa apontaria de repente
para a próxima instrução como se nada tivesse acontecido.
As interrupções estão sempre desligadas ou desabilitadas ao sair de reset. Na verdade, elas são duplamente
desativadas.
Cada fonte de interrupção individual é desabilitada, assim como o sinalizador de interrupção global da CPU.
Para que uma interrupção aconteça e seu ISR seja executado, ambas as interrupções individuais devem ser
habilitadas e o mesmo deve suceder com as interrupções globais da CPU.
Finalmente, e como não poderia deixar de ser, a própria condição de interrupção deve ocorrer. Um pino do
microcontrolador sendo direcionado para nível baixo por um evento externo, por exemplo, pode ser um
acionamento programado de uma interrupção específica. Nesse caso, seria uma interrupção externa.
Cabe ao programador selecionar quais interrupções habilitar e em quais pontos da execução do programa
habilitá-las. Uma vez habilitada, a interrupção pode ser desabilitada novamente e então reabilitada, tão
frequentemente quanto for necessário.
O programa também terá uma ISR (muito semelhante a uma função) para cada interrupção que será habilitada,
e cada uma dessas ISRs será mapeada para sua fonte de interrupção correspondente.
•
Atenção
É crucial que qualquer interrupção ativada sempre tenha uma ISR válida mapeada para ela. Caso
contrário, quando a interrupção disparar, a CPU começará a tentar executar o código em algum local
sem instruções, causando uma quebra no programa.
Aplicações de interrupções
Interrupções são úteis em muitas situações de programação. Por exemplo:
1
Interrupção
Para evitar que a CPU seja travada enquanto aguarda um processo iniciar. Um uso comum para uma
interrupção é notificar o processador de que uma transferência de dados pode ocorrer.
2
Condições importantes
Para responder a condições importantes do hardware, tais como o pressionamento de um
interruptor, o desencaixe de uma alavanca ou a ação em um sensor.
3
Eventos críticos
Para responder a eventos críticos quanto ao tempo, como uma ação que deve ocorrer
imediatamente em uma condição de falha de energia.
4
Condição de erro
Para fornecer uma saída de uma rotina na ocorrência de uma condição de erro.
5
Controle do tempo
Para manter o controle do tempo entre execuções de uma rotina.
Como podem ser habilitadas várias interrupções no programa, elas são comumente associadas para a
realização de uma tarefa.
Imagine que se deseje contar quantos pulsos aparecem em um pino de um microcontrolador a cada
segundo.
•
Uma forma de interrupção à qual todos os microcontroladores podem responder é uma simples
mudança digital de estado em um pino especificado (geralmente chamado de pino de interrupção
externa).
É possível programar uma interrupção externa de modo que, cada vez que um pulso apareça, uma
rotina de interrupção seja ativada para acumular a contagem desses pulsos em uma variável contador.
Outra rotina, disparada pelo temporizador e chamada a cada segundo, pode pegar esse contador e guardar o
valor resultante, este valor representa a contagem de pulsos por segundo, gerando assim, uma taxa de
contagem.
Essa mesma rotina pode fazer cálculos, apresentar os dados ao usuário ou enviá-los por rede para outro
sistema.
Tipos e formas de interrupções externas
Existem algumas maneiras diferentes de configurar uma interrupção externa, dependendo do que o
microcontrolador permite.
Em primeiro lugar, as interrupções externas podem ser disparadas por borda ou por nível.
Uma interrupção disparada por borda gera uma solicitação de interrupção apenas em uma borda, isto é,
quando a linha de interrupção vai de um estado para o estado oposto (1 -> 0 ou 0 -> 1).
Uma interrupção acionada por nível gera uma solicitação de interrupção sempre que a linha de interrupção
está no estado ativo. Sendo assim, uma interrupção acionada por baixo nível irá gerar solicitações sempre que
a linha estiver baixa. Além disso, um dado importante de ser registrado é que ela continuará a gerar
solicitações até que a linha seja elevada.
Se o software ou o hardware forem projetados incorretamente, uma única interrupção disparada por nível
pode resultar em um ciclo furioso de interrupções, com milhares ou milhões de vezes por segundo.
O segundo aspecto de uma interrupção externa é a polaridade da interrupção.
Para uma interrupção disparada por borda, a polaridadepode ser disparada pela borda ascendente (0 -> 1) ou
pela borda descendente (1 -> 0).
Para uma interrupção acionada por nível, a polaridade pode ser acionada por nível baixo (sempre que a
entrada for 0) ou por nível alto (sempre que a entrada for 1).
Novamente, essas são opções de configuração possíveis, mas nem toda entrada de interrupção externa em
cada microcontrolador oferecerá suporte a todas essas opções.
•
•
É ainda possível haver uma entrada de interrupção externa que dispara em qualquer mudança lógica, ou seja,
gerará uma solicitação de interrupção em uma transição alto-baixo (1 -> 0) ou em uma transição baixo-alto (0
-> 1). Essa é apenas uma interrupção disparada por borda que dispara automaticamente em ambas as bordas
possíveis, em vez de apenas em uma borda especificada.
Atenção
Observe também que a distinção entre interrupções acionadas por borda e por nível é fundamental para
todos os tipos de interrupções, não apenas as externas.
É importante, ao escrever a rotina de interrupção, que se tenha uma ideia do que precisa ser feito
imediatamente, no início da rotina, e o que pode ser feito depois.
Frequentemente, se desejará alterar uma ou mais saídas ou registradores ou ler uma ou mais entradas/saídas
bem no início da rotina.
Em seguida, é possível concluir todas as outras tarefas que devem ser feitas na rotina. Essa regra depende
intimamente de qual é a fonte de interrupção, de qual é a ação rotina desejada e, na maioria dos casos, do que
o hardware associado precisa.
Sinalizadores e prioridade de interrupção
Qualquer sinalizador de solicitação de interrupção definido, considerando que a permissão de interrupção
associada esteja definida, é considerado “pendente”.
Se as interrupções globais do microcontrolador estiverem habilitadas e o mesmo acontecer com uma ou mais
interrupções separadas, o hardware da CPU verificará automaticamente todos esses sinalizadores de
interrupção separados durante a execução de cada instrução.
Se apenas um sinalizador for encontrado definido, no final da instrução atual a CPU salta para a rotina dessa
interrupção. Ao fazer isso, o sinalizador de interrupção pode ser apagado automaticamente (isso depende do
projeto), o endereço de retorno (o endereço da próxima instrução que seria executada) é salvo e talvez alguns
sinalizadores de CPU também o sejam. Com isso, possivelmente, alguns registradores de CPU serão
comutados e a rotina de interrupção começará a ser executada.
Se os sinalizadores de solicitação de mais de uma interrupção habilitada forem encontrados definidos, isto é,
se mais de uma interrupção estiver pendente, a situação fica mais complicada.
Sempre haverá uma prioridade atribuída a cada fonte de interrupção. Essa prioridade pode ser fixada no
hardware ou configurável pelo programador.
A interrupção pendente com a prioridade mais alta é a que será atendida.
Os outros sinalizadores de solicitação de interrupção permanecerão definidos, ou seja, as outras interrupções
permanecerão em um estado pendente.
O que acontece, a seguir, com uma rotina de interrupção em execução e outras interrupções pendentes
também depende do projeto da CPU.
Alguns microcontroladores desabilitam interrupções globais como parte do processo de vetorização de
interrupção.
Começo da rotina
Assim, a rotina começa a funcionar com as
interrupções globais desabilitadas e quaisquer
outras interrupções pendentes “em espera”.
Final da rotina
Quando a rotina termina, o estado de
interrupção global será definido como parte da
restauração do estado que ocorre no final de
uma rotina de interrupção.
Definir o estado de interrupção global aqui é legítimo, uma vez que ele teve que ser habilitado para que a
rotina fosse inserida em primeiro lugar. Portanto, a partir daí a próxima instrução do código de fundo
interrompido começará a ser executada e a CPU reconhecerá que há uma ou mais interrupções pendentes.
Novamente, a interrupção pendente de maior prioridade será atendida no final dessa instrução, com quaisquer
outras restantes pendentes, e assim por diante.
Até agora, foram descritas apenas interrupções que interceptam o código de segundo plano. Mas o que
acontece quando uma interrupção tenta barrar outra interrupção?
Exemplo
Suponha duas interrupções externas habilitadas, INT1 e INT2. INT1 foi acionada e sua rotina de
interrupção está em execução, então vem uma solicitação de interrupção em INT2. O que acontece
agora? Nesse caso, uma das duas situações a seguir pode ocorrer. A nova interrupção pode ser
bloqueada até que a rotina INT1 termine, quando, então, ela será atendida. Ou, se as interrupções foram
habilitadas dentro da rotina de INT1, ou se INT2 foi definido com uma prioridade mais alta do que INT1, a
rotina de INT1 será interrompida assim como aconteceu com o código de segundo plano, então a rotina
de interrupção de INT2 será executada.Quando terminar INT2, a rotina de INT1 continuará a funcionar.
Cuidados no uso de interrupção
Com tamanha flexibilidade e potencial, o uso de interrupções é recomendado.
Elas são essenciais para o cumprimento de requisitos de tempo em sistemas embarcados, mas também
podem destruir os dados de maneiras que pareceriam impossíveis.
É importante prestar atenção nas chamadas de interrupção programadas e nas prioridades atribuídas para
que seus perigos sejam evitados.
A maioria dos problemas com interrupções está relacionada ao seu principal benefício, que é a execução do
código poder ser interrompida praticamente a qualquer momento. E o código nunca sabe que foi interrompido.
É como se fosse possível congelar o tempo para o código de segundo plano, fazer o que quiser com os dados
ou E/S e, em seguida, reiniciar o tempo para o código de segundo plano.
Em uma analogia, seria como se alguém muito mais rápido que você retirasse a sua cadeira no exato instante
em que se preparava para se sentar. É assim que pode ser um software mal projetado que usar interrupções.
Um exemplo de cuidado que se deve ter é na passagem de dados entre o código de segundo plano e a rotina
de interrupção. Ela deve ser evitada, mas não é uma circunstância rara para uma interrupção. Em boa parte
das rotinas de interrupção os programadores se utilizam de passagem de dados para o código de segundo
plano e/ou a recuperação de dados do código de segundo plano, e é aí que algo pode dar errado. A
ocorrência de problemas pode se dar sempre que o código de segundo plano está no meio do acesso aos
dados e uma interrupção acontece, fazendo com que a rotina associada acesse os mesmos dados.
Recomendação
Evitar ao máximo que duas rotinas atualizem os mesmos dados é mais seguro, mas nem sempre
contornável. Por isso, alguns testes e uma análise minuciosa no código são sempre boas práticas na
utilização de interrupções.
Programação de interrupção externa
Um exemplo de programação poderia ser um LED acionado por interrupção ao toque em um botão. Dessa
forma, se o pino é mantido alto por um resistor pullup e, em seguida, é reduzido por algum sinal ou ação, a
transição de alto para baixo no pino pode desencadear uma interrupção.
Podemos realizar um programa de LED controlado por botão para detectar a transição desse botão por meio
de interrupção externa, alterando o LED de acordo. Outro LED pode ser acionado por uso de função delay
(atraso). Isso nos dará uma introdução simples às interrupções externas.
O primeiro passo é consultar a ficha técnica do chip para ver qual(is) pino(s) suporta(m) interrupções
externas.
Interrupção externa no PIC
O programa a seguir demonstra o uso de interrupção externa com o compilador CCS no PIC18F4550 e pode
ser testado na placa 4 do PICSimLab.
O PIC18F4550 possui três interrupções externas de hardware – INT0, INT1 e INT2.
Elas estão nos pinos RB0, RB1 e RB2 da PORTB. Essas interrupções são acionadas pela borda, ou seja,
disparadas pela borda ascendente ou pela borda descendente do pulso.
c
#include
#fuses XT, NOWDT, NOMCLR