PICマイコン(PIC16F1827)でLED8×8マトリックスを操作する

組み込みエンジニア
本記事はプロモーションが含まれています。

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

LED8×8マトリックスはLED8個を行と列にそれぞれ配置してアノードとカソードを共通に接続したモジュールです。DOを16本使用するとLED8×8マトリックスを操作することができます。任意の点灯パターンで動作させる例をまとめました。

LED8×8マトリックスはOSL641501-BXを使用しています。PIC16F1827ではDOが不足するのでDOの拡張のためマルチプレクサを使用しています。マルチプレクサは74HC4051AP(東芝セミコン)を使用しています。

PIC16F1827で動作確認したことについてリンクをまとめています。

PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ

以下ではLED8×8マトリックスをLEDマトリックスを表記します。

LEDマトリックスの使用方法

引用:1..5 Inch Siixty-Four (8x8) Dot Matriix OSL641501-XX VER A.1.2
引用:1..5 Inch Siixty-Four (8×8) Dot Matriix OSL641501-XX VER A.1.2

LEDはアノードからカソードに向けて電流を流すと点灯します。LEDマトリックスは行の8個のLEDのアノードを共通接続し、列の8個のLEDのカソードを共通接続したモジュールです。LEDの向きが引用と逆の場合はHIGHとLOWを逆に読み替えてください。

LEDを点灯は行(ROW)をHIGH、列(COL)をLOWにします。例えばROW1のCOL3のLEDを点灯させる場合はROW1(PIN NO.9)をHIGH、COL3(PIN NO.4)をLOWにします。行と列の両方がHIGHまたは両方ともLOWの場合、LEDには電流が流れないためLEDは点灯しません。

LEDマトリックスでLEDを点灯させる場合、電流を制限するために抵抗を実装する必要があります。抵抗がない場合は過負荷となりDOの出力電流が大きくなることで発熱して故障の原因になります。

LEDマトリックスは行列の対する制御となるため複雑な記号の表示などはできません。例えばLEDマトリックス上に×を表示することはできません。

広告

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

マルチプレクサは8つのスイッチを切り替えて使用する部品です。スイッチの片方はコモン(COM)であり、COMとマイコンのDIOに接続することでDIOの拡張できます。

マルチプレクサのCOMにDIとDOのどちらでも接続はできますが、DOで使用する場合は出力の保持ができないので注意が必要です。

マルチプレクサはLEDマトリックスのROWの操作に使用します。

スポンサーリンク

DO出力の切り替え

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

マルチプレクサはINHをLにするとデバイスが動作開始します。COMの出力をA、B、Cで0~7のチャンネルを切り替えて出力します。PIC16F1827のDOをCOMに入力しA、B、Cで出力先を切り替えて使用します。

広告

チャンネルの切り替え

マルチプレクサのチャンネルの切り替えの方法を説明します。チャンネルの切り替えのためDOを3つ準備します。PA0をA、PA1をB、PA2をCとして操作します。

void MultiSet(int8_t r ){
    uint8_t porta;
    
    IO_RA7_SetLow(); //INHをLOW
    porta = PORTA;
    porta &= 0xD8; //PA0~PA2をマスクする
    PORTA = porta | (r & 0x07); //A,B,Cを論理和
    IO_RA6_SetHigh(); //COMをHIGH
}

マルチプレクサの操作は自作の関数であるMultiSet()関数で行います。引数は操作するチャンネル番号を指定します。

マルチプレクサを使用するためINHをLOWにします。PA0~PA2以外のポートを操作しないようにPORTAで現在のポートを取得してからPA0~PA2の部分をマスクします。引数で取得したチャンネル番号の論理和を計算してPORTAをセットします。

マルチプレクサのCOMをHIGHにすると指定したチャンネルの出力がHIGHになります。

データシートによるとVCC=2.0V時で最大で1000nsのスイッチングになりますが、PIC16F1827でシステムクロックを4MHzにすると動作クロックが1MHz(1us)になるためスイッチングを気にする必要はありません。

void Led8Clr(void){
    
    IO_RA7_SetHigh(); //INHをHIGH
    IO_RA6_SetLow(); //COMをLOW
    PORTB = 0xFF; //COLをOFF
}

Led8Clr()関数はLEDマトリックスをすべて消灯する自作の関数です。マルチプレクサのINHをHIGHにすることでマルチプレクサの動作を停止します。COMをLOW、COL側をHIGHにすることでLEDが点灯しない条件にしています。

広告

LEDマトリックスを操作する

PIC16F1827のDOをマルチプレクサで拡張してLEDマトリックスを点灯させます。ROW(行)の制御をPORTAからマルチプレクサを介して行い、COL(列)の制御をPORTBで行います。

PIC16F1827とLEDマトリックスの配線

PIC16F1827とLEDマトリックスの配線
PIC16F1827とLEDマトリックスの配線

PIC16F1827とLEDマトリックスの配線例を示しています。マルチプレクサでROW(行)を制御しますが、電流制限の為1kΩの抵抗を実装しています。LEDマトリックス周りの配線は以下の通りです。

LEDマトリックスマルチプレクサPIC16F1827
9(ROW1)13(0)
14(ROW2)14(1)
8(ROW3)15(2)
12(ROW4)12(3)
1(ROW5)4(1)
7(ROW6)5(5)
2(ROW7)6(2)
5(ROW8)7(4)
13(COL1)PB0
3(COL2)PB1
4(COL3)PB2
10(COL4)PB3
6(COL5)PB4
11(COL6)PB5
15(COL7)PB6
16(COL8)PB7
LEDマトリクス周りの配線図

ROW1からROW8までをマルチプレクサのチャンネル名に合わせて配線しています。PA0~PA2はマルチプレクサのチャンネルの切り替えに使用し、PA6はマルチプレクサのCOM、PA7はINHで使用します。

PB0~PB7はLEDマトリックスのCOL1~COL8の順で配線しています。

LEDマトリックスのROW側をHIGH、COL側をLOWにすると対応するLEDが点灯します。

LEDマトリックスの点灯パターンを作る

if(timLed8 == TIME_UP){
  timLed8 = LED8_WAIT;
  Led8Clr(); //すべてOFF
  Led8Set(r_cnt, c_cnt); //ROWとCOLを選択してON

  if(++c_cnt >= 8){
    c_cnt = 0;
    
    if(++r_cnt >=8){
      r_cnt = 7;
      c_cnt = 7;
      patnMode = 1;
    }
  }
}

LEDマトリックスの点灯パターンの例を説明します。タイマで一定周期でLEDマトリックスを操作します。最初にLed8Clr()でLEDマトリクスを初期化を行います。次にLedSet()関数で行列の番号を指定して点灯します。

c_cntは列の番号を管理する変数、r_cntは行を管理する変数とします。c_cntを+1ずつ更新しながらLed8Clr()とLed8Set()を処理すると1列目、2列目と移動するようにLEDを点灯させることができます。c_cntを8回更新するとc_cntをクリアしてr_cntを更新します。

r_cntを+1するとLEDの点灯箇所が1行下がります。この状態でc_cntを更新すると2行列の1列目から8列目まで移動するようにLEDを点灯させることができます。

PR:テックジム:プログラミングの「書けるが先で、理解が後」を体験しよう!

LEDマトリクスを列方向に複数点灯させる

if( timLed8 == TIME_UP){ 
  timLed8 = LED8_WAIT;
  
  if(++c_cnt >=8){ //列の更新
    r_cnt =0;
    c_cnt = 0;
    patnMode = 0;
   }
}

//以下を毎回更新
Led8Clr();
Led8Set(r_cnt,c_cnt);

if(++r_cnt >= 8){ //行を更新
  r_cnt = 0;
}

ROWの制御にマルチプレクサを使用しているため複数のROWを同時に使用することができません。そのため列方向にLEDを複数点灯させる場合は工夫が必要です。

ROWの更新をメインループ1周ごとに行いマルチプレクサの操作を高速に切り替えることで列方向で複数のLEDを点灯させることができます。

ROWを高速に切り替えて列方向に複数のLEDを点灯させた例
ROWを高速に切り替えて列方向に複数のLEDを点灯させた例

例のソースコードで動作させると列方向にLEDが複数点灯します。マルチプレクサはROWを1つずつ操作していますが、メインループ毎にROWを切り替えているため複数のLEDが点灯しているように見えています。

ROWの更新を高速で行っている状態でCOLを切り替えるとLED列が移動して見えます。

スポンサーリンク

MCCの設定

PIC16F1827の設定はMCCを使っています。MCCの使い方については下記記事にまとめています。

MPLAB Code Configurator(MCC)の追加と使い方

今回はMCCを使用して動作クロックとTMR0の設定を行っています。

System Moduleの設定

最初に共通事項であるSystem Moduleの設定を行います。MPLAB X IDEを起動しMCCのアイコンをクリックしてMCCを有効にします。

System Moduleの設定(MCC)
System Moduleの設定(MCC)

内部クロックを使用するためINTOSCを選択しています。クロック周波数は任意でもよいですが4MHz_HFを選択しています。クロックを高速にするほど消費電流が増えてしまいますが消費電流が気にならない場合はクロック周波数を高くしても問題ありません。他にも設定項目はありますがクロックの設定のみとしています。

広告

TMR0を設定する

TMR0はDevice Resources内のPeripherals欄のTimerを選択しTMR0をクリックして追加します。

TMR0の設定(MCC)
TMR0の設定(MCC)

Timer Clock内でクロックに関する設定を行います。Clock Sourceは内部クロックを使用しているためFOSC(FOSC/4)を指定します。PrescalerはClock Sourceに対してTMR0のカウントアップする分周比を指定します。

Timer PeriodはTMR0がオーバーフローするタイミングを設定します。1ms毎にオーバーフロー割り込みが発生しますがCallback Function Rateを0x0A(10)を指定しているため10ms毎にコールバック関数が呼び出されます。

void mainTimer(void); //タイマー0割り込みのコールバック関数
//コールバック関数の使用例
TMR0_SetInterruptHandler(mainTimer); //コールバック関数を指定

上記例ではmainTimer()が10ms毎にコールされます。

MCCが生成した関数の使用例

IO_RB0_SetHigh();//DOをHIGH
IO_RB0_SetLow();//DOをLOW

IO_RB0_SetHigh()関数はRB0のDO出力をHighにします。IO_RB0_SetLow()関数はRB0のDO出力をLowにします。他のポートのDOの場合はRB0の部分が対象のポート名になります。Header Files内のpin_manager.h内で#defineで定義されています。

ピンの設定

MCCによるピン設定は以下の通りです。

MCCのピン設定
MCCのピン設定

ポートの設定はDOのみです。

PR:テックジム:プログラミングの「書けるが先で、理解が後」を体験しよう!

動作確認

電源を入れるとパターン1~パターン6までを繰り返します。ROW1、COW1を1行目1列目としてパターンの動作の確認を行います。

パターン1とパターン2のLEDの点灯
パターン1とパターン2のLEDの点灯

パターン1:1行1列目(Start)から右に列を更新しながらLEDを点灯し、8列目まで行くと行を更新して1列目から8列目までLEDを点灯します。これを繰り返して8行8列目(End)まで動作するとパターン2に遷移します。

パターン2:8行8列目(Start)からパターン1とは逆の動きでLEDを点灯しながら移動します。1行1列目(End)まで動作するとパターン3に遷移します。

パターン3とパターン4のLEDの点灯
パターン3とパターン4のLEDの点灯

パターン3:1行1列目(Start)から行のLEDを更新しながらLEDを点灯し、8行目まで行くと列を更新して1行目から8行目までLEDを点灯します。これを繰り返して8行8列目(End)まで動作するとパターン4に遷移します。

パターン4:8行8列目(Start)からパターン3とは逆の動きでLEDを点灯しながら移動します。1行1列目(End)まで動作するとパターン4に遷移します。

パターン5とパターン6のLEDの点灯
パターン5とパターン6のLEDの点灯

パターン5:1行目の1列目から8列目までのLEDを点灯させた状態(Start)で行を更新します。8行目(End)まで動作するとパターン6に遷移します。

パターン6:1列目の1行目から8行目までのLEDを点灯させた状態(Start)で列を更新します。8列目(End)まで動作するとパターン1に戻ります。

パターン6はマルチプレクサのチャンネル切り替えを高速に行うことで列に対する行のLEDを点灯しています。

パターン6以外のLEDの点灯のパターンの切り替えは100ms毎に行うようにしています。変更する場合はソースコード全体のLED8_WAIT(デフォルトは10)の値を変更します。

広告

ソースコード全体

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

#include "mcc_generated_files/mcc.h"

//--------------定数定義----------------------------
#define TIME_OFF -1         //タイマーを使用しない場合
#define TIME_UP 0           //タイムアップ
#define LED8_WAIT 10

//→→→→→→→ ROW 1?8(行)
//↓
//↓
//↓
//COL 1?8(列)

//--------------変数定義----------------------------
int16_t timLed8;
int8_t c_cnt;
int8_t r_cnt;
uint8_t patnMode;

//--------------関数プロトタイプ宣言----------------------------
void mainApp(void);     //メイン関数内での処理
void mainTimer(void);
void Led8Clr(void);
void Led8Set(int8_t r, int8_t c);
void MultiSet(int8_t r);

/* Main application */
void main(void)
{
    SYSTEM_Initialize();
    TMR0_SetInterruptHandler(mainTimer);
    INTERRUPT_GlobalInterruptEnable();
    INTERRUPT_PeripheralInterruptEnable();

    while (1)
    {
        mainApp();
    }
}

/* タイマ管理 */
void mainTimer(void){
    
    if( timLed8 > TIME_UP ){
      --timLed8;
    }    
    
}
/* メインアプリ */
void mainApp(void){
    int8_t i;
    
    switch(patnMode){
        case 0:
            if(timLed8 == TIME_UP){
                timLed8 = LED8_WAIT;
                Led8Clr();
                Led8Set(r_cnt, c_cnt);

                if(++c_cnt >= 8){
                    c_cnt = 0;
                    if(++r_cnt >=8){
                        r_cnt = 7;
                        c_cnt = 7;
                        patnMode = 1;
                    }
                }
            }
            break;
        case 1:
            if(timLed8 == TIME_UP){
                timLed8 = LED8_WAIT;
                Led8Clr();
                Led8Set(r_cnt,c_cnt);

                if(--c_cnt < 0){
                    c_cnt = 7;
                    if(--r_cnt < 0){
                        r_cnt = 0;
                        c_cnt = 0;
                        patnMode = 2;
                    }
                }
            }
            break;
        case 2:
            if(timLed8 == TIME_UP){
                timLed8 = LED8_WAIT;
                Led8Clr();
                Led8Set(r_cnt,c_cnt);

                if(++r_cnt >= 8){
                    r_cnt = 0;
                    if(++c_cnt >=8){
                        r_cnt = 7;
                        c_cnt = 7;
                        patnMode = 3;
                    }
                }
            }
          break;
        case 3:
            if(timLed8 == TIME_UP){
                timLed8 = LED8_WAIT;
                Led8Clr();
                Led8Set(r_cnt,c_cnt);

                if(--r_cnt < 0){
                    r_cnt = 7;
                    if(--c_cnt < 0 ){
                        r_cnt = 0;
                        c_cnt = 0;
                        patnMode = 4;
                    }
                }
            }            
            break;          
        case 4:
            if(timLed8 == TIME_UP){
                timLed8 = LED8_WAIT;
                Led8Clr();

                for(i=0; i < 8; i++){
                    Led8Set(r_cnt,i);
                }
                if(++r_cnt >=8){
                    r_cnt = 0;
                    c_cnt = 0;
                    patnMode = 5;
                }
            }
          break;
        case 5:
            if( timLed8 == TIME_UP){
                timLed8 = LED8_WAIT;
                if(++c_cnt >=8){
                    r_cnt =0;
                    c_cnt = 0;
                    patnMode = 0;
                }
            }

            Led8Clr();
            Led8Set(r_cnt,c_cnt);

            if(++r_cnt >= 8){
                r_cnt = 0;
            }            
            break;
        }
}    

/* LEDを座標でON/OFFする */
void Led8Set(int8_t r, int8_t c){
 
    MultiSet(r);
    
    switch(c){
        case 0:
            IO_RB0_SetLow();
            break;
        case 1:
            IO_RB1_SetLow();
            break;            
        case 2:
            IO_RB2_SetLow();
            break;
        case 3:
            IO_RB3_SetLow();
            break;
        case 4:
            IO_RB4_SetLow();
            break;
        case 5:
            IO_RB5_SetLow();
            break;
        case 6:
            IO_RB6_SetLow();
            break;
        case 7:
            IO_RB7_SetLow();
            break;            
    }
}
/* マルチプレクサの操作 */
void MultiSet(int8_t r ){
    uint8_t porta;
    
    IO_RA7_SetLow(); //INHをLOW
    porta = PORTA;
    porta &= 0xD8; //PA0?PA2をマスクする
    PORTA = porta | (r & 0x07); //A,B,Cを論理和
    IO_RA6_SetHigh(); //COMをHIGH
}
/* LEDマトリクスをすべてOFF */
void Led8Clr(void){
    
    IO_RA7_SetHigh(); //INHをHIGH
    IO_RA6_SetLow(); //COMをLOW
    PORTB = 0xFF; //COLをOFF
}
/* End of File */

本ソースコードはMPLAB X IDEにMCCのプラグインをインストールしていることが前提となります。MCCをインストールする方法は下記記事を参考にしてください。

MPLAB Code Configurator(MCC)の追加と使い方

関連リンク

PICマイコンを使ってマイコンのレジスタの設定やMPLAB X IDEのプラグインであるMCCを使用して動作確認したことについてまとめています。

PICマイコン(PIC12F675)で実現できる機能と解説リンクまとめ

PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ

PR:(即戦力のスキルを身に着ける:DMM WEBCAMP 学習コース(はじめてのプログラミングコース))

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

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