以前、バーLEDアレイを使って流れるようなライトを作りました。
この時の実装ではESP32-S3の GPIOポートが10個使われました。 GPIOポートは多ければ多いほど、ESP32-S3に接続できる周辺機器の数が増えるため、貴重な資源です。では、GPIOの数を節約して流れるライトを作成するにはどうしたら良いでしょうか?その答えの一つがシフトレジスター「74HC595」です。
74HC595について
74HC595チップは、シリアルデータ(直列データ)をパラレルデータ(並列データ)に変換するのに用いられます。74HC595チップは、1バイトのシリアルデータを8ビットに変換し、各ビットに対応する信号レベルを8つの出力ポートへそれぞれ出力することができます。この特徴から、74HC595チップはESP32-S3のIOポートを拡張するのに活用できます。74HC595チップの8つの出力ポートを制御するには、最低でも3つのGPIOポートが必要となります。限られた数の出力ポートを効率的に活用し、LEDマトリックスやモーター制御など、多くの出力信号が必要な場面で活躍します。
74HC595の主な機能
- シリアルデータ(1ビットずつ)をパラレルデータ(8ビット同時)に変換
- 8つの出力ポート(Q0~Q7)で制御
- データ保持機能(ラッチ機能)搭載
- 3つの制御ピンで最大64個の74HC595をカスケード接続可能
74HC595の動作の仕組み
- データ入力: シリアルデータ(SRCLKピン)とデータ(SDINピン)を用いて、1ビットずつシリアルデータを入力します。
- シフト: 各ビットは、SH_CPピン(シフトクロック)のLOWからHIGHへの遷移(上昇エッジ、立ち上がり、クロックとも)で次のレジスタ段へシフトされます。
- ラッチ: ST_CP(ラッチクロック)の上昇エッジで、現在のシフトレジスタのデータが保持されます。このラッチされたデータが出力ポート(Q0~Q7)に出力されます。
74HC595の利用例
- LEDマトリックス制御: 74HC595を複数個カスケード接続することで、64×64個など大規模なLEDマトリックスを制御できます。
- モーター制御: 8つのステッピングモーターを独立制御したり、複数のDCモーターを同時に駆動したりできます。
- センサーデータ出力: 温度センサーや湿度センサーなどのアナログセンサーデータを、8ビットADCでデジタル化し、74HC595を使って出力できます。
74HC595のデメリット
- シリアルデータ入力とラッチ操作が必要なため、マイコン側でプログラムによる制御が必要
- 高速なデータ転送には不向き(高速なデータ転送が必要な場合は、高速動作が可能な74HC595Hなどの種類を選択する必要があります)
74HC595のピンアサイン
Pin name | GPIO number | 説明 |
---|---|---|
Q0-Q7 | 15, 1-7 | パラレルデータのアウトプット |
VCC | 16 | 電源のプラス極、電圧は2~6Vです。 |
GND | 8 | 電源のマイナス極 |
DS | 14 | シリアルデータのインプット |
OE | 13 | 出力イネーブル: このピンがハイレベルの場合、Q0-Q7はハイインピーダンス状態になります。 このピンがローレベルの場合、Q0-Q7は出力モードになります。 |
ST_CP | 12 | STorage register Clock Pin/ラッチピン:このポートの状態が立ち上がりを検知するとパラレルデータをアウトプットします。 |
SH_CP | 11 | シリアルシフトクロック:このポートの状態が立ち上がりを検知するとシリアルデータ入力レジスタをシフトします。 |
MR | 10 | シフトレジスタクリア:このピンがローレベルの場合、シフトレジスタの内容がクリアされます。 |
Q7′ | 9 | シリアルデータ出力:さらに多くの74HC595を直列接続することができます。 |
より詳細な情報はデータシートを確認して下さい。みんな大好き秋月さんのサイトに同等品のデータシートがあります。
回路図
接続図
動作イメージ
コード
/**********************************************************************
Filename : FlowingLight02
Description : Use 74HC575 to drive the ledbar to display the flowing light.
Auther : www.freenove.com
Modification: 2022/10/24
**********************************************************************/
int latchPin = 13; // Pin connected to ST_CP of 74HC595(Pin12)
int clockPin = 14; // Pin connected to SH_CP of 74HC595(Pin11)
int dataPin = 12; // Pin connected to DS of 74HC595(Pin14)
void setup() {
// set pins to output
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
}
void loop() {
// Define a one-byte variable to use the 8 bits to represent the state of 8 LEDs of LED bar graph.
// This variable is assigned to 0x01, that is binary 00000001, which indicates only one LED light on.
byte x = 0x01; // 0b 0000 0001
for (int j = 0; j < 8; j++) { // Let led light up from right to left
writeTo595(LSBFIRST, x);
x <<= 1; // make the variable move one bit to left once, then the bright LED move one step to the left once.
delay(50);
}
delay(100);
x = 0x80; //0b 1000 0000
for (int j = 0; j < 8; j++) { // Let led light up from left to right
writeTo595(LSBFIRST, x);
x >>= 1;
delay(50);
}
delay(100);
}
void writeTo595(int order, byte _data ) {
// Output low level to latchPin
digitalWrite(latchPin, LOW);
// Send serial data to 74HC595
shiftOut(dataPin, clockPin, order, _data);
// Output high level to latchPin, and 74HC595 will update the data to the parallel output port.
digitalWrite(latchPin, HIGH);
}
来ました!久しぶりにプログラムらしいプログラムですね!
ピンアサイン、setup()あたりは特に問題ないですね。
22行目でLEDアレイの光り方のパターンをxというバイト型変数に格納しています。『0x』は16進数を意味しています。『0b』は2進数を意味しています。1が光って0が消えている状態という前提でシリアルデータを用意しています。
byte x = 0x01; // 0b 0000 0001
コメントをつけるくらいなら、最初から2進数表記で定義したほうが可読性が良いのではないかと思います。以下のように修正できますね。
byte x = 0b00000001;
23~27行目は8個のLEDの端から端まで光る場所を(xのビット列の中の1の場所を)ずらしながらwriteTo595()関数を実行しています。30~37行目は逆向きの動作ですね。
for (int j = 0; j < 8; j++) { // Let led light up from right to left
writeTo595(LSBFIRST, x);
x <<= 1; // make the variable move one bit to left once, then the bright LED move one step to the left once.
delay(50);
}
いよいよシフトレジスターのメイン処理です!
38行目からのwriteTo595()関数を見てみましょう。
orderで受け取っているのはLSBFIRSTというマクロ定義です。実態としては『0』として定義されています。LSBFIRSTは『最下位ビットから送る』という意味です。最上位ビットから送る時は『MSBFIRST』です。shiftOut()関数の使い方という程度の理解で十分です。
40行目でラッチピンをLOWにしています。ラッチピンをHIGHにするとパラレルデータをアウトプットするという大事なピンですね。shiftOut()する前にLOWにしておくという所作です。
42行目で実際のシフトアウト処理ですね。これでシフトレジスターにデータが送られます。ピッピッピッピッピッピッピッピッ と、8ビット分がシリアル(直列)に送られます。データピンに正しくデータが届いたらクロックピンが1度反転します。シフトレジスターを直列に繋いでいる場合は、この動作によってデータが順送りされます。
44行目でいよいよラッチピンをHIGHにして、せーのでパラレルにデータをアウトプットします。
void writeTo595(int order, byte _data ) {
// Output low level to latchPin
digitalWrite(latchPin, LOW);
// Send serial data to 74HC595
shiftOut(dataPin, clockPin, order, _data);
// Output high level to latchPin, and 74HC595 will update the data to the parallel output port.
digitalWrite(latchPin, HIGH);
}
そしてここに来て唐突にビットシフトについての説明が書いてありました。もっと早く書いてほしかったですね。
シフトしたときの動作と、シフト結果を変数に戻すときの書き方について書いてありました。符号を一旦無視して、新しく追加されるビットには0が入るということ、『x=x>>1』と『x>>=1』が同じ意味だということが書いてあります。
今回は久しぶりにプログラミングをした感覚がありましたね。
次回は7セグメントディスプレイをシフトレジスターで制御してみましょう。
コメント