Expandindo as portas digitais com Shift Register

Eletrônica Arduino Circuitos digitais 16 de Novembro de 2019 às 23:01

Quando possuímos um projeto que está crescendo conforme o tempo, e as portas digitais OUTPUT do microcontrolador estão ficando escassas para a utilização, é necessário implementar um expansor de portas, ou normalmente conhecido por Registrador de deslocamento, ou no inglês, Shift Register. Este método é famoso entre os hobbytas e profissionais na área de hardware, onde reservamos três outputs para as operações internas, como Latch, Data e Clock. Mas o que seria um Shift Register? E como ele funciona?

Flip Flops do tipo D

No artigo Prática de circuitos combinacionais e sequenciais foi abordado como um Latch D Transparent NOR funciona, este circuito possui a característica de disparo por níveis lógicos de alto e baixo, e a função de armazenar temporariamente um bit, fazendo o efeito memória.

Os Flip Flops do tipo D trabalham de uma forma diferenciada, analisando este tipo de lógica que possui internamente um Latch S-R NAND, eles são disparados por transição, ou seja, sensível a borda de subida da onda quadrada, e a entrada Enable é substituída por uma entrada CLK recebendo um sinal de clock. Como já mencionado no artigo, eles são ideias para sistemas síncronos.

Latch D "Clocked" com Latch S-R NAND (Equivalência De Morgan).

Para construir um Shift Register de 4 bits de uma forma simplificada, é necessário quatro flip flops do tipo D compartilhando o sinal de clock, e a saída do primeiro flip flop é a entrada do segundo flip flop e assim por diante, com esse tipo de característica é possível expandi-lo.

A entrada dos dados seriais são injetadas na entrada do primeiro flip flop chamada Data, onde em cada atualização de clock, os bits são deslocados um de cada vez em cascata entre os flip flops conforme o valor 0 ou 1 de Data. O Latch S-R NAND interno é responsável em atualizar a saída em cada pulso de clock.

Shift Register de 4 bits com quatro Flip Flops do tipo D.

O circuito integrado de Shift Register

Não é necessário construir um Shift Register do zero utilizando flip flops do tipo D em um protoboard para ser utilizado no dia a dia, podemos utilizar um circuito integrado da família TTL de série HC (High-Speed Si-Gate CMOS) dedicado a esta função, o 74HC595. Este CI é um Shift Register de 8 bits expansível, com inputs em forma serial e outputs em paralelo com tri-states, ou seja, as saídas podem ser setadas em LOW, HIGH ou Alta Impedância, propriedades essenciais para controle em um sistema.

Para a utilização básica em um microcontrolador conforme o datasheet, é necessário utilizar os pinos SRCLK (CLK), RCLK (LATCH) e SER (DATA), e para as saídas utiliza-se do pino QA ao QH, onde QH' é utilizado na expansão com outro 74HC595. Os demais pinos como, !OE (OUT ENABLE) e !SRCLR (CLEAR) são destinados a desabilitar/habilitar e limpar os bits de saída.

74HC595 - Shift Register pinagem.

Shift Register com Arduino ou NodeMCU

Observando o datasheet do 74HC595 novamente, podemos conectar no pino 16 (VCC) tensões entre 2V até 6v no máximo, isso é importante salientar visto que as plataformas Arduino e NodeMCU operam em tensões diferentes.

Para você realizar a montagem do projeto utilizando Arduino ou NodeMCU, é recomendado que siga a lista de materiais necessários abaixo e os diagramas esquemáticos.

  • 1 Arduino Uno ou NodeMCU ESP12E, ambos com cabo USB.
  • 1 Protoboard, ou 2 protoboards se for utilizar o NodeMCU.
  • 1 74HC595 - Shift Register.
  • 8 LEDs.
  • 8 Resistores de 220R ou 1K.
  • 1 Capacitor cerâmico/poliéster de 100nF.
  • Fios jumper.
NodeMCU LOLin ESP12E com Shift Register 74HC595 no protoboard.

Com a montagem pronta, podemos iniciar a parte do código para manipular o Shift Register e seus respectivos LEDs. Desenvolvemos três códigos de exemplo para você testar e entender o funcionamento do circuito.

* Para programar o NodeMCU foi utilizada a IDE oficial do Arduino.

Exemplo 1: Pisca LED

O primeiro exemplo é simples para entender o funcionamento do Shift Register no código, nele vamos utilizar os quatro LEDs do lado esquerdo e os quatro LEDs do lado direito, fazendo-os alternar seus estados de ligado e desligado.

/**
 * Manipulando dados no Shift Register 74HC595
 * 
 * EXEMPLO1: PISCA LED
 * 
 * Autor: tecdicas
 * 
 * https://tecdicas.com/
 * 
 * 23/01/2019
 * 
 * MIT
 */

/**
 * Constantes para GPIOs do NodeMCU ESP-12E
 */
#define DATA  12 // D6
#define LATCH 13 // D7
#define CLK   15 // D8

/**
 * Constantes para GPIOs do Arduino Atmega328p
 */
//#define DATA  8
//#define LATCH 9
//#define CLK   10

byte valorByte = 0x00;

void setup()
{
  pinMode(CLK, OUTPUT);
  pinMode(LATCH, OUTPUT);
  pinMode(DATA, OUTPUT);  
}

void loop()
{
  // Em binário 0b11110000
  valorByte = 0xF0;
  digitalWrite(LATCH, LOW);
  shiftOut(DATA, CLK, LSBFIRST, valorByte);  
  digitalWrite(LATCH, HIGH);
  delay(500);

  // Em binário 0b00001111
  valorByte = 0xF;
  digitalWrite(LATCH, LOW);
  shiftOut(DATA, CLK, LSBFIRST, valorByte);
  digitalWrite(LATCH, HIGH);
  delay(500);
}

Para o Shift Register funcionar corretamente, ele necessita da atualização do estado da constante LATCH e da função shiftOut(dataPin, clkPin, bitOrder, value). Quando LATCH está em LOW a saída não é atualizada, e quando ativamos em HIGH a saída em forma paralela é atualizada, onde é possível observar os LEDs piscando.

Analisando a função shiftOut() ela requer os pinos de DATA, CLK, a ordem dos bits e o valor do byte que desejamos. A constante DATA recebe o valor do byte de forma serializada, verificando se o parâmetro bitOrder é MSBFIRST (O bit mais significativo primeiro) ou LSBFIRST (O bit menos significativo primeiro). Para cada serialização é realizado um pulso de clock ativando e desativando a constante CLK, com isso os bits são deslocados internamente e guardados esperando um pulso de LATCH.

"Explodindo" a shiftOut();

/**
 * Função retirada do arquivo wiring_shift.c
 */
void shiftOut(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder, uint8_t val)
{
  uint8_t i;

  for (i = 0; i < 8; i++)  {
    if (bitOrder == LSBFIRST)
      digitalWrite(dataPin, !!(val & (1 << i)));
    else      
       digitalWrite(dataPin, !!(val & (1 << (7 - i))));
                 
       digitalWrite(clockPin, HIGH);
       digitalWrite(clockPin, LOW);            
     }
}

Exemplo 2: Efeito "vai e vem" de LEDs

Este segundo exemplo será realizado o efeito vai e vem dos LEDs com Shift Register, manipulando bit a bit aplicando a função montarByte(byte &valor, int posicao, int estado), que utiliza as funções oficiais da IDE Arduino bitSet(valor, posicao) e bitClear(valor, posicao).

/** 
  * 
  *  EXEMPLO2: EFEITO "VAI E VEM" DE LEDS - V1.2
  * 
  * Autor: tecdicas
  * 
  * https://tecdicas.com/
  * 
  * 23/01/2019
  * 
  * MIT
  */

/**
 * Constantes para GPIOs do NodeMCU ESP-12E
 */
#define DATA  12 // D6
#define LATCH 13 // D7
#define CLK   15 // D8

/**
 * Constantes para GPIOs do Arduino Atmega328p
 */
//#define DATA  8
//#define LATCH 9
//#define CLK   10

byte valorByte = 0x00;

/**
 * Monta e limpa um byte para ser manipulado no Shift Register
 * 
 * @param byte &valor    - Valor do byte de até 0xFF/255/8 bits
 * @param int posicao    - Posição do bit de 0 a 7.
 * @param int estado     - Estado de HIGH/1 ou LOW/0 para o bit.
 */
void montarByte(byte &valor, int posicao, int estado)
{
  //Serial.println(valorByte, BIN);
  
  if(estado == HIGH)
  {
    bitSet(valor, posicao);
  }
  else if(estado == LOW)
  {
    bitClear(valor, posicao);
  }
  
  digitalWrite(LATCH, LOW);
  shiftOut(DATA, CLK, LSBFIRST, valor); 
  digitalWrite(LATCH, HIGH);
  //Serial.println(valorByte, BIN);
}

void ledVaiVem()
{
  for(int i=0;i<=7;i++)
  {
    montarByte(valorByte, i, HIGH);
    delay(500);
  }
  for(int i=7;i>=0;i--)
  {
    montarByte(valorByte, i, LOW);
    delay(500);
  }
}

void setup()
{
  Serial.begin(9600);
  pinMode(DATA, OUTPUT);
  pinMode(LATCH, OUTPUT);
  pinMode(CLK, OUTPUT); 
}

void loop() 
{    
  ledVaiVem();  
  delay(1000);
}

A função criada montarBytes() recebe como parâmetro o valor do byte por referência, a posição e o estado, onde verificamos se é HIGH ou LOW para intercalar a chamada das funções bitSet() e bitClear(). A função bitSet() seta o bit em sua devida posição em 1/HIGH. A função bitClear() faz a mesma operação, mas setando em 0/LOW.

Este método de manipular bit a bit e suas posições individuais com estados LOW e HIGH é bastante semelhante à técnica Bitwise da linguagem C++, onde também é possível realizar operações de deslocamento de bits. Neste caso com a função criada ledVaiVem(), a primeira estrutura de repetição FOR instancia a variável local i, e em cada iteração a posição do byte é crescente. Já a segunda estrutura FOR, em cada iteração a posição do byte é decrescente, visualizando o efeito de vai e vem dos LEDs.

Exemplo 3: Controle de LEDs via software

Este exemplo é o mais complexo que os outros, pois envolve um software e comunicação serial entre o Arduino/NodeMCU e o computador. O código abaixo tem como objetivo receber dados da serial para controlar os estados dos oito LEDs individualmente.

/**
 * Easy Shift Register Serial
 * 
 * EXEMPLO2: CONTROLE DE LEDS VIA SOFTWARE - V2.0
 * 
 * Autor: tecdicas
 * 
 * https://tecdicas.com/
 * 
 * 22/01/2019
 * 
 * MIT
 */

/**
 * Constantes para GPIOs do NodeMCU ESP-12E
 */
#define DATA  12 // D6
#define LATCH 13 // D7
#define CLK   15 // D8

/**
 * Constantes para GPIOs do Arduino Atmega328p
 */
//#define DATA  8
//#define LATCH 9
//#define CLK   10

byte valorByte = 0x00;

/**
 * Monta e limpa um byte para ser manipulado no Shift Register.
 * 
 * @param byte &valor     - Valor do byte de até 0xFF/255/8 bits.
 * @param int posicao     - Posição do bit de 0 a 7.
 * @param int estado      - Estado de HIGH/1 ou LOW/0 para o bit.
 */
void montarByte(byte &valor, int posicao, int estado)
{  
  if(estado == HIGH)
  {
    bitSet(valor, posicao);
  }
  else if(estado == LOW)
  {
    bitClear(valor, posicao);
  }
  
  digitalWrite(LATCH, LOW);
  shiftOut(DATA, CLK, LSBFIRST, valor); 
  digitalWrite(LATCH, HIGH);
  //Serial.println(valorByte, BIN);
}

/**
 * Manipula a posição e estado de cada led.
 * 
 * @param int posicao - Posição do bit de 0 a 7.
 */
void manipulaLed(int posicao)
{ 
  int bRead = bitRead(valorByte, posicao);
  montarByte(valorByte, posicao, !bRead);
  Serial.println(bitRead(valorByte, posicao));
}
/**
 * Realiza a comunicação serial enviando os dados requeridos.
 * Utilizar Monitor Serial, Putty, terminal Linux ou software que realize comunicação serial.
 */
void comSerial()
{
  if(Serial.available())       
  {
    //int op = (int) Serial.read() - 48;    
    int op = Serial.parseInt();
    
    if (op >=0 && op <= 7)
    {
      manipulaLed(op);
    }
    else if(op == 8)
    {
      apagaByte();
    }
  }
}

/**
 * Seta todas posições dos bits em zero.
 */
void apagaByte()
{
  for (int i=0;i<=7;i++)
  {
    montarByte(valorByte, i, LOW);
  }
}

void setup()
{
  Serial.begin(9600);
  pinMode(DATA, OUTPUT);
  pinMode(LATCH, OUTPUT);
  pinMode(CLK, OUTPUT); 
  apagaByte();
}

void loop() 
{   
  comSerial(); 
}

O software foi projetado para enviar estes dados utilizando uma tela amigável, mas você também pode fazer o mesmo procedimento com o Monitor Serial da IDE oficial do Arduino, o terminal Putty ou terminal Linux.

Para este exemplo foi utilizada a ferramenta de desenvolvimento Microsoft Visual Studio Community 2017 para desenvolver um aplicativo do tipo Windows Forms. Este tutorial ensina passo a passo a criar este tipo de software para enviar dados para o Arduino/NodeMCU.

Tela do software NodeMCU Easy Shift Register.

Referências

  1. MALVINO, A. P; BROWN, J. A; Digital computer electronics. 3 ed. McGraw-Hill, 1999.
  2. TOCCI, R, J; WIDMER, N. S; MOSS, G. L; Sistemas digitais: princípios e aplicações. 10 ed. Pearson Prentice Hall, 2008.