As instruções para entrada no modo programação são descritas no manual/datasheet do ATmega328:
- Manter os pinos Vcc e Reset em 0V
- Manter em 0V os pinos PAGEL, XA0, XA1 e BS1
- Aplicar 5V no pino Vcc
- 20 a 60 microsegundos depois aplicar 12V no pino Reset
- Aguardar pelo menos 300 microsegundos
Para sair do modo programação basta colocar o pino Reset em 0 volts.
Uma vez no modo programação o ATmega328 passa a aceitar comandos. A tabela abaixo lista os códigos dos comandos disponíveis:
O comando que vamos usar é o de leitura da identificação do chip. O que temos que fazer é o seguinte:
- Setar XA1=1 e XA0=0, para indicar que vamos enviar um comando
- Setar BS1=0 para indicar que vamos transferir um byte menos significativo
- Colocar o código do comando (0x08) nos pinos de dados
- Pulsar o sinal XTAL1 para carregar o comando no ATmega
- Para os endereços 0, 1 e 2:
- Setar XA1=0 e XA0=0, para indicar que vamos enviar um endereço
- Setar BS1=0 para indicar que vamos transferir um byte menos significativo
- Colocar o endereço nos pinos de dados
- Pulsar o sinal XTAL1 para carregar o endereço no ATmega
- Setar OE=0 e BS1=1
- O ATmega colocará primeiro byte da assinatura nos dados
- Setar OE=1 para o ATmega soltar a via de dados
#include "ATmegaDetonator.h" #include <Wire.h> /* * ATmegaDetonator * Teste de leitura da identificação do ATmega * * (C) 2020, Daniel Quadros */ // pinos de saída int pinOut[] = { pinVcc, pin12V, pinOE, pinWR, pinBS1, pinBS2, pinXA0, pinXA1, pinPAGEL, pinXTAL1, 0 }; // comandos de programação const byte CMD_LEID = 0x08; // Iniciação void setup() { for (int i = 0; pinOut[i] != 0; i++) { pinMode(pinOut[i], OUTPUT); digitalWrite(pinOut[i], LOW); } pinMode (pinRdy, INPUT); Wire.begin(); PCF8574_Write(0); Serial.begin(115200); } void loop() { byte id[3]; Serial.println(); Serial.println("Coloque o ATmega e digite [ENTER]"); espera(); ATmega_ModoPgm(); ATmega_LeId(id); ATmega_Desliga(); Serial.print ("Identicacao: "); Serial.print (id[0], HEX); Serial.print ("."); Serial.print (id[1], HEX); Serial.print ("."); Serial.print (id[2], HEX); Serial.println(); } // Le os três bytes de identificação do ATmega void ATmega_LeId(byte *id) { ATmega_SendCmd (CMD_LEID); ATmega_SendAddr (HIGH, 0); for (byte addr = 0; addr < 3; addr++) { ATmega_SendAddr (LOW, addr); id[addr] = ATmega_Read(LOW); } } // Coloca ATmega no modo programação paralela void ATmega_ModoPgm() { Serial.println ("Colocando no modo de programacao paralela"); // Garantir que está sem alimentação e reset digitalWrite (pinVcc, LOW); digitalWrite (pin12V, LOW); // Condição de entrada no modo programação paralela digitalWrite (pinPAGEL, LOW); digitalWrite (pinXA0, LOW); digitalWrite (pinXA1, LOW); digitalWrite (pinBS1, LOW); // Ligar a alimentação digitalWrite (pinVcc, HIGH); // Estes sinais devem ficar normalmente em nível alto digitalWrite (pinOE, HIGH); digitalWrite (pinWR, HIGH); // Aguardar e colocar 12V no reset delayMicroseconds(40); digitalWrite (pin12V, HIGH); // Aguardar a entrada delayMicroseconds(400); if (digitalRead(pinRdy) == HIGH) { Serial.println ("Sucesso!"); } else { Serial.println ("Nao acionou sinal RDY..."); } } // Retira o ATmega do modo programação e o desliga void ATmega_Desliga() { // Desliga os 12V para sair do modo programação digitalWrite (pin12V, LOW); // Dá um tempo e desliga delayMicroseconds(300); digitalWrite (pinVcc, LOW); // Vamos deixar todos os pinos em nível LOW // Para poder tirar o chip for (int i = 0; pinOut[i] != 0; i++) { digitalWrite(pinOut[i], LOW); } PCF8574_Write(0); } // Envia um comando para o ATmega void ATmega_SendCmd (byte cmd) { // indica que vai enviar um comando digitalWrite (pinXA0, LOW); digitalWrite (pinXA1, HIGH); digitalWrite (pinBS1, LOW); // Coloca o comando na via de dados PCF8574_Write(cmd); // Pulsa XTAL1 para registrar o comando pulsaXTAL1(); } // Envia um byte de endereço para o ATmega void ATmega_SendAddr (byte ordem, byte addr) { // indica que vai enviar um endereço digitalWrite (pinXA0, LOW); digitalWrite (pinXA1, LOW); // indica se é o byte LOW ou HIGH digitalWrite (pinBS1, ordem); // Coloca o endereço na via de dados PCF8574_Write(addr); // Pulsa XTAL1 para registrar o endereço pulsaXTAL1(); } // Lê um byte do ATmega byte ATmega_Read (byte ordem) { byte dado; // Libera a via de dados PCF8574_Release(); delayMicroseconds(1); // indica se é o byte LOW ou HIGH digitalWrite (pinBS1, ordem); // Habilita a saída do byte digitalWrite (pinOE, LOW); delayMicroseconds(1); // Lê o byte dado = PCF8574_Read(); // Faz o ATMega soltar a via de dados digitalWrite (pinOE, HIGH); return dado; } // Gera um pulso em XTAL1 void pulsaXTAL1() { delayMicroseconds(1); digitalWrite (pinXTAL1, HIGH); delayMicroseconds(1); digitalWrite (pinXTAL1, LOW); delayMicroseconds(1); } // Espera digitar ENTER void espera() { while (Serial.read() != '\r') { delay (100); } } /** * Funções para interagir com o PCF8574A */ // Escreve um byte void PCF8574_Write(byte dado) { Wire.beginTransmission(PCF8574_Addr); Wire.write(dado); Wire.endTransmission(); } // Prepara para leitura // Somente os pinos colocados em HIGH // ser usados para entrada void PCF8574_Release() { Wire.beginTransmission(PCF8574_Addr); Wire.write(0xFF); Wire.endTransmission(); } // Lê um byte byte PCF8574_Read(){ Wire.requestFrom(PCF8574_Addr, 1); return Wire.read(); }Com pequenas variações neste código é possível ler a EEProm (basta mudar o comando) e os fuses (neste caso não tem endereço, o fuse é selecionado por BS1 e BS2). O resultado também está no github.
O uso bidirecional do PCF8574A é um pouco esquisito e merece um (futuro) post específico. Somando a isso a um mau contato esquisito, me deixou queimando a cabeça por dois dias... Como resultado parcial (que também merece um post futuro) cheguei a programar um expansor de I/O feito com um ATtiny!
No próximo post vamos nos aventurar a apagar as memórias e programar os fuses.
Nenhum comentário:
Postar um comentário