0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

7セグメントLEDシリアルドライバーをPICマイコンでSPIを使って制御する(その1)

0
Last updated at Posted at 2026-08-25

ナゾテックの名前で模型・ジオラマ用のLED電飾コントローラーや配線済みチップLEDの販売などを行っている者です。

Amazonストア

想定読者

PICマイコンを使った7セグメントLEDの制御でダイナミック点灯はいろいろ面倒だから何か簡単に出来るもの無いかなぁって方へ。
PICマイコンの開発環境や電子工作の基本に付いては下記をご参照ください。

目次

使用機材
このドライバーを使うと何が良いのか?
LEDシリアルドライバーの動作概念
準備
マイコン設定
接続
セグメントと送信データとの関係
表示プログラム
ちょっとした応用
続き

使用機材

PICマイコンはPIC16F18346を使っています。特に理由は有りません。他の機種でもSPIが使えれば問題なく使えるはずですから、やって見てください。
7セグメントLEDは単体ですとダイナミック制御をしないとダメで、そうなるとマイコンのピンもたくさん使うしプログラムも面倒、桁数を増やすとなるとさらに面倒になるので、今回はSPI通信が使える便利なやつを使います。
実際に使用したのはこれです。こちらは基板へのはんだ付けなどが必要なキット品です。

もちろん色は何でも良いです。色によって多少値段も違います。
また、7セグメントLED単体だと100円以下なので予算が少ない場合はダイナミック制御を頑張って見てください。
また、これは少しはんだ付けが必要ですが、少し高い完成品も売ってます。

キット品、完成品共に電源は3V、5V(青の場合は7V)で使用できます。
ただし、こちらの記事、

によると3.3Vでは青・緑が点灯しないとの報告も有りますが、私がやった限りでは青で3V、5Vどちらでも使えました。電源容量やシリアルドライバーの連結数にも関係しているのかも知れません。
なお、シリアルドライバーへの電源電圧とマイコンの電源電圧は同じ電圧にしてください。また桁数が多いと消費電力も大きくなるので、マイコンとは独立した電源にしておかないとマイコンが安定しない可能性が有ります。

このドライバーを使うと何が良いのか?

先にも書いたように単純な7セグメントLEDの場合、制御するには最低でも8ピン+GNDが必要で桁数を増やすと8+桁数分のピンが必要になります。そうなるとマイコンの選定も難しいしプログラムも難しい上に、LEDの制御にCPU能力が結構取られます。
7セグメントLEDシリアルドライバーの場合は電源を別にすればマイコンからは3本の信号を出力するだけで、理論的には何十桁でも制御可能になります。しかもプログラムも非常に簡単です。
さらに、ラッチ信号を切替える事で、表示内容の書き換えモードと内容保持モードが選択でき、内容保持モードにしておくとマイコン側では何もしなくても最後に送信した内容をそのまま表示し続けるようになっています。
表示を変えたい時だけ書き換えモードにした上でデータを送信し、送信が終わったら内容保持モードに切り替えると言う形です。
桁数が増えても信号線が増えないのは各LEDシリアルドライバーが隣のLEDシリアルドライバーへデータを送り出すからです。

LEDシリアルドライバーの動作概念

たとえば3桁分の7セグメントLEDシリアルドライバーがあって、適切に配線されているとします。下図では左から右へデータが流れるように配線されているとします。何も表示されていない状態から'7'を表示するようにデータを送ったとします。

DataSend1.png

最初のシリアルドライバーがこの'7'を受け取って表示します。

DataSend2.png

続けて'5'のデータを送ります。
すると最初のシリアルドライバーが今表示している'7'のデータを次のシリアルドライバーへ送信します。

DataSend3.png

最初のシリアルドライバーが今度は'5'を表示し、次のシリアルドライバーが受け取った'7'を表示します。

DataSend4.png

さらに'0'のデータを送ると、最初のシリアルドライバーが今表示している'5'を2番目に、2番目のシリアルドライバーが今表示している'7'を3番目のシリアルドライバーへ送信します。

DataSend5.png

左から'057'の順に表示されます。

DataSend6.png

つまり一つデータを送ると今表示しているデータを次のシリアルドライバーへ順々に送る形になります。これによって、信号線が一本でも理論的には何百個ものLEDにデータを表示できるようになります。

準備

まずは先ほどのキットの方を買われた方はこれを組み立てます。組み立てると言っても基板にLEDをはんだ付けしさらに基板にピンヘッダーをはんだ付けするだけです。
キット外観。
IMG_6222.png
内容物。左からピンヘッダー、シリアルドライバー基板、7セグメントLED。
IMG_6221.png
まず基板に7セグメントLEDのピンを差し込みますが、向きに注意してください。
基板には画像の面からLEDを差し込みます。図の赤い丸のついた部分を揃えるような向きで差し込みます。
IMG_6223.png
差し込むと裏側にピンが長く突き出ます。
IMG_6224.png
これを基板よりほんの少し突き出るくらいで切ってください。
IMG_6225.png
まずこのピンをハンダつけしてください。ハンダ付けはフラックスを塗ってから行うと綺麗に仕上がります。
次にピンヘッダーを5ピンずつに切って基板の外側の端子穴にハンダ付けします。
ブレッドボードにピンヘッダーの長い方を差し込み、短い方へ基板を差し込んで位置決めしてハンダ付けします。
IMG_6226.png
IMG_6227.png
出来上がり。
IMG_6228.png

マイコン設定

マイコンとSnapとの接続は下記記事を参照してください。

またMCCの使い方も同様に下記記事を参照してください。

こちらもご覧ください。

なおこの記事ではMCC Classicを使用します。
新規プロジェクトを作成します。マイコンは用意したものに設定してください。ここではPIC16F18346にしています。"Internal Clock"は遅くても構わないのでとりあえず"4MHz"、"Clock Divider"は"1"に設定しておきます。
スクリーンショット 2026-08-25 111925.png
次にDevice Resource->Peripherals->MSSP1を追加します。マイコン機種によってはSPIしか無いかも知れません(手元にSPIしかないマイコンが無いので確認できませんでした)。
初期値のまま特に変更するところはありませんが画像の内容で確認しておいてください。
スクリーンショット 2026-08-25 112105.png
MSSPには入出力のためのピンの設定が必要です。Pin Manager Grid Viewで"MSSP1"に"SCK1"、"SDI1"、"SDO1"の3つの機能があります。このうち"SDI1"は今回使いませんがピン設定を解除する事ができないので(私にはやり方が分からないので)とりあえずどれかのピンに割り当てておきます。今回は14番ピンとしておきます。
"SDO1"はMSSPからのデータが送信される側でシリアルドライバーに接続する必要があります。ここでは13番ピンに設定します。
"SCK1"はSPI通信を行うためのクロック信号を送信先へ供給するためのもので、これもシリアルドライバーへ接続します。ここでは12番ピンとしておきます。
シリアルドライバーを制御するためにもう一つ出力ピンが必要です。これは前述したシリアルドライバーのラッチ信号を送るためのもので、書き換えモード・内容保持モードの切り替えようです。ここでは11番ピンとして、これを出力設定にしておきます。下図の赤丸の部分です。
スクリーンショット 2026-08-25 114323.png
また分かりやすくするためにピンの名前を(Pin Managerの一覧表にある"Custom Name"の欄)"IO_LATCH"としておきましょう。
スクリーンショット 2026-08-25 114849.png
なお、MSSP1の"SDI1"のピン設定を無くそうとすると下記の確認画面が表示されます。
image.png
申し訳ありませんが対処が良く分からないので、OKをクリックしておいてください。"SDI"に付いては別のピンへの設定は出来ますが、無くすことは出来ないようです。
また遅延関数を使いたいので"Device Resources"->"Libraries"->DELAYも追加しておいてください。
設定は以上なので"Generate"ボタンをクリックしてコードを生成しておきます。

接続

まずはマイコンとSnapとの接続部分です。こちらの記事の接続写真を参考にしてください。もちろんNeopixelの接続はしません。
では7セグメントLEDシリアルドライバーをマイコンへ接続しましょう。
とりあえずは一桁だけの接続を行います。なお、桁数が増えてもシリアルドライバー側の配線は増えますが、マイコン側との接続は変化しません。
下図を参考にして各端子を電源とマイコンのそれぞれのピンに接続してください。
IMG_6209.png

シリアルドライバーピン名 接続先
VDD 電源プラス
GND 電源マイナス
SCK マイコン12番ピン
SDI マイコン13番ピン
Latch マイコン11番ピン

IMG_6210.png

シリアルドライバーのSDOピンに付いては後述しますが、その他のピンに付いては使用しません。また、VDDとGNDはそれぞれ2つずつありますが、どちらを使っても同じです。

セグメントと送信データとの関係

7セグメントLEDと言う名前は7つのセグメント=部分が独立したLEDになっていて、それらの組み合わせで数字や少しアルファベットなども表示できる事から来ています。
各セグメントには順番が有って下図のようになっています。
IMG_6209 - コピー.png
見て分かるように0~7なので、実際には8セグメント有ります。
シリアルドライバーは、光らせるセグメント・光らせないセグメントがどれなのかを指定するデータを受け取って適切にLEDを光らせる仕組みです。
セグメントを光らせる・光らせないの指定は1バイト中のビットが0か1かで決まります。PICマイコンで特に何も変更していなければ、最下位ビット(ビット0)がセグメント0、最上位ビット(ビット7)がセグメント7の光る・光らないを制御するようになっています。
たとえば"1"を表示したい場合は、セグメント1と2を光らせるのでビット1と2を"1"にします。コードで書くと下記の様になります(先頭の0bは二進数で表記する場合の記号)。

0b00000110

"8"を表示する場合はピリオド以外全部オンなので、

0b01111111

となります。5の場合は、

0b01101101

です。
小数点を光らせる場合は最上位ビットを"1"にしますが、通常は数字とピリオドの両方を点灯することになるので、各数字のビットパターンに0b10000000のデータをorで合成します。
プログラムで扱い易い様に各数字の表示パターンとアルファベットA,b,C,d,E,F、さらに-、.を表示するためのデータを配列に入れておきましょう。

 const uint8_t NumPatternData[] = {
          0b00111111 , // 0
          0b00000110 , // 1
          0b01011011 , // 2
          0b01001111 , // 3
          0b01100110 , // 4
          0b01101101 , // 5
          0b01111101 , // 6
          0b00000111 , // 7
          0b01111111 , // 8
          0b01101111 , // 9
          0b01110111 , // A
          0b01111100 , // b
          0b00111001 , // C
          0b01011110 , // d
          0b01111001 , // E
          0b01110001 , // F
          0b01000000 , // -
 	      0b10000000 , // dot
     };

これで"0"を表示したい時はNumPatternData[0]を、"5"の時はNumPatternData[5]とすれば良くなります。A以降は10、11、12・・・と続きます。
BとDが小文字なのは大文字だと8、0と同じ形で区別が付かないからです。これで16進数の一桁を表示することができます。
なお"uint8_t"は1バイトの符号が無いデータと言う意味のMAPLAB XのコンパイラであるXC8に定義されているデータ型です。

余談1 他にも uint16_t-->2バイト符号なし uint24_t-->3バイト符号なし uint32_t-->4バイト符号なし uint64_t-->8バイト符号なし

int8_t-->1バイト符号有り
int16_t-->2バイト符号有り
int24_t-->3バイト符号有り
int32_t-->4バイト符号有り
int64_t-->8バイト符号有り

も有ります。さらにfastが付いたint_fast8_tもあり、最速の変数と解説されていますが、少なくともXC8ではint_fast8_tはint8_tの別名になっているだけです。

余談2 なお、arduinoの標準的な設定の場合はデータ送信時のビットの順番が逆になるので、記事によっては下位ビット・上位ビットが反転したような形で書かれているかと思います。

表示プログラム

ではプログラムです。MCCでGenerateボタンを押しているので、既にmain.cは作られているはずですから、プロジェクトツリーからmain.cをダブルクリックして開いてください。
main関数の前側に先ほどの配列を定義してください。
main関数ではまずSPIの開始のためにOpenを行います。

    SPI1_Open(SPI1_DEFAULT);

データの送信はSPIへの書き込みです。

    SSP1BUF = data;
    while (!SSP1STATbits.BF);    

この部分は多分何度も使うので、関数にしておきます。

void sendSPI1Data(uint8_t data){
    SSP1BUF = data;
    while (!SSP1STATbits.BF);    
}

これで、"0"を表示したい時は、

    sendSPI1Data(NumPatternData[0]);

とするだけです。
ただし、シリアルドライバーの表示を変更するためにはシリアルドライバーを書き換えモードにする必要があり、そのためにIO_LATCHピンをLOWにします、データを送信し終わったらIO_LATCHピンをHIGHにします。
一連の流れで"5"を表示するコードです。

    IO_RB6_SetLow();
    sendSPI1Data(NumPatternData[5]);
    IO_RB6_SetHigh();

ここまでの配列の宣言から"5"を表示するまでの一連のコードです。
***コードに余計な要素が入っていたので修正しました(2026/08/27)

 const uint8_t NumPatternData[] = {
          0b00111111 , // 0
          0b00000110 , // 1
          0b01011011 , // 2
          0b01001111 , // 3
          0b01100110 , // 4
          0b01101101 , // 5
          0b01111101 , // 6
          0b00000111 , // 7
          0b01111111 , // 8
          0b01101111 , // 9
          0b01110111 , // A
          0b01111100 , // b
          0b00111001 , // C
          0b01011110 , // d
          0b01111001 , // E
          0b01110001 , // F
          0b01000000 , // -
 	      0b10000000 , // dot
          0b00000000   // 表示無し
     };

void sendSPI1Data(uint8_t data){
    SSP1BUF = data;
    while (!SSP1STATbits.BF);    
}
 
/*
                         Main application
 */
void main(void)
{
    // initialize the device
    SYSTEM_Initialize();

    // When using interrupts, you need to set the Global and Peripheral Interrupt Enable bits
    // Use the following macros to:

    // Enable the Global Interrupts
    //INTERRUPT_GlobalInterruptEnable();

    // Enable the Peripheral Interrupts
    //INTERRUPT_PeripheralInterruptEnable();

    // Disable the Global Interrupts
    //INTERRUPT_GlobalInterruptDisable();

    // Disable the Peripheral Interrupts
    //INTERRUPT_PeripheralInterruptDisable();

    SPI1_Open(SPI1_DEFAULT);
    
    IO_LATCH_SetLow();
    sendSPI1Data(NumPatternData[5]);
    IO_LATCH_SetHigh();
    
    while (1)
    {
        // Add your application code
    }
}

実行結果です。
IMG_6211.png
小数点も一緒に表示したい場合は下記の様にします。

    IO_LATCH_SetLow();
    sendSPI1Data(NumPatternData[5] | NumPatternData[17]);  //17は小数点のデータの要素番号
    IO_LATCH_SetHigh();

実行結果です。
IMG_6212.png
この実行結果を見ると、main関数内で一度データを送信した後はプログラムでは何もしていませんが7セグメントLEDはデータを表示したまま保持していることが分かります。

ちょっとした応用

もう少し応用として、1秒毎に0~9、A~Fまでを表示するプログラムです。whileループ内で何もしないことを強調するためにタイマー割込みを使って記述して見ます。
MPLAB X IDE+MCCでLチカやってみる_タイマーを使って見るの辺りにタイマーの追加、割込みハンドラの書き方を記述しているので参考にしてください。
プロジェクトに"TMR1"を追加します。参考記事とはクロック数が違うので、タイマー設定の初期値が違いますから注意してください。
"Time Period"に設定できる設定値が小さいので、まず"Postscaler"を"1:8"にします。これでタイマーで測れる時間が8倍になります。それでも1秒には不足しているのですが、まずは"Timer Period"に"500 ms"を設定してください。また"Enable Timer Interrupt"のチェックも"On"にします。
これで割込み関数が500ミリ秒毎に呼ばれるようになりましたが”Callback Function Rate"に"2"を設定すると、割込み発生の2回に1回だけ割込みハンドラが呼び出されるようになり、結果的に500ミリ秒 X 2の1秒間隔で割込み関数が呼び出されるようになります。
赤丸の部分を設定してください。
スクリーンショット 2026-08-25 170700.png
忘れないように"Generate"ボタンをクリックしてください。
main関数の少し前に割込みハンドラを記述します。また表示する番号を管理する変数"number"も宣言しておきます。

int number = 0;

void timer1Interrupt(void){
    IO_LATCH_SetLow();
    sendSPI1Data(NumPatternData[number++]);
    IO_LATCH_SetHigh();
    if(number>15){
        number = 0;
    }
}

次に割込み発生を許可し、割込みハンドラを設定します。

    // Enable the Global Interrupts
    INTERRUPT_GlobalInterruptEnable();      //ここと

    // Enable the Peripheral Interrupts
    INTERRUPT_PeripheralInterruptEnable();  //ここのコメントを外す

    TMR1_SetInterruptHandler(timer1Interrupt); //割込みハンドラの設定

さらに最初の1秒は何も表示されないので、プログラム開始してSPIを使用可能にしたら"0"が表示されるように割込みハンドラを呼び出してやります。

    SPI1_Open(SPI1_DEFAULT);
    
    timer1Interrupt();    //最初に一回呼び出す

これでmainでは何もしていませんが順に数字が切り替わっていく様子を見る事が出来ます。Youtubeに実行結果の動画をアップロードしてあります。

続き

7セグメントLEDを複数個接続して多数桁数表示を行う記事を下記に投稿しています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?