How the HL1606 REALLY works

I've explained in my last two Arduino posts how I got the HL1606 working with the Arduino's SPI support and hardware timers. If you haven't already read them, I suggest you read at least the first post before this one, otherwise what follows won't make much sense.

The setup I'm building needs to drive four strips at the same time, so the next step was to add another strip. The SPI protocol uses a /SS line for each slave, so that multiple devices can coexist on the bus. To communicate with a particular slave, you pull /SS for that slave low, send the data on the bus, then pull /SS high. The other devices on the bus ignore the traffic as their /SS lines are held high during the transaction. After wiring everything up and making the necessary code changes, I ran my existing text code to make sure the first strip was till working OK. Well, it was, but the new, second strip was going completely nuts, displaying a flickering version of the pattern I was sending to the the first strip - not at all what I expected. I did the usual things of checking the wiring and the code, which was all OK. I even tried tying the /CS line of the second strip to +5V to be sure it was unselected, but it made no difference. Hmm...

After some further thought, I began to suspect that despite what the datasheet said, the HL1606 was not in fact a SPI device. A clue to this was that the datasheet shows a high pulse on the /SS line (labelled L_I on the datasheet) rather than a continuous high level. OK, so what was it doing? As I said in my earlier post, the HL1606 has two shift buffers (A & B) and two output drivers (A & B). Pulling the /SS line low does indeed suppress the copying of the shift buffer contents into the output latches, and pulling it high does indeed result in the shift buffer contents being copied into the output driver to light the LEDS. However there's one vital but missing piece of behaviour - when the /SS line is high the HL1606 should ignore the contents of the data bus, but it doesn't. Instead, if /SS is high the current contents of the shift buffers are copied to the output driver immediately. That explained why the second strip was displaying a manically-flashing version of the pattern displayed on the first strip - as the data was being shifted out to the first strip, the second strip was shifting in and displaying the data as it progressed down the strip. Duh, that is clearly not how SPI works.

OK, that's a big problem. The AVR only has one set of SPI hardware and I didn't have enough pins to implement five SPI buses using bit-banging, as each requires four pins. What I needed was some way of disabling each strip other than using the dysfunctional /SS line. The SPI bus uses clock line supplied by the bus master, with the clock line determining when the data should be sampled. Perhaps if I suppressed the clock, the HL1606 would ignore the data on the bus? That was easy to test, I just disconnected the clock line on the second strip and the mad flashing stopped. Yay.

So now I needed to come up with a way of controlling the SPI clock being supplied to each strip. I could have done that by using another pin per strip to control an external gate on the clock line but I already had such a line, the per-strip /SS line, even though it didn't actually work correctly. All I needed was something that could use an active-low input to gate the clock line. After a little digging I found a 7400 series chip, the 74HC125 quad bus buffers. Each one has four channels - great, I had four strips to control, and at 32p each they weren't going to break the bank. I connected the /CS lines to the /G pin of the 74125, the clock line from the Arduino to the A lines and the clock line of the strips to the Y lines and fired everything up - perfect, the strips now only respond when their particular /SS line was active. The only extra tweak required was the addition of a 20K pull-down resistor - the outputs of the 74HC125 are tri-state when switched off, so the resistors are needed to stop the clock lines to the LED strips from floating (and false-triggering) when the strips are deselected.

Each strip has 20 LEDs so I extended my existing code to generate a 40-LED pattern and then in the output code I split the pattern into two halves, sending each half to one strip so that I could test out patterns that span more than one strip. You can see the results below.

I've still more work to do - I need to drive the SPI radio chipsets I'm going to be using, I need a task management framework to coordinate everything, I need a way of generating the patterns easily and so forth, but the major hurdle of driving multiple strips simultaneously using the minimum number of pins has been solved. I'll be writing further posts as I go, so please check back for more :-)

Categories : Tech, AVR


Re: How the HL1606 REALLY works

Thanks for the HL6106 konw-how :) The datasheet is really a piece of shit :( It seems you saved a lot of my time - from now on it should be only a question of SW ...  PS I am working on a ilumination of a living room - I need primarily three functions a) white light and static/dynamic white patterns for celebrations, b) changeable static pastel colours for ambience (dinner, TV watching, coffee/tea, chess/cards), c) colorfull dynamic discoefects for parties (of course controlled by music - FFT ?). Total count will be 24m of RGB stripes and 48m of white LED stripes ...

Re: How the HL1606 REALLY works

Thanks for the great information.

Did you try to daisy chain the 2nd strip to the end of the first strip and that did not work?  They are daisy chain capable and there was no mention in this article about hooking the size out wires to the 6 input wires of the next strip and explaining what happened in that case.  So I am curious if you tried it    Since the strips are normally 200 leds daisy chained .. and a 20 led strip is only a part chopped off the longer length, it seems daisy chaining would work to run 5 strips.

Re: How the HL1606 REALLY works

As delivered from the factory, the 'near' end of the strip has a ribbon cable and separate ground and power wires, the 'far' end has a ribbon cable and ground (but no power) so you can connect the ground and control wires to another strip. You do however need to provide a separate 5V supply for each 200-LED strip. In electrical terms, until I chopped the strip into 20-LED segments it was in effect a daisy chain of 10 20-LED segments, so yes of course it would work.

In my case I'm using a 20-LED strip per arm and leg, and if I wanted to wire them up as a single, continuous chain I'd have to run a cable from the wrist/ankle end of each of the four strips back up the arm/leg and across to the next strip. That would be both more bulky and fragile than driving each strip separately. The downside is you then need a dedicated /SS pin per strip, plus the 74HC125 chip needed to make the strips into a 'proper' SPI device. but I think that's a worthwhile trade-off.

In my test code I actually model the strip as an array of 40 LEDs and generate the patterns to use all 40, then in the output code I simply split the array into two halves, sending out the 'left' 20 reversed, and the 'right' 20 normally, so it is easy to hide the actual electrical arrangement and treat a number of strips as being logically contiguous, even when they aren't physically contiguous.

Re: How the HL1606 REALLY works

 Another reason why you want to do this in chains of 20 - I found that when I have 40 leds in a single strip, I need to run the SPI line at only 1Mhz, instead of 2Mhz.  As you can imagine, that turns into quite a problem, performance wise.

(Also - another reason to use hardware SPI instead of bit banging - the hardware SPI support is at least an order of magnitude faster than what you can get twiddling port bits by hand).

For my projects, most of this will soon be moot.  My lighting projects are moving to be wsc2801 based - chips that take 256 levels of brightness for each of r,g and b, and provide their own pwm clock.  No more running PWM from the arduino itself.  There's also the lpd6803's (the led pixels that bliptronics.com sells) which take 32 levels of brightness information, but require you to strobe the clock to drive PWM on them.  Finally, there's the CYT3005's, which appear to be like the wsc2801s except they provide 512 levels of brightness.  I also forget, whather it's just the cyt3005 or the wsc2801 - but one or both of those chipsets also allows you to set calibration information so that you can white balance the lights.

Re: How the HL1606 REALLY works

I've taken a look at your blog over at waitingforbigo.com and I agree with you about the awfulness of the HL1606.  The WSC2801 and CYT3005 sound interesting, any ideas where you can get them from?

Re: How the HL1606 REALLY works

Hmmm, rather than disabling the clocks using the 74 logic, why not just strobe the /SS line of the one strip after clocking the data into all the strips. This is how I understand the timing diagram, I have used strips in parallel, but not yet on separate /ss latch strobes because a) I dont need it and b) I am PWMing so there may not be time to do many 2metre strips on 'one' serial bus.

Re: How the HL1606 REALLY works

That will work if you only have one strip attached or if you are driving them all in parallel,  However, if you have more than one they'll also see the data intended for the other strips and will flicker like mad.

Re: How the HL1606 REALLY works

They will see the data, but they wont flicker because you only strobe the  latch line - it is not edge triggered, it will reflect the serial buffers in the output latches all the while the latch is held high. The data sheet shows the latch going high for only 1 ns at the end of the full transfer.

You are right only if you are keeping the latch high at all times unless clocking data. It is a latch strobe on positive, not a circuit select on negative signal.

Just tried it here - it works.

Re: How the HL1606 REALLY works

Hmm... Are you using the hardware SPI support, i.e the SPCR, SPSR and SPDR registers? At the moment the code I'm using to drive each strip looks like this:

Initialise:
    set /CS high 

Write data to strip:
    set /CS low
    foreach LED
        write LED value to SPDR
        wait for SPIF bit of SPSR to be set
    end
    set /CS high

Whereas if I'm following you correctly you could do this:

Initialise:
    set /CS low 

Write data to strip:
    foreach LED
        write LED value to SPDR
        wait for SPIF bit of SPSR to be set
    end
    toggle /CS high/low

Is that right? If so, that would remove the need for the 74HC125 altogether.

Re: How the HL1606 REALLY works

Sorry, I have to ask: *Did* it work?  ...Did you eliminate the need for external 74 devices?
 

Re: How the HL1606 REALLY works

No, I didn't try it because I figured out it would only work in a limited set of circumstances. I'll try and illustrate the problem below. "S/R" is the shift register on a strip, "O/P" is the output being displayed. Dots are off, letters represent colours and there are 20 LEDs per strip. The aim is to load repeating RGB sequences onto strip 1 and repeating CYM sequences onto strip 2, alternating between each strip:

Step 1: load RGB into strip 1 S/R, however strip 2 will also see the same pattern
Strip 1 S/R RGB.................
Strip 1 O/P ....................
Strip 2 S/R RGB.................
Strip 2 O/P ....................
Step 2: toggle strip 1 /CS to output the RGB pattern, so far, so good
Strip 1 S/R RGB.................
Strip 1 O/P RGB.................
Strip 2 S/R RGB.................
Strip 2 O/P ....................
Step 3: output the CYM pattern to strip 2, strip 1 sees it too
Strip 1 S/R CYMRGB..............
Strip 1 O/P RGB.................
Strip 2 S/R CYMRGB..............
Strip 2 O/P ....................
Step 4: toggle strip 2 /CS to output the CYM pattern - oh dear, where did those RGBs come from?
Strip 1 S/R CYMRGB..............
Strip 1 O/P RGB.................
Strip 2 S/R CYMRGB..............
Strip 2 O/P CYMRGB..............
Step 5: Add another RGB  triplet to strip 1, but yet again strip 2 sees it as well
Strip 1 S/R RGBCYMRGB...........
Strip 1 O/P RGB.................
Strip 2 S/R RGBCYMRGB...........
Strip 2 O/P CYMRGB..............
Step 6: toggle strip 1 /CS - oh dear, not at all what we wanted...
Strip 1 S/R RGBCYMRGB...........
Strip 1 O/P RGBCYMRGB...........
Strip 2 S/R RGBCYMRGB...........
Strip 2 O/P CYMRGB..............

The only case when this approach would work is when you fully reload each 20 slots in the shift register chain each time, because then it doesn't matter what's already in there so the 'interference' from other strips content is irrelevant. That is the way I'm driving the strips at the moment but I want to retain the ability to drive very long strips where I won't have sufficient time to completely refresh the strips on each cycle, so I left the 74HC125s in place.

Re: How the HL1606 REALLY works

Since I really found this information useful I feel like I should make my contribute.

If you only need two hardware SPIs then you can use the USART in SPI mode, at least on the atmega168. Asynchronous mode on other devices will probably work too, but I have not tested it. For the Atmega168 just init the USART something like this:

  //  TXD and XCK are output pins (PD1 and PD4 on Atmega168)
  DDRD |= (1<<PD1) | (1<<PD4);

  // RXCIEn TXCIEn UDRIE RXENn TXENn - - -  =  UCSRnB
  UCSR0B = 0b00001000;
  
  // UMSELn1 UMSELn0 - - - UDORDn UCPHAn UCPOLn  = UCSRnC
  UCSR0C = 0b11000100;
  
  //  Baud rate is similar to SPI clock rate
  UBRR0L = 3; // 115200

Then the USART can be used as hardware SPI:

void sendByte (uint8_t   out)
{
  while ( !( UCSR0A & (1<<UDRE0)) );
  UDR0 = out;
}

Re: How the HL1606 REALLY works

Yes, the datasheets for the various AVR chips explain how to use the UART for SPI. Although there are some minor differences, they probably aren't relevant when the MCU is acting as a SPI master.  However the whole point of SPI is it is supposed to be a bus, so having to use 2 SPI channels on the MCU to work around brokenness in the devices is a little lame.