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