2019年09月18日 | 公開。 |
いよいよ、74HC595を制御するスケッチを作る方法の説明をします。
この項では、digitalWrite関数を使って、Arduinoにつながった1つの74HC595に、LSBファーストでデータを送信するスケッチを作る事にします。
まず74HC595の制御の方法の説明をして、最後に制御スケッチを示しますが、説明を飛ばして手っ取り早くスケッチをご覧になりたい方は、リスト11をご覧ください。
前のページで説明しましたが、図12の回路を使って、LSBファーストでデータを送信するには、図16のタイミングチャートに従って、74HC595に制御信号を送ればいいのでした。
Arduinoから74HC595に送るべき制御信号は、SER信号、SRCLK信号、およびRCLK信号の3つです。これらの信号をそれぞれ、Arduinoのデジタル出力ピン(digitalWrite関数で信号を出力できるピン)に割り当てる必要があります。
Arduino Unoの場合は0番~13番ピンの、デジタル出力専用ピンと、A0~A5のアナログ入力・デジタル出力兼用ピンの中から3つピンを選んで、SER信号、SRCLK信号、およびRCLK信号に割り当てる事になります。
スケッチ中で、ピンの番号を直書きすると、割り当てるピンの番号に変更があった場合に、スケッチの書き換えが大変になりますから、3つの信号に割り当てるピン番号を、定数として宣言します。
SER信号、SRCLK信号、およびRCLK信号にに割り当てるピン番号の定数の名前を、それぞれSER、SRCLK、およびRCLKとし、SER信号には2番ピン、SRCLK信号には3番ピン、RCLK信号には4番ピンを割り当てるとすると、定数の宣言は、例えばリスト1の様になります。
const int SER =2; // SER制御用にするピンの番号を宣言
const int SRCLK=3; // SRCLK制御用にするピンの番号を宣言
const int RCLK =4; // RCLK制御用にするピンの番号を宣言
リスト1ではconst int型の型付き定数にしましたが、もちろん#define指令を使ってリスト2の様に宣言しても結構です。
#define SER 2 // SER制御用にするピンの番号を宣言
#define SRCLK 3 // SRCLK制御用にするピンの番号を宣言
#define RCLK 4 // RCLK制御用にするピンの番号を宣言
次に、74HC595の制御に使用する3つのピンと74HC595自体の初期化を行う関数(initHc595と命名する)を作ってみます。
initHc595関数で行うべき事は3つあります。
まず、74HC595の制御に使う3つのI/Oピンを、pinMode関数を使って出力モードに設定する必要があります。(リスト3参照)
// 74HC595制御用ピンを出力モードにする
pinMode(SER,OUTPUT);
pinMode(SRCLK,OUTPUT);
pinMode(RCLK,OUTPUT);
次に、SRCLK信号用のピンとRCLK信号用のピンの出力をLに初期化します。(リスト4参照) というのは、図16のタイミングチャートで74HC595を制御する場合は、データを送信していないアイドル状態では、SRCLK信号とRCLK信号はLになっている前提だからです。
// SRCLKとRCLKをLOWにする(これらのピンはデータを送信していない時はLOWであるべき)
digitalWrite(SRCLK,LOW);
digitalWrite(RCLK,LOW);
最後に、74HC595のパラレル出力ピン(QA端子~QH端子)を全てLにします。(リスト5参照)
// 0を送信して74HC595のパラレル出力ピンを全て0にする(74HC595の初期状態を気にしない場合は省略可能)
outputOneByteLsbFirst(0);
74HC595のパラレル出力ピンを全てLにする理由は、電源投入直後のパラレル出力ピンの電圧が不定(LになるかHになるか分からない)になるからです。別にパラレル出力ピンをLに初期化しなくてもいい場合は、リスト5の処理は不要です。
なお、パラレル出力ピンを初期化するには、実際に74HC595へ8ビットのデータを送信する必要があります。データを送信する関数はまだ作っていませんが、outputOneByteLsbFirst関数という名前にしました。この関数に符号なし8ビット整数を引数として渡すと、74HC595にその引数を送信しますが、今回は0を引数とする事で、74HC595のパラレル出力ピンを全てLに初期化します。
リスト3~リスト5の処理をまとめてinitHc595関数にすると、その全体像はリスト6の様になります。
// 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ビット以上のデータを送信する場合に、コードの変更が少なくなるのです。
const int DATA_BITS=8; // 送信するデータのビット数
次に、SER信号にデータを送信しながら、SRCLK信号にクロックを送信するループを記述します。(リスト8参照) 前提として、関数にuint8_t型(符号なし8ビット整数)のvalという仮引数が渡されるものとします。
// シフトレジスタにデータを送信
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クロック送信して、ストレージレジスタに信号を転送します。
// ストレージレジスタにデータを転送
digitalWrite(RCLK,HIGH); // RCLKを立ち上げる(この時に74HC595のシフトレジスタのパラレル出力がストレージレジスタに転送され、74HC595のしパラレル出力端子にデータが出力される)
digitalWrite(RCLK,LOW ); // RCLKを立ち下げる(次にRCLKを立ち上げるため)
以上の説明を踏まえて、outputOneByteLsbFirst関数を作ると、その全体像はリスト10の様になります。
// 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の定数宣言と、リスト6のinitHc595関数と、リスト10のoutputOneByteLsbFirst関数の3つで、74HC595にデータを送信する準備が出来ました。
setup関数とloop関数を実装して、実際に74HC595に何かのデータを送る様にしたテストスケッチを、リスト11に示します。
// 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です。
基板上のピンヘッダから信号を取り出し、ZEROPLUS製のLAP-C(16064)というロジックアナライザで、波形を測定してみました。測定した波形を図19に、測定風景を写真10に示します。
図19のSER信号を見ると、8クロックのSRCLK信号に同期して、L、L、L、L、L、H、L、Hの順にデータを送信している事が分かります。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に送信するスケッチについて説明します。