First completed: August 2014. Additional work carried out late 2025/early 2026.
Introduction
Previously, the author had made a very small dot matrix clock, but he had ambitions to make something much bigger, with a new goal of making a display similar in size to the 6-inch jumbo radio-controlled LED clock/timer but with all the versatility of a dot matrix display.
This new design would be an expansion of the first design with a number of enhancements:
- Larger dot matrix displays, 5mm LED size, 120mm overall display height with two rows of displays
- Changed to 74HC595 shift registers to add latch feature, essential if shift registers are to be used for row drivers as well
- Much more powerful power supply
- MOSFETs for row drivers due to much higher current requirements
The principle of operation is exactly the same as before, just expanded to a bigger display. The new display would be 96x16 pixels, requiring 12 displays across and two rows. 14 shift registers (two for rows, 12 for columns) were cascaded so that a single data pin on the microcontroller would supply data to all of them.
Hardware design
To ensure that the maximum current rating of the shift register wouldn't be exceeded when a large number of pixels are on, some small PNP transistors were selected for the column drivers (each one would only have to drive one pixel at a time) and large N-channel MOSFETs were selected for the row drivers as each one would have to drive an entire row of pixels.
It is common to overdrive multiplexed LEDs because driving them at their normal continuous rated current would result in a dim display once multiplexing is introduced. The trouble with that is that the LEDs will be quickly destroyed if the microcontroller freezes up for any reason and leaves a row continuously illumnated. The 75 ohm resistors used for the columns limit LED current to about 43mA, which achieves a good level of brightness without immediately destroying the display if a row is illumnated continuously. This also results in a maximum possible current for a fully illuminated row of 4.096A.
Here is the design for the row drivers:
Here is the design for the column drivers:
And the overall circuit diagram of the entire display module:
Assembly
The displays were painstakingly assembled onto a very large piece of stripboard and soldered by hand. This option made sense at the time, but in 2026, a proper custom-made PCB would make more sense given how cheap they are nowadays.
The control electronics were assembled onto a smaller piece of stripboard. As with the small display, an ATmega328p was used here. The two shift registers for the row drivers are contained on this board along with some other circuitry including a power supply voltage supervisor IC which turns off the output enable signals on the shift registers to ensure that a row does not get stuck on when the power supply voltage is falling during power-down.
Software development
The SPI interface is bit-banged in code using direct register access to the port I/O which makes that technique adequately fast. As the row driver shift registers are first in the chain followed by the column drivers from left to right, the pixel data for the rightmost column must be shifted out first followed by the rest of the display in right-left order, then the 16 row enable signals are shifted out last. This arrangement allows some time to be saved in the brightness control procedure; brightness control is achieved by turning all the rows off a certain duration after a row is turned on, but as all the rows are off, there is no need to update the column data so only the 16 bits for row control need to be shifted out for this cycle.
[Add waveform captures]
As with the small display, a timer interrupt is used for the display code. The radio controlled time reception is also handled in the timer interrupt as is reading an LDR to automatically set the brightness level. The actual 'application software' is implemented in functions which are called from the main loop which just holds the menu structure.
The 'applications' implemented are:
- Big clock - time displayed using 16 pixel high digits
- Small clock - time and date displayed using 7 pixel high digits
- Stopwatch - shows just the timer with 16 pixel high digits or timer and lap or split time simultaneiously with 7 pixel high digits
- Labour timer - similar to the stopwatch, but also displays the cost of the time based on an hourly labour rate set in the settings
- Countdown to time - counts down to a specific time on any day of the week (maximum 7 days in the future)
- Serial display - shows received text on a 16x2 grid, similar to a 16x2 HD44780-compatible LCD
- Message display - shows received text as a scrolling message. Special character codes can be used to insert any part of the current time and date into the message.
Software development was mainly carried out in 2014, then it was worked on again in 2026 to add the 'Numbers Game' application which replicates the numbers round from a popular TV game show which has letters and numbers.
Display brightness problem in newer versions of Arduino IDE
It was discovered that when the code was recompiled in a newer version of Arduino IDE, the brightness of the individual rows was no longer consistent. A lot of time was spent trying different compiler optimisation levels, using pragmas to disable optimisation of the timer interrupt and rewriting the code in different ways but none of them fixed the problem.
Of course, one option was to just use the old IDE, but the newer IDE produces smaller code, which would free up some program memory for new features. The program took up nearly all of the program memory under the old compiler prior to the addition of the 'Numbers Game' application.
Shift register control waveform with pre-1.5.x Arduino IDE:
Shift register control waveform with 1.5.x or later Arduino IDE:
The author eventually resorted to asking ChatGPT about the issue. It explained that the AVR only has instructions for shifting a single bit at a time (true) and that the older compiler apparently used a "partially unrolled strategy" which pre-selected the relevant byte from the 16-bit display data then shifted the bits in the relevant half whereas the newer one used the generic libcc helper which operated directly on the 16-bit register. The timing from the old compiler seemed just too consistent for that to be the case but the explanation of the behaviour of the new compiler was believable.
As AI has a tendency to make stuff up, the assembly language generated from the original code will be analysed to see if the AI's assessment is accurate.
But first, the AI suggested a new implementation for the display bit calculation which moved the shift outside the loop, meaning it no longer had to be executed for every column, and this resolved the problem.
uint16_t mask = (uint16_t)1 << rowcounter;
for (loopcounter = 0; loopcounter < cols; loopcounter++) {
uint8_t bit = (display[cols - loopcounter - 1] & mask) ? 0 : 1;
PORTD = (bit << 2) | 0x08; // data + clock high
asm("nop");
PORTD = PORTD & 0x04; // clock low, retain data
}
To extract the assembly language file, the avr-objdump program (avr-objdump -I. --all-headers -S file.elf) was used on the compiled binary (.elf file). To get the .elf file in Arduino 2.x, go to Sketch > Export Compiled Binary and this will generate a folder inside the project folder with the .elf file inside. In Arduino 1.0.x, verify your project then go into your temp folder, sort by date modified, and go into the newest folder to find the .elf file.
Old compiler assembly code:
5c02: 80 91 af 02 lds r24, 0x02AF ; 0x8002af <rowcounter>
5c06: 48 2f mov r20, r24
5c08: 50 e0 ldi r21, 0x00 ; 0
5c0a: 21 e0 ldi r18, 0x01 ; 1
5c0c: 30 e0 ldi r19, 0x00 ; 0
5c0e: 02 c0 rjmp .+4 ; 0x5c14 <__vector_11+0x136>
5c10: 22 0f add r18, r18
5c12: 33 1f adc r19, r19
5c14: 8a 95 dec r24
5c16: e2 f7 brpl .-8 ; 0x5c10 <__vector_11+0x132>
5c18: ef e6 ldi r30, 0x6F ; 111
5c1a: f3 e0 ldi r31, 0x03 ; 3
5c1c: 80 81 ld r24, Z
5c1e: 91 81 ldd r25, Z+1 ; 0x01
5c20: 82 23 and r24, r18
5c22: 93 23 and r25, r19
5c24: 89 2b or r24, r25
5c26: 11 f4 brne .+4 ; 0x5c2c <__vector_11+0x14e>
5c28: 84 e0 ldi r24, 0x04 ; 4
5c2a: 01 c0 rjmp .+2 ; 0x5c2e <__vector_11+0x150>
5c2c: 80 e0 ldi r24, 0x00 ; 0
5c2e: 8b b9 out 0x0b, r24 ; 11
New compiler assembly code:
4514: 20 91 fb 02 lds r18, 0x02FB ; 0x8002fb <rowcounter>
4518: 92 91 ld r25, -Z
451a: 82 91 ld r24, -Z
451c: 02 c0 rjmp .+4 ; 0x4522 <__vector_11+0x96>
451e: 95 95 asr r25
4520: 87 95 ror r24
4522: 2a 95 dec r18
4524: e2 f7 brpl .-8 ; 0x451e <__vector_11+0x92>
4526: 80 fd sbrc r24, 0
4528: 91 c0 rjmp .+290 ; 0x464c <__vector_11+0x1c0>
452a: 84 e0 ldi r24, 0x04 ; 4
452c: 8b b9 out 0x0b, r24 ; 11
As well as being completely different to the new compiler's code, it is noteworthy that the old compiler's output did not match ChatGPT's explanation of how the old compiler generated the code. In the old compiler's code, the ror instruction is nowhere to be seen, and the code much more closely resembles the mask-shifting code that the AI wrote instead. The left shift of the mask is implemented by adding it to itself (add r18, r18). The new compiler's code, which fits in with ChatGPT's explanation of how the newer compiler performs the right shift, takes up less program memory, though none of the various compiler optimisation options were able to make it work like the old compiler did.
More hardware
After assembly of the electronics was completed, it was discovered that some STP36NF06 MOSFETs had been purchased instead of STP36BF06L. The difference is the former is not logic level. It just about worked at 5V exactly but the slightest voltage drop would result in the rows with the logic level MOSFETs being brighter than the ones without. New logic level MOSFETs were eventually ordered to replace the non-logic level ones and the improvement in the consistency of the brightness of the display was noticeable.
Originally, the only mechanical strength in the hardware came from the stripboard and solder joints. Thin strips of stripboard were soldered along the bottom edge of the display and bolted to a piece of wood. This made it very weak mechanically, as the strips on the stripboard have little mechanical strength and will rip off quite easily. This all changed in 2025 when the author designed a 3D printed case for the display.
The case was designed in sections for the reasons of the display being far too big to fit on most 3D printers and the fact that much less filament would be wasted in the case of a design error being made as long as it was discovered before the whole case was printed.
Remote control
The serial port on the AVR microcontroller, usually used for programming it using the Arduino bootloader, was also used to enable the display to be controlled from a computer. This is where the text to display in the 'Serial Display' and 'Message Display' modes would be entered. The five buttons from the side of the display were also replicated here.
The discovery of these low cost Bluetooth TTL UART adapters resulted in another idea...
The open-source Bluetooth-spp-terminal was tested and the display responded as expected once the correct commands were entered.
This app was modified in Android Studio to add a set of buttons similar to the desktop application. This worked, and resulted in an app with all the same functions as the desktop version. No changes were needed to the display's firmware to add support for the mobile app.















