von Ralf Beesner, DK5BU
Wenn ich mit 8-poligen ATtinys herumbastele, benutze ich
gern die kleine Platine aus dem LP Mikrocontroller und flashe die Chips mit
Burkhard Kainkas LPmicroISP.exe oder mit AVRDude (in der avrdude.conf habe ich
einen Eintrag für die LP-Mikrocontroller-Hardware hinzugefügt).
Leider taugt die serielle Schnittstelle meines Notebooks nicht viel; statt +/-8
macht sie nur knapp +/-5 V, und gelegentlich führt das dazu, dass ein
kritisches Bit "umkippt" und der Attiny dann so verfust wird, dass er
nicht mehr per ISP- Programmierung zugänglich ist.
Man kann ihn dann zwar noch per High Voltage Programming retten (das ist ein
weiterer Progammiermodus, der anders als SPI abläuft und durch das Anlegen von
+12 V an den Reset-Pin eingeleitet wird; Details kann man im ATtiny13-Datenblatt
nachschlagen). Der bekannteste High-Voltage-fähige Programmer ist das STK500, das etwa 80 Euro
kostet. Es gibt auch einige andere HV-Programmer; die Hardware ist aber bei
allen recht aufwendig und man muss schon zahlreiche Controller verfusen, damit
sich die Anschaffung lohnt. Nach dem halben Dutzend ATtiny13, die ich schon verfust und weggeworfen habe,
traf es nun zwei ATtiny45, die ja etwas teurer sind, und nun begann es mich
doch zu ärgern.
Ich habe daher auf der LP-Mikrocontroller-Platine die beiden 100-kOhm-Widerstände
endlich mal auf 22 kOhm verkleinert (wie es Burkhard mal für seinen PingPong-Programmer
empfohlen hat), um die Übertragung stabiler zu machen.
Und ich habe im Internet einen einfachen HV-Programmierzusatz für den Arduino
gefunden. Das zugehörige Programm ist kein vollwertiger HV-Programmer, sondern
schreibt lediglich die Default-Fuses in den ATtiny, so dass er wieder per ISP
zugänglich ist. Der Link lautet:
www.rickety.us/2010/03/arduino-avr-high-voltage-serial-programmer/
Damit konnte ich die beiden ATtiny45 retten. Einen verfusten ATtiny13 hatte ich
nicht zur Hand, aber ein "heiler" Attiny, der auf 4,8 MHz (ohne
Vorteilung) gefust war, wurde erfolgreich auf die Default-Werte (9,6 MHz;
Teilerfaktor 8) zurückgesetzt.
Hier nun die Details:
Der zu rettende ATtiny wird über 4 Schutzwiderstände an die Arduino-Pins 8-12
(das sind die Ports PB0 ... PB4) gelegt. Der Reset-Pin des ATtiny wird über
einen Vorwiderstand an extern zuzuführende +12 V gelegt. Ein Schalttransistor
zieht den Reset gegen Masse, wenn an Pin 13 (PB5) des Arduino ein
"high" liegt.Die Widerstände haben bis auf eine Ausnahme 1 kOhm; R5 hat 10 kOhm.
Mit dem beigefügten Programm habe ich mich nicht nicht näher beschäftigt,
sondern nur die Default-Fusebytes für ATtiny25/45/85 durch die für den ATtiny13
ersetzt.
Ablauf der "Rettungsaktion":
Die Tochterplatine ("Shield") mit dem verfusten Attiny wird an den
Arduino gesteckt, dann werden USB und die externen 12V angeschlossen (erst
Masse, dann Plus, damit es keine unliebsamen Effekte gibt).
Die Arduino-Entwicklungsumgebung wird gestartet, die korrekte virtuelle
serielle Schnittstelle gewählt und das Programm in den Arduino geflasht.
Danach wird das Terminal ("Serial Monitor") der Entwicklungsumgebung
gestartet. Das Programm schreibt "Hello?" als Bereitschaftsmeldung,
und wenn man ein Byte, z.B. eine "1", an den Arduiono sendet, liest
er die vorhandenen Fusebytes aus, schreibt die Defaultwerte in den ATtiny und
liest sie zur Kontrolle nochmals aus. Und schon ist der Attiny gerettet
...
// AVR High-voltage Serial Programmer
// Originally created by Paul Willoughby 03/20/2010
// http://www.rickety.us/2010/03/arduino-avr-high-voltage-serial-programmer/
// Inspired by Jeff Keyzer http://mightyohm.com
// Serial Programming routines from ATtiny25/45/85 datasheet
// ATtiny25/45/85 default fuses replaced by ATtiny 13 default fuses
// Desired fuse configuration
// #define HFUSE 0xDF //this were the defaults for ATtiny 25/45/85
// #define LFUSE 0x62
#define HFUSE 0xFF // Defaults for ATtiny13
#define LFUSE 0x6A
#define RST 13 // Output to level shifter for !RESET from transistor to Pin 1
#define CLKOUT 12 // Connect to Serial Clock Input (SCI) Pin 2
#define DATAIN 11 // Connect to Serial Data Output (SDO) Pin 7
#define INSTOUT 10 // Connect to Serial Instruction Input (SII) Pin 6
#define DATAOUT 9 // Connect to Serial Data Input (SDI) Pin 5
#define VCC 8 // Connect to VCC Pin 8
int inByte = 0; // incoming serial byte Computer
int inData = 0; // incoming serial byte AVR
void setup()
{
// Set up control lines for HV parallel programming
pinMode(VCC, OUTPUT);
pinMode(RST, OUTPUT);
pinMode(DATAOUT, OUTPUT);
pinMode(INSTOUT, OUTPUT);
pinMode(CLKOUT, OUTPUT);
pinMode(DATAIN, OUTPUT); // configured as input when in programming mode
// Initialize output pins as needed
digitalWrite(RST, HIGH); // Level shifter is inverting, this shuts off 12V
// start serial port at 9600 bps:
Serial.begin(9600);
establishContact(); // send a byte to establish contact until receiver responds
}
void loop()
{
// if we get a valid byte, run:
if (Serial.available() > 0) {
// get incoming byte:
inByte = Serial.read();
Serial.println(byte(inByte));
Serial.println("Entering programming Mode\n");
// Initialize pins to enter programming mode
pinMode(DATAIN, OUTPUT); //Temporary
digitalWrite(DATAOUT, LOW);
digitalWrite(INSTOUT, LOW);
digitalWrite(DATAIN, LOW);
digitalWrite(RST, HIGH); // Level shifter is inverting, this shuts off 12V
// Enter High-voltage Serial programming mode
digitalWrite(VCC, HIGH); // Apply VCC to start programming process
delayMicroseconds(20);
digitalWrite(RST, LOW); //Turn on 12v
delayMicroseconds(10);
pinMode(DATAIN, INPUT); //Release DATAIN
delayMicroseconds(300);
//Programming mode
readFuses();
//Write hfuse
Serial.println("Writing hfuse");
shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x40, 0x4C);
shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, HFUSE, 0x2C);
shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x00, 0x74);
shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x00, 0x7C);
//Write lfuse
Serial.println("Writing lfuse\n");
shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x40, 0x4C);
shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, LFUSE, 0x2C);
shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x00, 0x64);
shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x00, 0x6C);
readFuses();
Serial.println("Exiting programming Mode\n");
digitalWrite(CLKOUT, LOW);
digitalWrite(VCC, LOW);
digitalWrite(RST, HIGH); //Turn off 12v
}
}
void establishContact() {
while (Serial.available() <= 0) {
Serial.println("Hello?"); // send an initial string
delay(1000);
}
}
int shiftOut2(uint8_t dataPin, uint8_t dataPin1, uint8_t clockPin, uint8_t bitOrder, byte val, byte val1)
{
int i;
int inBits = 0;
//Wait until DATAIN goes high
while (!digitalRead(DATAIN));
//Start bit
digitalWrite(DATAOUT, LOW);
digitalWrite(INSTOUT, LOW);
digitalWrite(clockPin, HIGH);
digitalWrite(clockPin, LOW);
for (i = 0; i < 8; i++) {
if (bitOrder == LSBFIRST) {
digitalWrite(dataPin, !!(val & (1 << i)));
digitalWrite(dataPin1, !!(val1 & (1 << i)));
}
else {
digitalWrite(dataPin, !!(val & (1 << (7 - i))));
digitalWrite(dataPin1, !!(val1 & (1 << (7 - i))));
}
inBits <<=1;
inBits |= digitalRead(DATAIN);
digitalWrite(clockPin, HIGH);
digitalWrite(clockPin, LOW);
}
//End bits
digitalWrite(DATAOUT, LOW);
digitalWrite(INSTOUT, LOW);
digitalWrite(clockPin, HIGH);
digitalWrite(clockPin, LOW);
digitalWrite(clockPin, HIGH);
digitalWrite(clockPin, LOW);
return inBits;
}
void readFuses(){
//Read lfuse
shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x04, 0x4C);
shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x00, 0x68);
inData = shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x00, 0x6C);
Serial.print("lfuse reads as ");
Serial.println(inData, HEX);
//Read hfuse
shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x04, 0x4C);
shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x00, 0x7A);
inData = shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x00, 0x7E);
Serial.print("hfuse reads as ");
Serial.println(inData, HEX);
//Read efuse
shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x04, 0x4C);
shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x00, 0x6A);
inData = shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x00, 0x6E);
Serial.print("efuse reads as ");
Serial.println(inData, HEX);
Serial.println();
}