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.
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.
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).
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
Agora, podemos formar um circuito que possui 8 flip-flops tipo D o qual é representado pela Figura 5.
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.
A figura 7 mostra o circuito após receber um pulso positivo na entrada Clock. Os valores são armazenados nas saídas Q.
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.
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.
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.
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.
A figura 12 mostra o circuito.
A figura 13 mostra o diagrama de blocos do circuito da figura.
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.
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.
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.
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.
A Figura 19 mostra quando a CPU lê a instrução e a Figura 20 depois de executar a instrução.
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.
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.
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.
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.
Se você quiser incluir a biblioteca em qualquer projeto, vá ao menu: Programa-> Incluir Biblioteca-> EEPROM. Para isso, veja a Figura 26.
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.
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.