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):
  1. #include "ATmegaDetonator.h"  
  2. #include <Wire.h>  
  3.   
  4. /* 
  5.  * ATmegaDetonator 
  6.  * Teste de leitura da identificação do ATmega 
  7.  *  
  8.  * (C) 2020, Daniel Quadros 
  9.  */  
  10.   
  11. // pinos de saída  
  12. int pinOut[] = {  
  13.   pinVcc, pin12V, pinOE, pinWR, pinBS1, pinBS2,  
  14.   pinXA0, pinXA1, pinPAGEL, pinXTAL1,  
  15.   0  
  16. };  
  17.   
  18. // comandos de programação  
  19. const byte CMD_LEID = 0x08;  
  20.   
  21. // Iniciação  
  22. void setup() {  
  23.   for (int i = 0; pinOut[i] != 0; i++) {  
  24.     pinMode(pinOut[i], OUTPUT);  
  25.     digitalWrite(pinOut[i], LOW);  
  26.   }  
  27.   pinMode (pinRdy, INPUT);  
  28.   Wire.begin();  
  29.   PCF8574_Write(0);  
  30.   Serial.begin(115200);  
  31. }  
  32.   
  33. void loop() {  
  34.   byte id[3];  
  35.     
  36.   Serial.println();  
  37.   Serial.println("Coloque o ATmega e digite [ENTER]");  
  38.   espera();  
  39.   ATmega_ModoPgm();  
  40.   ATmega_LeId(id);  
  41.   ATmega_Desliga();  
  42.   Serial.print ("Identicacao: ");  
  43.   Serial.print (id[0], HEX);  
  44.   Serial.print (".");  
  45.   Serial.print (id[1], HEX);  
  46.   Serial.print (".");  
  47.   Serial.print (id[2], HEX);  
  48.   Serial.println();  
  49. }  
  50.   
  51. // Le os três bytes de identificação do ATmega  
  52. void ATmega_LeId(byte *id) {  
  53.   ATmega_SendCmd (CMD_LEID);  
  54.   ATmega_SendAddr (HIGH, 0);  
  55.   for (byte addr = 0; addr < 3; addr++) {  
  56.     ATmega_SendAddr (LOW, addr);  
  57.     id[addr] = ATmega_Read(LOW);  
  58.   }  
  59. }  
  60.   
  61. // Coloca ATmega no modo programação paralela  
  62. void ATmega_ModoPgm() {  
  63.   Serial.println ("Colocando no modo de programacao paralela");  
  64.   // Garantir que está sem alimentação e reset  
  65.   digitalWrite (pinVcc, LOW);  
  66.   digitalWrite (pin12V, LOW);  
  67.   // Condição de entrada no modo programação paralela  
  68.   digitalWrite (pinPAGEL, LOW);  
  69.   digitalWrite (pinXA0, LOW);  
  70.   digitalWrite (pinXA1, LOW);  
  71.   digitalWrite (pinBS1, LOW);  
  72.   // Ligar a alimentação  
  73.   digitalWrite (pinVcc, HIGH);  
  74.   // Estes sinais devem ficar normalmente em nível alto  
  75.   digitalWrite (pinOE, HIGH);  
  76.   digitalWrite (pinWR, HIGH);  
  77.   // Aguardar e colocar 12V no reset  
  78.   delayMicroseconds(40);  
  79.   digitalWrite (pin12V, HIGH);  
  80.   // Aguardar a entrada  
  81.   delayMicroseconds(400);  
  82.   if (digitalRead(pinRdy) == HIGH) {  
  83.     Serial.println ("Sucesso!");  
  84.   } else {  
  85.     Serial.println ("Nao acionou sinal RDY...");  
  86.   }  
  87. }  
  88.   
  89. // Retira o ATmega do modo programação e o desliga  
  90. void ATmega_Desliga() {  
  91.   // Desliga os 12V para sair do modo programação  
  92.   digitalWrite (pin12V, LOW);  
  93.   // Dá um tempo e desliga  
  94.   delayMicroseconds(300);  
  95.   digitalWrite (pinVcc, LOW);  
  96.   // Vamos deixar todos os pinos em nível LOW  
  97.   // Para poder tirar o chip  
  98.   for (int i = 0; pinOut[i] != 0; i++) {  
  99.     digitalWrite(pinOut[i], LOW);  
  100.   }  
  101.   PCF8574_Write(0);  
  102. }  
  103.   
  104. // Envia um comando para o ATmega  
  105. void ATmega_SendCmd (byte cmd) {  
  106.   // indica que vai enviar um comando  
  107.   digitalWrite (pinXA0, LOW);  
  108.   digitalWrite (pinXA1, HIGH);  
  109.   digitalWrite (pinBS1, LOW);  
  110.   // Coloca o comando na via de dados  
  111.   PCF8574_Write(cmd);  
  112.   // Pulsa XTAL1 para registrar o comando  
  113.   pulsaXTAL1();  
  114. }  
  115.   
  116. // Envia um byte de endereço para o ATmega  
  117. void ATmega_SendAddr (byte ordem, byte addr) {  
  118.   // indica que vai enviar um endereço  
  119.   digitalWrite (pinXA0, LOW);  
  120.   digitalWrite (pinXA1, LOW);  
  121.   // indica se é o byte LOW ou HIGH  
  122.   digitalWrite (pinBS1, ordem);  
  123.   // Coloca o endereço na via de dados  
  124.   PCF8574_Write(addr);  
  125.   // Pulsa XTAL1 para registrar o endereço  
  126.   pulsaXTAL1();  
  127. }  
  128.   
  129. // Lê um byte do ATmega  
  130. byte ATmega_Read (byte ordem) {  
  131.   byte dado;  
  132.     
  133.   // Libera a via de dados  
  134.   PCF8574_Release();  
  135.   delayMicroseconds(1);  
  136.   // indica se é o byte LOW ou HIGH  
  137.   digitalWrite (pinBS1, ordem);  
  138.   // Habilita a saída do byte    
  139.   digitalWrite (pinOE, LOW);  
  140.   delayMicroseconds(1);  
  141.   // Lê o byte  
  142.   dado = PCF8574_Read();  
  143.   // Faz o ATMega soltar a via de dados   
  144.   digitalWrite (pinOE, HIGH);  
  145.   return dado;  
  146. }  
  147.   
  148. // Gera um pulso em XTAL1  
  149. void pulsaXTAL1() {  
  150.   delayMicroseconds(1);  
  151.   digitalWrite (pinXTAL1, HIGH);  
  152.   delayMicroseconds(1);  
  153.   digitalWrite (pinXTAL1, LOW);  
  154.   delayMicroseconds(1);  
  155. }  
  156.   
  157. // Espera digitar ENTER  
  158. void espera() {  
  159.   while (Serial.read() != '\r') {  
  160.     delay (100);  
  161.   }  
  162. }  
  163.   
  164. /** 
  165.  * Funções para interagir com o PCF8574A 
  166.  */  
  167.   
  168. // Escreve um byte  
  169. void PCF8574_Write(byte dado) {  
  170.   Wire.beginTransmission(PCF8574_Addr);  
  171.   Wire.write(dado);  
  172.   Wire.endTransmission();  
  173. }  
  174.   
  175. // Prepara para leitura  
  176. // Somente os pinos colocados em HIGH  
  177. // ser usados para entrada  
  178. void PCF8574_Release() {  
  179.   Wire.beginTransmission(PCF8574_Addr);  
  180.   Wire.write(0xFF);  
  181.   Wire.endTransmission();  
  182. }  
  183.   
  184. // Lê um byte  
  185. byte PCF8574_Read(){  
  186.   Wire.requestFrom(PCF8574_Addr, 1);  
  187.   return Wire.read();  
  188. }  
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: