quinta-feira, julho 10, 2014

Programador de "Alta Voltagem" para ATtiny: Protótipo

Agora que vimos a teoria, vamos fazer algumas experiências usando um Arduino para controlar o nosso programador. O que apresento aqui é uma releitura da excelente página "ATTiny Fuse Reset with 12 Volt Charge Pump" de Wayne Holder.


Gerando 12V

A primeira ideia que vem é usar uma fonte separada de 12V (como já fiz em algumas experiências com motor de passo). Para um programador standalone, eu pensaria em ter uma fonte de 12V e passar a saída por um regulador de 5V (7805, etc) para alimentar a parte lógica. Óbvio, simples, fácil mas (como diria Greg House) booooring. O que Wayne usa é um circuito simples que transforma 5V em 12V.

A teoria toda está descrita nas páginas dele. Resumindo, a ideia é termos três capacitores que podem ser dinamicamente configurados para ligação em paralelo ou série. Configurados em paralelo e ligados a 5V, cada um dos capacitores irá carregar até atingir os 5V. Mudando a configuração para série, obtemos 15V que podem ser usados para carregar um quarto capacitor. Enquanto estes 15V alimentam o resto do circuito, volta-se à configuração paralelo para recarregar os capacitores. Parece algo complicado? Na verdade bastam quatro diodos e dois sinais digitais para controlar isto. Este circuito é chamado "Dickson Charge Pump".

Ok, mas nós queremos 12V e não 15V.  Para isto usamos o ADC do Arduino para medir a tensão de saída e controlar a carga dos capacitores conforme necessário. Como o ADC é limitado a 5V, usamos um divisor resistivo para fazer esta leitura. Por último, colocamos um transistor para forçar a descarga do capacitor de saída, desligando a alta tensão.

O Hardware do Nosso Programador

A figura abaixo é quase idêntica à da página do Wayne. Mudei apenas um dos resistores do divisor para o ADC, pois eu tinha resistores de 470K ao invés de 510K (isto requer alterar o valor de referência no software) Nos meus testes iniciais usei um 2N222 ao invés do 2N3904 e um capacitor eletrolítico de 1uF 25V ao invés do capacitor cerâmico multicamadas de 2,2uF que ele usou. Usei resistores de 1% para o divisor, provavelmente você não vai ter problemas com resistores de 5%; se quiser ficar mais seguro meça a tensão de saída da charge pump e altere o valor de referência no software.


O Software de Teste

O teste abaixo é uma adaptação do software feito pelo Wayne. Como o meu objetivo é fazer testes e não apenas recuperar ATtinys, implementei mais alguns comandos e permiti selecionar pela serial o comando a executar.
  1. #include <TimerOne.h>  
  2.   
  3. // Protótipo de programador serial de alta tensão para ATtiny  
  4. // Adaptado do código de Wayne Holder:  
  5. // https://sites.google.com/site/wayneholder/attiny-fuse-reset-with-12-volt-charge-pump  
  6.   
  7. #define LED      13  
  8.   
  9. // Conexões para programação  
  10. #define  SCI     12    // Target Clock Input  
  11. #define  SDO     11    // Target Data Output  
  12. #define  SII     10    // Target Instruction Input  
  13. #define  SDI      9    // Target Data Input  
  14. #define  VCC      8    // Target VCC  
  15.   
  16. // Comandos de leitura e escrita dos FUSES e LOCK  
  17. #define  HFUSE_RD  0x7A7E  
  18. #define  LFUSE_RD  0x686C  
  19. #define  EFUSE_RD  0x6A6E  
  20. #define  LOCK_RD   0x787C  
  21. #define  HFUSE_WR  0x747C  
  22. #define  LFUSE_WR  0x646C  
  23. #define  EFUSE_WR  0x666E  
  24.   
  25. // Define ATTiny series signatures  
  26. #define  ATTINY13   0x9007  // L: 0x6A, H: 0xFF             8 pin  
  27. #define  ATTINY24   0x910B  // L: 0x62, H: 0xDF, E: 0xFF   14 pin  
  28. #define  ATTINY25   0x9108  // L: 0x62, H: 0xDF, E: 0xFF    8 pin  
  29. #define  ATTINY44   0x9207  // L: 0x62, H: 0xDF, E: 0xFFF  14 pin  
  30. #define  ATTINY45   0x9206  // L: 0x62, H: 0xDF, E: 0xFF    8 pin  
  31. #define  ATTINY84   0x930C  // L: 0x62, H: 0xDF, E: 0xFFF  14 pin  
  32. #define  ATTINY85   0x930B  // L: 0x62, H: 0xDF, E: 0xFF    8 pin  
  33.   
  34. // Definições para acesso direto aos pinos do Charge Pump  
  35. #define P1  0x04  // Pin D2  
  36. #define P2  0x08  // Pin D3  
  37. #define PWR 0x10  // Pin D4  
  38. #define GND 0x20  // Pin D5  
  39. #define REF 420   // valor do ADC para 12V  
  40.   
  41. // Variaveis para controle do Charge Pump  
  42. volatile char phase = 0;  
  43. volatile char onOff = 0;  
  44. volatile char pwrOn = 0;  
  45.   
  46. // Rotina de controle do Charge Pump  
  47. // Disparada pelo Timer 1  
  48. void ticker () {  
  49.   if (onOff) {  
  50.     DDRD = P1 | P2 | PWR | GND;  
  51.     int volts = analogRead(A0);  
  52.     if (volts < REF) {  
  53.       if (phase) {  
  54.         PORTD = P1 | PWR;  
  55.       } else {  
  56.         PORTD = P2 | PWR;  
  57.       }  
  58.       phase ^= 1;  
  59.     } else {  
  60.       pwrOn = 1;  
  61.     }  
  62.   } else {  
  63.     pwrOn = 0;  
  64.     DDRD = GND;  
  65.     PORTD = GND;  
  66.   }  
  67. }  
  68.   
  69. // Iniciação  
  70. void setup() {  
  71.   pinMode(LED, OUTPUT);  
  72.     
  73.   // Inicia os pinos de programação  
  74.   pinMode(VCC, OUTPUT);  
  75.   pinMode(SDI, OUTPUT);  
  76.   pinMode(SII, OUTPUT);  
  77.   pinMode(SCI, OUTPUT);  
  78.   pinMode(SDO, OUTPUT);  
  79.     
  80.   // Inicia serial  
  81.   Serial.begin(57600);  
  82.   Serial.println("HVSP Prototipo");  
  83.   Serial.println("I - Le identificacao");  
  84.   Serial.println("F - Le os fuses");  
  85.   Serial.println("L - Le o lock");  
  86.   Serial.println("C - Chip Erase");  
  87.   Serial.println("R - Reinicia fuses");  
  88.   Serial.println("T - Trava");  
  89.     
  90.   // Inicia ADC e Timer1 para o Charge Pump  
  91.   analogReference(DEFAULT);  
  92.   Timer1.initialize(500);  
  93.   Timer1.attachInterrupt(ticker);  
  94.   
  95.   Serial.println("Pronto");  
  96. }  
  97.   
  98. // Programa principal  
  99. void loop() {  
  100.   int c;  
  101.   byte id[3];  
  102.   unsigned int sig;  
  103.     
  104.   if (Serial.available() > 0) {  
  105.     c = Serial.read();  
  106.       
  107.     // Entra no modo programação  
  108.     digitalWrite(LED, HIGH);  
  109.     pinMode(SDO, OUTPUT);  
  110.     digitalWrite(SDI, LOW);  
  111.     digitalWrite(SII, LOW);  
  112.     digitalWrite(SDO, LOW);  
  113.     onOff = 0;                // 12v Off  
  114.     digitalWrite(VCC, HIGH);  // Vcc On  
  115.     delayMicroseconds(20);  
  116.     onOff = 1;                // 12v On  
  117.     while (pwrOn == 0)  
  118.       ;  
  119.     delayMicroseconds(10);  
  120.     pinMode(SDO, INPUT);      // Set SDO to input  
  121.     delayMicroseconds(300);  
  122.       
  123.     // Trata o comando  
  124.     switch (c) {  
  125.       case 'I':  case 'i':  
  126.         readSignature(id);  
  127.         Serial.print("Identificacao: ");  
  128.         Serial.print(id[0], HEX);  
  129.         Serial.print(' ');  
  130.         Serial.print(id[1], HEX);  
  131.         Serial.print(' ');  
  132.         Serial.println(id[2], HEX);  
  133.         break;  
  134.       case 'F':  case 'f':  
  135.         Serial.print("LFUSE: ");  
  136.         Serial.println(readFuse(LFUSE_RD), HEX);  
  137.         Serial.print("HFUSE: ");  
  138.         Serial.println(readFuse(HFUSE_RD), HEX);  
  139.         Serial.print("EFUSE: ");  
  140.         Serial.println(readFuse(EFUSE_RD), HEX);  
  141.         break;  
  142.       case 'L':  case 'l':  
  143.         Serial.print("LOCK: ");  
  144.         Serial.println(readFuse(LOCK_RD) & 3, HEX);  
  145.         break;  
  146.       case 'C':  case 'c':  
  147.         Serial.println("Apagando...");  
  148.         chipErase();  
  149.         break;  
  150.       case 'R':  case 'r':  
  151.         Serial.println("Reiniciando fuses...");  
  152.         sig = readSignature(id);  
  153.         if (sig == ATTINY13) {  
  154.           writeFuse(LFUSE_WR, 0x6A);  
  155.           writeFuse(HFUSE_WR, 0xFF);  
  156.         } else if (sig == ATTINY24 || sig == ATTINY44 || sig == ATTINY84 ||  
  157.                    sig == ATTINY25 || sig == ATTINY45 || sig == ATTINY85) {  
  158.           writeFuse(LFUSE_WR, 0x62);  
  159.           writeFuse(HFUSE_WR, 0xDF);  
  160.           writeFuse(EFUSE_WR, 0xFF);  
  161.         }  
  162.         break;  
  163.       case 'T':  case 't':  
  164.         Serial.println("Travando...");  
  165.         writeLock (0);  
  166.         break;  
  167.     }  
  168.   
  169.     // espera concluir operação  
  170.     while (!digitalRead(SDO))  
  171.       ;  
  172.         
  173.     // sai do modo programação      
  174.     digitalWrite(SCI, LOW);  
  175.     digitalWrite(VCC, LOW);    // desliga Vcc  
  176.     onOff = 0;                 // desliga 12V  
  177.     digitalWrite(LED, LOW);  
  178.     Serial.println("Pronto");  
  179.   }  
  180. }  
  181.   
  182. // Envia instrução e dado e lê resposta  
  183. byte shiftOut (byte dado, byte instr) {  
  184.   int inBits = 0;  
  185.     
  186.   // Espera final da operação anterior  
  187.   while (!digitalRead(SDO))  
  188.     ;  
  189.   unsigned int dout = (unsigned int) dado << 2;  
  190.   unsigned int iout = (unsigned int) instr << 2;  
  191.   for (int ii = 10; ii >= 0; ii--)  {  
  192.     digitalWrite(SDI, !!(dout & (1 << ii)));  
  193.     digitalWrite(SII, !!(iout & (1 << ii)));  
  194.     inBits <<= 1;  
  195.     inBits |= digitalRead(SDO);  
  196.     digitalWrite(SCI, HIGH);  
  197.     digitalWrite(SCI, LOW);  
  198.   }  
  199.   return inBits >> 2;  
  200. }  
  201.   
  202. // Le um fuse (ou lock)  
  203. byte readFuse (unsigned int fuse)  
  204. {  
  205.   shiftOut(0x04, 0x4C);  
  206.   shiftOut(0x00, (byte) (fuse >> 8));  
  207.   return shiftOut(0x00, (byte) fuse);  
  208. }  
  209.   
  210. // Escreve em um fuse  
  211. void writeFuse (unsigned int fuse, byte val) {  
  212.   shiftOut(0x40, 0x4C);  
  213.   shiftOut( val, 0x2C);  
  214.   shiftOut(0x00, (byte) (fuse >> 8));  
  215.   shiftOut(0x00, (byte) fuse);  
  216. }  
  217.   
  218. // Le a identificação do chip  
  219. unsigned int readSignature (byte *sig) {  
  220.   shiftOut(0x08, 0x4C);  
  221.   for (int ii = 0; ii < 3; ii++) {  
  222.     shiftOut( ii, 0x0C);  
  223.     shiftOut(0x00, 0x68);  
  224.     sig[ii] = shiftOut(0x00, 0x6C);  
  225.   }  
  226.   return (sig[1] << 8) | sig[2];  
  227. }  
  228.   
  229. // Faz apagamento completo do chip  
  230. void chipErase() {  
  231.   shiftOut(0x80, 0x4C);  
  232.   shiftOut(0x00, 0x64);  
  233.   shiftOut(0x00, 0x6C);  
  234. }  
  235.   
  236. // Escreve no lock  
  237. byte writeLock(byte val) {  
  238.   shiftOut(0x20, 0x4C);  
  239.   shiftOut(val, 0x2C);  
  240.   shiftOut(0x00, 0x64);  
  241.   shiftOut(0x00, 0x6C);  
  242. }  
Este software usa a biblioteca TimerOne, que você pode baixar do Arduino Playground.


Futuramente vou mostrar o projeto de um recuperador standalone de ATtinys.

Nenhum comentário: