ATtiny85 Bricked? No problem!

Messing with fuses may brick ATtiny85!

On Attiny85 (also on other Attiny__) chips from Atmel, I recently made mistake, not knowing exact ‘truth’ about how to set fuses properly, so that it work as expected. The problem is in online documentation and implementation in some of available programmers as is “Khazama AVR programmer”. Even online calculator is wrong – if for example you want to use 8 MHz and above external quartz crystal oscillator, then you should select CSEL = 1110, but calculator showing that three boxes should be unchecked (CSEL[3:1]), while last box (CSEL0) should be checked. THIS IS NOT TRUE!!! If you look in datasheet about Attiny85, then it is exactly 1110, but online calculators, and programmer showing confusing and contradictory messages about what enables or disables what. So, I made (not so short) video about this issue + how to ‘un-brick’ Bricked Attiny__ (in my case Attiny85).

Is there hope for bricked chip?

YES! Bricked Attiny85 simply does not accept ISP programmer, but it can be ‘rescued’ by so called “HV programming”, where high voltage is actually 12V to the reset pin (pin number 1 on Attiny85), then some weird commutation of +5V and +12V on the chip brings it into programming mode. Down below is diagram and sketch, which is not my idea, but rather I with to thanks and give credit to those people who designed it and modified so that we can rescue our favorite chips.

Attiny85

On the image above, there is credit to someone who made it possible, but someone else changed sketch slightly, so that it now works as is expected.

The sketch for Arduino nano:

// AVR High-voltage Serial Fuse Reprogrammer
 // Adapted from code and design by Paul Willoughby 03/20/2010
 // http://www.rickety.us/2010/03/arduino-avr-high-voltage-serial-programmer/
 // Fuse Calc:
 // http://www.engbedded.com/fusecalc/

 #define RST 13 // Output to level shifter for !RESET from transistor
 #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

 #define HFUSE 0x747C
 #define LFUSE 0x646C
 #define EFUSE 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

 void setup() {
 pinMode(VCC, OUTPUT);
 pinMode(RST, OUTPUT);
 pinMode(SDI, OUTPUT);
 pinMode(SII, OUTPUT);
 pinMode(SCI, OUTPUT);
 pinMode(SDO, OUTPUT); // Configured as input when in programming mode
 digitalWrite(RST, HIGH); // Level shifter is inverting, this shuts off 12V
 Serial.begin(19200);
 Serial.println("Code is modified by Rik. Visit riktronics.wordpress.com and electronics-lab.com for more projects");
 Serial.println("-------------------------------------------------------------------------------------------------");
 Serial.println("Enter any character to start process..");}


 void loop() {
 if (Serial.available() > 0) {
 Serial.read();
 pinMode(SDO, OUTPUT); // Set SDO to output
 digitalWrite(SDI, LOW);
 digitalWrite(SII, LOW);
 digitalWrite(SDO, LOW);
 digitalWrite(RST, HIGH); // 12v Off
 digitalWrite(VCC, HIGH); // Vcc On
 delayMicroseconds(20);
 digitalWrite(RST, LOW); // 12v On
 delayMicroseconds(10);
 pinMode(SDO, INPUT); // Set SDO to input
 delayMicroseconds(300);
 unsigned int sig = readSignature();
 Serial.println("Reading signature from connected ATtiny......");
 Serial.println("Reading complete..");
 Serial.print("Signature is: ");
 Serial.println(sig, HEX);
 readFuses();
 if (sig == ATTINY13) {

 Serial.println("The ATtiny is detected as ATtiny13/ATtiny13A..");
 Serial.print("LFUSE: ");
 writeFuse(LFUSE, 0x6A);
 Serial.print("HFUSE: ");
 writeFuse(HFUSE, 0xFF);
 Serial.println("");
 } else if (sig == ATTINY24 || sig == ATTINY44 || sig == ATTINY84 ||
 sig == ATTINY25 || sig == ATTINY45 || sig == ATTINY85) {

 Serial.println("The ATtiny is detected as "); 
 if(sig == ATTINY24) Serial.println("ATTINY24..");
 else if(sig == ATTINY44) Serial.println("ATTINY44..");
 else if(sig == ATTINY84) Serial.println("ATTINY84..");
 else if(sig == ATTINY25) Serial.println("ATTINY25..");
 else if(sig == ATTINY45) Serial.println("ATTINY45..");
 else if(sig == ATTINY85) Serial.println("ATTINY85..");
 
 writeFuse(LFUSE, 0x62);
 writeFuse(HFUSE, 0xDF);
 writeFuse(EFUSE, 0xFF);
 }

 Serial.println("Fuses will be read again to check if it's changed successfully..");
 readFuses();
 digitalWrite(SCI, LOW);
 digitalWrite(VCC, LOW); // Vcc Off
 digitalWrite(RST, HIGH); // 12v Off

 Serial.println("");
 Serial.println("");
 Serial.println("");
 Serial.println("");
 }
 }

 byte shiftOut (byte val1, byte val2) {
 int inBits = 0;
 //Wait until SDO goes high
 while (!digitalRead(SDO))
 ;
 unsigned int dout = (unsigned int) val1 << 2;
 unsigned int iout = (unsigned int) val2 << 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;
 }

 void writeFuse (unsigned int fuse, byte val) {
 
 Serial.println("Writing correct fuse settings to ATtiny.......");
 
 shiftOut(0x40, 0x4C);
 shiftOut( val, 0x2C);
 shiftOut(0x00, (byte) (fuse >> 8));
 shiftOut(0x00, (byte) fuse);

 Serial.println("Writing complete..");
 }

 void readFuses () {

 Serial.println("Reading fuse settings from connected ATtiny.......");
 
 byte val;
 shiftOut(0x04, 0x4C); // LFuse
 shiftOut(0x00, 0x68);
 val = shiftOut(0x00, 0x6C);
 Serial.print("LFuse: ");
 Serial.print(val, HEX);
 shiftOut(0x04, 0x4C); // HFuse
 shiftOut(0x00, 0x7A);
 val = shiftOut(0x00, 0x7E);
 Serial.print(", HFuse: ");
 Serial.print(val, HEX);
 shiftOut(0x04, 0x4C); // EFuse
 shiftOut(0x00, 0x6A);
 val = shiftOut(0x00, 0x6E);
 Serial.print(", EFuse: ");
 Serial.println(val, HEX);
 Serial.println("Reading complete..");
 }

 unsigned int readSignature () {
 unsigned int sig = 0;
 byte val;
 for (int ii = 1; ii < 3; ii++) {
 shiftOut(0x08, 0x4C);
 shiftOut( ii, 0x0C);
 shiftOut(0x00, 0x68);
 val = shiftOut(0x00, 0x6C);
 sig = (sig << 8) + val;
 }
 return sig;
 }

Be careful about the “Fuse calc”!!!

You may notice that “Fuse calc” gives (for example) result of 1110 for external quartz oscillator, but boxes below are incorrectly checked! Pay attention to that. Here is my video and some conclusions about that:

Instead ugly 12V lead acid battery, consider buying this 5V to 12V step-up converter.

PWM modulation as DAC on Atmega328p

PWM or Pulse Width Modulation

It is good to have 8 ADC inputs on Atmega328p, of which last one is actually thermometer inside chip, but there are no DAC converter for making sound out of digital data. Tried one DAC chip, MCP4725, which is 12 bit DAC with I2C data transfer, but turns out that it is pretty slow even at 800 kHz I2C bus. That is because of something weird inside this chip, where time required for output signal to reach maximum, starting from zero is 6 microseconds, no matter what I do. So, I decided to use PWM as possible way to do DAC conversion.

PWM

How it works?

By using fixed frequency, but variable length of the pulses, it is possible to change output voltage from digital output (!) from zero to maximum voltage, depend of voltage which is applied to Atmega328p. If pulses are narrow, then after filtering by low pass filter (RC or LC filters) – output voltage is low. If pulses are wide, voltage is higher. So, in the case of 5V on the Atmega chip, if duty cycle is 50%, then output voltage is  2.5V.

How good is sound quality?

Not so good if PWM frequency is low. Also depends of how many bits are used. If 16 bit timer is used, in theory it is possible to use all 16 bits, which will give 65536 voltage steps, but then PWM frequency will be pretty low. For driving motors or LEDs it is not a problem, but it is problem for audio applications. Some relatively good quality for voice is possible by using 62.5 kHz PWM with 8 bit resolution on Atmega328p with quartz crystal on 16 MHz. That is because Fast PWM with timer(s) and divider of F_CPU/1 (no prescalling) require 256 counts, which can be calculated by formula: FPWM=F_CPU/256. In the case of 16 MHz crystal, this is 62500 Hz, or 62.5 kHz. This frequency is pretty good for voice audio, but not so great for music. But, this is trade-off of using inexpensive way to make DAC out of this chip.

Other way than using timer(s)?

Yes. I had problem making exactly 40 kHz for ultrasonic purpose, because of all frequencies at which PWM can work, neither one can be get by using dividers, and/or number of bit manipulations. So, I made an “PWM imitation” by using two delay loops (for (;;) ), where variable delay is obtained by changing number to where loop goes. For example: “for (i=0;i<32;i++);” together with the rest of the code gives me 12.5 microseconds, which is half of 25 microseconds needed for 40 kHz (1/25 uS = 40 kHz). There are two loops, where first one gives ON time, and second one OFF time. Sum of ON+OFF time gives duty cycle in steps depending of how much bites are used. Since whole process is relatively slow, I am forced o use just 5 bites, which is okay for voice audio. This mins that 5 bites gives duty cycles in 32 steps, from near zero to maximum (which in my case is not 100% but rather close to 80% due to other commands in the C code). One of this additional codes are ADC conversion and mathematics conversion from 10 bits to 5 bits. This just slow cycles enough so that it can’t reach 100% pulse width.

The code:

// Ultrasonic parametric speaker by
// Milan Karakaš, Croatia
// https://wildlab.org
// Working, but require more investigation
// about quality... feel free to upgrade

uint8_t i;
int on=16; //halfway between 0 and 32

void setup() 
{
  DDRB |= (1<<PB0);//output for 40 kHz
  TIMSK0=0;//stops timer 0 which may cause distortions
  /*ADC setup*/
  DIDR0 = (1<<ADC0D);//disabling digital in/out, and enabling analog input on ADC0
  //ADMUX = (1<<REFS0);//reference is VCC, input is analog0, ADLAR=0 (lower 10 bits)
  ADMUX = (1<<REFS0)|(1<<REFS1);
  ADCSRA = (1<<ADEN)|(1<<ADPS1); //enabled, interrupt disabled(!),prescaller is F_CPU/4 (4 MHz, faster loop)
}

void loop() 
{
  /*PWM "imitation" at 40 kHz */
  while(1) //infinite loop, but faster than "loop()" itself!
  {
    ADCSRA |= (1<<ADSC);//start ADC conversion
    on=(ADC>>5);// ADC/32, or binary shift five to the right
    /*opens output high*/
    PORTB |= (1<<PB0); //plus delay
    for (i=0;i<on;i++);//on time is as ADC value/32
    /*closes output low*/
    PORTB &= ~(1<<PB0);
    for (i=0;i<(32-on);i++);//off time is 32-on time, always total of 25 uS (40 kHz)
  }
  /*end of PWM "imitation" at 40 kHz */
}


Note that I wrote “parametric speaker”, because this code is intended to do job, but I found many problems with sound quality out of ultrasonic speakers with this modulation. Still working on improvements. But, this code is good for exercise in advanced programming. Later on, I will make code for audio with PWM at 62.5 kHz for telecommunication purpose (voice over digital radio-modem). Here is video rant about this PWM stuff:

Enjoy!