2019年10月02日 | 公開。 |
今までは、digitalWrite関数のみで信号線の制御をしていましたが、今度はshiftOut関数も使ってみましょう。
shiftOut関数はArduinoの標準関数のひとつで、8ビットの符号なし整数の引数を、クロック同期方式のシリアルバスに出力する働きをします。
データ出力に使うピンとクロック出力に使うピンを指定すると、クロック出力ピンから8クロックが出力され、そのクロックに同期して、8ビットのデータがシリアル方式で出力されます。
shifitOut関数は、次の書式を取ります。
第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関数には返り値はありません。
shiftOut関数の第3引数にLSBFIRSTを渡し、LSBファーストでデータを送信する場合のタイミングチャートを図21に示します。
CLOCK信号は、shiftOut関数の第2引数で指定されたピンから出力される信号を表しています。
CLOCK信号の最初の部分(左端の部分)にUと書いてある部分がありますが、これは不定(LかHか分からない事)を表しています。この部分がLになるかHになるかは、shiftOut関数が呼び出される直前に、digitalWrite関数の呼び出しなどにより、CLOCK信号にLとHの内、どちらの電圧が出力されたかにより決まります。
なお、shiftOut関数は、必ずCLOCK信号がLになった状態で帰ってきますから、同じI/Oピンに対してshiftOut関数を連続で複数回呼び出す場合は、2回目以降の呼び出しでは、CLOCK信号の最初の部分は不定ではなく、電圧がLに確定しています。
DATA信号は、shiftOut関数の第1引数で指定されたピンから出力される信号を表しています。
DATA信号の最初の部分も不定です。この部分がLになるかHになるかは、shiftOut関数が呼び出される直前に、別のshiftOut関数の呼び出しや、digitalWrite関数の呼び出しなどにより、DATA信号にLとHの内、どちらの電圧が出力されたかにより決まります。
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の様な波形になります。
このタイミングチャートと、LSBファーストでArduinoから74HC595へデータを送信する場合のタイミングチャート(図16)とを比較すると、図22のCLOCK信号を図16のSRCLK信号にして、図22のDATA信号を図16のSER信号にすれば、shiftOut関数が図16のSRCLK信号とSER信号の生成に使えそうな事が分かります。
ただし、RCLK信号はshiftOut関数では生成されませんので、shiftOut関数の呼び出しの後に、digitalWrite関数により波形を生成する必要があります。
shiftOut関数の第3引数にMSBFIRSTを渡し、MSBファーストでデータを送信する場合のタイミングチャートを図23に示します。
LSBファーストの場合の波形(図21)と比較すると、DATA信号に出力されるデータのビット順だけが変わっている事が分かります。図21の場合はLSBが最初に出力され、MSBが最後に出力されていましたが、図23の場合は、MSBが最初に出力され、LSBが最後に出力されています。
図23のCLOCK信号の左端の部分がUになっていますが、shiftOut関数を呼び出す前にdigitalWrite関数などで、CLOCK信号をLに初期化しておけば、図24の様な波形になります。
このタイミングチャートと、MSBファーストでArduinoから74HC595へデータを送信する場合のタイミングチャート(図18)とを比較すると、図24のCLOCK信号を図18のSRCLK信号にして、図24のDATA信号を図18のSER信号にすれば、shiftOut関数が図18のSRCLK信号とSER信号の生成に使えそうな事が分かります。
ただし、RCLK信号はshiftOut関数では生成されませんので、shiftOut関数の呼び出しの後に、digitalWrite関数により波形を生成する必要があります。
LSBファーストでデータを送信するにせよ、MSBファーストでデータを送信するにせよ、shiftOut関数でSRCLK信号とSER信号を生成できる事が分かりました。
この事を利用して、前のページで作成したoutputOneByte関数(リスト16)を、shiftOut関数を使って作り直してみます。
shiftOut関数を使って作り直したoutputOneByte関数をリスト19に示します。
// 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です。
// 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のスケッチ同様に動作します。
shiftOut関数を使わずに作ったリスト18のスケッチと、shiftOut関数を使って作ったリスト20のスケッチについて、Arduino Unoで実際に動作させ、その波形をロジックアナライザで測定し、通信にかかる時間を測ってみます。
リスト18(shiftOut関数不使用)を動作させた時の波形を図25に、リスト20を(shiftOut関数使用)を動作させた時の波形を図26に示します。
RCLK信号にデータが出力され始めてからRCLKが立ち下がるまでの時間を測定したところ、リスト18の場合は108.2μs、リスト20の場合は123.6μsとなりました。shiftOut関数を使うと、digitalWrite関数だけの場合より、14%ほど実行時間が延びます。大きな差とはいえませんが、実行速度を重視する場合はshiftOut関数を使わない方がいいでしょう。
shiftOut関数の実行に時間がかかる理由はここでは詳しく説明しませんが、要点だけを説明すると、(少なくともAVRマイコン用の)shiftOut関数は、実行速度が出る様に最適化されていないからです。
次のページでは、74HC595への転送速度が大幅に向上する、FASTIOライブラリを使用した方法について説明します。