Attiny Motorboard

tinystepperFor a project I needed to drive a steppermotor, a servo and possibly a small regular DC motor. Piece of cake of course with an Arduino, but I am anal retentive where it comes to  underusing an Arduino and as I had come by a couple of Attiny13A’s why not use those.
attiny-stepperA circuit is easily drawn up and doesn’t hold too many surprises. The chip of choice is the ULN2003 that can take 500mA per port. Driving a steppermotor for a while can make the chip unpleasantly hot and adding a heatsink is a wise precaution. My Steppermotor, the 55SI-25DAWC (1350 g/cm torque) has a 6 prong connector with the two  midconnectors that receive the  Motor Voltage on one side and the 4 coil connectors next to that, so that kinda dictated my design on K1. It is 36 Ohm at 12 Volt, so  each coil  uses 333mA.
The servo connector K2 is rather standard: Signal-Vcc-Ground. The DC motor connector K3 is fed by the open collector output of the ULN2003. The signal for that is shared with the Servo, as for now I probably wont be using both, but If I need both, I might be feeding it of  PB5, though that would mean resetting the fuses to disable the reset that is attached to pin1. It also means I need a high voltage programmer if I need to  reprogram the chip.
tinystepper-stripboard

A prototype is easily set up on a piece of stripboard. The interruption of the second strip between F and G is just there in case I want to add a jumper to switch between servo use and ULN2003 use on Output Q3. Rather than making K3 just a two prong connector, I decided to add  an entire row that is connected to V++, so if I so wish, I can also use  Q1-Q7 just for  DC motors. It gives a bit more flexibility. As the Attiny13 has a max of 6 I/O pins and the ULN2003 has 7 drivers, one can always decide to  use the drivers in parallel to drive bigger loads.

Programming:
The Attiny13  has only 1 kByte available, but that is enough  for a simple application with a servo and a steppermotor.
As my steppermotor is a rather old one already, I doubt if too many people have it. so my program is most likely no use  for anybody else, but driving steppermotors is rather easy.
With the unipolar steppermotor that I use, there are 3 ways of driving it: Halfstepping, Full stepping or wave driving
Wave driving is the easiest: you just make each pin high in the right sequence.
coils.jpgFor a unipolor motor that would be: 1a-2a-1b-2b. That would spin the motor in low torque because there is always a moment in which there is no coil magnetized.
Half stepping requires alternating between energising a single coil and two coils at the same time. This increases the angular resolution, but the torque fluctuates between utilising a single coil and both coils.. In principle though it means that you keep one coil magnetized while you already switch on the second one. So that would be:
1a-1a2a-2a-2a1b-1b-1b2b-2b-2b1a,
Full stepping is when two coils are always energised at the same time. This allows the maximum torque of the motor to be used, at the expense of more current. This looks like:
1a2a-2a1b-1b2b-2b1a.
For completeness sake: the 55SI-25DAWC can also be used as bipolar stepper motor just by not using the white wires. This gives more torque and less power consumption
The below program is just a quick illustration of how to drive a stepper in Full stepping mode:

/*
                                _____
                               |     |
 Hardware:             +5V     |     |
             ________   |      |     |     
      ___   |        |  |      |     |  ___  
 +5V-|___|--|RES  VCC|--+   B3-|I4 O4|-|___|-+Q3 brown
            |        |         |     |  ___  
        B3--|PB3  PB2|---------|I5 O5|-|___|-+Q2 yellow
                     |         |     |  ___  
          --|PB4  PB1|---------|I6 O6|-|___|-+Q4 blue
            |        |         |     |  ___  
         |--|GND  PB0|---------|I7 O7|-|___|-+Q1 red
            |________|         |     |       
             ATtiny13        |-|GNDCC|------- +12Volt white
                               |_____|    |__ +12Volt white
                                ULN2003 
Funktioning: sequence: red-yellow-brown-blue
    Stepmotor drive sequence and encoding:
      Portbit      PB0   PB1   PB2   PB3   |            |
      Color         rd    bl    yl    bn   | Portbit    | Byte
      Step          O7    O6    O5    O4   | 3  2  1  0 |
      -------------------------------------+------------+------
         1          1      0    1     0    | 0  1  0  1 |  05
         2          0      0    1     1    | 1  1  0  0 |  12 / 0C
         3          0      1    0     1    | 1  0  1  0 |  10 / 0A
         4          1      1    0     0    | 0  0  1  1 |  03
If you would use D8-D11 on an Atmega328, you could use the same values 
for the Portbits, as D8-D11 is equivalent to PB0-PB3

Coils
 red    ------------+------------ brown
 yellow-------------+------------ blue
 */
 #define delayTime  200// time between steps
 void setup()
 {
 DDRB=31; // set Port 0-5 as output
 }
 void loop()
{
 stepper(3); //=90 degrees. The angle is 7.5 degr,=> 4 steps=30 degr.
 }
 void stepper(byte turn)
 {
 for (byte pos=0;pos<turn;pos++)
 {
 PORTB=5;
 delay(delayTime);
 PORTB=12;
 delay(delayTime);
 PORTB=10;
 delay(delayTime);
 PORTB=3; delay(delayTime);
 }
delay(1000);
 }

The above program is just rough, to show how to drive a stepper and it misses some  sophistication. Writing directly to PORTB is OK, but in this case it will also write a “0” to PB4 and PB5. PB5 is not much of a problem, but you may want to use PB4. In my case that is where I put my servo and that doesnt really cause a problem as I do not use them at the same time.
If you want to avoid writing to PB4 and PB5, use a mask to only write to bit 0-3. The mask to do that is B00001111.
If you then want to set bits bits 0 and 2, go like this:
PORTB=(PORTB &~mask) | (B00000101);
For those who find this too cryptic:
it  first ANDs the value of PORTB with NOT mask and OR’s the result with the value we want to write and assigns that back to PORTB.
So, suppose PORTB= 00010000 and we want to write 00000101 to it, we cannot assign that immediately because that would clear PB4.
However, if we do as described, it becomes:
PORTB=(PORTB & 11110000) | 00000101
PORTB=(00010000 & 11110000)  | 00000101
PORTB=00010000 | 00000101
PORTB= 00010101
We have written our value and kept PB4
So, why cant we immediately OR PORTB with the value we want in stead of AND-ing it first?
Well because  that might keep PB4 and PB5… but it also keeps PB3-PB0 unchanged if  one of them already contained a ‘1’
Of course inverting the  mask wouldn’t be necessary if we would define it already inverted, but it is common practice to do it as such

Writing to the Servo is quite easy:
add this  function:

void pulseOut( byte pin, byte p){
digitalWrite(pin,HIGH);
delayMicroseconds(300+p*(2500/180));
digitalWrite(pin,LOW);
}

and call that like this:

for (byte pos=0;pos<180;pos++)
{
pulseOut(servoPin,pos);
delay(20);
}

High Torque mode uses more power than low Torque mode. In Low Torque mode each step in my case (12V, 36 Ohm coil resistance) takes 333mA. In high torque mode, each step takes 667mA (2×333.33).

In case you are wondering what project I needed it for: I need  a doll to turn and  nod its head.

Self build alert
Smart-Electronics-28BYJ-48-5V-4-Phase-DC-Gear-Stepper-Motor-ULN2003-Driver-Board-for-ArduinoI enjoy building stuff, but it isn’t always the wisest thing to do. There is a ULN2003 based driver board, including a good steppermotor available for about 1.80 euro. That is a good deal. In my case, I  just wanted the Attiny13 and ULN2003 driver to be on one PCB, I already had a spare ULN2003 and I had the steppermotor so that was a no brainer, But for anybody else wanting to  drive a steppermotor, consider that cheap motor and driver.
My cost for just the driver section (chip,  pins, socket), I estimate 45 ct (euro). Not sure what my stepper costed me 30 years ago, but a decent hobby stepper now will be between 1.60 euro and 5 euro, so the savings in  self built (not even counting the solder and electricity use) are minimal to non existent. So only do it for fun, educational purposes or if you need something tailored like i did.

That trusty Attiny13
Finally I have to praise the Attiny13 for being extremely resilient. Due to some stupidity from my side, I connected PB3 with O4 instead of I4, So it received 12 Volt through a 36  ohm resistor and also tried to drive a 330mA motor coil. This situation lasted for at least a week in which it would sometimes be  steering the stepper for hours, with me  trying to find out why it wasn’t working optimally.
I only found out when I accidentally felt the chip was getting pretty hot.
I fixed the connection and  the chip was still working (and the motor turned better). A week of 12 Volt on one of its pins and getting hot and still working great

Advertisements

High Voltage programming/Unbricking for Attiny

Yikes! Invalid device signature

It sometimes happens: you are trying to program an Attiny and you get an error. There might be many causes for errors: you may not have selected the proper board, your programmer might be rotten, you did something wrong or the connections of your programmer are not correct, but sometimes it can be caused by the wrong bits being set in your Attiny: e.g. you set pin 1 (reset) to be an I/O pin. That makes it impossible to program it with ISP, or you set the wrong oscillator. For these cases a High Voltage programmer can be of help.
It happened to me when i was trying to ‘burn the bootloader’ on my attiny85 (there is no bootlaoder for the attiny 85 but this means setting the fuses). My  computer’s memory was kinda full when I  was busy and suddenly something crashed and I got the dreaded error message Yikes! Invalid device signature. As said, often this is because of a bad connection, but another chip didnt have that problem so I knew something was very wrong.
Time to build a High Voltage Programmer
Below you will see such a circuit. It is fairly simple: 6 resistors, a transistor  a DIL foot  and a 12 Volt source from somewhere. As I  didn’t expect to have to use the HVP often, I opted for a battery, but as I displaced it, I ended up using a 75 euroct 5 to 12 Volt converter that I plugged in.

Circuit
Circuit

 

 

 

 

 

 

 

 

 

And this is an easy way to build it on stripboard.

High Voltage programmer Stripboard
On Stripboard
The finished board ready to use
The finished board

 

 

 

 

 

 

 

I have built it such that it sticks into the D8-D13,ground header of an Arduino UNO. The only thing  you need to do is to attach a 12 Volt battery, or an other 12 Volt source.
There are various programs that can reset the bits back to factory setting. Below you will find 2 of them that all  go back to some initial work by Jos Keyzer.
The first one expects you to  set the  factory bits in the program depending on the chip you are using, the 2nd program actually reads what chip you are using, so I ended up using that one. Both programs  start after you send a random character to the serial port.

 

 

 

 

 

 

 

 

 

fuseburningWell, as it turned out, my fuses were set for E4 and DF. That means that the Attiny was expecting a 128 kHz oscillator signal. No idea how that happened as I have disabled that choice in my menu so I guess it happened coz of my computer crashing. We will never know, but the HVP set it back to factory settings: i.e. 8MHz internal oscillator with a prescaler of 8.
After that, I could just program my Attiny again.

In principle this programmer can be used for Attiny 15 and 12 as well, but as far as I recall they have some wires crossed, so you would need to make a hardware change (connect the D12 resistor to pin 3 instead of pin2), but Attiny 13/25/45/85 should work like a charm.
It can also be done on the 24/44/84 series, but they need a 14 pins DIL:
fusebit

 

 

 

5 to 12 Volt converterShould you need a cheap 12 Volt source, consider this 5 to 12 Volt converter

 

 

 

 

 

Program:

// 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 
// 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 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("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(); 
}

An other program is:

    // 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);
    }

    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.print("Signature is: ");
        Serial.println(sig, HEX);
        readFuses();
        if (sig == ATTINY13) {
          writeFuse(LFUSE, 0x6A);
          writeFuse(HFUSE, 0xFF);
        } else if (sig == ATTINY24 || sig == ATTINY44 || sig == ATTINY84 ||
                   sig == ATTINY25 || sig == ATTINY45 || sig == ATTINY85) {
          writeFuse(LFUSE, 0x62);
          writeFuse(HFUSE, 0xDF);
          writeFuse(EFUSE, 0xFF);
        }
        readFuses();
        digitalWrite(SCI, LOW);
        digitalWrite(VCC, LOW);    // Vcc Off
        digitalWrite(RST, HIGH);   // 12v Off
      }
    }

    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) {
      shiftOut(0x40, 0x4C);
      shiftOut( val, 0x2C);
      shiftOut(0x00, (byte) (fuse >> 8));
      shiftOut(0x00, (byte) fuse);
    }

    void readFuses () {
      byte val;
            shiftOut(0x04, 0x4C);  // LFuse
            shiftOut(0x00, 0x68);
      val = shiftOut(0x00, 0x6C);
      Serial.print("LFuse "); // this line may show up corrupted in some browsers it is a 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);
    }

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

---

One may find this article interesting as well
If you want to see how this board (in a skightly  more luxurious build) is used in practice you may want to check a youtube video by Ralph Bacon.