Sequência de LED piscando usando microcontrolador PIC
Em nosso tutorial anterior, aprendemos sobre como piscar um LED usando o microcontrolador PIC e construímos o mesmo circuito na placa de PCB Universal. Em seguida, usamos PICkit 3, ICSP e MPLAB IPE para despejar o programa em nossa placa PCB Universal. Agora, neste tutorial, avançaremos para usar mais pinos no microcontrolador PIC. Usaremos 7 saídas (LEDs) e uma entrada. Para este tutorial, usaremos a placa PCB Universal antiga (mostrada abaixo) e adicionaremos barramento de pinos para puxar os pinos necessários para a segunda placa de LED. No final deste tutorial, iremos gerar uma sequência de LEDs piscando usando o microcontrolador PIC PIC16F877A e aprenderemos como usar várias entradas e saídas, alguns princípios básicos sobre o loop ‘for’ e chamada de função.
A placa de LED nada mais é que outra placa de desempenho, na qual soldaremos os LEDs com um resistor limitador de corrente (mostrado abaixo). Também adicionaremos um botão de pressão para iniciar o LED de sequência piscando.
Diagrama de circuito:
Código de sequência piscando do microcontrolador PIC PIC16F877A LED e explicação de funcionamento:
O código completo foi fornecido abaixo (verifique no final), aqui nós o obteremos linha por linha. Este código começará a acender os LEDs de maneira sequencial quando o botão for pressionado. Para entender as sequências, assista ao vídeo no final do tutorial. Eu recomendo que você compare a saída mostrada no vídeo com o código abaixo e tente entender o programa.
Vejamos o código linha por linha. As primeiras linhas são para definir bits de configuração que foram explicados no tutorial anterior, portanto, estou pulando-os por enquanto. A melhor maneira de entender qualquer programa é começar pela função principal (void main()), então vamos fazer isso
TRISB0=1; //Instrua o MCU que o pino 0 da PORTB é usado como entrada para o botão. TRISD = 0x00; //Instrua o MCU que todos os pinos são enviados PORTD=0x00; //Inicializa todos os pinos para 0
A palavra TRIS é usada para definir se o pino está sendo usado como entrada/saída e a palavra PORTA é usada para fazer um pino alto/baixo. A linha TRISB0 = 1 fará o pino 0 da PORTA B como entrada. Este será o nosso botão. As linhas TRISD = 0x00; PORTD = 0x00; fará todos os pinos da porta D como saída e atribuirá um valor inicial LOW a esses pinos.
Como dissemos que B0 é usado como entrada, conectaremos uma extremidade do botão ao pino B0 e a outra extremidade ao terra. Nesse momento, sempre que pressionarmos o botão, o pino será mantido no solo conforme mostrado no diagrama de conexão acima. Mas para que isso aconteça, temos que usar um resistor pull up para que o pino seja mantido alto quando o botão não for pressionado. Um resistor pull up é algo assim.
Mas nosso PIC MCU tem um resistor pull up interno fraco que pode ser ativado por software dessa forma, economizando muito trabalho (quando mais botões devem ser conectados).
O que é um resistor pull up fraco?
Existem dois tipos de resistor pull up, um é Weak Pull Up e outro é Strong Pull Up. Os resistores pull up fracos são de alto valor e, portanto, permitem que uma corrente fraca flua e os resistores pull up fortes são de valor baixo, permitindo assim que uma corrente forte flua. Todos os MCU usam principalmente resistores pull up fracos. Para ativar isso em nosso PIC MCU, temos que olhar em nossa folha de dados para OPTION_REG (registro de opção), como mostrado no instantâneo abaixo.
Como mostrado, o bit 7 trata do resistor pull up fraco. Deve ser zerado para ativá-lo. Isso é feito por OPTION_REG<7>=0. Isso lida especificamente com o bit 7, deixando os outros bits com seus valores padrão. Com isso, entramos em nosso loop while, onde verifica se o botão está pressionado usando if (RB0==0). Se a condição for satisfeita, chamamos nossa função com os parâmetros 1, 3, 7 e 15.
sblink(1); //CHAMADA DE FUNÇÃO 1 com parâmetro 1 sblink(3); //CHAMADA DE FUNÇÃO 3 com parâmetro 3 sblink(7); //CHAMADA DE FUNÇÃO 7 com parâmetro 7 sblink(15); //CHAMADA DE FUNÇÃO 4 com parâmetro 15
Por que usamos funções?
Funções são usadas para reduzir o número de linhas em nosso código. Isso é o que a maioria de nós saberia. Mas por que precisamos reduzir o número de linhas, especialmente quando se trata de programação MCU. O motivo é o espaço limitado em nossa memória de programa. Se não otimizarmos o código adequadamente, podemos ficar sem espaço de memória. Isso será útil quando escrevermos longas páginas de códigos.
Qualquer função terá uma função Definition (sblink(int get) em nosso caso) e uma função Call (sblink(1) em nosso caso). É opcional ter uma declaração de função; para evitá-lo, coloquei minha definição de função antes de chamar a função em minha função principal.
Os parâmetros da função são o valor que será passado da chamada da função para a definição da função. Em nosso caso, os valores inteiros (1, 3, 7, 15) são os parâmetros que são passados da chamada de função e a variável “get” obtém o valor dos parâmetros na definição da função para processá-los. Uma função pode ter mais de um parâmetro.
Assim que a função for chamada, as linhas abaixo na definição da função serão executadas.
for (int i=0; i<=7 && RB0==0; i++) { PORTD = get << i; //LED move sequência à esquerda __delay_ms(50); } for (int i=7; i>=0 && RB0==0; i--) { PORTD = get << i; //LED move sequência à esquerda __delay_ms(50); }
Agora, esta linha parece estranha: PORTD = get << i. Vou explicar o que realmente está acontecendo aqui.
“<<” é um operador de deslocamento à esquerda que desloca todos os bits para sua posição à esquerda. Agora, quando chamamos a função sblink (int get) com o parâmetro ‘1’ como sblink (1), ele transforma o valor de ‘get’ em 1, que em binário é 0b00000001. Portanto, esta linha será como PORTD = 0b00000001 << i.
O valor de “i” irá variar de 0 a 7, uma vez que usamos um ‘for loop’ para (int i = 0; i <= 7 && RB0 == 0; i ++). O valor de ‘i’ sendo de 0 a 7 mudará o resultado da seguinte forma:
Como você pode ver, ligamos um LED por vez (da esquerda para a direita), mantendo os demais desligados. O próximo ‘for loop’ para (int i=7; i>=0 && RB0==0; i–), também fará o mesmo, mas desta vez o LED será ligado da direita para a esquerda em uma sequência, pois começamos de 7 e descemos para 0. Usamos um atraso de 200ms para que possamos visualizar o LED sendo LIGADO e DESLIGADO.
Agora, quando passamos o valor 3 na função sblink(int get), a função sblink(3) será executada, o que torna o valor de ‘get’ como 0b00000011, portanto, o resultado em PORTD será:
Portanto, desta vez, dois LEDs serão ligados a qualquer momento usando sblink(3). Da mesma forma para sblink(7) e sblink(15), três e quatro LEDs estarão LIGADOS em sequência. Quando terminar, faremos com que todos os LEDs acendam usando a linha PORTD=0xFF.
Espero que você tenha entendido o código e, assim, tenha aprendido como usar as funções de loop ‘for’ e ‘while’ para obter as saídas desejadas. Agora você pode ajustar o código para fazer sua sequência diferente de LED piscar. Vá em frente, compile seu código e despeje-o em seu MCU e aproveite a saída. Você pode usar a seção de comentários se ficar preso em algum lugar. Também anexei a simulação e os arquivos do programa aqui.
Por enquanto, em nosso próximo tutorial, aprenderemos como usar os temporizadores PIC16F877A em vez de usar funções de atraso.
Código
// 'C' source line config statements // CONFIG #pragma config FOSC = HS // Oscillator Selection bits (HS oscillator) #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled) #pragma config BOREN = ON // Brown-out Reset Enable bit (BOR enabled) #pragma config LVP = OFF // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming) #pragma config CPD = OFF // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off) #pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control) #pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off) // #pragma config statements should precede project file includes. // Use project enums instead of #define for ON and OFF. #include <xc.h> #define _XTAL_FREQ 20000000 //Specify the XTAL crystall FREQ /*******Sequence blink*******/ sblink(int get) //Function definition with "get" as parameter { for (int i=0; i<=7 && RB0==0; i++) { PORTD = get << i; //LED move Left Sequence __delay_ms(50); } for (int i=7; i>=0 && RB0==0; i--) { PORTD = get << i; //LED move Left Sequence __delay_ms(50); } } /*******MAIN Function*******/ void main() //The main function { TRISB0=1; //Instrua o MCU que o pino 0 da PORTB é usado como entrada para o botão. TRISD = 0x00; //Instrua o MCU que todos os pinos são enviados PORTD=0x00; //Inicializa todos os pinos para 0 OPTION_REG= 0b00000000; //Ativar pull up R na porta B while(1) //Entre no loop infinito While { if (RB0==0) { sblink(1); //CHAMADA DE FUNÇÃO 1 com parâmetro 1 sblink(3); //CHAMADA DE FUNÇÃO 3 com parâmetro 3 sblink(7); //CHAMADA DE FUNÇÃO 7 com parâmetro 7 sblink(15); //CHAMADA DE FUNÇÃO 4 com parâmetro 15 while(RB0==0) //Se o botão ainda estiver pressionado { PORTD=0xFF; //LIGUE todos os LEDs } } } }