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

このページをスマホなどでご覧になる場合は、画面を横長にする方が読みやすくなります。
このエントリーをはてなブックマークに追加
目次へ  前のページへ (1) (2) (3) (4) (5) (6) (7) (8) 次のページへ
2019年10月02日 公開。

4-1-6.shiftOut関数を使って1つの74HC595を制御するスケッチを作る方法

今までは、digitalWrite関数のみで信号線の制御をしていましたが、今度はshiftOut関数も使ってみましょう。

広告
4-1-6-1.shiftOut関数とは

shiftOut関数はArduinoの標準関数のひとつで、8ビットの符号なし整数の引数を、クロック同期方式のシリアルバスに出力する働きをします。

データ出力に使うピンとクロック出力に使うピンを指定すると、クロック出力ピンから8クロックが出力され、そのクロックに同期して、8ビットのデータがシリアル方式で出力されます。

4-1-6-2.shiftOut関数の書式

shifitOut関数は、次の書式を取ります。

void shiftOut(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder, uint8_t val)

第1引数dataPinは、uint8_t型(符号なし8ビット整数)で、データを出力するピンの番号を指定します。

第2引数clockPinは、uint8_t型で、クロックを出力するピンの番号を指定します。

第3引数bitOrderは、uint8_t型で、データをMSBファーストで送信するか、LSBファーストで送信するかの指定に使います。LSBファーストで送信する場合は、第3引数に定数LSBFIRSTを指定します。MSBファーストで送信する場合は、第3引数に定数MSBFIRSTを指定します。

第4引数valは、uint8_t型で、送信したい8ビットのデータを指定します。

なお、shiftOut関数には返り値はありません。

4-1-6-3.LSBファーストの場合shiftOut関数のの出力波形

shiftOut関数の第3引数にLSBFIRSTを渡し、LSBファーストでデータを送信する場合のタイミングチャートを図21に示します。

図21、shiftOut関数のLSBファーストの場合のタイミングチャート(CLOCK信号の初期化なし)
↑ 画像をクリックすると拡大
図21、shiftOut関数のLSBファーストの場合のタイミングチャート(CLOCK信号の初期化なし)

CLOCK信号は、shiftOut関数の第2引数で指定されたピンから出力される信号を表しています。

CLOCK信号の最初の部分(左端の部分)にXと書いてある部分がありますが、これは不定(LHか分からない事)を表しています。この部分がLになるかHになるかは、shiftOut関数が呼び出される直前に、digitalWrite関数の呼び出しなどにより、CLOCK信号にLHの内、どちらの電圧が出力されたかにより決まります。

なお、shiftOut関数は、必ずCLOCK信号がLになった状態で帰ってきますから、同じI/Oピンに対してshiftOut関数を連続で複数回呼び出す場合は、2回目以降の呼び出しでは、CLOCK信号の最初の部分は不定ではなく、電圧がLに確定しています。

DATA信号は、shiftOut関数の第1引数で指定されたピンから出力される信号を表しています。

DATA信号の最初の部分も不定です。この部分がLになるかHになるかは、shiftOut関数が呼び出される直前に、別のshiftOut関数の呼び出しや、digitalWrite関数の呼び出しなどにより、DATA信号にLHの内、どちらの電圧が出力されたかにより決まります。

shiftOut関数が呼び出されると、DATA信号にまず第4引数(val)のビット0(LSB)が出力されます。

DATA信号にvalのビット0が出力されている間に、CLOCK信号が一度Hになって、直後にLに戻ります。

その次にDATA信号にvalのビット1が出力され、その出力中にCLOCK信号が一度Hになって、直後にLに戻ります。

同様の事が合計8回繰り返され、最後の繰り返しの時は、DATA信号にvalのビット7(MSB)が出力され、その出力中にCLOCK信号が一度Hになって、直後にLに戻ります。

最後の繰り返しの時に、CLOCK信号がLに戻ったら、DATA信号にはvalのビット7を出力したままで、shiftOut関数から帰ってきます。

先ほど、CLOCKの最初の部分が不定だと説明しましたが、shiftOut関数を呼び出す前にdigitalWrite関数などでCLOCK信号がLに初期化されていた場合は、図22の様な波形になります。

図22、shiftOut関数のLSBファーストの場合のタイミングチャート(CLOCK信号をLに初期化)
↑ 画像をクリックすると拡大
図22、shiftOut関数のLSBファーストの場合のタイミングチャート(CLOCK信号をLに初期化)

このタイミングチャートと、LSBファーストでArduinoから74HC595へデータを送信する場合のタイミングチャート(図16)とを比較すると、図22のCLOCK信号を図16のSRCLK信号にして、図22のDATA信号を図16のSER信号にすれば、shiftOut関数が図16のSRCLK信号とSER信号の生成に使えそうな事が分かります。

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

ただし、RCLK信号はshiftOut関数では生成されませんので、shiftOut関数の呼び出しの後に、digitalWrite関数により波形を生成する必要があります。

4-1-6-4.MSBファーストの場合のshiftOut関数の出力波形

shiftOut関数の第3引数にMSBFIRSTを渡し、MSBファーストでデータを送信する場合のタイミングチャートを図23に示します。

図23、shiftOut関数のMSBファーストの場合のタイミングチャート(CLOCK信号の初期化なし)
↑ 画像をクリックすると拡大
図23、shiftOut関数のMSBファーストの場合のタイミングチャート(CLOCK信号の初期化なし)

LSBファーストの場合の波形(図21)と比較すると、DATA信号に出力されるデータのビット順だけが変わっている事が分かります。図21の場合はLSBが最初に出力され、MSBが最後に出力されていましたが、図23の場合は、MSBが最初に出力され、LSBが最後に出力されています。

図23のCLOCK信号の左端の部分がXになっていますが、shiftOut関数を呼び出す前にdigitalWrite関数などで、CLOCK信号をLに初期化しておけば、図24の様な波形になります。

図24、shiftOut関数のMSBファーストの場合のタイミングチャート(CLOCK信号をLに初期化)
↑ 画像をクリックすると拡大
図24、shiftOut関数のMSBファーストの場合のタイミングチャート(CLOCK信号をLに初期化)

このタイミングチャートと、MSBファーストでArduinoから74HC595へデータを送信する場合のタイミングチャート(図18)とを比較すると、図24のCLOCK信号を図18のSRCLK信号にして、図24のDATA信号を図18のSER信号にすれば、shiftOut関数が図18のSRCLK信号とSER信号の生成に使えそうな事が分かります。

図18(再掲)、1つの74HC595を制御する場合のタイミングチャート(MSBファースト)
↑ 画像をクリックすると拡大
図18(再掲)、1つの74HC595を制御する場合のタイミングチャート(MSBファースト)

ただし、RCLK信号はshiftOut関数では生成されませんので、shiftOut関数の呼び出しの後に、digitalWrite関数により波形を生成する必要があります。

4-1-6-5.outputOneByte関数をshiftOut関数を使って作り直す

LSBファーストでデータを送信するにせよ、MSBファーストでデータを送信するにせよ、shiftOut関数でSRCLK信号とSER信号を生成できる事が分かりました。

この事を利用して、前のページで作成したoutputOneByte関数(リスト16)を、shiftOut関数を使って作り直してみます。

shiftOut関数を使って作り直したoutputOneByte関数をリスト19に示します。

リスト19shiftOut関数を用いて作り直り直したoutputOneByte関数
// 74HC595に1バイトを送信する。valは送信するデータ。
// bitOrderにLSBFIRSTを指定するとLSBファーストで送信。bitOrderにMSBFIRSTを指定するとMSBファーストで送信。
void outputOneByte(uint8_t val,uint8_t bitOrder)
{
  // シフトレジスタにデータを送信
  shiftOut(SER,SRCLK,bitOrder,val);

  // ストレージレジスタにデータを転送
  digitalWrite(RCLK,HIGH); // RCLKを立ち上げる(この時に74HC595のシフトレジスタのパラレル出力がストレージレジスタに転送され、74HC595のしパラレル出力端子にデータが出力される)
  digitalWrite(RCLK,LOW ); // RCLKを立ち下げる(次にRCLKを立ち上げるため)
} // outputOnebyte

リスト19を、作り変える前のリスト16と比較すると、大きなif文がshiftOut関数に置き換わったり、DATA_BITS定数の宣言がなくなったりと、ずいぶんシンプルになったのが分かります。

この様に、シリアルバスにデータを送信する場合にshiftOut関数を使うと、スケッチを大幅に簡略化できる事があります。(シリアルバスには色々な仕様の物があるので、shiftOut関数の波形が目的の波形とは異なり、shiftOut関数が使えない場合もあります)

前のページリスト18のサンプルスケッチの中のoutputOneByte関数を、リスト19の物に置き換えて作ったスケッチがリスト20です。

リスト20、1つの74HC595を制御するテストスケッチ(LSBファースト・MSBファースト両対応、shiftOut関数使用)
// oneByteShiftOut.ino

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

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

void loop() {
  for(int i=0; i<256; i++) { // iを0から255までカウントアップ
    outputOneByte(i,LSBFIRST); // iをLSBファーストで74HC595に送信
    delay(100); // 0.1秒待つ
  } // for i

  delay(1000); // 1秒待つ

  for(int i=0; i<256; i++) { // iを0から255までカウントアップ
    outputOneByte(i,MSBFIRST); // iをMSBファーストで74HC595に送信
    delay(100); // 0.1秒待つ
  } // for i

  delay(1000); // 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の初期状態を気にしない場合は省略可能)
  outputOneByte(0,LSBFIRST);
} // initHc595

// 74HC595に1バイトを送信する。valは送信するデータ。
// bitOrderにLSBFIRSTを指定するとLSBファーストで送信。bitOrderにMSBFIRSTを指定するとMSBファーストで送信。
void outputOneByte(uint8_t val,uint8_t bitOrder)
{
  // シフトレジスタにデータを送信
  shiftOut(SER,SRCLK,bitOrder,val);

  // ストレージレジスタにデータを転送
  digitalWrite(RCLK,HIGH); // RCLKを立ち上げる(この時に74HC595のシフトレジスタのパラレル出力がストレージレジスタに転送され、74HC595のしパラレル出力端子にデータが出力される)
  digitalWrite(RCLK,LOW ); // RCLKを立ち下げる(次にRCLKを立ち上げるため)
} // outputOnebyte

このスケッチを動作させると、リスト18のスケッチ同様に動作します。

4-1-6-6.shiftOut関数を使った場合のデータの送信速度

shiftOut関数を使わずに作ったリスト18のスケッチと、shiftOut関数を使って作ったリスト20のスケッチについて、Arduino Unoで実際に動作させ、その波形をロジックアナライザで測定し、通信にかかる時間を測ってみます。

リスト18(shiftOut関数不使用)を動作させた時の波形を図25に、リスト20を(shiftOut関数使用)を動作させた時の波形を図26に示します。

図25、リスト18(shiftOut関数不使用)を動作させた時の波形
図25、リスト18(shiftOut関数不使用)を動作させた時の波形
図26、リスト20(shiftOut関数使用)を動作させた時の波形
図26、リスト20(shiftOut関数使用)を動作させた時の波形

RCLK信号にデータが出力され始めてからRCLKが立ち下がるまでの時間を測定したところ、リスト18の場合は108.2μs、リスト20の場合は123.6μsとなりました。shiftOut関数を使うと、digitalWrite関数だけの場合より、14%ほど実行時間が延びます。大きな差とはいえませんが、実行速度を重視する場合はshiftOut関数を使わない方がいいでしょう。

shiftOut関数の実行に時間がかかる理由はここでは詳しく説明しませんが、要点だけを説明すると、(少なくともAVRマイコン用の)shiftOut関数は、実行速度が出る様に最適化されていないからです。

次のページでは、74HC595への転送速度が大幅に向上する、FASTIOライブラリを使用した方法について説明します。

このエントリーをはてなブックマークに追加
目次へ  前のページへ (1) (2) (3) (4) (5) (6) (7) (8) 次のページへ

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

関連ページ

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