74HC595の7セグ表示がちらつく理由とQUAD7SHIFTのラッチ設計
目次
Arduino(小型マイコンボード)で温度計やカウンターを自作するとき、数値の表示に使う定番パーツが7セグメントLEDだ。
目覚まし時計や電子レンジのタイマーに付いている、棒状のLED7本で「0」〜「9」を描くあのディスプレイのこと。
ただ、Arduino Unoのデジタル出力ピンは14本しかない。
7セグLED4桁を直結しようとすると、セグメント用に7本+桁選択用に4本、ほかにドットや制御線も要るのですぐ足りなくなる。
そこで使うのが74HC595というシフトレジスタICで、データを1本の信号線からビットずつ送り込むと、IC側が8本の出力ピンに展開してくれる。
これを2個デイジーチェーン(数珠つなぎ)にして16本分の出力を確保し、4桁の7セグLEDを駆動する、というのがArduino界隈の定番構成だ。
ブレッドボードで最初の数字を出すところまではチュートリアルが大量にあるが、表示がなぜか薄くちらつく、という次の壁にぶつかる人も多い。
DEV Communityに、Why Most 74HC595 Display Drivers Flicker という記事が出ていた。
チュートリアル通りに書いた表示ドライバがなぜちらつくのか、という話だ。
面白いのは、QUAD7SHIFT作者が「ちらつき対策として設計したわけではない」と書いているところ。
74HC595のラッチの役割に沿って実装した結果、よくあるゴーストの条件を最初から作らなかった、という説明になっている。
問題は2回の転送ではなく、その間の状態
74HC595には、ざっくり言うと2つのレジスタがある。
| 部分 | 役割 |
|---|---|
| シフトレジスタ | シリアル入力をクロックごとに受け取る |
| ストレージレジスタ | ラッチ信号の立ち上がりで出力に反映する |
Nexperiaの74HC595データシートでも、シフトレジスタへの入力クロックと、ストレージレジスタへ転送するクロックは分かれている。
ここがこのICの便利なところで、データを流している途中の状態を出力ピンに見せず、最後にまとめて反映できる。
よくある7セグ表示では、1個目の74HC595でセグメント線、2個目で桁選択線を制御する。
たとえば「次に表示するセグメント」と「次に点灯する桁」を順に送って、最後にラッチを上げる。
見た目は正しそうだが、ソフトウェアの shiftOut() を2回呼ぶ実装だと、2回の呼び出しの間に割り込みやジッタが挟まる。
この時点の中途半端な状態が外部ピンへ出るかどうかは、ラッチと桁選択の扱い次第だ。
少なくとも、表示更新を「セグメント更新」と「桁選択更新」という別々の操作として扱うほど、境界を間違える余地が増える。
よくある実装
LATCH LOW -------------------------------- HIGH
DATA [新しいセグメント] [新しい桁選択]
↑
ここで割り込みが挟まると、
古い桁に新しいセグメントが乗る
実際にはストレージレジスタがまだ更新されていないので、厳密には「出力が即座に変わる」わけではない。
ただし表示ドライバ全体では、桁選択、ブランキング、次桁更新、ラッチの順序が少しでも崩れると、前の桁の残像や明るさムラとして見える。
カメラ越しや蛍光灯下で気づきやすいタイプの失敗だ。
QUAD7SHIFTは16bitを1つの表示状態として扱う
QUAD7SHIFT は、Arduino Uno/NanoやATtiny85向けの4桁7セグ表示ライブラリだ。
GitHubのREADMEでは、74HC595を使う4桁7セグ表示、共通アノード/共通カソード、数値や文字列表示、リフレッシュレート設定に対応すると説明されている。
原典で取り上げられている肝は、セグメントと桁選択を別々の「表示操作」として扱わないこと。
2個の74HC595に入れる16bitを1つの表示状態として作り、ラッチを下げたまま連続で流し、最後に一度だけラッチを上げる。
QUAD7SHIFT型の考え方
LATCH LOW -------------------------- HIGH
DATA [セグメント | 桁選択]
↑
16bit分が揃ってから出力へ反映
AVRではハードウェアSPIの16bit転送を使い、ATtiny85ではUSI経由で2バイトを連続転送する。
実装手段は違っても、出力へ見せる境界は同じだ。
「1バイト送った」「もう1バイト送った」ではなく、「1桁分の表示状態を作ってからラッチする」。
この差は、小さいようで大きい。
ソフトウェア上の関数呼び出しが2回でも、外部ピンから見た更新境界が1回なら、表示側には中間状態が出にくい。
マルチプレクス表示はラッチだけでは安定しない
7セグLEDを4桁まとめて点ける場合、多くはマルチプレクス表示になる。
全桁を同時に点けるのではなく、1桁ずつ高速に切り替えて、人間の目には同時点灯に見せる方式だ。
ここでは3種類の不安定さが混ざる。
| 不安定さ | 出方 | 主な原因 |
|---|---|---|
| ゴースト | 前の桁や隣の桁に薄く出る | ラッチ、桁選択、ブランキングの順序が甘い |
| 明るさムラ | 桁ごとに濃さが違う | 各桁の点灯時間が揃っていない |
| ちらつき | 全体が脈打つ | 更新周期が loop() の処理時間に引きずられる |
QUAD7SHIFTの記事では、表示更新を単に loop() に任せず、一定時間だけ表示更新ループを回す設計にも触れている。
センサー読み取りやシリアル出力が増えたときに、表示更新の頻度まで一緒に落ちると、ラッチが正しくてもちらつく。
ここはArduinoの小物を作るときに踏みやすい。
最初は数字だけ出ていて問題なく見える。
あとから温度センサー、ボタン処理、シリアルログ、通信待ちを足すと、急に表示が暗くなったり、桁ごとの明るさが揺れたりする。
配線より先に波形の境界を見る
この手のちらつきを見ると、まず電源容量、抵抗値、LEDの個体差を疑いたくなる。
もちろんそれも原因になり得るが、74HC595の2段構成では、先に見るべき信号がある。
flowchart TD
A["表示がちらつく"] --> B["RCLK/STCPの回数を見る"]
B --> C["1桁分の全データ後に1回だけか"]
C -->|違う| D["ラッチ境界を作り直す"]
C -->|合っている| E["桁選択の消灯期間を見る"]
E --> F["各桁の点灯時間を見る"]
F --> G["loop内の重い処理や割り込みを見る"]
ロジックアナライザがあるなら、DATA、SHCP、STCP、桁選択線を同時に見るのが早い。
最低限でも、STCPが「セグメント更新のたび」「桁選択更新のたび」に余計に動いていないかを見る。
カスケードした74HC595では、全ビットを送り終える前に出力へ反映すると、古い桁と新しいセグメントの組み合わせを作りやすい。
もうひとつは、桁を切り替える前に一瞬全桁を消すブランキングを入れるかどうか。
高速に切り替えているつもりでも、LEDとトランジスタの応答、配線容量、ソフトウェアの順序で残像が出る。
明るさを少し落としてでも、消灯を挟むほうが安定する場合がある。
QUAD7SHIFTをそのまま使えば終わり、とは限らない
QUAD7SHIFTはArduino Uno/NanoとATtiny系を主対象にしたライブラリで、Arduino Library Managerにも登録されている。
Arduino Librariesのページでは、カテゴリはDisplay、ライセンスはGPL 3.0、アーキテクチャはavrとされている。
つまり、ESP32やRP2040でそのまま使える万能ドライバというより、AVR上で74HC595の4桁7セグを手早く動かすための実装だ。
別マイコンで同じ考え方を移植するなら、見るのはライブラリ名ではなく更新単位になる。
セグメントと桁選択を1つの状態として組み立て、全ビットを送り終えるまで出力へ反映しない。
ラッチは1表示状態につき1回。
loop() の重い処理と表示更新周期は分けて、必要なら桁切り替え前にブランキングを入れる。
74HC595は古い定番ICなので、サンプルコードも大量にある。
ただ、表示が出るコードと、波形の境界がきれいなコードは別物だ。
QUAD7SHIFTの記事は、新しいライブラリ紹介というより、「データシート通りに抽象化すると、あとからちらつき対策として説明できる性質が自然に出る」という例として読むほうが使いやすい。
ソフトウェアの言葉で見る74HC595
ここまで「シフトレジスタ」「ラッチ」と当たり前に使ってきたが、ハードウェアに馴染みがないと何を指しているか分かりにくいと思う。
74HC595の主なピンをソフトウェアの概念で書き直すとこうなる。
| 74HC595のピン | 信号名 | ソフトウェアで言うと |
|---|---|---|
| SER (14番ピン) | シリアルデータ入力 | SPIのMOSI。1bitずつデータを送る線 |
| SRCLK (11番ピン) | シフトクロック | SPIのSCK。立ち上がりで1bit取り込む |
| RCLK (12番ピン) | ラッチクロック | DBのCOMMIT。内部バッファを出力に一括反映 |
| OE (13番ピン) | 出力イネーブル | マスタースイッチ。LOWで出力ON |
「シフトレジスタ」は、クロックが来るたびにデータを1ビットずつ隣のセルに押し出す回路だ。
固定長8のFIFOキューをイメージすればいい。
8回クロックを叩くと、8ビット分が内部に並ぶ。
「ラッチ」は、その並んだデータを出力ピンへ一括で反映する操作のこと。
DBのトランザクションに近くて、シフト操作がINSERT、ラッチがCOMMITに相当する。
COMMITするまで外のピンには変化が出ない。
Arduinoの shiftOut() はハードウェアSPIを使わない。
forループでGPIOピンを1本ずつ上げ下げして、SERとSRCLKを手動操作する関数だ。
Cのforループなので、途中で割り込みが入れば中断される。
QUAD7SHIFTがAVRのハードウェアSPIを使うのはこれが理由で、ハードウェアSPIはペリフェラル(CPUとは別のハードウェアブロック)が転送を担当するため、CPUが割り込みを処理している間も送信が止まらない。
ちなみにATtiny85で使われるUSI(Universal Serial Interface)は、AVRの廉価チップに載っている簡易版シリアル通信回路だ。
SPIほどの機能はないが、ハードウェア支援があるぶん、ソフトウェアbit-bangよりは安定する。
Raspberry PiやESP32でも起きるのか
74HC595のちらつきはIC側のインターフェースで起きる話なので、つなぐマイコンが何であっても条件は変わらない。
ただし、ちらつきの出やすさはプラットフォームで結構違う。
Raspberry Piは、Linuxの上でGPIOを操作する。
PythonやCで shiftOut() 相当のbit-bangを書いた場合、カーネルのタスクスケジューラがいつCPUを横取りするか予測できない。
Arduinoはベアメタル(OSなし)で動くから、割り込み以外でCPUが奪われることは基本ない。
ラズパイでソフトウェアbit-bangをやると、Arduinoより状況が悪くなる方向だ。
ただ、ラズパイにもハードウェアSPIはある。
/dev/spidev 経由で16ビット転送すれば、カーネルドライバがペリフェラル経由で送るため、ユーザープロセスがスケジュールアウトされても転送自体は止まらない。
QUAD7SHIFTと同じ「全ビット送り切ってからラッチ」を実現できる。
ESP32はFreeRTOS上で動いていて、WiFiやBLEのスタックが裏で定期的に割り込みをかける。
ESP32向けArduinoフレームワークで shiftOut() を使うと、WiFi処理のたびにbit-bangが中断される可能性がある。
ESP32もハードウェアSPIを2系統持っているので、SPI転送にすればちらつきは回避できる。
RP2040(Raspberry Pi Pico)はちょっと特殊で、PIO(Programmable I/O)という専用のステートマシンでGPIO操作を組める。
CPUとは完全に独立して動くので、bit-bangでもCPU側の割り込みに影響されない。
| プラットフォーム | ソフトウェアbit-bang | ハードウェア転送 |
|---|---|---|
| Arduino Uno/Nano (AVR) | shiftOut() で割り込み以外は安定 | QUAD7SHIFTが使用。SPI 16bit一括 |
| ATtiny85 | QUAD7SHIFTがUSI経由でサポート | フルSPIペリフェラルなし |
| Raspberry Pi | Linuxスケジューラで中断。最も不安定 | /dev/spidev 16bit転送で安定 |
| ESP32 | WiFi割り込みで中断される | SPIペリフェラルで安定 |
| RP2040 (Pico) | PIOなら安定、通常bit-bangは不安定 | ハードウェアSPIも使える |
74HC595に16ビット分を途切れず送り切れるかどうかが分岐点だ。
shiftOut() のようなソフトウェアbit-bangでは、CPUが途中で別の仕事に取られると転送が途切れる。
ハードウェアSPI、DMA、RP2040のPIOなど、CPUから独立して転送を完了する手段を使えば、ラッチ境界の問題は構造的に起きなくなる。
74HC595自体は40年以上前のICで、Arduinoの7セグ表示も入門ネタの定番なのに、ラッチ境界をまともに扱ったライブラリが最近まで出てこなかったのか、というのはちょっと意外だった。
枯れた技術なら解決策も枯れてるだろうと思い込んでいたけど、定番チュートリアルがコピーされ続けた結果、問題のほうも一緒にコピーされ続けていたんだろう。
16bitを一塊で送る、表示更新の周期をloop()から切り離す、みたいな制約の中でやりくりする感覚は、昔のソフトウェア開発でもあった。
パケットサイズに収めるためにデータを詰めたり、限られたメモリでバッファを回したり。
組み込みの世界で今も同じことをやってる人がいると思うと、妙に懐かしい。