2019年10月04日 | 公開。 |
今回は、FASTIOライブラリを使って74HC595を制御する方法について説明します。FASTIOライブラリを使うと、今まで説明したdigitalWrite関数を使う方法や、shiftOut関数を使う方法に比べて、スケッチを作る際の記述量が少なく、実行速度も高速になります。その反面、FASTIOライブラリが対応しているArduinoが少なく(現在のところArduino Unoとその互換機のみ)、Arduinoの機種間のスケッチの移植性が悪くなります。
ここで、LSBファーストで74HC595にテータを送信する場合のタイミングチャートを振り返っておきましょう。LSBファーストの場合のタイミングチャートを図16に示します。また、Arduinoと74HC595の接続図を図12に示します。
Arduinoから74HC595への信号線は、SRCLK信号、RCLK信号、およびSER信号の3本です。
digitalWrite関数を使う方法では、SRCLK信号、RCLK信号、およびSER信号の3つの波形を、全てdigitalWrite関数で生成する必要がありました。そのため、スケッチの記述量が多くなる欠点がありました。
そして、shiftOut関数を使う方法では、SRCLK信号とSER信号の生成を一回のshiftOut関数の呼び出しで行うため、スケッチの記述量が減りました。
さらに、FASTIOライブラリを使用すると、SRCLK信号、RCLK信号、およびSER信号の全てを、FASTIOライブラリがやってくれますので、shiftOut関数を使う方法に比べて、スケッチの記述量がさらに大幅に減ります。
加えて、FASTIOライブラリを使うと、スケッチの実行速度が大幅に向上するというメリットもあります。
それでは、最初は理屈抜きに、FASTIOライブラリを使って作った、74HC595制御用のテストスケッチを見ていただきましょう。
リスト21に、FASTIOライブラリを使ったテストスケッチを示します。このスケッチは、4ページで紹介した、digitalWrite関数を使ってLSBファーストでデータを送信するスケッチ(リスト11)と同様の動作をします。
// oneByteFastio.no
#include <fastio.h> // FASTIOライブラリのヘッダファイルをインクルード
hc595<2,3,4,1,LSBFIRST> my595;
// Arduinoに接続したシフトレジスタに、my595という名前をつける。
// 1番目のパラメータは、74HC595のSER端子と接続するArduinoのピンの番号。
// 2番目のパラメータは、74HC595のSRCLK端子と接続するArduinoのピンの番号。
// 3番目のパラメータは 74HC595のRCLK端子と接続するArduinoのピン番号。
// 4番目のパラメータは、カスケード接続する74HC595の個数。
// 5番目のパラメータは、1バイト内のLSBから送出するか、MSBから送出するかの区別。
void setup()
{
my595.init(); // 74HC595とそのドライバの初期化
} // setup
void loop()
{
static uint8_t cnt=0;
my595.shiftOut(cnt++); // 74HC595にcntの内容を送信してから、cntをカウントアップする。
delay(100); // 0.1秒休む
} // loop
このスケッチを動作させるには、Arduino IDEにFASTIOライブラリをインストールしておく必要があります。FASTIOライブラリのダウンロードとインストール方法については、FASTIOライブラリ(1)をご覧ください。
このスケッチにはコメントが結構たさん入っていますが、それを除くと、記述量はわずかです。
まず、FASTIOライブラリを使うスケッチは、最初にfastio.hというヘッダファイルをインクルードする必要があります。
#include <fastio.h>
次に、74HC595の制御信号を発生するオブジェクト変数を、大域変数(グローバル変数)として宣言します。
hc595<2,3,4,1,LSBFIRST> my595;
hc595<2,3,4,1,LSBFIRST>
というのが変数の型(オブジェクトのクラス)です。Arduinoの世界では使われる機会が少ないテンプレートという手法を使っているので、変数の型の中にパラメータ(2とか3とかLSBFIRSTなど)が入っています。慣れていない人には気持ち悪いかも知れませんが、パラメータも含めて型です。
変数の型の右に書いてあるmy595が変数名です。
なお、変数の型には山括弧に囲まれて、5つのパラメータが並んでいます。これらのパラメータの意味を順に説明します。
1番目のパラメータ(2)は、74HC595のSER端子と接続するArduinoのI/Oピンの番号です。
2番目のパラメータ(3)は、74HC595のSRCLK端子と接続するArduinoのI/Oピンの番号です。
3番目のパラメータ(4)は、74HC595のRCLK端子と接続するArduinoのI/Oピンの番号です。
4番目のパラメータ(1)は、1番目~3番目のパラメータで指定したI/Oピンに接続する、74HC595の個数です。
5番目のパラメータ(LSBFIRST)には、データをLSBファーストで送信するか、MSBファーストで送信するかを指定します。(LSBFITSTを指定すればLSBファーストで、MSBFIRSTを指定すればMSBファーストで送信します)
この様に、使用するピンや接続する74HC595の個数、LSBファースト/MSBファーストの区別を、変数宣言の段階でしてしまいます。ピンアサイン(ピン割り当て)などの仕様変更があっても、この行を書き換えるだけでいいので、プログラムの保守性がよくなります。
変数my595を使って74HC595を制御するためには、setup関数の中でmy595.init()を呼び出し、初期化処理をします。
my595.init();
この初期化処理では、SER信号、SRCLK信号、およびRCLK信号の出力用に使うI/Oピンを出力ピンに設定したり、SRCLK信号とRCLK信号の初期値をLに設定したり、あるいはオブジェクト内部の初期化処理をしたりします。
実際に74HC595にデータを送るためには、my595.shiftOut(送信するデータ)の書式でshiftOut関数を呼び出します。このshiftOut関数は、Arduino標準のshiftOut関数ではなく、FASTIOライブラリで定義されたshiftOut関数です。
my595.shiftOut(cnt++);
hc595<2,3,4,1,LSBFIRST>型の変数であるmy595は、変数の型の中に既に、使用するI/Oピンの番号や、LSBファースト/MSBファーストの区別などの情報が含まれているので、Arduino標準のshiftOut関数とは違って、my595.shiftOut関数を呼び出す時の引数は、実際に送りたいデータだけになります。この呼び出しで、SRCLK信号、RCLK信号、およびSER信号の3つの信号を生成できます。
FASTIOライブラリを使って74HC595を制御した場合の波形をロジックアナライザで観察してみます。
Arduino Unoを使ってリスト21のスケッチを実行した場合の波形を図27に示します。
この図から、RCLK信号にデータが出力され始めてからRCLKが立ち下がるまでの一連の動作に4.57μsかかる事が分かりました。digitalWrite関数で制御する場合(4ページの図19参照)は107.0μs、shiftOut関数で制御する場合(7ページの図26参照)は123.6μsかかりましたから、FASTIOライブラリを使うと、これらよりも圧倒的に速く制御できる事が分かります。
ただし、ロジックアナライザの波形から測定できる実行時間は、関数を呼び出したり、関数から帰ってくる時間を含みませんから、実際には波形から測定できる時間より少しだけ長い時間がかかります。
関数の呼び出しや関数からのリターンなどのオーバーヘッドを含んだ実行時間を測定するために、リスト22~リスト24の実行時間測定用スケッチを作成しました。これらのスケッチはそれぞれ、dititalWrite関数を使った場合、shiftOut関数を使った場合、およびFASTIOライブラリを使った場合について、データの送信を10万回行い、その送信にかかった時間をms単位でシリアルモニタに表示します。
// benchDigitalWrite.ino
const int SER =2; // SER制御用にするピンの番号を宣言
const int SRCLK=3; // SRCLK制御用にするピンの番号を宣言
const int RCLK =4; // RCLK制御用にするピンの番号を宣言
void setup()
{
Serial.begin(9600); // シリアルポートを9600bpsで開く
initHc595(); // 74HC595の制御ピン等を初期化
} // setup
void loop()
{
uint32_t startTime; // 計測開始時刻[ms]
startTime=millis(); // 計測開始時刻を取得
// 10万回データを転送
for(int i=0; i<10000; i++) { // 1万回ループ
// 10回0を送信
outputOneByteLsbFirst(0);
outputOneByteLsbFirst(1);
outputOneByteLsbFirst(2);
outputOneByteLsbFirst(3);
outputOneByteLsbFirst(4);
outputOneByteLsbFirst(5);
outputOneByteLsbFirst(6);
outputOneByteLsbFirst(7);
outputOneByteLsbFirst(8);
outputOneByteLsbFirst(9);
} // for i
Serial.println(millis()-startTime); // シリアルポートに計測結果を表示
} // 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
// benchShiftOut.ino
const int SER =2; // SER制御用にするピンの番号を宣言
const int SRCLK=3; // SRCLK制御用にするピンの番号を宣言
const int RCLK =4; // RCLK制御用にするピンの番号を宣言
void setup()
{
Serial.begin(9600); // シリアルポートを9600bpsで開く
initHc595(); // 74HC595の制御ピン等を初期化
} // setup
void loop()
{
uint32_t startTime; // 計測開始時刻[ms]
startTime=millis(); // 計測開始時刻を取得
// 10万回データを転送
for(int i=0; i<10000; i++) { // 1万回ループ
// 10回0を送信
outputOneByte(0,LSBFIRST);
outputOneByte(1,LSBFIRST);
outputOneByte(2,LSBFIRST);
outputOneByte(3,LSBFIRST);
outputOneByte(4,LSBFIRST);
outputOneByte(5,LSBFIRST);
outputOneByte(6,LSBFIRST);
outputOneByte(7,LSBFIRST);
outputOneByte(8,LSBFIRST);
outputOneByte(9,LSBFIRST);
} // for i
Serial.println(millis()-startTime); // シリアルポートに計測結果を表示
} // 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
// benchFastio.ino
#include <fastio.h> // FASTIOライブラリのヘッダファイルをインクルード
hc595<2,3,4,1,LSBFIRST> my595;
// Arduinoに接続したシフトレジスタに、my595という名前をつける。
// 1番目のパラメータは、74HC595のSER端子と接続するArduinoのピンの番号。
// 2番目のパラメータは、74HC595のSRCLK端子と接続するArduinoのピンの番号。
// 3番目のパラメータは 74HC595のRCLK端子と接続するArduinoのピン番号。
// 4番目のパラメータは、カスケード接続する74HC595の個数。
// 5番目のパラメータは、1バイト内のLSBから送出するか、MSBから送出するかの区別。
void setup()
{
Serial.begin(9600); // シリアルポートを9600bpsで開く
my595.init(); // 74HC595とそのドライバの初期化
} // setup
void loop()
{
uint32_t startTime; // 計測開始時刻[ms]
startTime=millis(); // 計測開始時刻を取得
// 10万回データを転送
for(int i=0; i<10000; i++) { // 1万回ループ
// 10回0を送信
my595.shiftOut(0);
my595.shiftOut(1);
my595.shiftOut(2);
my595.shiftOut(3);
my595.shiftOut(4);
my595.shiftOut(5);
my595.shiftOut(6);
my595.shiftOut(7);
my595.shiftOut(8);
my595.shiftOut(9);
} // for i
Serial.println(millis()-startTime); // シリアルポートに計測結果を表示
} // loop
リスト22を実行して、シリアルモニタを9600bpsに設定して観察すると、図28に示す様に、データを10万回送信した時にかかる時間を、ms単位で繰り返し表示します。リスト23やリスト24でも、同様にデータを10万回送信した時にかかる時間を測定できます。
リスト22~リスト24のスケッチをArduino Unoで実行して、実行時間を測定した結果をまとめたのが表2です。
データ送信10万回の 実行時間[ms] |
データ送信1回あたりの 実行時間[μs] |
(参考)波形から求めたデータ送信 1回あたりの実行時間[μs] |
|
---|---|---|---|
リスト22 (digitalWrite関数) |
11239 | 112.39 | 107.0 (図19参照) |
リスト23 (shiftOut関数) |
12453 | 124.53 | 123.6 (図26参照) |
リスト24 (FASTIOライブラリ) |
609 | 6.09 | 4.57 (図27参照) |
表2には、データ送信1回あたりにかかる時間も計算して、10万回の送信にかかる時間の右の列に、μs単位で書いてあります。
さらに右の列には、ロジックアナライザで観測した波形から測定した、データ送信1回あたりの実行時間(関数の呼び出しや、関数からのリターンにかかる時間を含まない時間)を、参考データとして書いてあります。
参考:データ送信10万回あたりの実行時間をms単位で表示した数字を100で割ると、データ送信1回あたりの実行時間をμs単位で表示した数に換算できます。
表2を基に計算すると、FASTIOライブラリを使った場合の送信時間は、digitalWrite関数を使った場合の5.42%、shiftOut関数を使った場合の4.89%になります。この様に、実行速度の大幅な上昇が観察されました。
次回は、カスケード接続した複数の74HC595をArduinoで制御する方法について書く予定です。続きを書くのはぼちぼちと。