terça-feira, junho 30, 2020

Programação "Alta Voltagem" do ATmega - Parte 4

Montado o nosso protótipo vamos começar a conversar com o ATmega pelo modo de programação paralelo. O objetivo desta etapa é colocar o ATmega no modo programação e ler a sua identificação.


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
Então vamos ao código (o código completo você pode pegar no meu github):
#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: