74HC595を使ってArduinoの出力ピンを拡張する方法(4)

このページをスマホなどでご覧になる場合は、画面を横長にする方が読みやすくなります。
目次へ  前のページへ (1) (2) (3) (4) (5) (6) (7) (8) 次のページへ
2019年09月18日 公開。

4-1-3.digitalWrite関数を使って1つの74HC595を制御するスケッチを作る方法(LSBファースト)

いよいよ、74HC595を制御するスケッチを作る方法の説明をします。

この項では、digitalWrite関数を使って、Arduinoにつながった1つの74HC595に、LSBファーストでデータを送信するスケッチを作る事にします。

まず74HC595の制御の方法の説明をして、最後に制御スケッチを示しますが、説明を飛ばして手っ取り早くスケッチをご覧になりたい方は、リスト11をご覧ください。

前のページで説明しましたが、図12の回路を使って、LSBファーストでデータを送信するには、図16のタイミングチャートに従って、74HC595に制御信号を送ればいいのでした。

図12(再掲)、Arduinoに1つの74HC595を接続する場合の配線図
↑ 画像をクリックすると拡大
図12(再掲)、Arduinoに1つの74HC595を接続する場合の配線図
図16(再掲)、1つの74HC595を制御する場合のタイミングチャート(LSBファースト)
↑ 画像をクリックすると拡大
図16(再掲)、1つの74HC595を制御する場合のタイミングチャート(LSBファースト)

Arduinoから74HC595に送るべき制御信号は、SER信号、SRCLK信号、およびRCLK信号の3つです。これらの信号をそれぞれ、Arduinoのデジタル出力ピン(digitalWrite関数で信号を出力できるピン)に割り当てる必要があります。

Arduino Unoの場合は0番~13番ピンの、デジタル出力専用ピンと、A0~A5のアナログ入力・デジタル出力兼用ピンの中から3つピンを選んで、SER信号、SRCLK信号、およびRCLK信号に割り当てる事になります。

スケッチ中で、ピンの番号を直書きすると、割り当てるピンの番号に変更があった場合に、スケッチの書き換えが大変になりますから、3つの信号に割り当てるピン番号を、定数として宣言します。

SER信号、SRCLK信号、およびRCLK信号にに割り当てるピン番号の定数の名前を、それぞれSERSRCLK、およびRCLKとし、SER信号には2番ピン、SRCLK信号には3番ピン、RCLK信号には4番ピンを割り当てるとすると、定数の宣言は、例えばリスト1の様になります。

リスト1、74HC595の制御に使うピンの宣言(const int型)COPY
const int SER  =2; // SER制御用にするピンの番号を宣言
const int SRCLK=3; // SRCLK制御用にするピンの番号を宣言
const int RCLK =4; // RCLK制御用にするピンの番号を宣言

リスト1ではconst int型の型付き定数にしましたが、もちろん#define指令を使ってリスト2の様に宣言しても結構です。

リスト2、74HC595の制御に使うピンの宣言(#define指令を使用)COPY
#define SER   2 // SER制御用にするピンの番号を宣言
#define SRCLK 3 // SRCLK制御用にするピンの番号を宣言
#define RCLK  4 // RCLK制御用にするピンの番号を宣言

次に、74HC595の制御に使用する3つのピンと74HC595自体の初期化を行う関数(initHc595と命名する)を作ってみます。

initHc595関数で行うべき事は3つあります。

まず、74HC595の制御に使う3つのI/Oピンを、pinMode関数を使って出力モードに設定する必要があります。(リスト3参照)

リスト3、74HC595の制御に使う3つのI/Oピンを出力モードにするコードCOPY
  // 74HC595制御用ピンを出力モードにする
  pinMode(SER,OUTPUT);
  pinMode(SRCLK,OUTPUT);
  pinMode(RCLK,OUTPUT);

次に、SRCLK信号用のピンとRCLK信号用のピンの出力をLに初期化します。(リスト4参照) というのは、図16のタイミングチャートで74HC595を制御する場合は、データを送信していないアイドル状態では、SRCLK信号とRCLK信号はLになっている前提だからです。

リスト4、SRCLK信号とRCLK信号の初期値をLにするコードCOPY
  // SRCLKとRCLKをLOWにする(これらのピンはデータを送信していない時はLOWであるべき)
  digitalWrite(SRCLK,LOW);
  digitalWrite(RCLK,LOW);

最後に、74HC595のパラレル出力ピン(QA端子~QH端子)を全てLにします。(リスト5参照)

リスト5、74HC595のパラレル出力ピンを全てLにするコードCOPY
  // 0を送信して74HC595のパラレル出力ピンを全て0にする(74HC595の初期状態を気にしない場合は省略可能)
  outputOneByteLsbFirst(0);

74HC595のパラレル出力ピンを全てLにする理由は、電源投入直後のパラレル出力ピンの電圧が不定(LになるかHになるか分からない)になるからです。別にパラレル出力ピンをLに初期化しなくてもいい場合は、リスト5の処理は不要です。

なお、パラレル出力ピンを初期化するには、実際に74HC595へ8ビットのデータを送信する必要があります。データを送信する関数はまだ作っていませんが、outputOneByteLsbFirst関数という名前にしました。この関数に符号なし8ビット整数を引数として渡すと、74HC595にその引数を送信しますが、今回は0を引数とする事で、74HC595のパラレル出力ピンを全てLに初期化します。

リスト3~リスト5の処理をまとめてinitHc595関数にすると、その全体像はリスト6の様になります。

リスト6initHc595関数(LSBファースト)COPY
// 74HC595の制御用ピンおよび74HC595を初期化する
void initHc595()
{
  // 74HC595制御用ピンを出力モードにする
  pinMode(SER,OUTPUT);
  pinMode(SRCLK,OUTPUT);
  pinMode(RCLK,OUTPUT);

  // SRCLKとRCLKをLOWにする(これらのピンはデータを送信していない時はLOWであるべき)
  digitalWrite(SRCLK,LOW);
  digitalWrite(RCLK,LOW);

  // 0を送信して74HC595のパラレル出力ピンを全て0にする(74HC595の初期状態を気にしない場合は省略可能)
  outputOneByteLsbFirst(0);
} // initHc595

それでは次に、74HC595に実際にデータを送信するoutputOneByteLsbFirst関数を作ります。

関数内部で、74HC595に送信するデータのビット数(8ビット)を定数宣言しておきます。(リスト7参照) コード内でビット数を書く部分に数字の8を書いてもいいのですが、定数宣言しておくと、後で複数の74HC595を使って16ビット以上のデータを送信する場合に、コードの変更が少なくなるのです。

リスト7、送信するデータのビット数の宣言COPY
  const int DATA_BITS=8; // 送信するデータのビット数

次に、SER信号にデータを送信しながら、SRCLK信号にクロックを送信するループを記述します。(リスト8参照) 前提として、関数にuint8_t型(符号なし8ビット整数)のvalという仮引数が渡されるものとします。

リスト8、SER信号にデータを送信しながらSRCLK信号にクロックを送信するループ(LSBファースト)COPY
  // シフトレジスタにデータを送信
  for(int i=0; i<DATA_BITS; i++) {
    digitalWrite(SER,(val&1) ? HIGH : LOW); // SERにvalの最下位ビットを出力
    digitalWrite(SRCLK,HIGH); // SRCLKを立ち上げる(この時74HC595がシフト動作)
    digitalWrite(SRCLK,LOW ); // SRCLKを立ち下げる(次にSRCLKを立ち上げるため)
    val>>=1; // valを1ビットだけ右シフト
  } // for i

val最下位ビットを取り出すには

val&1

と、valと1のANDを取ります。この式は、最下位ビットが1なら1を返し、最下位ビットが0なら0を返します。

digitalWrite関数に渡す第2引数はLOWまたはHIGHのいずれかの値ですので、次の3項演算子で、valの最下位ビットをLOWまたはHIGHに変換します。

(val&1) ? HIGH : LOW

参考:LOWという定数が0と宣言されており、HIGHという定数が1と宣言されている事を考えれば、val&1という式と、(val&1) ? HIGH : LOWという式は、実は同じ値を返します。よって、本当は3項演算子をわざわざ使う必要はありません。

この事を踏まえて、valの最下位ビットをSER信号に出力するには、

digitalWrite(SER,(val&1) ? HIGH : LOW);

と記述します。

その後、

digitalWrite(SRCLK,HIGH);
digitalWrite(SRCLK,LOW );

と記述して、SRCLK信号に1クロック送信します。

ループの最後には、

val>>=1;

と記述して、ループの次の回に備えてvalを1ビットだけ右シフトします。

これらの処理をforループでDATA_BITS回(8回)回せば、リスト8の様になり、8ビット分のSER信号とSRCLK信号の波形を生成できます。

リスト8の処理で8ビット分のデータを74HC595内のシフトレジスタに送信できたら、次にリスト9の様にRCLK信号に1クロック送信して、ストレージレジスタに信号を転送します。

リスト9、RCLK信号に1クロック送信するコードCOPY
  // ストレージレジスタにデータを転送
  digitalWrite(RCLK,HIGH); // RCLKを立ち上げる(この時に74HC595のシフトレジスタのパラレル出力がストレージレジスタに転送され、74HC595のしパラレル出力端子にデータが出力される)
  digitalWrite(RCLK,LOW ); // RCLKを立ち下げる(次にRCLKを立ち上げるため)

以上の説明を踏まえて、outputOneByteLsbFirst関数を作ると、その全体像はリスト10の様になります。

リスト10outputOneByteLsbFirst関数COPY
// LSBからMSBの順に74HC595に1バイトを送信する
void outputOneByteLsbFirst(uint8_t val)
{
  const int DATA_BITS=8; // 送信するデータのビット数

  // シフトレジスタにデータを送信
  for(int i=0; i<DATA_BITS; i++) {
    digitalWrite(SER,(val&1) ? HIGH : LOW); // SERにvalの最下位ビットを出力
    digitalWrite(SRCLK,HIGH); // SRCLKを立ち上げる(この時74HC595がシフト動作)
    digitalWrite(SRCLK,LOW ); // SRCLKを立ち下げる(次にSRCLKを立ち上げるため)
    val>>=1; // valを1ビットだけ右シフト
  } // for i
  
  // ストレージレジスタにデータを転送
  digitalWrite(RCLK,HIGH); // RCLKを立ち上げる(この時に74HC595のシフトレジスタのパラレル出力がストレージレジスタに転送され、74HC595のしパラレル出力端子にデータが出力される)
  digitalWrite(RCLK,LOW ); // RCLKを立ち下げる(次にRCLKを立ち上げるため)
} // outputOnebyteLsbFirst

リスト2の定数宣言と、リスト6initHc595関数と、リスト10のoutputOneByteLsbFirst関数の3つで、74HC595にデータを送信する準備が出来ました。

setup関数loop関数を実装して、実際に74HC595に何かのデータを送る様にしたテストスケッチを、リスト11に示します。

リスト11、1つの74HC595を制御するテストスケッチ(LSBファースト)COPY
// oneByteLsbFirst.ino

const int SER  =2; // SER制御用にするピンの番号を宣言
const int SRCLK=3; // SRCLK制御用にするピンの番号を宣言
const int RCLK =4; // RCLK制御用にするピンの番号を宣言

void setup()
{
  initHc595();
} // setup

void loop() {
  static uint8_t cnt=0; // 74HC595へ出力する値を格納するカウンタ変数。uint8_tは8ビット符号なし整数。staticを付けるとloop関数を抜けても値を保持し続ける

  outputOneByteLsbFirst(cnt++); // cntの値を74HC595に送信してからcntをカウントアップ
  delay(100); // 0.1秒待つ
} // loop

// 74HC595の制御用ピンおよび74HC595を初期化する
void initHc595()
{
  // 74HC595制御用ピンを出力モードにする
  pinMode(SER,OUTPUT);
  pinMode(SRCLK,OUTPUT);
  pinMode(RCLK,OUTPUT);

  // SRCLKとRCLKをLOWにする(これらのピンはデータを送信していない時はLOWであるべき)
  digitalWrite(SRCLK,LOW);
  digitalWrite(RCLK,LOW);

  // 0を送信して74HC595のパラレル出力ピンを全て0にする(74HC595の初期状態を気にしない場合は省略可能)
  outputOneByteLsbFirst(0);
} // initHc595

// LSBからMSBの順に74HC595に1バイトを送信する
void outputOneByteLsbFirst(uint8_t val)
{
  const int DATA_BITS=8; // 送信するデータのビット数

  // シフトレジスタにデータを送信
  for(int i=0; i<DATA_BITS; i++) {
    digitalWrite(SER,(val&1) ? HIGH : LOW); // SERにvalの最下位ビットを出力
    digitalWrite(SRCLK,HIGH); // SRCLKを立ち上げる(この時74HC595がシフト動作)
    digitalWrite(SRCLK,LOW ); // SRCLKを立ち下げる(次にSRCLKを立ち上げるため)
    val>>=1; // valを1ビットだけ右シフト
  } // for i
  
  // ストレージレジスタにデータを転送
  digitalWrite(RCLK,HIGH); // RCLKを立ち上げる(この時に74HC595のシフトレジスタのパラレル出力がストレージレジスタに転送され、74HC595のしパラレル出力端子にデータが出力される)
  digitalWrite(RCLK,LOW ); // RCLKを立ち下げる(次にRCLKを立ち上げるため)
} // outputOnebyteLsbFirst

このスケッチでは、setup関数の中でinitHc595関数を呼び出して初期化処理をしています。

そして、loop関数では、cntという、0に初期化されたstatic uint8_t型の変数をoutputOneByteLsbFirst関数に渡して、その値を74HC595に送信します。

次にcnt変数をカウントアップして、delay関数で0.1秒待ちます。

以上の処理で、QA端子~QH端子には、0.1秒ごとに0、1、2、…と、1ずつ増えたビットパターンが表示されます。(255を表示したら0に戻ります)

図15の回路図の基板をArduino Unoに接続してリスト11のスケッチを実行した様子を撮影したのが写真6です。

図15(再掲)、74HC595の動作確認用の基板の回路図
↑ 画像をクリックするとPDFファイルで表示
図15(再掲)、74HC595の動作確認用の基板の回路図
写真6(再掲)、図15の回路をユニバーサル基板で組み立ててArduinoに接続して動作させた様子
↑ 画像をクリックすると拡大
写真6(再掲)、図15の回路をユニバーサル基板で組み立ててArduinoに接続して動作させた様子

基板上のピンヘッダから信号を取り出し、ZEROPLUS製のLAP-C(16064)というロジックアナライザで、波形を測定してみました。測定した波形を図19に、測定風景を写真10に示します。

図19、Arduino Unoでリスト11のスケッチを動作させた場合の波形
図19、Arduino Unoでリスト11のスケッチを動作させた場合の波形
ロジックアナライザの画面をハードコピーした物です。ただし、水色のLHの文字や、上側中央やや右寄りの水色の長方形は、筆者が書き加えた物です。
写真10、ロジックアナライザによる波形測定の風景
↑ 画像をクリックすると拡大
写真10、ロジックアナライザによる波形測定の風景
11チャネルの波形の測定をすると、この様な状態になります。ブレッドボードを使ってもロジックアナライザで波形の測定をできますが、ユニバーサル基板にロジックアナライザ用のピンヘッダを設けて測定する方が、測定中のトラブルがかなり減ります。

図19のSER信号を見ると、8クロックのSRCLK信号に同期して、LLLLLHLHの順にデータを送信している事が分かります。LSBファーストで送信していますから、これらのデータを2進数で表わすと、逆順の10100000となります。(この記事では、2進数の数字を緑色の文字で表す事にします)

次にQA~QHの信号の波形を見ると、これらの信号はQA信号がMSBでQH信号がLSBですから、RCLK信号の立ち上がりの前後で10011111から10100000に変化している事が分かります。

この様に、SER信号で送ったデータが、RCLK信号の立ち上がりと同時にQA~QHの信号に出力されている様子が分かります。また、リスト11のスケッチは、送信するデータを1ずつカウントアップする物ですが、実際に10011111の次には、1だけ大きい10100000が出力されている事が確認できます。

SER信号にLSBを出力し始めてからRCLK信号を立ち下げる所までが、データ送信の一連の処理になりますが、図19より、この処理には107μsかかっている事が分かりました。(図19の上側中央やや右寄りの水色の長方形の中の、"A - T = 107.008us"と表示されている部分に注目してください)

次のページでは、MSBファーストでデータを74HC595に送信するスケッチについて説明します。

目次へ  前のページへ (1) (2) (3) (4) (5) (6) (7) (8) 次のページへ

このページで使われている用語の解説

関連ページ

Arduino 電子工作
このサイトの記事が本になりました。
書名:Arduino 電子工作
ISBN:978-4-7775-1941-5
工学社の書籍の内容の紹介ページ
本のカバーの写真か書名をクリックすると、Amazonの書籍購入ページに移動します。