Nyanrakaのアイコン画像
Nyanraka 2026年01月31日作成 (2026年01月31日更新) © MIT
製作品 製作品 閲覧数 54
Nyanraka 2026年01月31日作成 (2026年01月31日更新) © MIT 製作品 製作品 閲覧数 54

SPRESENSEで制御する7セグメントディスプレイの制作

SPRESENSEで制御する7セグメントディスプレイの制作

はじめに

7セグメントLEDは数字の表示に用いられていますが、複数個を組み合わせることで画像や任意のパターン表示を行うことも可能です。
本記事では、SPRESENSEを用いて多数の7セグメントLEDを制御し、画像データや自作パターンを大型ディスプレイとして表示する装置を制作したので、その構成と実装方法について紹介します。

システム構成

本システムの構成図を以下に示します.
PCで表示したいデータを作成したあとにSPRESENSEよりデータを7セグメントLED制御基板へ送信します.

システム構成

ハードウェア構成

基板構成

制御はSPRESENSEのメインボードと拡張ボードを使用して制御しました。
7セグメントディスプレイは縦8個 × 横8個、合計128個の7セグメントLEDを実装することで作製しました。 各7セグメントLEDは、シリアル接続されたシフトレジスタによって制御されます。

Autodesk EAGLEで制御基板データを作成し、JLCPCB様に発注しました。

回路図

PCB図

基板表面

基板裏面


制御方式

表示方式はダイナミック点灯を採用しました。
今回用いた7セグメントLEDはカソードコモンです。アノードは1つのセグメントあたり8本あり、行ごとにまとめてシフトレジスタの出力端子に接続しました。また,カソードは列ごとにまとめてトランジスタに接続しました。
カソード制御には合計で8個のトランジスタをシフトレジスタでスイッチング動作させて,制御しました。一般的なドットマトリクスと同じように、網目状に光らせたい7セグメントLEDを光らせることが可能です。

下図に制御基板の一部構成を示します。

制御基板構成

今回は左端の列から順にデジットをダイナミック点灯することで7セグメントLEDを制御しました。
以下に表示する手順を示します。

  1. アノード側のシフトレジスタに一番左の列に表示したいシリアルデータを送信します。
  2. カソード側のトランジスタアレイを1列だけ(今回は一番左の列)onすることで、一番左側の列を光らせることができます。

上記手順を高速に左端から右端まで行うことで、7セグメントディスプレイに好きなパターンを表示することができます。


ジグ基板の設計

LED制御用の基板は4つの基板を配線で接続して制御しました。
制御にはSPI通信を使用したのですが、CLK信号の劣化が確認されたため、NOT素子を2個用いたバッファ回路を挿入しました。
また制御基板用の電源回路も搭載しました。

ジグ基板


部品表

最後に使用した部品を以下に示します.

部品名 機能 入手先
Spresense メインボード マイコンボード ご提供品
Spresense 拡張基板 マイコンボード ご提供品
TC74HC595AF シフトレジスタ 秋月電子
TBD62083AFG トランジスタアレイ 秋月電子
NJM7805FA 3端子レギュレータ 秋月電子
抵抗/コンデンサ 受動部品 秋月電子

基板データ

基板データはこちらで公開しています.


ソフトウェア構成

SWの構成図とデータ処理のフローを以下に示します。
今回はPythonを使用してデータの前処理をした後に、SPRESENSEを使用してコードを制御基板に送信しました。

SW構成


表示パターン生成

表示パターンは以下の2通りで作成しました。

1. 画像データから生成

任意の画像を7セグメントディスプレイの画素数へとリサイズし画像を2値化することで点灯/非点灯を表現しました。

2. 自作パターン

Excel上で7セグメントのデジット配置を作成し、光らせたい部分を「1」、消灯させたい部分を「0」とすることで、任意の表示パターンを作成しました。


シリアルデータの加工

デジット配置を示すマスクバイナリデータを用意し,表示パターンとANDをとることで必要な部分のみを抽出し、シリアルデータとして連結します。
加工したシリアルデータをmicroSDカードにテキストファイルとして保存し、SPRESENSEより読み出すことで使用しました。

表示用データの作成


データ送信処理

SPRESENSEより読み出したデータは、LSBファーストで送信端から最も遠いシフトレジスタから順に送信します。
送信後、列単位でカソードをオンしてダイナミック点灯します。


作成したコード

完成したコードのうち主要なものを以下に示します.

SPRESENSEでの処理

#include <SDHCI.h> #include <File.h> #include <SPI.h> #define DATA_SIZE 2560 #define ROW_SIZE 8 // 1列当たりの7セグメントLEDの数 #define GROUP_SIZE 64 // 1セットあたりの7セグメントLEDの数 #define GROUP_COUNT 2 // セット数 #define LOOP_COUNT 8 uint8_t rowData[DATA_SIZE]; uint8_t sendData[DATA_SIZE]; SDClass SD; const int DATA_LATCH_PIN = 9; const int TRAN_LATCH_PIN = 3; const int OE_PIN = 1; int tran_pos = 0; uint8_t cathode_data = 0x80; void sendToShiftRegister(int loop) { // 最後のレジスタ分から送る for (int group = GROUP_COUNT; group > 0; group--) { // 列方向の遷移 下から上に上がっていく for (int offset = 0; offset < ROW_SIZE; offset++) { int index = group*GROUP_SIZE - offset - (loop * ROW_SIZE) - 1; SPI.transfer(sendData[index]); } } } void packBitsToBytes() { for (int byteIndex = 0; byteIndex < (DATA_SIZE/8); byteIndex++) { uint8_t value = 0; // 1バイトにする for (int bit = 0; bit < 8; bit++) { value <<= 1; // MSBから詰める value |= (rowData[byteIndex * 8 + bit] & 0x01); } sendData[byteIndex] = value; } } void setup() { Serial.begin(115200); while (!Serial); // SD初期化 if (!SD.begin()) { Serial.println("SD init failed"); return; } File file = SD.open("hi.txt"); if (!file) { Serial.println("File open failed"); return; } int idx = 0; while (file.available() && idx < DATA_SIZE) { char c = file.read(); if (c == '0' || c == '1') { rowData[idx++] = c - '0'; } } file.close(); Serial.print("Loaded: "); Serial.println(idx); packBitsToBytes(); // SPI初期化 SPI.begin(); SPI.beginTransaction(SPISettings(8000000, LSBFIRST, SPI_MODE0)); // ピン設定 pinMode(DATA_LATCH_PIN, OUTPUT); pinMode(TRAN_LATCH_PIN, OUTPUT); pinMode(OE_PIN, OUTPUT); digitalWrite(DATA_LATCH_PIN, HIGH); digitalWrite(TRAN_LATCH_PIN, HIGH); digitalWrite(OE_PIN, HIGH); } void loop() { digitalWrite(OE_PIN, HIGH); for (int loopCount = 0; loopCount < LOOP_COUNT; loopCount++) { digitalWrite(DATA_LATCH_PIN, LOW); digitalWrite(TRAN_LATCH_PIN, HIGH); sendToShiftRegister(loopCount); digitalWrite(DATA_LATCH_PIN, HIGH); digitalWrite(TRAN_LATCH_PIN, LOW); //uint8_t data = (0x80 >> tran_pos); SPI.transfer(cathode_data); digitalWrite(DATA_LATCH_PIN, HIGH); digitalWrite(TRAN_LATCH_PIN, HIGH); digitalWrite(OE_PIN, LOW); tran_pos += 1; cathode_data = cathode_data >> 1; if (tran_pos >= 8){ tran_pos = 0; cathode_data = 0x80; } delay(2); // 出力更新周期 } }

ディスプレイ用データの作成

import csv # ========================= # 1. CSV読み込み関数 # ========================= def load_1column_csv(path): data = [] with open(path, "r") as f: reader = csv.reader(f) for row in reader: if len(row) == 0: continue data.append(int(row[0])) return data # ========================= # 2. 64要素ブロックごとに並び替える関数 # ========================= def reorder_by_64_blocks(data): BLOCK = 64 ORDER_64 = [ 40, 0, 17, 16, 56, 41, 18, 1, 42, 2, 20, 19, 57, 43, 21, 3, 44, 4, 23, 22, 58, 45, 24, 5, 46, 6, 26, 25, 59, 47, 27, 7, 48, 8, 29, 28, 60, 49, 30, 9, 50, 10, 32, 31, 61, 51, 33, 11, 52, 12, 35, 34, 62, 53, 36, 13, 54, 14, 38, 37, 63, 55, 39, 15 ] assert len(data) % BLOCK == 0, "配列長は64の倍数である必要があります" reordered = [] for base in range(0, len(data), BLOCK): block = data[base:base + BLOCK] reordered.extend(block[i] for i in ORDER_64) return reordered # ========================= # 3. ファイルパス # ========================= MASK_CSV_PATH = "mask_data.csv" INPUT_CSV_PATH = "serial_data.csv" OUTPUT_CSV_PATH = "output.csv" # ========================= # 4. データ読み込み # ========================= MASK_DATA = load_1column_csv(MASK_CSV_PATH) input_data = load_1column_csv(INPUT_CSV_PATH) # ========================= # 5. サイズチェック # ========================= assert len(MASK_DATA) == 8000, "MASK_DATAは8000要素である必要があります" assert len(input_data) == 8000, "input_dataは8000要素である必要があります" # ========================= # 6. AND演算 # ========================= and_result = [ input_data[i] & MASK_DATA[i] for i in range(8000) ] # ========================= # 7. MASK_DATAが1の要素のみ抽出 # ========================= extracted_data = [ and_result[i] for i in range(8000) if MASK_DATA[i] == 1 ] # ========================= # 8. 並べ替え # ========================= result = reorder_by_64_blocks(extracted_data) # ========================= # 9. TXT出力 # ========================= with open("output.txt", "w") as f: for v in result: f.write(f"{v}¥n") # ========================= # 10. CSV出力 # ========================= with open("output.csv", "w", newline="") as f: writer = csv.writer(f) for v in result: writer.writerow([v]) # ========================= # 11. 結果表示 # ========================= print("処理完了") print(f"MASK_DATAの1の数 : {sum(MASK_DATA)}") print(f"抽出されたデータ数 : {len(extracted_data)}")

Pythonコード、SPRESENSEのコードの詳細はこちらを参照してください。


完成品

こちらが完成した作品です。
完成品

完成した装置が動作している様子を示します。
しっかりと表示したいパターンを視認することができました!
動作中の様子


今後の展望

今後は以下に対応し、より表現力の高い表示装置へ発展させたいと考えています。

  • 動画表示への対応(SPRESENSEのメインコアでmicroSD通信、サブコアで表示処理)
  • 桁数の増加
  • 筐体作製(できればカバンや工具箱など身の回りのものに搭載し,使える作製物にしたいです)
Nyanrakaのアイコン画像
ゆるふわエンジニア. 電子工作が趣味です.その他作品 > https://nanraka.net
ログインしてコメントを投稿する