こんにちは、ENGかぴです。
Arduinoは機能が充実しているためLCDや7セグメントLEDを使用しているとデジタルピンが不足することがあります。マルチプレクサを使うことでDIの不足を補うことができます。マルチプレクサでDIを拡張する方法をまとめました。
本記事はArduinoのDIをマルチプレクサで拡張しスイッチの状態変化を7セグメントLEDに表示することを目的としています。マルチプレクサは74HC4051APを使用しています。
Arduino UNOを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
DIをマルチプレクサで拡張する
マルチプレクサは8つのスイッチを切り替えて使用する部品です。スイッチの片方はコモン(COM)でありマイコンのデジタルピンに接続することで8つの入力を切り替えながら入力することができます。
マルチプレクサのCOMにDIとDOのどちらでも接続はできますが、DOで使用する場合は出力の保持ができないので注意が必要です。
マルチプレクサのスイッチ出力の切り替え

マルチプレクサはINHをLにするとデバイスが動作開始します。A、B、Cで0~7のチャンネルを切り替えて共通のCOMに入力します。マイコンのDIにCOMを接続するとA、B、Cで指定したチャンネルのDI情報をCOMを介して受けることができます。
PR:RUNTEQ(ランテック )- マイベスト4年連続1位を獲得した実績を持つWebエンジニア養成プログラミングスクール
マルチプレクサを制御する
マルチプレクサを使用するための初期化とビット操作について説明します。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ウェイトを置いています。
上の例では、ウェイト後に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と接続されます。
PR:わからないを放置せず、あなたにあったスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!
7セグメントLEDの表示パターンを追加

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のA0~A3をマルチプレクサ用のDIOに接続しています。A0~A2をマルチプレクサの切り替えの制御に使用しA3をDIにしています。DIはマイコンの内蔵プルアップを使用しているため抵抗によるプルアップを実装していません。
C1はマルチプレクサの電源間のバイパスコンデンサとして使用しています。ロジックICなどは電源間に0.1uF程度のバイパスコンデンサを実装するのが一般的です。
SW1とSW2をONするとLになるようにしています。マルチプレクサによってSWの情報がArduinoのDIに入力されます。DIは内部プルアップ付きのDIとしているためSWをONしていない箇所においてはHとなります。

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で学べるソフト開発と標準ライブラリの使い方
最後まで、読んでいただきありがとうございました。
 
 

 

