terça-feira, julho 14, 2020

Programação "Alta Voltagem" do ATmega - Parte 6

Para finalizar este estudo dos comandos de programação paralela vamos olhar a leitura e escrita na Flash. Aqui a parte importante é entender como é organizada a Flash do ATmega.


O primeiro ponto importante é que a Flash do ATmega é composta por palavras de 16 bits. No caso do ATmega328 temos 32K bytes de Flash, mas ela precisa ser acessada como 16K palavras de 16 bits. Como a via de dados é de 8 bits, a transferência de endereço e dados precisa ser feita em duas partes, com o sinal BS1 indicando se estamos transferindo o byte mais ou menos significativo.

O segundo ponto é que a gravação é feita é em blocos. Existe um buffer interno onde você monta os dados a gravar no bloco e depois comanda a escrita do buffer na Flash. No caso do ATmega328 um bloco tem 64 palavras (portanto teremos 256 blocos para obter 256x64x2 = 32K bytes).

Para otimizar o acesso podemos usar o fato de que o comando e as partes altas e baixos de endereço e dados são mantidos até que sejam escritos de novo.

Um detalhe interessante na descrição da escrita da Flash no datasheet é que ela inclui enviar no final o comando NOP (faz nada). Como o comando fica registrado ao final da execução, esta providência me parece útil para todos os comandos.

A leitura da Flash fica assim:
  • Carregar o comando de leitura (0x02)
  • Carregar o byte mais significativo do endereço
  • Carregar o byte menos significativo do endereço
  • Ler o byte menos significativo da palavra
  • Ler o byte mais significativo da palavra
  • Carregar o comando NOP (0x00)
Não coloquei aqui os detalhes de cada passo (que já vimos nos posts anteriores).

A escrita de toda a Flash fica assim:
  • Carregar o comando de escrita (0x10)
  • Iniciar o endereço com zero
  • Até gravar toda a Flash
    • Para cada bloco da Flash
      • Carregar o byte mais significativo do endereço
      • Para cada palavra do bloco
        • Carregar o byte menos significativo do endereço
        • Carregar o byte mais significativo da palavra
        • Carregar o byte mais significativo da palavra
        • Pulsar o sinal PAGEL para gravar a palavra no buffer
        • Incrementar o endereço
      • Pulsar o sinal WR para disparar a gravação
      • Aguardar o sinal RDY retornar ao nível alto, indicando o fim da gravação
  • Carregar o comando NOP (0x00)
No meu teste eu vou escrever valores sequenciais (00, 01, 02... FF, 00, 01, ...) na Flash e depois conferir. O código completo está no github, aqui vou mostrar somente as rotinas principais (que aproveitam as rotinas que escrevemos anteriormente).
  1. // Preenche a Flash com 00, 01, ... FF, 00, ...  
  2. // O ATmega328 tem 32K de Flash (16K palavras de 16 bits)  
  3. // Na gravação são 256 páginas de 64 palavras de 16 bits  
  4. void Grava() {  
  5.   Serial.println("Gravando...");  
  6.   // Envia o comando  
  7.   ATmega_SendCmd(CMD_GRAVAFLASH);  
  8.   // Loop por página  
  9.   byte valor = 0;  
  10.   uint16_t addr = 0;  
  11.   while (addr < 0x4000) {  
  12.     // Envia a parte alta do endereço  
  13.     ATmega_SendAddr(HIGH, (byte) (addr >> 8));  
  14.     // Preenche o buffer da página  
  15.     for (byte cont = 0; cont < 64; cont++) {  
  16.       // Enviar o byte menos significativo do endereço e   
  17.       // os dois bytes da palavra  
  18.       ATmega_SendAddr(LOW, (byte) addr);  
  19.       digitalWrite (pinXA0, HIGH);  
  20.       digitalWrite (pinXA1, LOW);  
  21.       digitalWrite (pinBS1, LOW);  
  22.       PCF8574_Write(valor++);  
  23.       pulsaXTAL1();  
  24.       digitalWrite (pinBS1, HIGH);  
  25.       PCF8574_Write(valor++);  
  26.       pulsaXTAL1();  
  27.       // Pulsa PAGEL para colocar a palavra no buffer  
  28.       digitalWrite (pinPAGEL, HIGH);  
  29.       delayMicroseconds(1);  
  30.       digitalWrite (pinPAGEL, LOW);  
  31.       addr++;  
  32.     }  
  33.     // Dispara a gravação  
  34.     digitalWrite (pinWR, LOW);  
  35.     delayMicroseconds(1);  
  36.     digitalWrite (pinWR, HIGH);  
  37.     delayMicroseconds(1);  
  38.     // Aguarda o fim da gravação  
  39.     while (digitalRead(pinRdy) == LOW) {  
  40.       delay(10);  
  41.     }  
  42.   }  
  43.   // Finaliza a programação  
  44.   ATmega_SendCmd(CMD_NOP);  
  45.   Serial.println("Gravado.");  
  46. }  
  47.   
  48. // Confere o que foi gravado na Flash  
  49. void Confere() {  
  50.   int erros = 0;  
  51.   Serial.println("Conferindo...");  
  52.   // Envia o comando  
  53.   ATmega_SendCmd(CMD_LEFLASH);  
  54.   // Repete para todas as páginas  
  55.   byte valor = 0;  
  56.   byte dado;  
  57.   uint16_t addr = 0;  
  58.   while (addr < 0x4000) {  
  59.     // Seleciona a parte alta do endereço  
  60.     ATmega_SendAddr(HIGH, (byte) (addr >> 8));  
  61.     for (int cont = 0; cont < 256; cont++) {  
  62.       // Enviar a parte baixa do endereço e ler os dois bytes da palavra  
  63.       ATmega_SendAddr(LOW, (byte) addr);  
  64.       PCF8574_Release();  
  65.       delayMicroseconds(1);  
  66.       digitalWrite (pinBS1, LOW);  
  67.       digitalWrite (pinOE, LOW);  
  68.       delayMicroseconds(1);  
  69.       dado = PCF8574_Read();  
  70.       if (dado != valor) {  
  71.         erros++;  
  72.         Serial.print ("ERRO: ");  
  73.         Serial.print (addr, HEX);  
  74.         Serial.print (": ");  
  75.         Serial.print (dado, HEX);  
  76.         Serial.print (" x ");  
  77.         Serial.println (valor, HEX);  
  78.       }  
  79.       valor++;  
  80.       digitalWrite (pinBS1, HIGH);  
  81.       delayMicroseconds(1);  
  82.       dado = PCF8574_Read();  
  83.       if (dado != valor) {  
  84.         erros++;  
  85.         Serial.print ("ERRO: ");  
  86.         Serial.print (addr, HEX);  
  87.         Serial.print (": ");  
  88.         Serial.print (dado, HEX);  
  89.         Serial.print (" x ");  
  90.         Serial.println (valor, HEX);  
  91.       }  
  92.       valor++;  
  93.       digitalWrite (pinBS1, LOW);  
  94.       digitalWrite (pinOE, HIGH);  
  95.       addr++;  
  96.     }  
  97.   }  
  98.   // Finaliza a operação  
  99.   ATmega_SendCmd(CMD_NOP);  
  100.   if (erros == 0) {  
  101.       Serial.println("Sucesso!");  
  102.   } else {  
  103.     Serial.print(erros);  
  104.     Serial.println(" erros!!!");  
  105.   }  
  106. }  
Com estes comandos dominados, temos o suficiente para fazermos a interação com o ATmega no "ATmega Detonator":
  • Identificar o modelo do ATmega
  • Limpar a memória Flash
  • Gravar código na memória Flash
  • Verificar a gravação
  • Gravar os fuses com os valores que quisermos
Agora é "só" fazer a interface com o operador e as funcionalidades para carregar o código a gravar em uma memória externa.

Nenhum comentário: