はじめに
7セグメントLEDは数字の表示に用いられていますが、複数個を組み合わせることで画像や任意のパターン表示を行うことも可能です。
本記事では、SPRESENSEを用いて多数の7セグメントLEDを制御し、画像データや自作パターンを大型ディスプレイとして表示する装置を制作したので、その構成と実装方法について紹介します。
システム構成
本システムの構成図を以下に示します.
PCで表示したいデータを作成したあとにSPRESENSEよりデータを7セグメントLED制御基板へ送信します.
ハードウェア構成
基板構成
制御はSPRESENSEのメインボードと拡張ボードを使用して制御しました。
7セグメントディスプレイは縦8個 × 横8個、合計128個の7セグメントLEDを実装することで作製しました。 各7セグメントLEDは、シリアル接続されたシフトレジスタによって制御されます。
Autodesk EAGLEで制御基板データを作成し、JLCPCB様に発注しました。
制御方式
表示方式はダイナミック点灯を採用しました。
今回用いた7セグメントLEDはカソードコモンです。アノードは1つのセグメントあたり8本あり、行ごとにまとめてシフトレジスタの出力端子に接続しました。また,カソードは列ごとにまとめてトランジスタに接続しました。
カソード制御には合計で8個のトランジスタをシフトレジスタでスイッチング動作させて,制御しました。一般的なドットマトリクスと同じように、網目状に光らせたい7セグメントLEDを光らせることが可能です。
下図に制御基板の一部構成を示します。
今回は左端の列から順にデジットをダイナミック点灯することで7セグメントLEDを制御しました。
以下に表示する手順を示します。
- アノード側のシフトレジスタに一番左の列に表示したいシリアルデータを送信します。
- カソード側のトランジスタアレイを1列だけ(今回は一番左の列)onすることで、一番左側の列を光らせることができます。
上記手順を高速に左端から右端まで行うことで、7セグメントディスプレイに好きなパターンを表示することができます。
ジグ基板の設計
LED制御用の基板は4つの基板を配線で接続して制御しました。
制御にはSPI通信を使用したのですが、CLK信号の劣化が確認されたため、NOT素子を2個用いたバッファ回路を挿入しました。
また制御基板用の電源回路も搭載しました。
部品表
最後に使用した部品を以下に示します.
| 部品名 | 機能 | 入手先 |
|---|---|---|
| Spresense メインボード | マイコンボード | ご提供品 |
| Spresense 拡張基板 | マイコンボード | ご提供品 |
| TC74HC595AF | シフトレジスタ | 秋月電子 |
| TBD62083AFG | トランジスタアレイ | 秋月電子 |
| NJM7805FA | 3端子レギュレータ | 秋月電子 |
| 抵抗/コンデンサ | 受動部品 | 秋月電子 |
基板データ
基板データはこちらで公開しています.
ソフトウェア構成
SWの構成図とデータ処理のフローを以下に示します。
今回はPythonを使用してデータの前処理をした後に、SPRESENSEを使用してコードを制御基板に送信しました。
表示パターン生成
表示パターンは以下の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
さんが
前の土曜日の23:38
に
編集
をしました。
(メッセージ: 初版)
-
Nyanraka
さんが
前の土曜日の23:47
に
編集
をしました。
ログインしてコメントを投稿する