Showing posts with label ATtiny. Show all posts
Showing posts with label ATtiny. Show all posts

Tuesday, March 11, 2014

Theory: shift registers, and the test jig code explained!

Ahh... Shift registers. The heart of most LED cubes.

So, what is a shift register and what does it do? Basically it's a serial input, parallel output IC; you send a stream of ones and zeroes to it on one pin and this makes a bunch of output pins go high or low. Usually you have two more pins: a clock pin and a latch pin. The clock pin is used to tell the shift register when to read a new bit on its input pin. The latch pin is used to tell it when to make the read bits appear on the output pins.

In my cube I use the 74HC595 shift register which is an 8 bit shift register. It simply means that it has 8 outputs. You can find these for almost a dime a dozen on eBay. If you need the smaller SMD type, search for 74HC595D.
The 595 (as I'll refer to it from now on) has two internal registers: the shift register and the storage register. We'll start at the beginning - the input pin - and work our way down to the output pins.

When you want to send data to the 595 you start by setting the DS pin (Serial Data Input) high or low, depending on whether you want to write a one or zero. Then you make the SHCP pin (Shift Register Clock Input) go from low to high to low again. The shift register will update whenever that pin goes from low to high. It will store whatever value is at DS and put it in bit 0 of the shift register. All the other bits of the register are shifted to the next one. So bit 1 will get the old value of bit 0, bit 2 the old value of bit 1, etc etc. The cool thing about this is that the old value of bit 7 will appear at a pin called Q7S. If you connect this pin to the DS pin of a second 595, and also connect the SHCP pins of both 595s, then bit 7 of the first 595 will be shifted to bit 0 of the second 595. You can chain a whole bunch of registers like this, effectively forming one large shift register.

Great, now that you've shifted 8 bits into the 595, you'll want to have them appear at the output pins. To do this you pull the STCP (Storage Register Clock Input, otherwise known as Latch) pin from low to high to low again. Whenever this pin goes from low to high all values from the shift register will be copied to the storage register.
This storage register is connected to the output pins, so any value in it will appear on the output pins. But only if the OE (Output Enable) pin is pulled low. If OE is high all output pins will be low.

One thing that's important to know is that when both the STCP and SHCP pins go high simultaneously, the shift will take place after the contents of the shift register have been copied to the storage register. This means that when you have STCP and SHCP connected to eachother, and you shift in a bit, that this bit will not appear on the output directly (since the content of the shift register has been copied to the output register before the new bit was shifted in).

Let's have another look at my pillar testing code. For this I have connected STCP and SHCP to the same pin on the controller (B4) and DS to B3. OE has been connected to ground.


1:  /*  
2:   * TinyPillarTester.c  
3:   *  
4:   * Created: 6-3-2014 21:48:11  
5:   * Author: Daniel  
6:   */   
7:    
8:    
9:  #include <avr/io.h>  
10:  #include <avr/interrupt.h>  
11:    
12:  int main(void)  
13:  {  
14:      PORTB = 7;  
15:      DDRB = _BV(DDB0) | _BV(DDB1) | _BV(DDB2) | _BV(DDB3) | _BV(DDB4);  
16:    
17:      TCNT0 = 0;  
18:      TIMSK0 = (1 << OCIE0A); // interrupt on compare match  
19:      TCCR0A = _BV(WGM01);  
20:      TCCR0B = _BV(CS01); // F_CPU / 8  
21:      OCR0A = 250;  
22:      asm("sei");  
23:    while(1)  
24:    {  
25:        // do nothing!  
26:        asm("sleep");  
27:    }  
28:  }  
29:    
30:  uint8_t cathodeCounter = 4;  
31:  uint8_t anodeCounter = 0;  
32:    
33:  ISR(TIM0_COMPA_vect, ISR_NAKED)  
34:  {  
35:      uint8_t sreg = SREG;  
36:      static uint8_t counter = 0;  
37:      ++counter;  
38:      if ( counter >= 75 )  
39:      {  
40:          counter = 0;  
41:          ++anodeCounter;  
42:          if ( anodeCounter == 8 )  
43:          {  
44:              anodeCounter = 0;  
45:              PORTB |= _BV(3);  
46:          }  
47:          else if ( anodeCounter == 1 )  
48:          {  
49:              PORTB |= cathodeCounter;  
50:              cathodeCounter <<= 1;  
51:              if ( cathodeCounter >= 8 )  
52:              {  
53:                  cathodeCounter = 1;  
54:              }  
55:              PORTB &= ~cathodeCounter;  
56:              PORTB &= ~_BV(3);  
57:          }  
58:          PORTB |= _BV(4);  
59:          PORTB &= ~_BV(4);  
60:      }  
61:      SREG = sreg;  
62:      asm("reti");  
63:  }  
64: 

Let's go through this line by line.

12:  int main(void)  
13:  {  
14:      PORTB = 7;  
15:      DDRB = _BV(DDB0) | _BV(DDB1) | _BV(DDB2) | _BV(DDB3) | _BV(DDB4);  
Line 14: Pins B0,B1 and B2 are set high, since they are connected to the cathodes of the LEDs this will make the LEDs go off. B3 (DS) and B4 (SHCP, STCP) are set low.
Line 15: All pins are configured as output pins (except B5, which is the reset pin)

17:      TCNT0 = 0;  
18:      TIMSK0 = (1 << OCIE0A); // interrupt on compare match  
19:      TCCR0A = _BV(WGM01);  
20:      TCCR0B = _BV(CS01); // F_CPU / 8  
21:      OCR0A = 250;  
Line 17-21: Timer initialization, it's set to generate an interrupt on compare match. The value to match is 250. The counter will reset when the match occurs (line 19) and its speed is 1/8th of the cpu speed (line 20).

22:      asm("sei");  
23:    while(1)  
24:    {  
25:        // do nothing!  
26:        asm("sleep");  
27:    }  
Line 22: Inline assembler, the "sei" command will enable interrupts
Line 23 - 27: Do nothing. The "sleep" command puts the cpu in idle state until there's an interrupt. This also reduces power consumption from 3 mA to 0.5 mA.

33:  ISR(TIM0_COMPA_vect, ISR_NAKED)  
34:  {  
35:      uint8_t sreg = SREG;  
Line 33: Start of interrupt handler for timer0 compare match. ISR_NAKED means the compiler won't put any code at the start and end specific to handling interrupts. One of the things you need do do is store the SREG register and restore it at the end, as you can see on line 35
Let's skip some stuff

61:      SREG = sreg;  
62:      asm("reti");  
63:  }  
64: 
Line 61: Restore SREG, not really necessary since we don't have any code in our main loop. But hey, it's good practice.
Line 62: Return from interrupt. Required special command if you exit an interrupt handler.

36:      static uint8_t counter = 0;  
37:      ++counter;  
38:      if ( counter >= 75 )  
39:      {  
40:          counter = 0;  
Line 36-40: The timer is configured to generate 600 interrupts per second, I only need 8, so this slows it down a bit :)

41:          ++anodeCounter;  
42:          if ( anodeCounter == 8 )  
43:          {  
44:              anodeCounter = 0;  
45:              PORTB |= _BV(3);  
46:          }  
Line 41: A counter to run through all the anodes. When it reaches 8, I reset it to 0. I also set B3 (DS) high (line 45).
47:          else if ( anodeCounter == 1 )  
48:          {
56:              PORTB &= ~_BV(3);  
57:          }  
Line 47: On the first interrupt (or the first one after the anode counter has been reset) I set B3 low again (line 56). It'll stay that way until the counter reaches 8 again. So it's high for 1 update, and low for 7.

58:          PORTB |= _BV(4);  
59:          PORTB &= ~_BV(4);  
Line 58: Set B4 (SHCP and STCP) high, this will cause the contents of the shift register to be copied to the storage register, and then shift a new bit from DS into the shift register.
Line 59: Set B4 low again.

And that's pretty much it. This clocks one bit to the shift register every 8th of a second, one one and seven zeroes. So one pin will be high and the others low, and the high pin will shift to the next every 8th of a second.

Now there's just a small piece of code left:
49:              PORTB |= cathodeCounter;  
50:              cathodeCounter <<= 1;  
51:              if ( cathodeCounter >= 8 )  
52:              {  
53:                  cathodeCounter = 1;  
54:              }  
55:              PORTB &= ~cathodeCounter;  
Line 49-55: This handles switching between red green and blue (cathodes). The cathodeCounter variable is a bit mask, only one bit will be set, which corresponds to one of the output pins B0, B1 or B2. I first apply this mask to the pins, which makes the current active pin go high (line 49) turning the LED off. Then I shift the variable left one bit to move to the next pin (line 50). If it goes past the last pin (line 51) I reset it back to the first pin (line 53). Finally I negate the mask and apply it, setting the new active pin low (line 55) turning the LED on.

The reason this piece of code sits inside the anodeCounter == 1 block is because the actual output of the 595 lags one clock cycle behind the content of the shift register. If I had put this code in the anodeCounter == 8 block then the jig would switch colors while the last LED was on. Since we don't want that, we have to wait one more cycle.

That's it! I hope this gives a better understanding of the 595 and the test jig :)

Thursday, March 06, 2014

Debugging the pillar test jig

When the cause of a problem is not immediately often it is often good practice to first check for the most obvious mistakes, isolate a few small portions and check if they work and then work your way up, combining the parts you've checked. This approach works pretty well in debugging software and I use it a lot.

I should've used it here as well.

As I wrote in my previous post, only the bottom LED in my pillar would light up properly when I inserted it in the jig. What I also noticed is that this is also when I put the serial data line high (for the other 7 points in the cycle it's low). I also noticed that the serial data pin on the shift register is right next to output pin 0, to which the bottom LED is connected. It's obvious to expect a bad soldering job (if you're familiar with my soldering) so I wanted to rule that out first.
What I did was modify the code a bit, so will put the data line high after 4 cycles as well. This would give me 2 lit LEDs in the loop and would give me a chance to see if only the bottom LED would light up brightly or not. That wasn't the case; both LEDs would light up brightly whenever the serial data pin went high.
Next up was measuring voltages and current on the bright and dim LEDs, this confirmed that the dim LEDs got less volts across them than the bright ones, but it still didn't explain why.
I slowed down the code a bit (one update per two seconds) and started to notice something odd: the shift register would reset after a brightly lit LED would go off. Expecting something more fundamentally to be wrong I ripped all of the jumper wires out of the protoboard and pulled the shift register IC out of its socket. Then I started checking all connections on the protoboard.

I should've started with that really.

I forgot to connect the power supply of the shift register to the power supply of the ATtiny. There was a wire going there and it was soldered to the board, but on the bottom I never connected the wire to the VCC pin... Doh!
I also forgot to solder the pull up resistor on the reset line to 5V and the ceramic capacitor was floating as well. What a mess. I'm a bit puzzled as to why the LEDs would even light up in the first place, probably the shift register got some current through its input pins just enough to keep running. Anyway, I hooked everthing up and it works like a charm now!


Here's the Arduino code. Yes, it's messy and more complicated than it should be since I'm not using any Arduino functions. This way I can simply copy/paste it to AVR studio so it can run on the ATtiny (apart from the timer prescaler setting and counters).I'm using PORTB since that's what the ATtiny13 also has. Arduino pins 8-10 should connect to cathodes R, G and B.Pin 11 should connect to DS and pin 12 to SHCP and STCP.


1:  void setup()  
2:  {  
3:      PORTB = 7; // pull cathode pins high, no current will flow
4:      DDRB = _BV(DDB0) | _BV(DDB1) | _BV(DDB2) | _BV(DDB3) | _BV(DDB4);  
5:      // initialize timer
6:      TCNT0 = 0;  
7:      TIMSK0 = (1 << OCIE0A); // interrupt on compare match  
8:      TCCR0A = _BV(WGM01);  
9:      TCCR0B = _BV(CS02); // F_CPU / 256
10:      OCR0A = 250;  
11:      asm("sei");  // enable interrupts
12:  }  
13:    
14:  uint8_t cathodeCounter = 4;  
15:  uint8_t anodeCounter = 0;  
16:    
17:  void loop()  
18:  {  
19:      // do nothing!  
20:      asm("sleep");  
21:  }  
22:    
23:  ISR(TIMER0_COMPA_vect, ISR_NAKED)  
24:  {  
25:      uint8_t sreg = SREG;  
26:      static uint8_t counter = 0;  
27:      ++counter;  
28:      if ( counter >= 20 )  
29:      {  
30:          counter = 0;  
31:          ++anodeCounter;  
32:          if ( anodeCounter == 8 )  
33:          {  
34:              PORTB |= _BV(3);  
35:              anodeCounter = 0;  
36:          }  
37:          else if ( anodeCounter == 1 )  
38:          {  
39:              PORTB |= cathodeCounter;  
40:              cathodeCounter <<= 1;  
41:              if ( cathodeCounter >= 8 )  
42:              {  
43:                  cathodeCounter = 1;  
44:              }  
45:              PORTB &= ~cathodeCounter;  
46:              PORTB &= ~_BV(3);  
47:          }  
48:          PORTB |= _BV(4);   
49:          PORTB &= ~_BV(4);  
50:      }  
51:      SREG = sreg;  
52:      asm("reti");  // return from interrupt
53:  }
Here is the board with the ATtiny installed. The jumper wires are there to supply power from USB. The second protoboard in the background is my home made programming socket for the ATtiny. It's just a DIP 8 socket with all pins available on female headers. There's a 10 pin male header connected to the necessary ISCP pins so I can just simply connect a flat cable to the USB programmer (shown on the left).
Now all that's left to do is fix the board to the jig and add a power connector, or maybe a 7805 voltage regulator so I can power it from a battery or other external source and make the whole thing more portable :)
As for the ATtiny code:
1:  /*  
2:   * TinyPillarTester.c  
3:   *  
4:   * Created: 6-3-2014 21:48:11  
5:   * Author: Daniel  
6:   */   
7:    
8:    
9:  #include <avr/io.h>  
10:  #include <avr/interrupt.h>  
11:    
12:  int main(void)  
13:  {  
14:      PORTB = 7;  
15:      DDRB = _BV(DDB0) | _BV(DDB1) | _BV(DDB2) | _BV(DDB3) | _BV(DDB4);  
16:    
17:      TCNT0 = 0;  
18:      TIMSK0 = (1 << OCIE0A); // interrupt on compare match  
19:      TCCR0A = _BV(WGM01);  
20:      TCCR0B = _BV(CS01); // F_CPU / 8  
21:      OCR0A = 250;  
22:      asm("sei");  
23:    while(1)  
24:    {  
25:        // do nothing!  
26:        asm("sleep");  
27:    }  
28:  }  
29:    
30:  uint8_t cathodeCounter = 4;  
31:  uint8_t anodeCounter = 0;  
32:    
33:  ISR(TIM0_COMPA_vect, ISR_NAKED)  
34:  {  
35:      uint8_t sreg = SREG;  
36:      static uint8_t counter = 0;  
37:      ++counter;  
38:      if ( counter >= 75 )  
39:      {  
40:          counter = 0;  
41:          ++anodeCounter;  
42:          if ( anodeCounter == 8 )  
43:          {  
44:              PORTB |= _BV(3);  
45:              anodeCounter = 0;  
46:          }  
47:          else if ( anodeCounter == 1 )  
48:          {  
49:              PORTB |= cathodeCounter;  
50:              cathodeCounter <<= 1;  
51:              if ( cathodeCounter >= 8 )  
52:              {  
53:                  cathodeCounter = 1;  
54:              }  
55:              PORTB &= ~cathodeCounter;  
56:              PORTB &= ~_BV(3);  
57:          }  
58:          PORTB |= _BV(4);  
59:          PORTB &= ~_BV(4);  
60:      }  
61:      SREG = sreg;  
62:      asm("reti");  
63:  }  
64:    
Notice how similar it is to the Arduino code. The name of the interrupt handler is a bit different, and I've chosen different values for the timer prescaler and counter, and the setup and loop functions are combined in one main function. This code uses 208 bytes (20%) of ROM and 4 bytes (6%) of SRAM. Even the ATtiny13A is a bit overkill for something like this :)

Anyway, that's it for today, I'm always glad when something is working by the time I decide to call it a day :)

Monday, March 03, 2014

Pillar testing jig

After soldering a pillar of LEDs you'll want to test it to make sure you didn't make any mistakes. It's much easier to fix mistakes at this point than fixing them when the cube's fully assembled. So here's a little setup I came up with to make testing easier:

It's an ATtiny13 connected to an 74HC595 shift register. The outputs of the shift register are connected to the anodes of the pillar. The cathodes of the pillar are connected to pins PB0-2 of the ATtiny and have a few series resistors. By configuring the pins as output pins we can sink some current when we write them low, turning the LEDs on. When we write a high to the pins we source current and the LEDs go off. So with the shift register we can control which LED should be on, and with the input pins we can select the color.
Normally you'd use some transistors instead of connecting the LEDs to the pins/register directly because the ATtiny nor the shift register will be able to handle the current of all LEDs going on simultaneously (3 x 8 x 20mA = 480 mA), but we'll program the ATtiny to only turn on one LED at a time so we can get away with this setup.

Both the storage and shift clock of the shift register are connected to PB4, this causes data that is shifted into the register to appear almost directly on the output pin, which is fine for this setup. The output enable is connected to ground so we'll have continuous output. The reset pins of both ICs are connected to +5V through a 10K pull-up resistor. The DS pin of the 595 is connected to PB3, the Q7S pin is left floating since we're not using it, which seems to be common practice. There's a 100nF ceramic cap added as a bypass for the ICs, and a power connector of course.
And for kicks, here's a dirty PCB:
For my test jig I'm not going to put those LEDs on the board, instead I'll have a three terminal connector near the resistors to connect the cathode wires of the pillar to. And an eight terminal connector for the anodes.

As you can see, I used Fritzing for this quick prototype. It just works a bit faster and the breadboard view is handy for this situation. If you want to download the Fritzing project, click here.

As for the programming: that's easy:
  • Setup
    • DDRB should be set as input for PB5 (reset) and output for PB0-4
    • PORTB should be set high for PB0, PB1 and PB2 so they don't sink any current. 
    • define an eight bit anode counter variable, initialize it with 1.
    • define a cathode counter variable, initialize it with 1.
  • Main loop
    • Start setting PORTB to 7, so we set PB0-2 high
    • Shift the anode counter variable out on PB3 while toggling PB4.
    • Set PB3 low and toggle PB4 twice to shift out a zero, because the 595's output is lagging behind one cycle.
    • Shift the anode counter variable left by one.
    • Toggle one of the output pins by writing the cathode counter variable to PINB.
    • Shift the anode counter variable left one position.
    • If the result is zero, set it to one. Shift the cathode counter variable left one position as well.
    • If the result is eight, set it to one.
    • Add a delay for 1/8th of a second.
It's probably easier to put everything in an interrupt handler and leave the main loop empty. All we need to do is set a timer. Factory default for an ATtiny13 is 9.6MHz (depends a lot on ambient temperature and supply voltage), If we set the timer prescaler to 64 we'll have the timer running at 150,000Hz. If we then set the timer to generate an interrupt every 250 timer ticks we get an interrupt frequency of 600Hz. If we execute our code every 75th interrupt we'll get exactly 8Hz, perfect!
One thing I can't remember correctly is whether or not the system clock prescaler is active by default. If so then it's set to 8, reducing the system clock to 1.2MHz. We can tackle this by changing the timer prescaler to 8, so we'll still have our 150,000Hz timer clock. Easy!

Here's a video of the setup without using the cathode shifting.
And here is the setup on a protoboard, connected to the pillar. Still no cathode shifting and driven by an Arduino; I've stuck some jumper wires into the DIP socket for the ATtiny to connect the Arduino.

I'm not too happy with the custom cable I made. It's a bit too much work to stick those connectors in and I risk bending the anode legs. I think I'll create a wooden jig with some contacts, to place the pillar in.