High Voltage Programmer

I’ve been experimenting with using non-standard pins on an ATtiny85. Using XTAL1 and XTAL2 is simple if you run off the internal oscillator at 8MHz/3.3v but I want to also use the RESET pin as D5/A0.

When you re-purpose the RESET pin, you lose the ability to program using an ISP, so we have to use a high voltage serial programmer (HVSP) which basically applies 12v to the RESET pin to set the fuses.

I redesigned the stripboard from here for use with perfboard and used a 2N2222 transistor as I have plenty of them, as well as a DC barrel jack for the 12v supply.

The hv_rescue.ino sketch which you load onto an Uno is as follows, I modified it slightly to compile nicely without needing the IDE:

// 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

// Desired fuse configuration
#define  HFUSE  0xDF   // Defaults for ATtiny25/45/85
#define  LFUSE  0x62

//#define  HFUSE  0x5F   // reset as gpio5
//#define  LFUSE  0xE2   // 8mhz internal oscillator

// For Attiny13 use
// #define HFUSE 0xFF
// #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 establishContact() {
  while (Serial.available() <= 0) {
    Serial.println("Enter a character to continue");   // 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(); 
}

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
  }
}

I then uploaded the following sketch to the ATtiny85 using make ispload, as you can see its using D5 to blink an LED:

const int led = 5;

void setup()
{
    pinMode(led, OUTPUT);
}

void loop()
{
    digitalWrite(led, HIGH);
    delay(1000);
    digitalWrite(led, LOW);
    delay(1000);
}

The Makefile is as follows, I used the latest IDE and the ATTinyCore as it supports more chips and I wanted to experiment with something other than the usual arduino-tiny or attiny-master with IDE 1.0.5:

ARDUINO_DIR = $(HOME)/arduino-1.8.5

ISP_PROG       = usbasp
AVRDUDE_OPTS   = -v
ALTERNATE_CORE = ATTinyCore
BOARD_TAG      = attinyx5
BOARD_SUB      = 85
F_CPU          = 8000000L
ISP_HIGH_FUSE  = 0x5F
ISP_LOW_FUSE   = 0xE2

include $(HOME)/Arduino-Makefile/Arduino.mk

The important bit is to then edit the hv_rescue.ino file to set the fuses:

#define  HFUSE  0x5F   // reset as gpio5
#define  LFUSE  0xE2   // 8mhz internal oscillator

Then upload that to the Uno, connect the HVSP board with the ATtiny85 mounted, and press a key to set the fuses. If you don’t edit the script it’ll just reset the fuses to their defaults of 0xDF and 0x62.

The order is important, as you can’t ISP the chip after you’ve set the fuse to disable RESET, so you can’t use make set_fuses for example, you have to use the HVSP to set the fuses, after ISP’ing the sketch. If you want to edit the sketch, you’ll have to reset the fuses, then ISP the sketch and set the fuses back again, so its not something you want to do often – maybe develop using a regular GPIO pin like D2, then when you’re ready switch to D5.

I made a PR to fix that – so you make ispload to upload the sketch, then make set_fuses to set the fuses, after which you need to reset them using the HVSP.

You can then blink six LED’s using an ATTiny85 (the RESET pin has lower power output) with just VCC and GND connected (no crystal etc.) using for example:

#include <avr/io.h>

void setup()
{
    // set d0-d5 as outputs
    DDRB = B11111111;
}

void loop()
{
    // set all high
    PORTB = B11111111;
    delay(500);

    // invert all
    PORTB ^= PORTB;
    delay(500);
}

There are other more complicated sketches around which can auto-detect the chip and prompt for the fuses to set, but they seem to require a button and a 12v DC-DC boost converter, as per the Rescue Shield