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