cascading shift registers to drive 7-segment displays with PC input

18,250

Circuit Image

Four 595 shift registers are used together to drive 7-segment displays, with additional code implemented to accept input from a PC. Unlike the previous design that utilized 10-LED bar graphs, the current configuration employs 7-segment displays, which present a greater challenge and yield more meaningful output. The 7-segment displays in use are common-cathode, wired as specified. The code to illuminate decimal digits on a 7-segment display is straightforward. Each of the seven segment pins on the display is assigned to an output on the 595 shift register, from QA through QG. Each pin from QA to QG is assigned a binary value corresponding to its pin number, allowing for easy determination of which pins to set high to display each decimal digit by summing the binary values of the necessary pins. This initialization is evident in the setup() function. The coding style has been improved by prefixing global variables with 'g_' to distinguish them from local variables. Defining unchanging variables as 'const' is also a good practice, as it allows the compiler to optimize the code and prevents accidental modifications, thereby avoiding difficult-to-debug errors.

Daisy-chaining the 595 shift registers is straightforward: the serial output (pin 9) of the first 595 connects to the serial input (pin 14) of the next, while all latch (pin 12) and clock (pin 11) inputs are connected together. When the first 595 receives a new bit, the highest bit in its register is pushed out to the next 595 in the chain. Sufficient bits must be sent to fill all the 595 registers with new data. In the previous configuration with a single 595, only 8 bits of data were necessary. With two 595s, 16 bits need to be pushed, with the first 8 bits flowing through the first 595 and into the second. This pattern continues with additional 595s. Care must be taken to send the 8-bit values in the correct order; the bits for the furthest 595 must be sent first, followed by those for the nearest 595. This is managed in the sendSerialData() function, which takes the number of registers in use and a pointer to an array of bytes. Each element in the array corresponds to the byte intended for each register, starting from the furthest register from the Arduino. The code iterates backwards through the array, sending each byte in succession.

Instead of implementing a simple count-from-0-to-9999 logic, the design incorporates functionality to read input from the PC. This is achieved in the readNumberFromPC() function, which detects incoming data from the PC and reads each character with a 10 ms delay between reads to ensure complete transmission. This delay is crucial because the Arduino processes data rapidly, and without it, the first character may be read while subsequent characters have not yet arrived, resulting in incomplete input. The delay does not affect the 595s, as their outputs are latched and remain unchanged until the next update, thus preventing display flickering. In the loop() function, the logic for placing the correct digit byte in the register array is currently hard-coded, with plans for future revisions to make it register-count agnostic. The circuit includes a 220-ohm resistor for each common cathode of the 7-segment displays, connected to ground.Four 595s together to drive 7-segment displays and alsoadded code to acceptinput from the PC. Instead of using 10-LED bar graphs like I did last time, I`ve moved on to 7-segment displays - which are more challenging and provide a more meaningful output. The 7-segment displays I have are common-cathode, wired as shown below: The code to illuminate decimal digits on a

7-segment display is pretty straightforward. I assigned each of the 7 segment-pins on the display to an output on the 595, from QA through QG (see the circuit diagram below). Each pin from QA to QG is assigned a binary valueof 2 pin#, so figuring out which pins to make high to produce each decimal digit is just a matter of adding together the binary values representing each pin necessary to light up the right segments.

You can see this array being initialized in the setup() function. I`ve also moved to a proper coding style, with g_ prefixing each global variable to distinguish them from local variables inside functions and methods. It`s a good practice to define unchanging variables as const to allow the compiler to optimize the code around them - this also prevents mistakes where the code accidentally tries to change the value - the compiler won`t allow it as the variable is effectively read-only and a difficult-to-debug bug is avoided.

To daisy-chain 595s together is really simple - connect the serial output (pin 9) of the `lowest` 595 to the serial input (pin 14) of the next one in the chain, and connect all the latch (pin 12) and clock (pin 11) inputs together. When the first 595 accepts a new bit, the highest bit in the register will be pushed out of it`s serial line as input to the next 595 in the chain.

Enough bits must be pushed to fill all the 595s with new data correctly. In the previous post I only had a single 595 so I only had to push 8 bits of data from the Arduino. With two 595s I need to push 16 bits, with the first 8 bits effectively flowing through the first 595 and into the second. And so on with more 595s in the chain. Care must be taken to push the 8-bit values in the correct order - the 8 bits for the 595 furthest from the Arduino must be pushed first, and the 8 bits for the 595 nearest the Arduino in the chain must be pushed last.

I do this in the code in the sendSerialData() function by passing in the number of registers in use, plus a pointer to an array of bytes. Each element in the array has the byte to be pushed to the corresponding register - with the highest register number the furthest away from the Arduino.

The code then iterates backwards through the away, pushing each byte in succession. Rather than doing the obvious count-from-0-to-9999 code, I decided to figure out how to read input from the PC. This is done in the readNumberFromPC() function. If it detects that something has been sent from the PC, it reads each character, with a 10ms delay between each character read to ensure that all characters are received from the PC.

Without the delay, the Arduino executes so fast that it may read the first character from the input stream, try again, and the next character hasn`t made it over the slow (compared to the Arduino!) link from the PC - resulting in the input being chopped into two parts. This delay doesn`t cause a problem with the 595s as their outputs are latched, and so stay the way they were set until the next update - preventing any display flickering.

In the loop() function, the logic to put the correct digit-byte in the register array is hard-coded. In the next rev I`ll make this register-count agnostic. As far as the circuit is concerned, I`ve got one 220ohm resistor on each 7-segment common cathode going to gr 🔗 External reference