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