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:
Postar um comentário