2019年09月29日 | 公開。 |
前のページでは、Arduinoにつながった74HC595にLSBファーストでデータを送信する方法を説明しましたが、今度はMSBファーストでデータを送信する方法について説明します。
3ページの説明のおさらいになりますが、Arduinoと74HC595は図12に示す配線図でつなぎます。
この回路において、MSBファーストで8ビットのデータを送信するには、図18のタイミングチャートに従って信号を送ります。
MSBファーストでデータを送信する場合にも、初期化用のinitHC595関数と、8ビットをMSBファーストで送信する関数outputOneByteMsbFirst関数(関数名がoutputOneByteLsbFirst関数から変わっている事に注意)の2つを作る事にします。
まずinitHc595関数は、リスト12の様になります。
// 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の初期状態を気にしない場合は省略可能)
outputOneByteMsbFirst(0);
} // initHc595
LSBファーストの場合のinitHC595関数(前のページのリスト6)と比較すると、リスト6でoutputOneByteLsbFirst(0);
と書いた部分が、送信方法がMSBファーストに変わった事により、リスト12ではoutputOneByteMsbFirst(0);
に書き換わっているのが分かります。
一方で、outputOneByteMsbFirst関数は、リスト13の様になります。
// MSBからLSBの順に74HC595に1バイトを送信する
void outputOneByteMsbFirst(uint8_t val)
{
const int DATA_BITS=8; // 送信するデータのビット数
// シフトレジスタにデータを送信
for(int i=0; i<DATA_BITS; i++) {
digitalWrite(SER,(val & 0x80) ? 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を立ち上げるため)
} // outputOnebyteMsbFirst
リスト13から、74HC595の内蔵シフトレジスタにデータを送信する部分を抜き出したのが、リスト14です。
// シフトレジスタにデータを送信
for(int i=0; i<DATA_BITS; i++) {
digitalWrite(SER,(val & 0x80) ? HIGH : LOW); // SERにvalの最上位ビットを出力
digitalWrite(SRCLK,HIGH); // SRCLKを立ち上げる(この時74HC595がシフト動作)
digitalWrite(SRCLK,LOW ); // SRCLKを立ち下げる(次にSRCLKを立ち上げるため)
val<<=1; // valを1ビットだけ左シフト
} // for i
for文で8回ループを回しています。
ループの内部を見ると、まず
digitalWrite(SER,(val & 0x80) ? HIGH : LOW);
でvalの最上位ビットをSER信号に出力します。
ちなみに、0x80は2進数で表記すると10000000(最上位ビットだけ1)ですから、val & 0x80
という式は、valの最上位ビットが1なら0x80を、最上位ビットが0なら0を返します。
よって、(val & 0x80) ? HIGH : LOW
という式は、valの最上位ビットが1ならHIGHを、最上位ビットが0ならLOWを返します。
参考:定数LOWが0に定義されており、定数HIGHが1に定義されている事を考えると、(val & 0x80) ? HIGH : LOW
という式は、(val & 0x80)!=0
とも書けます。これを!!(val & 0x80)
と書くプログラマーもいますが、直感的でないので筆者は好きではありません。いずれの書き方でも、コンパイラの最適化により、おそらく同じ機械語に変換されると思います。
valの最上位ビットをSER信号に出力した次は、
digitalWrite(SRCLK,HIGH);
digitalWrite(SRCLK,LOW );
によりSRCLK信号に1クロック送信して、74HC595内蔵のシフトレジスタを、1ビットだけシフトします。
その次に
val<<=1;
によってvalを1ビット左シフトして、次のループに備えます。
LSBファーストの場合のスケッチ(前のページのリスト11)をMSBファースト用に書き換えるには、initHc595関数をリスト12の物に置き換え、さらにoutputOneByteLsbFirst関数をリスト13のoutputOneByteMsbFirst関数に置き換え、さらにloop関数内でoutputOneByteLsbFirst関数を呼び出している部分をoutputOneByteMsbFirst関数の呼び出しに書き換えればいいので、結局リスト15に示すスケッチになります。
// oneByteMsbFirst.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関数を抜けても値を保持し続ける
outputOneByteMsbFirst(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の初期状態を気にしない場合は省略可能)
outputOneByteMsbFirst(0);
} // initHc595
// MSBからLSBの順に74HC595に1バイトを送信する
void outputOneByteMsbFirst(uint8_t val)
{
const int DATA_BITS=8; // 送信するデータのビット数
// シフトレジスタにデータを送信
for(int i=0; i<DATA_BITS; i++) {
digitalWrite(SER,(val & 0x80) ? 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を立ち上げるため)
} // outputOnebyteMsbFirst
前のページのリスト11で紹介したLSBファースト用のスケッチの場合、QH信号の状態を示すLED8(3ページの図15の回路図や写真9で一番右のLED)の点滅が一番速かったのに対し、リスト15に示したMSBファースト用のスケッチの場合、QA信号の状態を示すLED1(図15の回路図や写真9で一番左側のLED)の点滅が一番速くなります。
これは、LSBファーストの場合、QH信号が最下位ビット(LSB)になるのに対し、MSBファーストの場合、QA信号が最下位ビットになる事によります。
リスト15のMSBファーストのスケッチをArduino Unoで動作させた場合の波形を、ロジックアナライザで測定した結果を、図20に示します。
ロジックアナライザの画面をハードコピーしたものです。ただし、水色のLやHの文字や、上側中央やや右寄りの水色の長方形は、筆者が書き加えた物です。
図20のSER信号を見ると、8クロックのSRCLK信号に同期して、L、L、H、L、L、L、L、Lの順にデータを送信している事が分かります。MSBファーストで送信していますから、これらのデータを2進数で表わすと、00100000となります。
次にQA~QHの信号の波形を見ると、これらの信号はQA信号がLSBでQH信号がMSBですから、RCLK信号の立ち上がりの前後で00011111から00100000に変化している事が分かります。
この様に、SER信号で送ったデータが、RCLK信号の立ち上がりと同時にQA~QHの信号に出力されている様子が分かります。また、リスト15のスケッチは、送信するデータを1ずつカウントアップする物ですが、実際に00011111の次には、1だけ大きい00100000が出力されている事が確認できます。
SER信号にMSBを出力し始めてからRCLK信号を立ち下げる所までが、データ送信の一連の処理になりますが、図20より、この処理には108μsかかっている事が分かりました。(図20の上側中央やや右寄りの水色の長方形の中の、"A − T = 108us"と表示されている部分に注目してください) 前のページで説明したとおり、LSBファーストの場合はデータ送信に107μsかかっていましたが、MSBファーストの場合でも108μsと、ほぼ同じ時間がかかる事が分かります。
次のページでは、LSBファーストによるデータ送信でも、MSBファーストによるデータ送信でも、同じ関数で行えるように、outputOneByteLsbFirst関数とoutputOneByteMsbFirst関数を統合したoutputOneByte関数を作ります。