前回は、シフトレジスタ 74HC595 を使って LED を点滅させる「LED Scanner」の回路を作りました。
今回は、この回路を動かすためのスケッチを書きましょう。
スケッチ
毎度の、俺自身のための備忘録的解説です。
- // LED Scanner v.1 2022.07.22 meyon
- const byte scanPatterns[] = {
- // Pattrens - Larson Scanner
- 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1e,
- 0x3c, 0x78, 0xf0, 0xe0, 0xc0, 0x80,
- 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0x78,
- 0x3c, 0x1e, 0x0f, 0x07, 0x03, 0x01
- };
LED の点滅パターンを規定している配列です。
16 進数で書いてあるのでわかりにくいですが、ビット列と考えて下さい。たとえば、0x3c であれば「00111100」、0 が消灯、1 が点灯です。これは、4 個の LED がバウンスする Laeson Scanner のパターンです。ちなみに、フェーディング効果は、ないです。
ここを、0〜255 の整数列とすると、2 進数をカウントするようになります。お好みでどうぞ。
- class LedScanner {
- private:
- const byte SER_Pin = 7;
- const byte SRCLK_Pin = 8;
- const byte RCLK_Pin = 9;
- const byte OE_Pin = 10;
- const byte SRCLR_Pin = 11;
- const byte setSpeed_Pin = A0;
- const byte setBrightness_Pin = A1;
クラス宣言とメンバ変数の定義です。
使用するピン番号を、定数として初期化しています。OE_Pin は PWM の出力できるピンにします。アナログ入力ピンは、速度、輝度調整用ボリュームの入力です。
- public:
- LedScanner() {
- pinMode(SRCLR_Pin, OUTPUT);
- pinMode(OE_Pin, OUTPUT);
- pinMode(SER_Pin, OUTPUT);
- pinMode(SRCLK_Pin, OUTPUT);
- pinMode(RCLK_Pin, OUTPUT);
- // Initialze the shift registert
- digitalWrite(RCLK_Pin, HIGH);
- delayMicroseconds(5);
- digitalWrite(RCLK_Pin, LOW);
- digitalWrite(SRCLR_Pin, HIGH);
- }
コンストラクタです。
23〜27 行は、ピンモードの設定です。SRCLR ピンは、シフトレジスタの初期化のためにローインピーダンスに設定します。OE ピンはアナログ出力なのでモードを指定する必要はないのですが、これも初期化のためにローインピーダンスに設定しています。PWM 出力には影響はないです。
ちなみに、明示的に設定していませんが、ローインピーダンス (OUTPUT) に設定すると、I/O 出力は Low になります。
30〜32 行で、クロック RCLK を送ってシフトレジスタの出力を初期化します。その後、33 行で SRCLR を High にし、シフトレジスタを稼働させます。これに連動して、LED 駆動電源が立ち上がります。
- void setLedBrightness() {
- int brightnessInput = analogRead(setBrightness_Pin);
- int brightness = map(brightnessInput, 0, 1023, 255, 0);
- analogWrite(OE_Pin, brightness);
- }
LED の輝度設定のためのメンバ関数です。
ボリュームからの電圧を読み取り、OE_Pin へ 255 (暗) 〜 0 (明) の PWM 信号を出力します。
- int setScanSpeed() {
- int speedInput = analogRead(setSpeed_Pin);
- int scanSpeed = map(speedInput, 0, 1023, 500, 10);
- return scanSpeed;
- }
点滅速度の設定のためのメンバ関数です。
ボリュームからの電圧を読み取り、LED の状態更新の間隔値 500 (遅) 〜 10 (速) を返します。
- void updateScan(int scanSpeed) {
- static int numberOfScanPatterns = sizeof(scanPatterns) / sizeof(byte);
- static unsigned long previousMillis = 0;
- static int counterOfScanPattern = 0;
- if(scanSpeed < millis() - previousMillis) {
- digitalWrite(RCLK_Pin, LOW);
- shiftOut(SER_Pin, SRCLK_Pin, MSBFIRST, scanPatterns[counterOfScanPattern]);
- digitalWrite(RCLK_Pin, HIGH);
- counterOfScanPattern++;
- if(numberOfScanPatterns <= counterOfScanPattern) counterOfScanPattern = 0;
- previousMillis = millis();
- }
- }
- };
LED の状態を更新するメンバ関数です。
49 行で、点滅パターンの配列の数を計算しています。50 行は前回更新時刻、51 行は配列のループカウンタです。いずれも static をつけていますので、関数が呼び出された最初の一回だけ実行されます。
シフトレジスタへのデータ出力は shiftOut() を使っています。データ出力後に RCLK を立ち上げます。
経過時間が、引数 scanSpeed で渡された更新間隔を超えたら (53 行)、データをシフトレジスタへ送り (54〜56 行)、カウンタを加算 (58〜59 行)、更新時刻を更新 (60 行) します。
- void setup() {
- }
ピンモードの設定などはコンストラクタが行ないますので、setup() では何もすることがありません。
- void loop() {
- static LedScanner scanner;
- scanner.setLedBrightness();
- int scanSpeed = scanner.setScanSpeed();
- scanner.updateScan(scanSpeed);
- }
69 行、loop() のなかでオブジェクトの生成を行なっています。
普通は loop() の外でやります? C++ なら main() のなかですよね。要するにスコープの問題なので、loop() 内のローカル変数って感じです。そのために、static をつけています。
LED の輝度調整して (71 行)、スキャンスピードの値を受け取り (72 行)、LED の状態を更新 (73 行) しています。
ブレッドボード
完成したブレッドボードのようすです。
LED 部分をですねぇ、バーにするとか、テープにするとか、チューブにするとか、ネットにするとか、いろいろあります。
シフトレジスタを増やして、LED の系統を増やすとともに、一組の LED の数も増やして、色ミックスして、ってやると、けっこう賑やかになることでしょう、たぶん。
製作後記
今回は、シフトレジスタの OE と SRCLR について確認しました。
この 2 つの入力は、直接の動作に関係がないので、考慮しないことが多いです。でも、電源投入時に、チカチカと無用な点滅するのを放置するってのは、やっぱり、ない。LED チカチカは無視すれば終わるけど、別の回路に影響を与えるようなことがあると問題です。
いろんなところに配慮した回路が作れるようになれたら、エクセレント、ですよね。
「コメント書かなくても、処理内容がわかる関数名にする。」
最近どこかのブログだったかで読んで、心に残った言葉。変数も、用途がわかる名前にすること。
リモートワークでソースコードの確認をしていたプログラマの息子が「変数 f ってなに?」とつぶやきました。file の f だそうです。「なら file と書けよ。」
for() 文のループカウンタ「i」ってなんでしょ? だれもが普通に使っているから i って書いちゃうけど、意味不明? FORTRAN の整数型表記に由来するとか。数学にも i はよくでてくる。あちこちに i やら j やら k やらでてきて、わけわかんなくなりませんか? 用途をちゃんと書けって。
下手くそなりにも、クラスでスケッチが書けるようになってきました。あわせてそんなところにも配慮できたら、エクセレント、ですよね。
などと、思う、今日この頃です。