2019年10月03日 | 更新。 |
shiftOut関数は、Arduinoで使える標準関数のひとつで、8ビットの符号なし整数の引数を、クロック同期方式のシリアルバスに出力する働きをします。
データ出力に使うピンとクロック出力に使うピンを指定すると、クロック出力ピンから8クロックが出力され、そのクロックに同期して、8ビットのデータがシリアル方式で出力されます。
データは、MSBファースト(MSBから順にLSBまで出力する方式)でもLSBファースト(LSBから順にMSBまで出力する方式)でも出力できます。
shiftOut関数は、モード0あるいはモード1で動作する、SPI接続のスレーブ機器に対し、ソフトウェアSPI方式によりデータを送信する用途に使えます。
またshiftOut関数は、74HC595や74HC164等のシリアル-パラレル変換用シフトレジスタにデータをする用途にも使えます。
なお、Arduinoは、SPIライブラリにより、ハードウェアSPI方式にも対応しています。ハードウェアSPI方式の場合は、使える信号ピンが決まっているという欠点はあるものの、shiftOut関数と同様の動作をより高速に行える利点があります。SPIライブラリに関しては、この用語集のSPIの項目をご覧ください。
shifitOut関数は、次の書式を取ります。
第1引数dataPinは、uint8_t型(符号なし8ビット整数)で、データを出力するピンの番号を指定します。
第2引数clockPinは、uint8_t型で、クロックを出力するピンの番号を指定します。
第3引数bitOrderは、uint8_t型で、データをMSBファーストで送信するか、LSBファーストで送信するかの指定に使います。MSBファーストで送信する場合は、第3引数に定数MSBFIRSTを指定します。LSBファーストで送信する場合は、第3引数に定数LSBFIRSTを指定します。
第4引数valは、uint8_t型で、送信したい8ビットのデータを指定します。
なお、shiftOut関数には返り値はありません。
shiftOut関数を呼び出した時の出力波形を、LSBファーストの場合(第3引数にLSBFIRSTを指定した場合)と、MSBファーストの場合(第3引数にMSBFIRSTを指定した場合)に分けて、図説します。
LSBファーストの場合のタイミングチャートを図1に示します。
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関数から帰ってきます。
1つのビットをDATA信号に出力し続けている間に、CLOCK信号が1度立ち上がって1度立ち下がるため、Dフリップフロップや、あるいはDフリップフロップをカスケード接続したシフトレジスタでデータを受け取る場合、ポジティブエッジトリガでもネガティブエッジトリガでもデータを取得できます。
また、CLOCK信号がHの状態でshiftOut関数が呼び出されると、DATA信号にビット0を出力している間にCLOCK信号は立ち上がりませんから、データを受信する機器がCLOCK信号の立ち上がりでデータを取り込む場合は、データを1ビット取りこぼしてしまいます。
この取りこぼしを防ぐためには、あらかじめ一度CLOCK信号をLに初期化しておき、shiftOut関数を呼び出す前にCLOCK信号の電圧をLに確定しておく必要があります。初期化を行った場合のshiftOut関数のタイミングチャートを図2に示します。
注:データをCLOCK信号の立ち下がりで取り込む場合(SPI通信ならモード1の場合)は、CLOCK信号を初期化しておく必要はありません。
MSBファーストの場合のタイミングチャートを図3に示します。
MSBファーストの場合のタイミングチャートは、LSBファーストの場合のタイミングチャート(図1)と比較すると、DATA信号に出てくるval(第4引数)の内容のビット順だけが異なります。LSBファースト(図1)の場合はLSBが最初に出力され、最後にMSBが出力されましたが、MSBファースト(図2)の場合はMSBが最初に出力され、最後にLSBが出力されます。
MSBファーストの場合も、データを受信する機器が、CLOCK信号の立ち上がりでデータを取り込むなら、最初に一度、CLOCK信号をLに初期化してからshiftOut関数を呼ばないと、最初のビットを取りこぼします。CLOCK信号を初期化した時のshiftOut関数のタイミングチャートを図4に示します。
SPI通信ではMSBファーストでデータを送る事になっているので、SPI機器ににデータを送る目的でshiftOut関数を使用する場合は、第3引数にMSBFIRSTを渡します。
shiftOut関数では、1ビットの情報をDATA信号に出力している間に、CLOCK信号が一度立ち上がって再び立ち下がるため、クロックの立ち上がりでデータを取得するモード0の機器にでも、クロックの立下りでデータを取得するモード1の機器にでも、データを送信できます。(図5参照)
ただし、shiftOut関数は、必ず正極性のクロックパルスを発生するため、負極性のクロックパルスを要求するモード2やモード3の機器へのデータの送信には使えません。
参考:SPI通信をする場合は、CLOCK信号をSCLK信号と読み替えてください。またDATA信号は、MOSI信号と読み替えてください。SS信号については、shiftOut関数で生成する事はできませんので、別途digitalWrite関数で生成する必要があります。
これら4つのモードの内、shiftOut関数を使って実現できるのは、クロックパルスが正極性の(クロックが出ていない時にSCLK信号がLになる)モード0とモード1です。クロックパルスが負極性の(クロックが出ていない時にSCLK信号がHになる)モード2とモード3は、shiftOut関数では実現できません。
shiftOut関数はピンの入出力モードを変えませんので、CLOCK信号に使うピンとDATA信号に使うピンとを、あらかじめpinMode関数により出力モード(OUTPUT)に設定しておく必要があります。
また、データを受信する機器がCLOCK信号の立ち上がりでDATA信号を読み込む場合は、CLOCK信号はLに初期化されていなければなりませんので、digitalWrite関数によりL(LOW)に初期化しておかなければなりません。
これらの初期化は、スケッチの実行の最初に1度だけ行えばいいので、通常はsetup関数内に記述します。
shiftOut関数を使うスケッチの例として、Arduinoに74HC595を接続して出力ピンを拡張する回路(図6)の制御を、shiftOut関数を用いて行うスケッチを紹介します。
この回路では、ArduinoのデジタルI/Oピンを3個消費して、74HC595上に8個の出力ピンを拡張しますので、差し引き5個の出力ピンが増える事になります。
74HC595にLSBファーストでデータを送信する場合のタイミングチャートを図7に示します。
この図でXと書いてある部分(背景が水色の部分)は、Don't care(LでもHでも構わない)を表しています。また、Uと書いてある部分(背景がオレンジ色の部分)は、不定(LかHか分からない)を表しています。
このタイミングチャートにおいて、Arduinoから74HC595に送信する制御信号はSRCLK、RCLK、SERの3本です。これらの内、SRCLKとSERの2本については、shiftOut関数で生成できます。RCLK信号はshiftOut関数では生成できませんので、別途digitalWrite関数で生成する必要があります。
図7のタイミングチャートを踏まえて、作成したスケッチを、リスト1に示します。
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関数を抜けても値を保持し続ける
outputOneByte(cnt++,LSBFIRST); // iをLSBファーストで74HC595に送信
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の初期状態を気にしない場合は省略可能)
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
このスケッチは、0~255のデータを74HC595のQA(MSB)~QH(LSB)の端子に出力し、255まで出力したら、次に0に戻るという動作を繰り返します。
スケッチの最初でSER、SRCLK、およびRCLKの3つの定数が宣言されていますが、それぞれSER信号用のピンの番号、SRCLK信号用のピンの番号、およびRCLK信号用のピンの番号を表しています。これらの定数の値を変える事により、自由にArduinoのピン配置を変えられます。
このスケッチでは、initHc595関数とoutputOneByte関数の2つの関数を定義しています。
initHc595関数は、制御ピンをpinMode関数で出力ピンに設定し、またSRCLK信号とRCLK信号の初期値をLに設定しています。
outputOneByte関数が、データを74HC595に送信する関数です。この関数の中で、shiftOut関数を呼び出しています。
shiftOut関数を使って74HC595を制御する方法については、74HC595を使ってArduinoの出力ピンを拡張する方法(7)という記事で詳しく説明していますので、詳細な情報についてはそちらをご覧ください。