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.
#include <TimerOne.h> // Protótipo de programador serial de alta tensão para ATtiny // Adaptado do código de Wayne Holder: // https://sites.google.com/site/wayneholder/attiny-fuse-reset-with-12-volt-charge-pump #define LED 13 // Conexões para programação #define SCI 12 // Target Clock Input #define SDO 11 // Target Data Output #define SII 10 // Target Instruction Input #define SDI 9 // Target Data Input #define VCC 8 // Target VCC // Comandos de leitura e escrita dos FUSES e LOCK #define HFUSE_RD 0x7A7E #define LFUSE_RD 0x686C #define EFUSE_RD 0x6A6E #define LOCK_RD 0x787C #define HFUSE_WR 0x747C #define LFUSE_WR 0x646C #define EFUSE_WR 0x666E // Define ATTiny series signatures #define ATTINY13 0x9007 // L: 0x6A, H: 0xFF 8 pin #define ATTINY24 0x910B // L: 0x62, H: 0xDF, E: 0xFF 14 pin #define ATTINY25 0x9108 // L: 0x62, H: 0xDF, E: 0xFF 8 pin #define ATTINY44 0x9207 // L: 0x62, H: 0xDF, E: 0xFFF 14 pin #define ATTINY45 0x9206 // L: 0x62, H: 0xDF, E: 0xFF 8 pin #define ATTINY84 0x930C // L: 0x62, H: 0xDF, E: 0xFFF 14 pin #define ATTINY85 0x930B // L: 0x62, H: 0xDF, E: 0xFF 8 pin // Definições para acesso direto aos pinos do Charge Pump #define P1 0x04 // Pin D2 #define P2 0x08 // Pin D3 #define PWR 0x10 // Pin D4 #define GND 0x20 // Pin D5 #define REF 420 // valor do ADC para 12V // Variaveis para controle do Charge Pump volatile char phase = 0; volatile char onOff = 0; volatile char pwrOn = 0; // Rotina de controle do Charge Pump // Disparada pelo Timer 1 void ticker () { if (onOff) { DDRD = P1 | P2 | PWR | GND; int volts = analogRead(A0); if (volts < REF) { if (phase) { PORTD = P1 | PWR; } else { PORTD = P2 | PWR; } phase ^= 1; } else { pwrOn = 1; } } else { pwrOn = 0; DDRD = GND; PORTD = GND; } } // Iniciação void setup() { pinMode(LED, OUTPUT); // Inicia os pinos de programação pinMode(VCC, OUTPUT); pinMode(SDI, OUTPUT); pinMode(SII, OUTPUT); pinMode(SCI, OUTPUT); pinMode(SDO, OUTPUT); // Inicia serial Serial.begin(57600); Serial.println("HVSP Prototipo"); Serial.println("I - Le identificacao"); Serial.println("F - Le os fuses"); Serial.println("L - Le o lock"); Serial.println("C - Chip Erase"); Serial.println("R - Reinicia fuses"); Serial.println("T - Trava"); // Inicia ADC e Timer1 para o Charge Pump analogReference(DEFAULT); Timer1.initialize(500); Timer1.attachInterrupt(ticker); Serial.println("Pronto"); } // Programa principal void loop() { int c; byte id[3]; unsigned int sig; if (Serial.available() > 0) { c = Serial.read(); // Entra no modo programação digitalWrite(LED, HIGH); pinMode(SDO, OUTPUT); digitalWrite(SDI, LOW); digitalWrite(SII, LOW); digitalWrite(SDO, LOW); onOff = 0; // 12v Off digitalWrite(VCC, HIGH); // Vcc On delayMicroseconds(20); onOff = 1; // 12v On while (pwrOn == 0) ; delayMicroseconds(10); pinMode(SDO, INPUT); // Set SDO to input delayMicroseconds(300); // Trata o comando switch (c) { case 'I': case 'i': readSignature(id); Serial.print("Identificacao: "); Serial.print(id[0], HEX); Serial.print(' '); Serial.print(id[1], HEX); Serial.print(' '); Serial.println(id[2], HEX); break; case 'F': case 'f': Serial.print("LFUSE: "); Serial.println(readFuse(LFUSE_RD), HEX); Serial.print("HFUSE: "); Serial.println(readFuse(HFUSE_RD), HEX); Serial.print("EFUSE: "); Serial.println(readFuse(EFUSE_RD), HEX); break; case 'L': case 'l': Serial.print("LOCK: "); Serial.println(readFuse(LOCK_RD) & 3, HEX); break; case 'C': case 'c': Serial.println("Apagando..."); chipErase(); break; case 'R': case 'r': Serial.println("Reiniciando fuses..."); sig = readSignature(id); if (sig == ATTINY13) { writeFuse(LFUSE_WR, 0x6A); writeFuse(HFUSE_WR, 0xFF); } else if (sig == ATTINY24 || sig == ATTINY44 || sig == ATTINY84 || sig == ATTINY25 || sig == ATTINY45 || sig == ATTINY85) { writeFuse(LFUSE_WR, 0x62); writeFuse(HFUSE_WR, 0xDF); writeFuse(EFUSE_WR, 0xFF); } break; case 'T': case 't': Serial.println("Travando..."); writeLock (0); break; } // espera concluir operação while (!digitalRead(SDO)) ; // sai do modo programação digitalWrite(SCI, LOW); digitalWrite(VCC, LOW); // desliga Vcc onOff = 0; // desliga 12V digitalWrite(LED, LOW); Serial.println("Pronto"); } } // Envia instrução e dado e lê resposta byte shiftOut (byte dado, byte instr) { int inBits = 0; // Espera final da operação anterior while (!digitalRead(SDO)) ; unsigned int dout = (unsigned int) dado << 2; unsigned int iout = (unsigned int) instr << 2; for (int ii = 10; ii >= 0; ii--) { digitalWrite(SDI, !!(dout & (1 << ii))); digitalWrite(SII, !!(iout & (1 << ii))); inBits <<= 1; inBits |= digitalRead(SDO); digitalWrite(SCI, HIGH); digitalWrite(SCI, LOW); } return inBits >> 2; } // Le um fuse (ou lock) byte readFuse (unsigned int fuse) { shiftOut(0x04, 0x4C); shiftOut(0x00, (byte) (fuse >> 8)); return shiftOut(0x00, (byte) fuse); } // Escreve em um fuse void writeFuse (unsigned int fuse, byte val) { shiftOut(0x40, 0x4C); shiftOut( val, 0x2C); shiftOut(0x00, (byte) (fuse >> 8)); shiftOut(0x00, (byte) fuse); } // Le a identificação do chip unsigned int readSignature (byte *sig) { shiftOut(0x08, 0x4C); for (int ii = 0; ii < 3; ii++) { shiftOut( ii, 0x0C); shiftOut(0x00, 0x68); sig[ii] = shiftOut(0x00, 0x6C); } return (sig[1] << 8) | sig[2]; } // Faz apagamento completo do chip void chipErase() { shiftOut(0x80, 0x4C); shiftOut(0x00, 0x64); shiftOut(0x00, 0x6C); } // Escreve no lock byte writeLock(byte val) { shiftOut(0x20, 0x4C); shiftOut(val, 0x2C); shiftOut(0x00, 0x64); shiftOut(0x00, 0x6C); }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:
Postar um comentário