惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园_首页
Engineering at Meta
Engineering at Meta
量子位
A
About on SuperTechFans
阮一峰的网络日志
阮一峰的网络日志
Recent Announcements
Recent Announcements
博客园 - 司徒正美
V
Visual Studio Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
The GitHub Blog
The GitHub Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
F
Fortinet All Blogs
Martin Fowler
Martin Fowler
腾讯CDC
Jina AI
Jina AI
C
Check Point Blog
H
Help Net Security
罗磊的独立博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
V2EX
爱范儿
爱范儿
I
InfoQ

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Why Most 74HC595 Display Drivers Flicker (And How QUAD7SH...
Alex Rosito · 2026-04-29 · via DEV Community

A Chinese technical site cited QUAD7SHIFT as a flicker-free reference implementation. I didn't design it to solve flicker. I just built it correctly.


The Problem Nobody Talks About

Search for "74HC595 7-segment display Arduino" and you'll find dozens of tutorials. Most of them work — in the sense that the display shows numbers. But watch closely under fluorescent lighting, or point a camera at it, and you'll see it: a faint but persistent flicker, sometimes a ghost of the previous digit bleeding into the next.

This isn't a power supply issue. It isn't a loose connection. It's a structural problem in how most drivers handle the latch.


How the Naive Pattern Works

The typical approach looks something like this:

// Naive pattern — found in most tutorials
void showDigit(uint8_t segments, uint8_t digitSelect) {
    digitalWrite(LATCH_PIN, LOW);
    shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, segments);   // first transfer
    shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, digitSelect); // second transfer
    digitalWrite(LATCH_PIN, HIGH);
}

Enter fullscreen mode Exit fullscreen mode

Two separate shiftOut() calls, then a single latch pulse. Looks fine. The problem is what happens between those two calls.

When the first shiftOut() completes, the shift register holds the new segment data — but the latch hasn't fired yet. At that exact moment, the previous digit select is still active in the storage register. If anything delays the second shiftOut() — an interrupt, a timer ISR, a millisecond of jitter — the display briefly shows the new segment pattern on the wrong digit.

That's ghosting. And even without interrupts, the asymmetry in timing between two sequential software calls is enough to produce uneven brightness across digits.


The Timing Problem Visualized

Naive approach — two 74HC595 chips, sequential transfers:

Time →

LATCH:   ___LOW_________________________HIGH___
SRCLK:        ↑↑↑↑↑↑↑↑  ↑↑↑↑↑↑↑↑
DATA:    [segments byte ] [digit select byte ]
                         ↑
                    HERE: shift reg 1 has new segments.
                    Storage reg still holds OLD digit select.
                    Any interrupt here = ghost on wrong digit.

Enter fullscreen mode Exit fullscreen mode

QUAD7SHIFT — single 16-bit atomic transfer:

Time →

LATCH:   ___LOW___________________HIGH___
SRCLK:        ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
DATA:    [segments byte | digit select byte]
                                 ↑
                    Both bytes shift as one unit.
                    Latch fires once. No intermediate state.

Enter fullscreen mode Exit fullscreen mode


What QUAD7SHIFT Does Instead

The hardware is two 74HC595s in cascade — one controls the segment lines (a–g, dp), the other controls which digit is active (digit select bits). The key is that both chips share a single latch line (RCLK).

The entire transfer is packed into a single uint16_t:

void QUAD7SHIFT::transferDigit(uint16_t result) {
    writePin(LATCHPIN, LOW);

    #if defined(__AVR_ATmega328P__) || (__AVR_ATmega168__)
        SPI.beginTransaction(SPISettings(1000000, LSBFIRST, SPI_MODE0));
        SPI.transfer16(result);   // both bytes in one SPI transaction
        SPI.endTransaction();
    #endif

    writePin(LATCHPIN, HIGH);     // single latch pulse — both registers update simultaneously
}

Enter fullscreen mode Exit fullscreen mode

SPI.transfer16() shifts all 16 bits in one continuous hardware transaction. The latch fires exactly once. There is no intermediate state where one register has new data and the other doesn't.

For ATtiny85, which lacks hardware SPI and uses the USI (Universal Serial Interface), the same principle applies — two usiTransferByte() calls back to back with the latch held LOW throughout:

void QUAD7SHIFT::transferDigit(uint16_t result) {
    writePin(LATCHPIN, LOW);
    usiTransferByte(reverseBits(result & 0xFF));         // segments
    usiTransferByte(reverseBits((result >> 8) & 0xFF));  // digit select
    writePin(LATCHPIN, HIGH);
}

Enter fullscreen mode Exit fullscreen mode

The latch is LOW for the entire duration of both transfers. The storage registers don't see anything until the latch goes HIGH — at which point both update atomically.


Why This Works: The 74HC595 Latch Mechanism

The 74HC595 has two internal registers:

  • Shift register — receives serial data on each SRCLK rising edge
  • Storage register — holds the parallel output, only updates on RCLK rising edge

When LATCH (RCLK) is LOW, data shifting into the shift register has zero effect on the outputs. The outputs are frozen. You can shift as much data as you want — through one chip, through ten chips in cascade — and nothing changes on the output pins until you pulse the latch HIGH.

This is the mechanism. The naive pattern doesn't violate it, but it does use it sloppily — latching between two logical operations instead of after both are complete.

QUAD7SHIFT latches once, after all 16 bits are in place.


The Multiplexing Loop

Eliminating ghosting on each individual transfer is necessary but not sufficient. You also need a stable refresh cycle.

The naive approach typically calls the display function from loop(), which means refresh rate is coupled to whatever else loop() is doing. If you add a sensor read or a serial print, the display dims or flickers because some digits get fewer refresh cycles per second.

QUAD7SHIFT decouples refresh rate from loop() timing:

void QUAD7SHIFT::printNumber(uint16_t number, uint8_t decimalPointPosition) {
    // ... digit extraction ...

    unsigned long endtime = millis() + _refreshRate;
    uint8_t i = 0;

    while (millis() <= endtime) {
        printDigit(digitsToPrint[i], i, i == decimalPointPosition);
        i = (i + 1) % numberOfDigitsToDisplay;
    }
}

Enter fullscreen mode Exit fullscreen mode

Each call to print() runs the multiplexing loop for exactly _refreshRate milliseconds, cycling through all digits at a consistent rate regardless of what happened before or after. The display gets a guaranteed time slice.


See It Running


Comparison Summary

Naive pattern QUAD7SHIFT
Latch pulses per digit update 1 (but between two transfers) 1 (after both transfers complete)
Intermediate state where ghost can appear Yes No
Refresh rate coupled to loop() Yes No — fixed time slice
Works on ATtiny85 Depends on implementation Yes (USI, bit-reversal handled)
Hardware SPI on AVR Sometimes Yes (SPI.transfer16)

I Didn't Design This to Solve Flicker

Worth being explicit about this: I didn't build QUAD7SHIFT by identifying the ghost problem and engineering a solution. I built it by reading the 74HC595 datasheet, understanding that the latch is what separates "data in transit" from "data on outputs," and constructing the transfer accordingly.

The flicker never appeared because the design never created the conditions for it.

A Chinese technical site (CSDN, April 2026) independently analyzed cascaded 74HC595 display drivers and cited QUAD7SHIFT as a reference implementation for eliminating flicker — specifically noting its "dynamic scan algorithm." They found the pattern by looking for the problem. I found it by not creating the problem in the first place.

Both paths lead to the same place. But I think the second one is more instructive: correct hardware abstractions tend to be correct in ways you didn't plan for.


The Library

QUAD7SHIFT is available on GitHub and in the Arduino Library Manager.

If this was useful, there's a Buy Me a Coffee link on the GitHub page.


Alex Rosito — self-taught electronics engineer. ATtiny85 · ESP32 · KiCad · C++