ArduinoのDIをマルチプレクサで拡張して状態変化を表示する

組み込みエンジニア

こんにちは、ENGかぴです。

Arduinoは機能が充実しているためLCDや7セグメントLEDを使用しているとデジタルピンが不足することがあります。マルチプレクサを使うことでDIの不足を補うことができます。マルチプレクサでDIを拡張する方法をまとめました。

本記事はArduinoのDIをマルチプレクサで拡張しスイッチの状態変化を7セグメントLEDに表示することを目的としています。マルチプレクサは74HC4051APを使用しています。

Arduino UNOを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

DIをマルチプレクサで拡張する

マルチプレクサは8つのスイッチを切り替えて使用する部品です。スイッチの片方はコモン(COM)でありマイコンのデジタルピンに接続することで8つの入力を切り替えながら入力することができます。

マルチプレクサのCOMにDIとDOのどちらでも接続はできますが、DOの場合信号の保持が難しいため外部機器がキープリレーなど値を保持できるものに用途が限定されます。

マルチプレクサのスイッチ出力の切り替え

TC74HC4051APのピン配置と真理値表(引用:東芝セミコンダクターデータシート)
TC74HC4051APのピン配置と真理値表(引用:東芝セミコンダクターデータシート)

マルチプレクサはINHをLにするとデバイスが動作開始します。COMを共通としてA、B、Cの入力信号によって信号名0~7を切り替えながら出力します。COMをマイコンのDIに接続することでマルチプレクサの8本のDIをCOMのDI1本で受けることができます。

出力する信号名はAをLSB(最下位ビット)、CをMSB(最上位ビット)となるように接続するマイコンのDOの出力を調整する必要があります。

マルチプレクサを制御する

マルチプレクサを使用するための初期化とビット操作について説明します。ArduinoのA0~A5はアナログ入力ピンとデジタルピンを兼用しているため今回はA0~A3をマルチプレクサ用のピンとして使用します。

#define PIN_MUL_DOA 14 //A0でもよい
#define PIN_MUL_DOB 15 //A1でもよい
#define PIN_MUL_DOC 16 //A2でもよい
#define PIN_DI1 17 //A3でもよい

void setup(){
    pinMode(PIN_DI1,INPUT_PULLUP); //マルチプレクサのCOMに接続
    pinMode(PIN_MUL_DOA,OUTPUT); //マルチプレクサのAに接続
    pinMode(PIN_MUL_DOB,OUTPUT); //マルチプレクサのBに接続
    pinMode(PIN_MUL_DOC,OUTPUT); //マルチプレクサのCに接続
}

マルチプレクサの信号の切り替えはA、B、Cによって制御するためマイコンでDOを3つ準備します。マルチプレクサのCOMをマイコンのDIに取り込むためにマイコン内蔵のプルアップ付きのDIとして初期化を行います。

uint8_t di[MUL_MAX];

void loop(){
    uint8_t mulmode;
    bool loopflg = true;

    mulmode = MUL_0;

    do{ 
        MulDiCng(mulmode); //マルチプレクサの制御
        delayMicroseconds(10); //10us切り替え後の遅延

        di[ mulmode ] = digitalRead(PIN_DI1);
        
        if( ++mulmode >= MUL_MAX){
            loopflg = false;
        }
    }while(loopflg);
}

マルチプレクサの制御の例を示しています。関数MulDiCng()をコールしてマルチプレクサの制御を切り替えています。切り替えた後は信号安定化のため10usウェイトを置いています。

データシートによるとVCC=4.5V時で最大で500nsのスイッチングになるため余裕をもって10usのウェイトにしています。

上の例では、ウェイト後に0ピン(PIN_DI1)を読み込んでいますが、外部信号を取り込む場合はチャタリングなどによって信号が安定しないことがあるためフィルタ処理を追加することがあります。チャタリング防止の方法については下記記事にまとめています。

Arduino環境でのタイマ管理とDIのチャタリング防止の方法

ソースコード全体では4回一致のフィルタを実装しています。

#define USE_BITS 3 //3ビット使用する

/* Led7Seg pattern function add */
void MulDiCng(uint8_t no){
    uint8_t i;
    uint8_t bit;    

    for( i=0; i < USE_BITS; i++){
        bit = 1 << i;

        if( bit & no){ //ビットが立っているかの確認
            digitalWrite(i + PIN_MUL_DOA, HIGH);
        }
        else{
            digitalWrite(i + PIN_MUL_DOA, LOW); 
        }
    }
}

引数に0~7までの値を指定し引数の値に応じて下位ビットから順にビットが立っているかの確認を行い、ビットが立っている場合はDOにHをセットします。引数に3を指定した場合を例にして説明します。

例)引数に3を指定した場合
0x03と0x01の論理積は1なので14ピン(PIN_MUL_DOA)がHとなる。
0x03と0x02の論理積は1なので15ピン(PIN_MUL_DOB)がHとなる。
0x03と0x04の論理積は0なので16ピン(PIN_MUL_DOC)がLとなる。

0x03と各ビットの論理積が1であれば対象のDOにHをセットし0であればLをセットするようにします。真理値表によるとAとBがHとなるため3が出力対象になりCOMと接続されます。

7セグメントLEDの表示パターンを追加

7セグLEDの表示パターン
7セグLEDの表示パターン

7セグメントLEDの使い方や表示パターンの作り方については下記記事にまとめています。

ArduinoのDO制御で7セグメントLEDを表示する

表示パターンを0~9だけでなく「A、b、c、d、E、F、ー」を追加します。表示パターンは7セグメントLEDのa~gまでの各LEDに対して点灯させたい箇所を1として2進数で並べて16進数に変換した値をパターンとして準備しています。

#define LED7SEG_NEGATIVE //負論理の時コメントアウトを外す

byte Seg7Set[17]={ 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x27, 0x7F, 0x6F,
                   0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x40};// A,b,C,d,E,F,-

void setup(){

    #ifdef LED7SEG_NEGATIVE
        for(uint8_t i=0; i < sizeof(Seg7Set); i++){
            Seg7Set[i] = (~Seg7Set[i] & 0x7F); //負論理の7セグメントLEDの場合に反転する
        }
    #endif  
}

7セグメントLEDの表現方式から大文字と小文字が混在していますが、このパターンによって16進数での表現が可能となります。

7セグメントLEDのLEDの極性がLOWアクティブのものを使っている場合はSeg7Set[]内のパターンを論理反転して使用します。

動作確認

Arduinoにマルチプレクサを接続して動作確認した回路図
Arduinoにマルチプレクサを接続して動作確認した回路図

ArduinoのA0~A3をマルチプレクサ用のDIOに接続しています。A0~A2をマルチプレクサの切り替えの制御に使用しA3をDIにしています。DIはマイコンの内蔵プルアップを使用しているため抵抗によるプルアップを実装していません。

C1はマルチプレクサの電源間のバイパスコンデンサとして使用しています。ロジックICなどは電源間に0.1uF程度のバイパスコンデンサを実装するのが一般的です。

SW1とSW2をONするとLになるようにしています。マルチプレクサによってSWの情報がArduinoのDIに入力されます。DIは内部プルアップ付きのDIとしているためSWをONしていない箇所においてはHとなります。

状態変化を7セグメントLEDに表示
状態変化を7セグメントLEDに表示

SWを操作したときの状態変化を検出して7セグメントLEDにできていることが確認できました。

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。チェックはしておりますがコメントなど細かな部分で間違っている可能性があります。参考としてお使いいただければと思います。

#include <MsTimer2.h>

#define PIN_DO_LEDA 0
#define PIN_DO_LEDB 1
#define PIN_DO_LEDC 2
#define PIN_DO_LEDD 3
#define PIN_DO_LEDE 4
#define PIN_DO_LEDF 5
#define PIN_DO_LEDG 6

#define PIN_DO_LEDDG_P4 7
#define PIN_DO_LEDDG_P3 8
#define PIN_DO_LEDDG_P2 9

#define PIN_MUL_DOA 14 //A0でもよい
#define PIN_MUL_DOB 15 //A1でもよい
#define PIN_MUL_DOC 16 //A2でもよい
#define PIN_DI1 17 //A3でもよい
#define LED7SEG_NEGATIVE //負論理の時コメントアウトを外す

#define DI_FILT_MAX 4
#define MUL_MAX 8
#define USE_BITS 3 //3ビット使用する
#define TIME_UP 0
#define TIME_OFF -1
#define BASE_CNT 10 //10msがベースタイマとなる
#define FILT_MIN 1

struct DIFILT_TYP{
    uint8_t wp;
    uint8_t buf[MUL_MAX][DI_FILT_MAX];
    uint8_t di[MUL_MAX];
    uint8_t olddi[MUL_MAX];
};

enum LED7SEG{
    LED_A = 0,
    LED_B,
    LED_C,
    LED_D,
    LED_E,
    LED_F,
    LED_G,
    LED_MAX
};

enum MUL_NO{
    MUL_0 = 0,
    MUL_1,
    MUL_2,
    MUL_3,
    MUL_4,
    MUL_5,
    MUL_6,
    MUL_7,
    MUL_NO_MAX
};

typedef enum{
    COL_NO1 = 0,
    COL_NO2,
    COL_NO3,
    COL_NO4,
    COL_MAX
}COL_NO;

// application use
DIFILT_TYP difilt;
int16_t timdifilt = TIME_OFF;
int16_t cnt10ms;
byte Seg7Set[17]={ 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x27, 0x7F, 0x6F,
                   0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x40};// A,b,C,d,E,F,-
uint8_t dicng;
/*** Local function prototypes */
void TimerCnt();
void mainTimer();
void DiFilter();
void Led7SegClr();
void Led7SegSet(uint8_t no);
void Led7SegMng();

void setup(){
    uint8_t initcnt;

    pinMode(PIN_DO_LEDA,OUTPUT);
    pinMode(PIN_DO_LEDB,OUTPUT);
    pinMode(PIN_DO_LEDC,OUTPUT);
    pinMode(PIN_DO_LEDD,OUTPUT);
    pinMode(PIN_DO_LEDE,OUTPUT);
    pinMode(PIN_DO_LEDF,OUTPUT);
    pinMode(PIN_DO_LEDG,OUTPUT);
    pinMode(PIN_DO_LEDDG_P4,OUTPUT);
    pinMode(PIN_DO_LEDDG_P3,OUTPUT);
    pinMode(PIN_DO_LEDDG_P2,OUTPUT);

    pinMode(PIN_DI1,INPUT_PULLUP); //マルチプレクサのCOMに接続
    pinMode(PIN_MUL_DOA,OUTPUT); //マルチプレクサのAに接続
    pinMode(PIN_MUL_DOB,OUTPUT); //マルチプレクサのBに接続
    pinMode(PIN_MUL_DOC,OUTPUT); //マルチプレクサのCに接続

    MsTimer2::set(1,TimerCnt); //1msごとに関数へ遷移
    MsTimer2::start();

    #ifdef LED7SEG_NEGATIVE
        for(uint8_t i=0; i < sizeof(Seg7Set); i++){
            Seg7Set[i] = (~Seg7Set[i] & 0x7F);
        }
    #endif    

    timdifilt = FILT_MIN;

    initcnt = 0;
    while(initcnt< 10){ //DIを確定する
        mainTimer();
        DiFilter();
        ++initcnt;
        delay(10);
    }
    dicng = 0;

    Led7SegClr();
}

void loop(){

    mainTimer();
    DiFilter();
    Led7SegMng();
}
/* callback function add */
void TimerCnt(){
    ++cnt10ms;
}
/* Timer Management function add */
void mainTimer(){

    if( cnt10ms >= BASE_CNT ){
        cnt10ms -=BASE_CNT; //1msごとにここに遷移する

        if( timdifilt > TIME_UP ){
            timdifilt--;
        }
    }
}
/* DiFilter function add */
void DiFilter(){
    uint8_t mulmode;
    bool loopflg = true;

    if( timdifilt == TIME_UP ){
        mulmode = MUL_0;
        do{ 
            MulDiCng(mulmode); //マルチプレクサの制御
            delayMicroseconds(10); //10us切り替え後の遅延

            difilt.buf[mulmode][difilt.wp] = digitalRead(PIN_DI1);

            if( difilt.buf[mulmode][0] == difilt.buf[mulmode][1] &&
                difilt.buf[mulmode][1] == difilt.buf[mulmode][2] &&
                difilt.buf[mulmode][2] == difilt.buf[mulmode][3] ){ //4回一致を確認
                difilt.di[mulmode] = difilt.buf[mulmode][0];
                }

            if( ++mulmode >= MUL_MAX){
                loopflg = false;
            }

        }while(loopflg);

        if( ++difilt.wp >= DI_FILT_MAX ){
            difilt.wp = 0;
        }

        for( uint8_t i=0; i < MUL_MAX; i++){
            if( difilt.di[i] ^ difilt.olddi[i]){ //対象のDIに変化があるか
                dicng = i;
            }
            difilt.olddi[i] = difilt.di[i];
        }        

        timdifilt = FILT_MIN;
    }
}
/* Led7Seg pattern function add */
void MulDiCng(uint8_t no){
    uint8_t i;
    uint8_t bit;    

    for( i=0; i < USE_BITS; i++){ //ビットが立っているかの確認
        bit = 1 << i;

        if( bit & no){
            digitalWrite(i + PIN_MUL_DOA, HIGH);
        }
        else{
            digitalWrite(i + PIN_MUL_DOA, LOW); 
        }
    }
}

/* Led7Seg Clear function add */
void Led7SegClr(){
    uint8_t i;
    uint8_t out;

    #ifdef LED7SEG_NEGATIVE
        out = HIGH;
    #else
        out = LOW;
    #endif
    
    for( i = 0; i < 7; i++){ //a~gまでの総数
        digitalWrite(i + PIN_DO_LEDA, out);
    }
}
/* Led7Seg pattern function add */
void Led7SegSet(uint8_t no){
    uint8_t i;
    uint8_t bit;    

    for( i=0; i < 8; i++){
        bit = 1 << i;

        if( bit & no){
            digitalWrite(i + PIN_DO_LEDA, HIGH);
        }
        else{
            digitalWrite(i + PIN_DO_LEDA, LOW); 
        }
    }
}

/* Led7Seg Management function add */
void Led7SegMng(){
    bool loopflg=true;
    COL_NO col;
    uint8_t out_off;
    uint8_t out_on;

    #ifdef LED7SEG_NEGATIVE
        out_on = HIGH;
        out_off = LOW;
    #else
        out_on = LOW;
        out_off = HIGH
    #endif

    do{
        switch (col)
        {
            case COL_NO::COL_NO1:
                digitalWrite(PIN_DO_LEDDG_P4,HIGH);
                Led7SegSet(Seg7Set[dicng]);
                digitalWrite(PIN_DO_LEDDG_P4,out_off);
                col = COL_NO::COL_NO2;
                break;
            case COL_NO::COL_NO2:
                digitalWrite(PIN_DO_LEDDG_P3,out_on); //右から2番目の桁を選択

                #ifdef LED7SEG_NEGATIVE
                    Led7SegSet(Seg7Set[16]); //-を表示
                #else
                    Led7SegSet(Seg7Set[16]);
                #endif

                digitalWrite(PIN_DO_LEDDG_P3,out_off); //右から2番目の桁の選択を解除
                col = COL_NO::COL_NO3;
                break;
            case COL_NO::COL_NO3:
                digitalWrite(PIN_DO_LEDDG_P2,out_on); //右から3番目の桁を選択
                Led7SegSet(Seg7Set[13] ); //dを表示
                digitalWrite(PIN_DO_LEDDG_P2,out_off); //右から3番目の桁の選択を解除
                col = COL_NO::COL_NO1;
                loopflg = false;
                break;
            default:
                break;
        }
        
        Led7SegClr();
     
    }while(loopflg);
}

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

GEEKJOB-未経験からITエンジニアに【オンライン無料体験】

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました