● 少ないピンで多くの入出力

■ 入力方法を考える

▼アナログピンを使った入力方法
アナログピンは電圧を測定することができます。
そこで、
抵抗分圧で電圧を下げるの方法により、スイッチ毎に電圧を変えて、一つのアナログピンで複数のスイッチのON/OFFを取得します。
複数スイッチの同時押しや、もっとスイッチを増やす事も可能でしょうけど、たぶん抵抗値のバラツキや精度、湿度やノイズなどを考慮する必要がありそうです。


スイッチのONにより発生する電圧は抵抗分圧の計算式を使って求めます。

Vccが5Vの場合
・SW1の電圧の計算
( 10K / ( 100K + 10K )) * 5V = 0.45V
・SW2の電圧の計算
( 10K / ( 10K + 10K )) * 5V = 2.5V
・SW3の電圧の計算
( 10K / ( 1K + 10K )) * 5V = 4.54V
・SW4の電圧の計算
( 10K / ( 0 + 10K )) * 5V = 5V

次に、この電圧が分解能1024のアナログ/デジタル変換によりどのくらいの値を取るのか計算します。
分解能1024は、0から1023の値をとります。
SW1は、 ( 0.45 / 5 ) * 1023 = 92
SW2は、 ( 2.5 / 5 ) * 1023 = 511
SW3は、 ( 4.54 / 5 ) * 1023 = 929
SW4は、 ( 5 / 5 ) * 1023 = 1023

実際に作成してArduinoに接続しました。
Vccは5V、GNDはGND、アナログピンはA0に接続しています。


Arduinoには次のコードを書き込みます。
void setup()
{
  pinMode(A0, INPUT);
  Serial.begin( 9600 );
}

void loop()
{
  int buttonValue = analogRead(A0);
  Serial.println(buttonValue);
  delay(2000);
}
シリアルモニタを開いて、SW1から順にスイッチをON/OFFしました。
出力された値は計算値と近い値になっています。



・チャタリング対策
アナログ値といえどもスイッチのチャタリングが発生します。
そこで以下のコードを作成しました。
SW2のON/OFFにより、Lチカ(基板上のLED)が点灯/消灯します。
チャタリングにより正しく動作しないはずです。
void setup()
{
  pinMode(A0, INPUT);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop () {
  int buttonValue = analogRead(A0);
  //SW2のON状態の判定
  if ((511 - 10) < buttonValue && (511 + 10) > buttonValue) {
    //アナログ値が変化するまで待つ
    do {
      buttonValue = analogRead(A0);
    } while ((511 - 10) < buttonValue && (511 + 10) > buttonValue);
    //LEDを点灯/消灯する
    Led_blink();
  }
}

//LEDを点灯/消灯する
void Led_blink(void) {
  //点灯状態を反転する
  static char sw;
  sw = !sw;
  digitalWrite(LED_BUILTIN, sw);
}

そこで、アナログピンの過去4回の値の平均値と現在値を取得してON/OFF判定することにしました。
void setup()
{
  pinMode(A0, INPUT);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop () {
  int avrValue = average(analogRead(A0));
  int buttonValue = analogRead(A0);
  if (sw_judge(avrValue, buttonValue, 92)) {//SW1のON状態の判定
    run_only_once();
  } else if (sw_judge(avrValue, buttonValue, 511)) {//SW2のON状態の判定
    run_only_once();
  } else if (sw_judge(avrValue, buttonValue, 929)) {//SW3のON状態の判定
    run_only_once();
  } else if (sw_judge(avrValue, buttonValue, 1023)) {//SW4のON状態の判定
    run_only_once();
  } else if (sw_judge(avrValue, buttonValue, 0)) { //SWを押してない状態の判定
    run_only_once_reset();
  }
}

bool run_only_once_flag;
//複数回呼ばれても最初の1回だけ実行
void run_only_once(void) {
  if (false == run_only_once_flag) {
    Led_blink();
    run_only_once_flag = true;
  }
}
//最初の1回だけ実行のリセット
void run_only_once_reset(void) {
  run_only_once_flag = false;
}

//LEDを点灯/消灯する
void Led_blink(void) {
  //点灯状態を反転する
  static char sw;
  sw = !sw;
  digitalWrite(LED_BUILTIN, sw);
}

//閾値の間に入っているか判定する
bool sw_judge(int avr, int but, int threshold) {
  if ((threshold - 10) < but && (threshold + 10) > but && (threshold - 10) < avr && (threshold + 10) > avr) {
    return true;
  }
  return false;
}

//平均を計算する
unsigned int average(int data) {
  static int buf[4];
  buf[0] = buf[1];
  buf[1] = buf[2];
  buf[2] = buf[3];
  buf[3] = data;
  return (buf[0] + buf[1] + buf[2] + buf[3]) / 4;
}
SW1〜SW4のスイッチを押すと、基板上のLEDがON/OFFしてチャタリングは発生しませんでした。


■ 出力方法を考える

▼ 74HC595 8bitシフトレジスタを使う方法
74HC595はシリアル入力で、パラレル出力の8ビットのシフトレジスタです。

SRCLRはシフトレジスタのクリア端子でHIGH(Vcc)を入れるとクリアしない、OEは出力のON/OFFでLOW(GND)を入れると常に出力
クロックはSRCLKに合わせてシリアルでSERにデータを入れて、RCLKで出力側に転送して実際に出力します。
QA〜QHがパラレル変換された出力端子になります。

シリアル入力パラレル出力を行うために次の回路を作成しました。

回路中の抵抗値は全て200Ω程度です。
SRCLKをArduinoのD3に、RCLKをD4に、SERをD5に、Vccを5V、GNDをGNDに接続しました。

実際に作成してArduinoと接続しました。
Arduinoには次のコードを書き込みました。
#define SRCLK 3
#define RCLK 4
#define SER 5

void setup() {
  Serial.begin(9600);
  pinMode(SRCLK, OUTPUT );
  pinMode(RCLK, OUTPUT );
  pinMode(SER, OUTPUT );
}

char i;
void loop() {
  shiftOut(SER, SRCLK, MSBFIRST, i);
  digitalWrite(RCLK, LOW);
  digitalWrite(RCLK, HIGH);
  Serial.println((unsigned char)i, BIN);
  i++;
  delay(100);
}
実行すると、変数 i の値のビットパターンに従いLEDが点灯します。
シリアルモニタを開くと点灯しているパターンと同じ2進数が表示されます。

点灯していないLEDも瞬間的に点灯しているんじゃないかと気になりますが、 シフトレジスタが受信したデータを赤色部分にて出力レジスタに転送していますので、
digitalWrite(RCLK, LOW);
digitalWrite(RCLK, HIGH);

この部分が実行されるまで出力は維持されたままで変化しないですから、瞬間的に点灯することは無いようです。


・ shiftOutを使わない方法
シリアルでビットを送信してくれるという便利な方法を使わない方法です。
シフトレジスタに対して赤文字部分にて信号を送っています。
#define SRCLK 3
#define RCLK 4
#define SER 5

void setup() {
  pinMode(SRCLK, OUTPUT );
  pinMode(RCLK, OUTPUT );
  pinMode(SER, OUTPUT );
  //LEDを全部消す
  shiftOut(SER, SRCLK, MSBFIRST, 0x00);
  digitalWrite(RCLK, LOW);
  digitalWrite(RCLK, HIGH);
}

void loop() {
  delay(1000);
  digitalWrite(SRCLK, LOW); //シフトクロック入力を下げる
  digitalWrite(SER, HIGH);  //LEDの点灯信号
  digitalWrite(SRCLK, HIGH);//シフトクロック入力を上げる この時にSERの状態が取り込まれる
  digitalWrite(RCLK, LOW);  //ストレージレジスタへの書き込みを下げる
  digitalWrite(RCLK, HIGH); //ストレージレジスタへの書き込みを上げる この時にストレージレジスタに転送される

  delay(1000);
  digitalWrite(SRCLK, LOW);
  digitalWrite(SER, LOW); //LEDの消灯信号
  digitalWrite(SRCLK, HIGH);
  digitalWrite(RCLK, LOW);
  digitalWrite(RCLK, HIGH);
}
このコードを実行するとONとOFFが順次送られて点灯します。


・複数のシフトレジスタを連結する
8個の出力で足りない場合には複数段接続することができます。
シフトレジスタはタイミングに従って信号を順次伝えていく仕組みで動いています。
1段目のシフトレジスタの最終段の出力を次のシフトレジスタの入力に入れてやれば何個でも連結できます。
ココに出ている QH' が次の段への出力です。


このQH'を次のシフトレジスタのSERに入れてやります。
SER以外の他の接続はすべて1段目と共有です。


実際に作成してみた所


LEDを点灯させると、1段目のLEDの延長上に2段目のLEDが点灯します。
転送時間と場所が許す限り、シフトレジスタは何段でも接続可能です。


■ マルチプレクサを使用する

マルチプレクサとは切り替え器の事で、複数の信号線から欲しい線を取り出す回路です。
CD74HC4067 高速 CMOS 16チャンネル アナログマルチプレクサ を使用しました。
このモジュールは一つの入出力を16箇所にスイッチで切り替える動作をする物でした。

S0〜S3 の4ビットの端子に HIGH/LOW の値を与えて、つまり2進数を与えて切り替える仕組みです。
C0〜C15 が切り替える相手で SIG とつながる動作をします。
あとは、EN を HIGH にすると全て遮断という動きをします。

私はこのような回路を作り動作を確認しました。
Arduino    Multiplexer
  A0 ------- SIG
  A1 ------- S3
  A2 ------- S2
  A3 ------- S1
  A4 ------- S0
  A5 ------- EN

マルチプレクサの切り替え先端子には LEDと抵抗100Ω を取り付け GND に接続しました。
  C0 --- LED+100Ω --- GND
  C1 --- LED+100Ω --- GND
  C2 --- LED+100Ω --- GND
  C3 --- LED+100Ω --- GND
面倒なので4個だけ取り付けましたが、全部で C0〜C15 の端子で16個取り付けられます。

なんでこんな妙な配線にしたかって?それは写真を見れもらえれば。
直接Arduinoに刺して回路をシンプルにしたかったのです。
void setup(void)
{
  pinMode(A0, OUTPUT);
  pinMode(A1, OUTPUT);
  pinMode(A2, OUTPUT);
  pinMode(A3, OUTPUT);
  pinMode(A4, OUTPUT);
  pinMode(A5, OUTPUT);
}

void loop(void)
{
  //接続先のアドレスを指定
  digitalWrite(A1, LOW);
  digitalWrite(A2, LOW);
  digitalWrite(A3, LOW);
  digitalWrite(A4, HIGH);

  digitalWrite(A5, HIGH); //接続を切断
  digitalWrite(A0, HIGH); //接続先のLEDを点灯
  digitalWrite(A5, LOW); //接続をつなげる
  delay(1000);
}
このプログラムだとアドレスが 0001 となるため、0001番である C1 に SIG が接続されてLEDが点灯します。

ある端子のLEDをONにして他の端子に切り替えると点灯していたLEDがOFFになりました、ラッチとか固定の仕組みは無いようです。


▼ 流水ランプを作成する
一つ一つのアドレスの指定が面倒なので union 型を作成してビットフィールドを使いました。
1バイトしか使ってないのでエンディアンは関係ないです、気にするのは2バイトからです。
union Field
{
  struct Bit
  {
    unsigned char b1 : 1;
    unsigned char b2 : 1;
    unsigned char b3 : 1;
    unsigned char b4 : 1;
    unsigned char b5 : 1;
    unsigned char b6 : 1;
    unsigned char b7 : 1;
    unsigned char b8 : 1;
  } bit;
  unsigned char ch;
};

void setup(void)
{
  Serial.begin(9600);
  pinMode(A0, OUTPUT);
  pinMode(A1, OUTPUT);
  pinMode(A2, OUTPUT);
  pinMode(A3, OUTPUT);
  pinMode(A4, OUTPUT);
  pinMode(A5, OUTPUT);
}

void _write(int no, int value) {
  Field f;
  f.ch = no;
  //接続先のアドレスを指定
  digitalWrite(A1, f.bit.b4);
  digitalWrite(A2, f.bit.b3);
  digitalWrite(A3, f.bit.b2);
  digitalWrite(A4, f.bit.b1);

  digitalWrite(A5, HIGH); //接続を切断
  digitalWrite(A0, value);//LEDを点灯
  digitalWrite(A5, LOW); //接続をつなげる
}

void loop(void)
{
  _write(0, HIGH);//C0を点灯
  delay(1000);
  _write(1, HIGH);//C1を点灯
  delay(1000);
  _write(2, HIGH);//C2を点灯
  delay(1000);
  _write(3, HIGH);//C3を点灯
  delay(1000);
}
動作させると4個のLEDが1秒毎に流れるように点灯します。


▼ アナログ値を読み込む
可変抵抗の両端をVCCとGNDに接続しました抵抗を回すと中央の端子の電圧が変化します。
中央の端子をC14とC15に接続し電圧を読み取ります。

C14とC15だけでなくて、C0〜C15までの複数のアナログ入力を一つのアナログピンで読み取る事が出来ます。
union Field
{
  struct Bit
  {
    unsigned char b1 : 1;
    unsigned char b2 : 1;
    unsigned char b3 : 1;
    unsigned char b4 : 1;
    unsigned char b5 : 1;
    unsigned char b6 : 1;
    unsigned char b7 : 1;
    unsigned char b8 : 1;
  } bit;
  unsigned char ch;
};

void setup(void)
{
  Serial.begin(9600);
  pinMode(A0, OUTPUT);
  pinMode(A1, OUTPUT);
  pinMode(A2, OUTPUT);
  pinMode(A3, OUTPUT);
  pinMode(A4, OUTPUT);
  pinMode(A5, OUTPUT);
}

int _read(int no) {
  Field f;
  f.ch = no;
  //接続先のアドレスを指定
  digitalWrite(A1, f.bit.b4);
  digitalWrite(A2, f.bit.b3);
  digitalWrite(A3, f.bit.b2);
  digitalWrite(A4, f.bit.b1);

  digitalWrite(A5, LOW); //接続をつなげる
  pinMode(A0, INPUT);//入力に切り替える
  int r = analogRead( A0 );
  pinMode(A0, OUTPUT);//出力に切り替える
  digitalWrite(A5, HIGH); //接続を切断
  return r;
}

void loop(void)
{
  Serial.print(14, DEC);
  Serial.print(" ");
  Serial.println(_read(14), DEC);

  Serial.print(15, DEC);
  Serial.print(" ");
  Serial.println(_read(15), DEC);
  delay(1000);
}
2個の可変抵抗の電圧がアナログ値として読み取られてシリアルモニタに出力されます。


▲トップページ > マイコンなど