A memória é o lugar onde armazenamos informações ou dados. As memórias eletrônicas são circuitos usados para armazenar dados. Microcontroladores têm vários tipos de memória e quase todos os projetos ou equipamentos eletrônicos modernos as utilizam para armazenar dados. Isso é muito análogo ao cérebro humano, que tem uma memória onde armazenamos os dados. Neste artigo, aprenderemos como é uma memória eletrônica e como podemos usá-la para armazenar dados.
Todas as instruções de um microcontrolador podem ser classificadas em 3 classes principais:
1 - instruções de salto condicional.
2 - Instruções de memória (movimentos variáveis).
3 - Instruções Aritméticas e Lógicas.
Neste artigo vamos estudar e colocar em prática as Instruções de Movimentação de Dados na Memória. Começar a programar microcontroladores pode parecer uma tarefa que exige muito conhecimento ou conhecimento, mas com Arduino, vamos iniciar-nos no desenvolvimento de projetos e circuitos utilizando microcontroladores, de uma forma fácil e divertida. A primeira coisa que precisamos saber é como um microcontrolador é por dentro, de quais partes é formado, como funciona e qual é a relação com o compilador ou a linguagem de programação.
PARTES DE UM MICROCONTROLADOR.
Existem três partes principais de um microcontrolador:
1 - A memória.
2 - CPU ou Unidade Central de Processamento.
3 - Os Periféricos.
A figura 1 mostra as principais partes de um microcontrolador.
![Figura 1_ Partes básicas de um microcontrolador
Figura 1_ Partes básicas de um microcontrolador](/images/stories/artigos2018/mic0182_0001.png)
A memória é a parte do microcontrolador responsável pelo armazenamento de instruções e dados.
A CPU é a parte do microcontrolador que é responsável por executar os comandos ou instruções do programa escrito em linguagem C.
Periféricos, é a parte do microcontrolador que é responsável por remover ou inserir informações ou dados do lado de fora. Normalmente, ela é conhecida como entradas ou saídas (E / S).
MEMÓRIA.
Para entender a memória, podemos fazer uma analogia com a mente humana. A mente armazena informações ou dados; O mesmo ocorre com a memória digital, ela é usada para armazenar informações ou dados. Podemos representar uma memória como uma série de gavetas ou gavetas onde a informação é colocada.
Na eletrônica digital existe um circuito chamado flip-flop tipo D ou data, este circuito armazena informações depois de receber um pulso digital. A figura 2 mostra um esquema desse circuito. O que este circuito faz é ler o nível lógico presente na entrada D e quando um pulso é recebido pela entrada Clock, armazenando-o na saída Q.
![Figura 2 - Diagrama básico do Flip-Flop tipo D
Figura 2 - Diagrama básico do Flip-Flop tipo D](/images/stories/artigos2018/mic0182_0002.png)
A Figura 3 mostra um exemplo do circuito, em um determinado estado, antes de receber um pulso na entrada Clock. Neste estado, a saída Q está no nível lógico 1 (alto) e a entrada no nível lógico 0 (baixo).
![Figura 3_ Flip-Flop antes de receber o pulso de clock
Figura 3_ Flip-Flop antes de receber o pulso de clock](/images/stories/artigos2018/mic0182_0003.png)
Agora, se tomarmos um pulso positivo (alto) na entrada do clock, o nível lógico presente na entrada D, é armazenado no flip-flop e seu valor é apresentado na saída Q. saída Podemos ver isso na Figura 4
![Figura 4_ Flip-Flop depois de receber o pulso de clock
Figura 4_ Flip-Flop depois de receber o pulso de clock](/images/stories/artigos2018/mic0182_0004.png)
Agora, podemos formar um circuito que possui 8 flip-flops tipo D o qual é representado pela Figura 5.
![Figura 5 -Circuito com 8 Flip-Flops tipo D
Figura 5 -Circuito com 8 Flip-Flops tipo D](/images/stories/artigos2018/mic0182_0005.png)
Temos 8 entradas D e 8 saídas Q independentes. O clock está em paralelo com os 8 flip-flops. A operação é exatamente a mesma para os 8 flip-flops, como explicado acima para um único flip-flop. Esse tipo de circuito é chamado de registro. A Figura 6 mostra um exemplo em um possível estado deste circuito, antes de receber um pulso através da entrada Clock.
![Figura 6_8 Flip-Flops D antes de receber o pulso de clock
Figura 6_8 Flip-Flops D antes de receber o pulso de clock](/images/stories/artigos2018/mic0182_0006.png)
A figura 7 mostra o circuito após receber um pulso positivo na entrada Clock. Os valores são armazenados nas saídas Q.
![Figura 7 - 8 Flip-Flops D após receber o pulso do relógio
Figura 7 - 8 Flip-Flops D após receber o pulso do relógio](/images/stories/artigos2018/mic0182_0007.png)
Assim, podemos armazenar dados ou informações digitais. Existem vários circuitos digitais que incorporam vários flip-flops e seguem a operação explicada acima.
Para formar uma memória, podemos juntar vários flip-flops, organizados como mostrado na Figura 8.
![Figura 8_Estrutura de uma memória com flip-flops
Figura 8_Estrutura de uma memória com flip-flops](/images/stories/artigos2018/mic0182_0008.png)
Os dados são colocados nas entradas D0, D1, D2, D3, D4, D5, D6, D7. Os dados são armazenados no respectivo grupo de flip-flops e exibidos ou removidos pelas saídas Q0, Q1, Q2, Q3, Q4, Q5, Q6, Q7. Uma vez que todas as saídas Q estejam em alta impedância ou seja, em estado de alta impedância, não há problemas com curto-circuitos e apenas o registro selecionado estará presente na saída Q
O que precisamos agora é uma maneira de selecionar cada registro. Para isso, podemos usar um circuito digital chamado decodificador. Este circuito é formado por algumas portas NÃO e AND digitais. A Figura 9 mostra a operação lógica de uma porta NÃO (inversor). Este circuito inverte o nível lógico presente na entrada.
![Figura 9_Porta NÃO
Figura 9_Porta NÃO](/images/stories/artigos2018/mic0182_0009.png)
A Figura 10 mostra a operação lógica de uma porta AND. Isso quer dizer que somente quando todas as suas entradas estão no nível lógico 1 (alto), sua saída é colocada no nível lógico 1; caso contrário, sua saída terá um nível lógico de 0 (baixo). Com esses componentes, podemos formar um decodificador.
![Figura 10_Portas AND
Figura 10_Portas AND](/images/stories/artigos2018/mic0182_0010.png)
A Figura 11 mostra um decodificador com 2 entradas e 4 saídas. Observe que quando as entradas estão no nível lógico 0, os inversores (negadores) selecionam a primeira saída. Agora vamos juntar o decodificador aos registradores para poder selecionar o desejado.
![Figura 11_Decodificador de 2 entradas 4 saídas
Figura 11_Decodificador de 2 entradas 4 saídas](/images/stories/artigos2018/mic0182_0011.png)
A figura 12 mostra o circuito.
![Figura 12_Decodificador de 2 entradas 4 para selecionar flip-flops (registros)
Figura 12_Decodificador de 2 entradas 4 para selecionar flip-flops (registros)](/images/stories/artigos2018/mic0182_0012.png)
A figura 13 mostra o diagrama de blocos do circuito da figura.
![Figura 13_Diagrama em blocos do decodificador 2X4 para seleção de flip-flops
Figura 13_Diagrama em blocos do decodificador 2X4 para seleção de flip-flops](/images/stories/artigos2018/mic0182_0013.png)
Agora precisamos de um circuito que nos permita usar o barramento de dados de entrada e saída em um único barramento. Para isso, usaremos um buffer tri-state (Transeiver). A Figura 14 mostra como um par de buffers Tri-State permite usar a mesma linha digital para que em um instante seja entrada e em outro instante a saída.
![Figura 14_Buffer tri-state
Figura 14_Buffer tri-state](/images/stories/artigos2018/mic0182_0014.png)
Quando o nível de entrada na linha de controle está em 0, a saída da porta NÃO inverte para o nível lógico 1 e seleciona o estado tri-state para o buffer B, o que faz com que o valor de Q, apareça na linha I / O. Quando a linha de controle é 1 lógico , e porta inverte e estará presente a lógica 0, desabilitando o buffer Tri-State B. Mas agora, está ativado o buffer A, de modo que o nível lógico presente na linha I / O, está disponpivel na linha A.
Com base nesse princípio, podemos fazer um circuito de controle para o circuito da figura 12. O circuito da figura 15 apresenta este circuito e a figura 16, seu respectivo diagrama de blocos.
![Figura 15 -Controle do barramento de dados com buffer tri-state
Figura 15 -Controle do barramento de dados com buffer tri-state](/images/stories/artigos2018/mic0182_0015.png)
![Figura 16_ Diagrama de blocos do controle de barramento de dados com tri-state
Figura 16_ Diagrama de blocos do controle de barramento de dados com tri-state](/images/stories/artigos2018/mic0182_0016.png)
PROGRAMA E MEMÓRIA DE DADOS.
Os microcontroladores têm uma memória para armazenar instruções chamada memória de programa. Nela as instruções são armazenadas sequencialmente. A Unidade Central de Processamento (CPU) procura as instruções nesta memória e as executa. Veja a Figura 17.
![Figura 17 – Memória de Programa e Memória de Dados
Figura 17 – Memória de Programa e Memória de Dados](/images/stories/artigos2018/mic0182_0017.png)
Os dados são armazenados na memória de dados. Os dados são valores que podem variar durante a execução do programa, por isso são chamados de variáveis.
A memória do programa é não volátil, ou seja, quando o microcontrolador é desconectado da fonte de alimentação, a informação permanece. Como por, exemplo, ROM, PROM, EPROM, EEPROM, FLASH, etc. A memória de dados é volátil, o que significa que quando a fonte de alimentação é desconectada, a informação é perdida. Como exemplo temos a RAM dinâmica (DRAM) ou RAM estática (SRAM).
As memórias são construídas com diferentes tipos de matrizes (arrays) ou arquiteturas, mas independentemente disso, todas armazenam as informações e seus princípios operacionais são os mesmos.
O COMPILADOR E O AMBIENTE DE DESENVOLVIMENTO (IDE).
O compilador é o editor onde os programas são feitos. É principalmente um editor de texto e as instruções são escritas sequencialmente, dependendo do modo que você quer que o microcontrolador as execute. No ambiente de desenvolvimento do Arduino (IDE), a função main () é chamada automaticamente. A função principal chama a função setup () e loop (). Mas a maioria dos livros e textos microcontroladores usam a função main (), por isso para compreender melhor como as memórias trabalham dentro do microcontrolador, na seguinte explicação vai ajudar.
O código começa a executar em uma função chamada main (). O seguinte é o código para esta função:
int main ()
{
}
Para atribuir nomes aos endereços da memória de dados, isso é feito antes desta função. Por exemplo, a próxima linha diz ao compilador para separar um byte na memória de dados e atribuir a ele a contagem de nomes.
usigned char count;
A linha de código a seguir diz ao compilador para gravar o valor 9 no endereço da memória count.
int main ()
{
count = 9;
}
Na Figura 18 podemos ver o que acontece antes de executar a instrução.
![Figura 18_Antes de executar a instrução
Figura 18_Antes de executar a instrução](/images/stories/artigos2018/mic0182_0018.png)
A Figura 19 mostra quando a CPU lê a instrução e a Figura 20 depois de executar a instrução.
![Figura 19_A CPU lê a instrução da memória do programa
Figura 19_A CPU lê a instrução da memória do programa](/images/stories/artigos2018/mic0182_0019.png)
![Figura 20_Depois de executar a instrução
Figura 20_Depois de executar a instrução](/images/stories/artigos2018/mic0182_0020.png)
No exemplo a seguir, vamos separar 3 posições na memória de dados:
usigned char count;
usigned char temperature;
usigned char humidity;
O código a seguir atribui valores a essas posições de memória. Para a pozição de memória count é atribuído o valor 7, para a posição de memória temperature, é atribuído o valor 22 e para a posição de memória de humidity é atribuído o valor 16:
usigned char count; usigned char temperature; usigned char humidity; int main() { count = 7; temperature = 22; humidity = 16; while(1) { } }
Veja a Figura 21 para entender o que aconteceu na memória de dados. A instrução while (1) é um loop infinito que no momento não faz nada.
![Figura 21_Exemplo do programa
Figura 21_Exemplo do programa](/images/stories/artigos2018/mic0182_0021.png)
No exemplo a seguir, vamos executar uma instrução de incremento na temperatura variável. A instrução:
temperature ++;
Aumenta em 1 o valor armazenado na variável temperatura. Em outras palavras, após o programa ter sido executado, a variável de temperatura permanecerá com o valor 23. A Figura 22 mostra este exemplo.
![Figura 22_Exemplo de programa com instrução de incremento
Figura 22_Exemplo de programa com instrução de incremento](/images/stories/artigos2018/mic0182_0022.png)
usigned char count; usigned char temperature; usigned char humidity; int main() { count = 7; temperature = 22; humidity = 16; temperature++; while(1) { } }
O OPERADOR DE ATRIBUIÇÃO (=).
Na Figura 23 podemos ver a placa do Arduino Uno e na Figura 24 o IDE ou ambiente de desenvolvimento, que pode ser baixado do site do Arduino. Para atribuir um valor a uma variável, usamos o operador de atribuição (=). No exemplo a seguir, a variável do sensor é armazenada na variável de exibição.
![Figura 23_ Placa de desenvolvimento do Arduino Uno
Figura 23_ Placa de desenvolvimento do Arduino Uno](/images/stories/artigos2018/mic0182_0023.png)
![Figura 24_Arduino Uno - ambiente de desenvolvimento
Figura 24_Arduino Uno - ambiente de desenvolvimento](/images/stories/artigos2018/mic0182_0024.jpg)
display = sensor;
Na instrução anterior, o valor contido na variável sensor é movido ou transferido para a localização da memória do displayr. Por exemplo, suponha que o valor da exibição da localização da memória seja 27 e o valor da variável do display seja 9. Após a execução da instrução display = sensor; o valor da variável de display será igual a 7. Assim, é como podemos mover dados de um local de memória para outro ou de uma porta para memória ou memória para uma porta.
A linguagem C usa o operador de atribuição (=) para transferir dados de um local de memória para outro. Além disso, existem funções prontas para uso, como no caso da memória EEPROM. A seguir estão as funções para gravar e ler nesta memória. Os códigos de exemplo podem ser encontrados no menu: Files-> Examples-> EEPROM. Veja a Figura 25.
![Figura 25_Menu do código de exemplo para memória EEPROM
Figura 25_Menu do código de exemplo para memória EEPROM](/images/stories/artigos2018/mic0182_0025.png)
Se você quiser incluir a biblioteca em qualquer projeto, vá ao menu: Programa-> Incluir Biblioteca-> EEPROM. Para isso, veja a Figura 26.
![Figura 26_Menu para incluir a biblioteca EEPROM
Figura 26_Menu para incluir a biblioteca EEPROM](/images/stories/artigos2018/mic0182_0026.png)
INSTRUÇÃO PARA ESCREVER NA MEMÓRIA EEPROM (EEPROM.write).
A instrução EEPROM.write permite gravar um byte na memória EEPROM. Para isso usamos a função write da seguinte maneira:
EEPROM.write (endereço, valor)
Parâmetros:
address: o local a ser gravado, a partir de 0 (int)
valor: o valor para gravar de 0 a 255 (byte)
Retorno: nenhum
No exemplo a seguir, armazenamos os valores lidos da entrada analógica 0, na memória EEPROM. Esses valores permanecerão na memória EEPROM, quando o circuito estiver desligado e puder ser lido por outro programa. A figura 27 mostra o circuito usado para este programa.
![Figura 27_Circuito para testar a memória EEPROM na placa do Arduino Uno
Figura 27_Circuito para testar a memória EEPROM na placa do Arduino Uno](/images/stories/artigos2018/mic0182_0027.png)
O programa pode ser encontrado no menu: Files-> Examples-> EEPROM-> eeprom_read.
#include /** the current address in the EEPROM (i.e. which byte we're going to write to next) **/int addr = 0;void setup() {/** Empty setup. **/ } void loop() { /** Need to divide by 4 because analog inputs range from 0 to 1023 and each byte of the EEPROM can only hold a value from 0 to 255. **/ int val = analogRead(0) / 4; /** Write the value to the appropriate byte of the EEPROM these values will remain there when the board is turned off. **/ EEPROM.write(addr, val); /**Advance to the next address, when at the end restart at the beginning. Larger AVR processors have larger EEPROM sizes, E.g: - Arduno Duemilanove: 512b EEPROM storage. - Arduino Uno: 1kb EEPROM storage. - Arduino Mega: 4kb EEPROM storage. Rather than hard-coding the length, you should use the pre-provided length function. This will make your code portable to all AVR processors. **/ addr = addr + 1; if (addr == EEPROM.length()) { addr = 0; } /** As the EEPROM sizes are powers of two, wrapping (preventing overflow) of an EEPROM address is also doable by a bitwise and of the length - 1. ++addr &= EEPROM.length() - 1; **/ delay(100); }
INSTRUÇÕES PARA LER UM BYTE DA MEMÓRIA EEPROM (EEPROM.read)
A instrução EEPROM.read permite ler um byte da memória EEPROM. Se por acaso a localização da memória for lida, nunca foi gravada, o valor retornado será 255 ou 0Xf em hexadecimal. O formato da instrução de leitura é o seguinte:
EEPROM.read (endereço)
Parâmetros:
address: o local a ser lido, a partir de 0 (int)
Retorno: O valor armazenado na localização (byte)
O programa a seguir lê os valores de cada byte da memória EEPROM e imprime no computador. Para isso é necessário abrir o monitor serial. Você pode encontrar este programa no menu: Arquivos-> Exemplos-> EEPROM-> eeprom_read.
#include // start reading from the first byte (address 0) of the EEPROM int address = 0; byte value; void setup() { // initialize serial and wait for port to open: Serial.begin(9600); while (!Serial) { ; // wait for serial port to connect. Needed for native USB port only } } void loop() { // read a byte from the current address of the EEPROM value = EEPROM.read(address); Serial.print(address); Serial.print("\t"); Serial.print(value, DEC); Serial.println();
/** Advance to the next address, when at the end restart at the beginning. Larger AVR processors have larger EEPROM sizes, E.g: - Arduno Duemilanove: 512b EEPROM storage. - Arduino Uno: 1kb EEPROM storage. - Arduino Mega: 4kb EEPROM storage. Rather than hard-coding the length, you should use the pre-provided length function. This will make your code portable to all AVR processors. **/ address = address + 1; if (address == EEPROM.length()) { address = 0; } /** As the EEPROM sizes are powers of two, wrapping (preventing overflow) of an EEPROM address is also doable by a bitwise and of the length - 1. ++address &= EEPROM.length() - 1; **/ delay(500); }
Concluindo, podemos ver que para transferir dados de um local de memória para outro, usamos o operador de atribuição (=). Para transferir dados para a memória EEPROM, podemos usar funções como EEPROM.write ou EEPROM.read. Assim, é muito fácil transferir ou mover dados entre locais de memória.