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).
// Preenche a Flash com 00, 01, ... FF, 00, ...
// O ATmega328 tem 32K de Flash (16K palavras de 16 bits)
// Na gravação são 256 páginas de 64 palavras de 16 bits
void Grava() {
  Serial.println("Gravando...");
  // Envia o comando
  ATmega_SendCmd(CMD_GRAVAFLASH);
  // Loop por página
  byte valor = 0;
  uint16_t addr = 0;
  while (addr < 0x4000) {
    // Envia a parte alta do endereço
    ATmega_SendAddr(HIGH, (byte) (addr >> 8));
    // Preenche o buffer da página
    for (byte cont = 0; cont < 64; cont++) {
      // Enviar o byte menos significativo do endereço e 
      // os dois bytes da palavra
      ATmega_SendAddr(LOW, (byte) addr);
      digitalWrite (pinXA0, HIGH);
      digitalWrite (pinXA1, LOW);
      digitalWrite (pinBS1, LOW);
      PCF8574_Write(valor++);
      pulsaXTAL1();
      digitalWrite (pinBS1, HIGH);
      PCF8574_Write(valor++);
      pulsaXTAL1();
      // Pulsa PAGEL para colocar a palavra no buffer
      digitalWrite (pinPAGEL, HIGH);
      delayMicroseconds(1);
      digitalWrite (pinPAGEL, LOW);
      addr++;
    }
    // Dispara a gravação
    digitalWrite (pinWR, LOW);
    delayMicroseconds(1);
    digitalWrite (pinWR, HIGH);
    delayMicroseconds(1);
    // Aguarda o fim da gravação
    while (digitalRead(pinRdy) == LOW) {
      delay(10);
    }
  }
  // Finaliza a programação
  ATmega_SendCmd(CMD_NOP);
  Serial.println("Gravado.");
}

// Confere o que foi gravado na Flash
void Confere() {
  int erros = 0;
  Serial.println("Conferindo...");
  // Envia o comando
  ATmega_SendCmd(CMD_LEFLASH);
  // Repete para todas as páginas
  byte valor = 0;
  byte dado;
  uint16_t addr = 0;
  while (addr < 0x4000) {
    // Seleciona a parte alta do endereço
    ATmega_SendAddr(HIGH, (byte) (addr >> 8));
    for (int cont = 0; cont < 256; cont++) {
      // Enviar a parte baixa do endereço e ler os dois bytes da palavra
      ATmega_SendAddr(LOW, (byte) addr);
      PCF8574_Release();
      delayMicroseconds(1);
      digitalWrite (pinBS1, LOW);
      digitalWrite (pinOE, LOW);
      delayMicroseconds(1);
      dado = PCF8574_Read();
      if (dado != valor) {
        erros++;
        Serial.print ("ERRO: ");
        Serial.print (addr, HEX);
        Serial.print (": ");
        Serial.print (dado, HEX);
        Serial.print (" x ");
        Serial.println (valor, HEX);
      }
      valor++;
      digitalWrite (pinBS1, HIGH);
      delayMicroseconds(1);
      dado = PCF8574_Read();
      if (dado != valor) {
        erros++;
        Serial.print ("ERRO: ");
        Serial.print (addr, HEX);
        Serial.print (": ");
        Serial.print (dado, HEX);
        Serial.print (" x ");
        Serial.println (valor, HEX);
      }
      valor++;
      digitalWrite (pinBS1, LOW);
      digitalWrite (pinOE, HIGH);
      addr++;
    }
  }
  // Finaliza a operação
  ATmega_SendCmd(CMD_NOP);
  if (erros == 0) {
      Serial.println("Sucesso!");
  } else {
    Serial.print(erros);
    Serial.println(" erros!!!");
  }
}
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: