Arduinoで 74HC165 (Arduinoでデジタルインを増やす)

Arduinoで 74HC165 (Arduinoでデジタルインを増やす)

はじめに

Arduinoなどのマイコンで入出力のためのピンが足りなくなった場合、ICを使って増やすことができます。
デジタルアウトピンを増やす場合は、汎用のシフトレジスタである74HC595が使われていますが、デジタルインピンを増やす場合は74HC165を使うのが一般的だそう。

今回は74HC165を使ってArduinoで大量のスイッチから入力しようという話。

74HC165 とは

概要

74HC165のピン配列は上の通り。パッケージによっては配列は違うのでデータシートを要確認。駆動電圧は2-6 Vと広いため、5Vでも3.3Vでも駆動する。

基本的にはAからHまで(パラレル入力)に入ってきた8つの値をQHまたはQHからシリアルデータとして出力する。ので、パラレルシリアル変換と呼ばれます。QHとQHは互いに値が反転しているだけなので基本的に使うのはどちらか一方だけで大丈夫です。

制御用としてSH/LD (Shift or Load input)、CLK (Clock)とQH (Serial Output)の3つのピンを使ってA-Hの8入力のデジタルインが増えるので、ArduinoのI/Oピンの拡張に持ってこれます。
きちんと動作させたい場合は加えてCLK INH (Clock Inhibit)の4ピンで制御させる必要があるもので、8ピンの拡張に4ピン使ってしまう。
さらに74HC595と同様に74HC165もSER (Serial Input)とQH’ピンを複数個をつなげ合わせることでデジタルピン3つだけでどんどんデイジーチェーンしていくことができるので8個、16個…とデジタルインをどんどん増やすことができます。

74HC165自体もシリアルのソフトウェア制御またはSPI通信で処理するが、ArduinoなどのGPIOピンと比べて処理に時間がかかる(数ns)ので早い処理が必要な時は74HC165を使わずGPIOピンで直接制御するのが良いです。

74HC165 の動作

はじめにSH/LD をLowにしたタイミングで74HC165に読み込ませます。SH/LD信号をHighにしたタイミングでシフトレジスタに取り込まれる情報が確定します。
SH/LDがHighになったタイミングでHに入っている値がQHから出力されており、その後CLKがHighに立ち上げるたびにシフトレジスタに格納されている情報が順次QHに出力されます。
QHの値が変わるタイミングはCLKがLowからHighに変わるタイミングなのでQHを読み取るタイミングはCLKがHighでもLowでもどちらでも良いです。

基本的にSH/LDがHighになっている間にはA-Hの各入力端子がHighでもLowでもシフトレジスタの値は変更されません。SH/LDがLowのタイミングで入力の値を読み取りHighになったタイミングで確定してしまうからです。

そのほか、CLK INHはCLKと論理和(OR)回路として74HC165のClockとして働くのでCLK INHがHighだとCLK入力を受け付けなくなります。タイミングを調整する場合はCLK INHを制御用に使いますが、その必要がなければGNDに接続します。
また使用しない入力ピンもGNDかVccに接続させて安定させておくのが良いです。

74HC165を使う時の処理まとめ

内部処理に関する理解が少しできたので、実際に74HC165を使う時には

  1. SH/LDをLowにして入力を受け付ける
    A-Hを入力しながら、SH/LDをHighにして入力を確定する
  2. QHの値を読み取る (H入力の値を読み取る)
  3. CLKをHighにしてQHの値をずらす
    QHの値を読み取る (H以外の入力の値を読み取る)
    CLKをLowにもどす
  4. 3の動作を7回繰り替えす

Arduinoで74HC165を使う

はじめに

用意するもの

Arduino本体 (今回はArduino Uno互換機)
Akizuki : http://akizukidenshi.com/catalog/g/gM-07385/

74HC165 (今回はSN74HC165N)
Digi-Key : https://www.digikey.jp/product-detail/ja/texas-instruments/SN74HC165N/296-8251-5-ND/376966

タクタイルスイッチ
Akizuki : http://akizukidenshi.com/catalog/g/gP-03647/

抵抗(10kΩ)
Akizuki : http://akizukidenshi.com/catalog/g/gR-25103/

ブレッドボードとケーブル
Akizuki : http://akizukidenshi.com/catalog/g/gP-00315/
Akizuki : http://akizukidenshi.com/catalog/g/gC-05371/

ArduinoのShiftIn()関数に関して

Arduinoには汎用の関数としてShiftIn()関数があります。

Arduinoリファレンス : https://www.arduino.cc/reference/jp/language/functions/advanced-io/shiftin/

Arduinoでシリパラ変換をする時に便利な関数としてShiftOut()ShiftIn()がありますが、74HC595などを使ったシリパラ変換の場合ShiftOut()を使えば問題なくパラレル出力ができていました。

今回同様にShiftIn()を使って入力しようとしますが、AからHまでの入力に対して、正常にシリアルデータを受信することができず、シフトがずれてしまうデータを受け取ってしまいます。

ShiftIn()の実際の処理が

  1. SH/LDをLowにして入力を受け付ける
    A-Hを入力しながら、SH/LDをHighにして入力を確定する
  2. QHの値を読み取る (H入力の値を読み取る)
  3. CLKをHighにしてQHの値をずらす
    QHの値を読み取る (H以外の入力の値を読み取る)
    CLKをLowにもどす
  4. 3の動作を7回繰り替えす

のうちの3.の動作を8回行うためで、CLKをあげてしまってからデータを読むためにH入力の情報が捨てられてしまうためです。
そのため対策として、ShiftIn()を走らせる前にHのデータを読み取らないといけなく、また74HC165をデイジーチェインしている場合はズレたビットは2番目以降の74HC165からのデータを受けることになるのでさらなる対策が必要になってきます。
ついでにSPI接続を使って処理した場合もこのズレは起きます。

74HC165を1つしか使わず、さらに8つの入力を処理しない(7個以下の入力で処理する)場合は、単純にH入力を使わないという対策でShiftIn()を使うことで対策できます。

それ以外の場合は、ShiftIn()関数を使わずに、シリパラ変換のための処理を自分でコーディングするという手が手っ取り早いですね。

Arduinoボード

配線はこんな感じ。SH/LD、CLK、QHをArduinoとつなぎます。各種入力用のスイッチはプルアップ抵抗をさせて74HC165とつなぎます。
CLK INHの制御はしないのでGNDに。74HC165を複数繋がないのでSERもGNDに落としています。

コードの全体

/*
 * 74HC165
 * SH/LD 2
 * CLK 3
 * QH 4
 */

int const SL = 2;
int const CLK = 3;
int const SER = 4;

int incomingData;
int incomingDataOld;

byte myShiftIn(int dataPin, int clockPin, int loadPin){

  byte data;

  digitalWrite(loadPin, LOW); //A-Hを格納
  digitalWrite(loadPin, HIGH); //確定
  
  data = digitalRead(dataPin); //Hの値を読む
  
  for (int i=1; i<8; i++){
    digitalWrite(clockPin, HIGH);
    data = data << 1 | (digitalRead(dataPin)); //G,F,E...Aの値を読む
    digitalWrite(clockPin, LOW);
  }

  return data;
}

void setup() {
  pinMode(CLK, OUTPUT);
  pinMode(SL, OUTPUT);
  pinMode(SER, INPUT);

  digitalWrite(SL, High);
  digitalWrite(CLK, Low);

  Serial.begin(9600);
}

void loop() {
  incomingData = myShiftIn(SER, CLK, SL);

  if (incomingData != incomingDataOld){
  Serial.println(incomingData, BIN);
  }
  incomingDataOld = incomingData;
}

全体のコードはこんな感じ。ShiftIn()関数の代わりにmyShiftIn()を作っただけという話です。loop()内部はmyShiftIn()からデータを読み取ってシリアル通信させるというだけの話ですね。

setup()内でdigitalWrite(SL, High);digitalWrite(CLK, Low);を行なっていますが、単純に初期状態にしているだけで74HC165を動作させているわけではありません。

myShiftIn()

byte myShiftIn(int dataPin, int clockPin, int loadPin){

  byte data;

  digitalWrite(loadPin, LOW); //A-Hを格納
  digitalWrite(loadPin, HIGH); //確定
  
  data = digitalRead(dataPin); //Hの値を読む
  
  for (int i=1; i<8; i++){
    digitalWrite(clockPin, HIGH);
    data = data << 1 | (digitalRead(dataPin)); //G,F,E...Aの値を読む
    digitalWrite(clockPin, LOW);
  }

  return data;
}

74HC165の処理動作と見比べながら見るとコードの内容が理解しやすいと思います。

  1. SH/LDをLowにして入力を受け付ける
    A-Hを入力しながら、SH/LDをHighにして入力を確定する
  2. QHの値を読み取る (H入力の値を読み取る)
  3. CLKをHighにしてQHの値をずらす
    QHの値を読み取る (H以外の入力の値を読み取る)
    CLKをLowにもどす
  4. 3の動作を7回繰り替えす

を順にやっていっているだけですね。
今回は「data << 1」としてシフトビットさせながら1byteに収めていますが、デジタルインなのでboolean型にして配列処理させてやったほうが後々の整理が楽になる可能性もありますね。

ついでながらfor()文を7回で止めています(i<8を終了条件にしています)が、これをさらに繰り返せば2個目より先のデイジーチェーンした74HC165の値を読み取ることができます。

まとめ

今回は74HC165の使い方をまとめました。
処理速度が早くなくていい場合、74HC165をデイジーチェーンしてやれば必要なデジタル入力ピンは3つで済むので、とても便利ですね。

参考

しなぷすのハード製作記 「74HC165」の解説 : https://synapse.kyoto/glossary/glossary.php?word=74HC165

電子部品の使い方 入力するピンが不足した場合に入力ピンを増やす方法 : http://zattouka.net/GarageHouse/micon/circuit/HC165.htm

Arduinoカテゴリの最新記事