HP QDSP6064 Bubble Display

The nostalgia is strong with this one, reminding me of innumerable calculators I had as a kid growing up, so when a bunch of these came up for sale, I bought a handful to play around with. This is, in fact, a direct contradiction of what I said in the MicroView review, “I’ve grown out of my ‘oooo shiny!’ phase”. But how could you not want to play with that thing? It’s so damn cute, I don’t know if I should build a circuit board with it, or make a cartoony caterpillar outline around it.



Bubble Display Basics

What you see is a four digit, seven segment display, but it’s very small — the size of a 12-pin DIP package. The display is arranged in a common cathode configuration, with the standard A-G + Decimal Point grid brought out to individual anodes. 8 segments + 1 cathode per digit = 12 pins. There are two remarkable things about these displays besides their size. First is the clarity of the individual LED segments which look like little incandescent filaments inside the acrylic bubble, and second, is the low power required to light them up, with an average current draw of 5mA/segment with a matching low forward voltage (1.6ish to 1.8ish). Underneath the DIP packaging, you see the model number “QDSP-6064”, the HP logo next to the manufacturing location, and what might be a date code or a lot number … finding package marking data on HP’s website was impossible … but all five of the ones I bought have the same indicator. The final mark is the PIN 1 indicator which you can see on the upper left.



The Guts

Each digit is comprised of a series of LED surfaces, arranged in a seven segment digit grid, and connected with bond wires to their respective pins. The whole display die is then encased in an acrylic body with a dome shaped face to provide some degree of magnification of the embedded circuitry. I found an old datasheet for the 1970s version of these and it says the red color is important for the following reason…

To improve display contrast, the plastic incorporates a red dye that absorbs strongly at all visible wavelengths except the 655nm emitted by the LED. An additional filter, such as Plexiglass 2423, Panelgraphic 60 or 63, and Homalite 100-1600, will further lower the ambient reflectance and improve display contrast.

So that explains why there was always that smoked plastic in front of your calculator display!

Bubble Display Connection Options

The hookup method for these is fairly simple: provide a switchable current source to each of the segments, and then provide a switchable current sink to each of the cathodes. By powering specific segments you create the pattern of a digit, and by sinking specific cathodes, you allow current to flow through one of the four displays.

For powering the anodes, you can connect these directly to your microcontroller’s digital IO pins, remember that you’ll need to supply current limiting resistors for each. You might be tempted to put resistors on the cathodes instead, but as all 8 LEDs are wired in parallel in each digit, you would be relying on the segments all having precisely the same current vs. forward voltage characteristics. Now, in the real world, you probably wouldn’t burn any of them out, but if one has slightly more internal resistance than one of the others, it won’t be as bright. Resistors are cheap, so put them on the anodes. According to the datasheet, you want to supply 5mA and can expect a forward voltage of around 1.6V. They don’t supply a forward voltage curve so you can get a better estimate, they just say at 10mA, expect Vf of 1.6V, but they want an average of 5mA. There, clear as mud. Anyway, 5V – 1.6V = 3.4V and 3.4V/0.005A = 680Ω current limiting resistor. How conveeeeeenient!

For sinking the cathodes of the QDSP6064, you need to do a little bit of figuring. For the ATMEGA328P that makes up the brains of an Arduino, you are able to sink 40mA of current on any particular IO pin, up to a maximum of 200mA for the chip overall (including the 5V out). If you have 8 segments lit (that’s A through G and the decimal place), with the 5mA current draw stated in the datasheet for the display, that would put you right at the max limit to be able to just plug the common cathode pins right into one of the digital pins. Alternatively, for the Texas Instruments Tiva C Series, you have a maximum of 18mA on specific pins. That means for the TI you’d need to come up with a different solution, like sinking through a transistor.

In coordination with reading the datasheet, I did some measuring. I placed the 680Ω resistor on the cathode for digit 1, and then provided 5V (4.82V actually after regulation and getting squeezed through the ammeter of my cheap multimeter) to each of the segments in turn, noting the forward voltage and current draw.

Segment Resistor Vsource Vforward Current (mA) A 680Ω 4.82 1.55 4.86 B 680Ω 4.82 1.55 4.86 C 680Ω 4.82 1.55 4.86 D 680Ω 4.82 1.55 4.86 E 680Ω 4.82 1.55 4.86 F 680Ω 4.82 1.55 4.86 G 680Ω 4.82 1.55 4.86 DP 680Ω 4.82 1.58 4.82

So, there is tremendous consistency across the QDSP6064 segments, and at full blast with all characters used you’d be at 38mA and change if you were sinking the digit through your Arduino digital pin. For purposes of building out a little test I’m fine with that. To double check, I connected all the segments up through 680Ω resistors and measured the current draw at the cathode: 34.77mA.

Here’s my thought process on why I’m not worried about sinking that much current into my Arduino’s digital pins…

It’s under the max current rating. Barely, but under the max rating. I know the code I’m going to run on it will only have each digit on for about 5ms out of every 20ms. I would only be at the max 38mA if I was displaying the value “8.”. Anything else would require less.

If this were going into a production board like the Education Shield Display, I’d definitely sink the current through a transistor so that it wasn’t even a marginal concern.

The cost for a little surface mount quad NPN transistor chip is around $1.75 — cheap insurance. Here is a section of a schematic showing what is meant by “sinking the current through a transistor.”

To enable the digit, you would bring Digital 5 high, which would allow current to flow from the collector to the emitter of the transistor, lighting the digit. The NPN transistor is easily capable of handling both the current we’re talking about, 40mA or so, and the switching frequency of the code I’m using, 50Hz. If you want a little more detail on using NPN transistors in circuits, check out the Building Things with NPN Transistors article.

To provide a comparison, this is an ATA8401ARBJ 20mm four digit seven segment display in red. It’s continuous forward current is 20mA with a Vf of 1.90V. With all the segments illuminated it draws 84.3mA, over twice as much as the Bubble Display! No way you could sink that to a digital pin, so you’d have to use transistors. I tried it with a set of 560Ω resistors instead of the 150Ω I had originally. If you do the math you’ll see that generates about the same current draw as the bubble display then. The 20mm display was still relatively bright – imagine a clock radio display with a “dim at night” feature.

So that’s the common cathode beat to death. Let’s talk quickly about supplying the current in the first place.

We could just connect each of the segment pins up to our digital ports, but that’s a LOT of pins to use… four for the cathodes and then eight for the anodes. You’d use up every single digital pin on an Arduino (always try to leave digital 0 and 1 free since they double as the Serial Receive and Serial Transmit respectively.) I think a far better option would be to connect them to a shift register, and then control the display via serial commands. Provided we don’t need to use the Output Enable or Shift Register Clear pins, we would be able to control all eight segments with only three pins.

Here is the full schematic, using the shift register…

And here is the circuit completely assembled. Down at the bottom (lost in the fuzzy focus of bad depth of field) a voltage divider is formed with a light dependent resistor and a 6800Ω resistor, creating a simple sensor I can sample with the Analog 5 ADC pin, and send the value to the display.

By using shift registers, you could easily expand the display to include a second QDSP6064, and just daisy chain the shift registers together: Qh’ pin to SER pin, with the SCLK pin connected in series as well as the SRCLK pins.

Coding for the Bubble Display

(It strikes me I should break out this entire section into a dedicated “seven segment display” coding post. Until then, I’ll leave this all here.)

First let’s describe how we expect the circuit to function at it’s most basic: we send a byte of data to the shift register with a 1 in the position for each segment we want turned on to create a specific number, and then we turn provide a path to ground for the cathode representing only the digit position we want to light up, leaving the other digits off.

Sending Data to the Shift Register

The segments are connected to the shift register in a straight forward fashion: SEG A = QA, SEG B = QB, etc with the decimal point connected to QH. The LEDs are arranged in the usual 7-segment pattern under the red acrylic…

So, if you wanted to create the number “2”, you would need to set the highlighted segments to 1 in the byte that you send to the shift register, the rest would remain 0 to keep them off.

Now that you have the correct bit pattern, you load it into the shift register by bringing the latch (SRCLK) low, sending the byte, then bringing the latch high.

digitalWrite (latchPin, LOW); shiftOut (serPin, sclkPin, MSBFIRST, B01011011); digitalWrite (latchPin, HIGH); 1 2 3 digitalWrite ( latchPin , LOW ) ; shiftOut ( serPin , sclkPin , MSBFIRST , B01011011 ) ; digitalWrite ( latchPin , HIGH ) ;

Enabling and Lighting a Digit

To turn one of the digits on, you first need to configure the pin as an output, and then set it low. When low, you are establishing a connection to ground for the current that wants to flow through the digit. When high, you are keeping it at the same level (actually slightly above, but the display is made of diodes so it doesn’t matter, as long as you don’t exceed the reverse voltage threshold) so no current flows.

In order to display different numbers on different digits, you need to turn one on while keeping the others off, show the number, then turn that one off, turn on the next, show the number, over and over. I found that with four digits, holding the value for 5ms was enough to make sure each number was bright, but no flickering was seen.

Putting It All Together

Some quick caveats about efficiency, specifically, this code isn’t. I had a calculus professor, the last time I tried to go to college, berate me for never finding the “elegant” solution – this is true, but my brain works in long hand and post-optimizes (and really, I’m not going to try to find the balance between “elegance” and “show your work” when a grade is involved). I also think that it’s important when writing these posts that the thought process be easily followed. So, here’s the code, and after the jump, I’ll show areas where improvements can be wrung out of it.

Bubble Display /* A sketch to play with the Bubble Display Experimentation Pack Datasheet: https://rheingoldheavy.com/wp-content/uploads/2014/11/BB_QDSP_DS.pdf Uses a shift register to control the anodes for segments A-G, + one output for the decimal point Common Cathodes sink their current to digital pins. The display uses 5mA per segment, with a Vf of 1.8V, and measured out pretty close, so I'm comfortable sinking a conceivably fully lit digit through a single pin. LED Map: |--A1--| F6 B2 |--G7--| E5 C3 |--D4--| DP8 Digit Encoding: 0 = B00111111 = 0x3F 1 = B00000110 = 0x06 2 = B01011011 = 0x5B 3 = B01001111 = 0x4F 4 = B01100110 = 0x66 5 = B01101101 = 0x6D 6 = B01111101 = 0x7D 7 = B00000111 = 0x07 8 = B01111111 = 0x7F 9 = B01101111 = 0x6F http://rheingoldheavy.com/hp-qdsp6064-bubble-display/ */ int ccDig1 = 2; // Digital Pin to sink current for digit 1 common cathode int ccDig2 = 3; // Digital Pin to sink current for digit 2 common cathode int ccDig3 = 4; // Digital Pin to sink current for digit 3 common cathode int ccDig4 = 5; // Digital Pin to sink current for digit 4 common cathode int dataPin = 7; // Shift Register Serial Data In "SER" int sclkPin = 8; // Shift Register Serial Clock "SCLK" int latchPin = 9; // Shift Register Latch "SRCLK" int place1 = 0; // Holds the value of the 1000s place of the bubble display int place2 = 0; // Holds the value of the 100s place of the bubble display int place3 = 0; // Holds the value of the 10s place of the bubble display int place4 = 0; // Holds the value of the 1s place of the bubble display int showMe = 0; // The value to be shown on the bubble display long previousMillis = 0; // Stores the last time Analog 5 was sampled long currentMillis = 0; // Stores the current millisecond increment void setup() { // These pins sink the current from the common cathodes, so they are configured as OUTPUT. // When they are in a HIGH state, they will be at the same potential as the display // When they are in a LOW state, they will provide a path to ground for current and the digit lights. pinMode (ccDig1, OUTPUT); pinMode (ccDig2, OUTPUT); pinMode (ccDig3, OUTPUT); pinMode (ccDig4, OUTPUT); // Start with all displays OFF digitalWrite (ccDig1, HIGH); digitalWrite (ccDig2, HIGH); digitalWrite (ccDig3, HIGH); digitalWrite (ccDig4, HIGH); // Set our pins up for the shift register pinMode (dataPin, OUTPUT); pinMode (sclkPin, OUTPUT); pinMode (latchPin, OUTPUT); currentMillis = millis(); Serial.begin (9600); } void loop() { currentMillis = millis(); if (currentMillis - previousMillis >= 1000) { // We need to only check the analog pin once a second showMe = analogRead(5); // we don't want to use a delay() or we'll interfere previousMillis = millis(); // with the timing of the multiplexing } place1 = showMe / 1000; // Break our analog reading value into individual pieces place2 = ( showMe % 1000) / 100; place3 = ((showMe % 1000) % 100) / 10; place4 = ((showMe % 1000) % 100) % 10; enableDigit1(); // Take each chunk of data and display it at the appropriate place writeNumber (place1); // the delay(5) provides sufficient POV brightness. delay (5); // Any longer looks bad. enableDigit2(); writeNumber (place2); delay (5); enableDigit3(); writeNumber (place3); delay (5); enableDigit4(); writeNumber (place4); delay (5); } // The enableDigit routines, just bring the relevant common cathode pin low and // the irrelevant ones high. void enableDigit1() { digitalWrite (ccDig1, LOW); digitalWrite (ccDig2, HIGH); digitalWrite (ccDig3, HIGH); digitalWrite (ccDig4, HIGH); } void enableDigit2() { digitalWrite (ccDig1, HIGH); digitalWrite (ccDig2, LOW); digitalWrite (ccDig3, HIGH); digitalWrite (ccDig4, HIGH); } void enableDigit3() { digitalWrite (ccDig1, HIGH); digitalWrite (ccDig2, HIGH); digitalWrite (ccDig3, LOW); digitalWrite (ccDig4, HIGH); } void enableDigit4() { digitalWrite (ccDig1, HIGH); digitalWrite (ccDig2, HIGH); digitalWrite (ccDig3, HIGH); digitalWrite (ccDig4, LOW); } // The writeNumber method accepts the individual digit, matches it with the corresponding LED pattern // and then sends the bit pattern to the shift register. void writeNumber(int digit) { int pattern = 0; switch (digit) { case 0: pattern = 0x3F; break; case 1: pattern = 0x06; break; case 2: pattern = 0x5B; break; case 3: pattern = 0x4F; break; case 4: pattern = 0x66; break; case 5: pattern = 0x6D; break; case 6: pattern = 0x7D; break; case 7: pattern = 0x07; break; case 8: pattern = 0x7F; break; case 9: pattern = 0x6F; break; case 10: pattern = 0x00; break; } digitalWrite (latchPin, LOW); // prepare the shift register for new data shiftOut (dataPin, sclkPin, MSBFIRST, pattern); // send the new data digitalWrite (latchPin, HIGH); // commit the new data } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 /* A sketch to play with the Bubble Display Experimentation Pack Datasheet: https://rheingoldheavy.com/wp-content/uploads/2014/11/BB_QDSP_DS.pdf Uses a shift register to control the anodes for segments A-G, + one output for the decimal point Common Cathodes sink their current to digital pins. The display uses 5mA per segment, with a Vf of 1.8V, and measured out pretty close, so I'm comfortable sinking a conceivably fully lit digit through a single pin. LED Map: |--A1--| F6 B2 |--G7--| E5 C3 |--D4--| DP8 Digit Encoding: 0 = B00111111 = 0x3F 1 = B00000110 = 0x06 2 = B01011011 = 0x5B 3 = B01001111 = 0x4F 4 = B01100110 = 0x66 5 = B01101101 = 0x6D 6 = B01111101 = 0x7D 7 = B00000111 = 0x07 8 = B01111111 = 0x7F 9 = B01101111 = 0x6F http://rheingoldheavy.com/hp-qdsp6064-bubble-display/ */ int ccDig1 = 2 ; // Digital Pin to sink current for digit 1 common cathode int ccDig2 = 3 ; // Digital Pin to sink current for digit 2 common cathode int ccDig3 = 4 ; // Digital Pin to sink current for digit 3 common cathode int ccDig4 = 5 ; // Digital Pin to sink current for digit 4 common cathode int dataPin = 7 ; // Shift Register Serial Data In "SER" int sclkPin = 8 ; // Shift Register Serial Clock "SCLK" int latchPin = 9 ; // Shift Register Latch "SRCLK" int place1 = 0 ; // Holds the value of the 1000s place of the bubble display int place2 = 0 ; // Holds the value of the 100s place of the bubble display int place3 = 0 ; // Holds the value of the 10s place of the bubble display int place4 = 0 ; // Holds the value of the 1s place of the bubble display int showMe = 0 ; // The value to be shown on the bubble display long previousMillis = 0 ; // Stores the last time Analog 5 was sampled long currentMillis = 0 ; // Stores the current millisecond increment void setup ( ) { // These pins sink the current from the common cathodes, so they are configured as OUTPUT. // When they are in a HIGH state, they will be at the same potential as the display // When they are in a LOW state, they will provide a path to ground for current and the digit lights. pinMode ( ccDig1 , OUTPUT ) ; pinMode ( ccDig2 , OUTPUT ) ; pinMode ( ccDig3 , OUTPUT ) ; pinMode ( ccDig4 , OUTPUT ) ; // Start with all displays OFF digitalWrite ( ccDig1 , HIGH ) ; digitalWrite ( ccDig2 , HIGH ) ; digitalWrite ( ccDig3 , HIGH ) ; digitalWrite ( ccDig4 , HIGH ) ; // Set our pins up for the shift register pinMode ( dataPin , OUTPUT ) ; pinMode ( sclkPin , OUTPUT ) ; pinMode ( latchPin , OUTPUT ) ; currentMillis = millis ( ) ; Serial . begin ( 9600 ) ; } void loop ( ) { currentMillis = millis ( ) ; if ( currentMillis - previousMillis >= 1000 ) { // We need to only check the analog pin once a second showMe = analogRead ( 5 ) ; // we don't want to use a delay() or we'll interfere previousMillis = millis ( ) ; // with the timing of the multiplexing } place1 = showMe / 1000 ; // Break our analog reading value into individual pieces place2 = ( showMe % 1000 ) / 100 ; place3 = ( ( showMe % 1000 ) % 100 ) / 10 ; place4 = ( ( showMe % 1000 ) % 100 ) % 10 ; enableDigit1 ( ) ; // Take each chunk of data and display it at the appropriate place writeNumber ( place1 ) ; // the delay(5) provides sufficient POV brightness. delay ( 5 ) ; // Any longer looks bad. enableDigit2 ( ) ; writeNumber ( place2 ) ; delay ( 5 ) ; enableDigit3 ( ) ; writeNumber ( place3 ) ; delay ( 5 ) ; enableDigit4 ( ) ; writeNumber ( place4 ) ; delay ( 5 ) ; } // The enableDigit routines, just bring the relevant common cathode pin low and // the irrelevant ones high. void enableDigit1 ( ) { digitalWrite ( ccDig1 , LOW ) ; digitalWrite ( ccDig2 , HIGH ) ; digitalWrite ( ccDig3 , HIGH ) ; digitalWrite ( ccDig4 , HIGH ) ; } void enableDigit2 ( ) { digitalWrite ( ccDig1 , HIGH ) ; digitalWrite ( ccDig2 , LOW ) ; digitalWrite ( ccDig3 , HIGH ) ; digitalWrite ( ccDig4 , HIGH ) ; } void enableDigit3 ( ) { digitalWrite ( ccDig1 , HIGH ) ; digitalWrite ( ccDig2 , HIGH ) ; digitalWrite ( ccDig3 , LOW ) ; digitalWrite ( ccDig4 , HIGH ) ; } void enableDigit4 ( ) { digitalWrite ( ccDig1 , HIGH ) ; digitalWrite ( ccDig2 , HIGH ) ; digitalWrite ( ccDig3 , HIGH ) ; digitalWrite ( ccDig4 , LOW ) ; } // The writeNumber method accepts the individual digit, matches it with the corresponding LED pattern // and then sends the bit pattern to the shift register. void writeNumber ( int digit ) { int pattern = 0 ; switch ( digit ) { case 0 : pattern = 0x3F ; break ; case 1 : pattern = 0x06 ; break ; case 2 : pattern = 0x5B ; break ; case 3 : pattern = 0x4F ; break ; case 4 : pattern = 0x66 ; break ; case 5 : pattern = 0x6D ; break ; case 6 : pattern = 0x7D ; break ; case 7 : pattern = 0x07 ; break ; case 8 : pattern = 0x7F ; break ; case 9 : pattern = 0x6F ; break ; case 10 : pattern = 0x00 ; break ; } digitalWrite ( latchPin , LOW ) ; // prepare the shift register for new data shiftOut ( dataPin , sclkPin , MSBFIRST , pattern ) ; // send the new data digitalWrite ( latchPin , HIGH ) ; // commit the new data }

So, how can this be tightened up?