このページの目次
ショップ
製品紹介
ダイナミック点灯方式の16x12ドットの緑色LEDマトリックスです。目の残像を利用したダイナミック点灯方式やタイマーと割り込みを使用した処理方式を学ぶのに最適です。
TRYGEARシリーズのマイコン基板に接続し、ブレッドボードでの複雑で間違えやすい配線を一切排除し、組込みプログラミングの重要技術である「ダイナミック点灯処理」や「タイマー割り込み処理」の確実な習得をサポートします。視認性に優れた16×12ドットの鮮やかな緑色LEDを採用し、グラフィック表示や英数字・日本語表示の基礎と、本格的な並行処理のプログラミングを直感的に学べる、教育現場に最適な拡張基板教材です。

■ ドットマトリックスLEDシールド「XDML-FUNX」の特長
マイコン基板を使用したプログラミング学習の発展的テーマとして、ドットマトリックスLEDを用いた表示システムの制作は非常に優れた課題です。この演習は、組込みプログラミングの核心である「ダイナミック点灯処理」や「割り込み処理」を体感的に学ぶのに最適な環境を提供します。
近年、ドットマトリックスLEDはArduino UNO R4 WiFiにも標準搭載されるなど、非常に身近な表示器となっています。しかしながら同ボードでは、省ピンを目的とした特殊回路「チャーリープレキシング(Charlieplexing)方式」が採用されており、専用APIを呼び出すだけであれば利用は容易なものの、その表示制御の仕組み自体を学習者がゼロからプログラミング(実装)することは極めて困難でした。また、同方式の特性上、表示が暗く実用性に欠けるという課題もありました。
「XDML-FUNX」は、これらの課題を「正統派の行・列構成のダイナミック表示回路」を採用することで一挙に解決。学習者が自らの手で「割り込みを利用したダイナミック表示」をプログラム(構築)することにより、メイン処理の複雑なロジックや負荷に左右されないマルチタスク制御の技術を深く理解できます。さらに、目が覚めるような明るい画面表示が実現できるため、学習者に高い達成感を提供します。
※製品名にある「FUNX(ファンクス)」は、[Function eXtension(機能拡張)]、および[Fun eXploration(つくる楽しさの探求)]の双方を意味しています。TRYGEARシリーズの可能性を広げるとともに、未知の仕組みを自ら紐解き、ものづくりの楽しさをどこまでも「探求」してほしいという、教材としての熱い想いが込められています。
■ 「XDML-FUNX」でLEDマトリックス表示方式を学ぶ4大メリット
1. 初心者でもゼロから書ける「正統派ダイナミック表示構造」
本製品は、行(12行)と列(16列)がマトリックス状に交差する、組込み工学の標準的な回路を採用しています。「1列目のLEDデータをポートに出力 ➔ 一瞬待つ ➔ 2列目を出力…」という、直感的で分かりやすいコードで制御可能です。タイマー割り込み処理と組み合わせることで、「バックグラウンドで高速かつ定間隔でLED表示を更新しながら、メインループで別の処理を行う」という理想的なマルチタスクプログラミングの本質を、学習者自身で理解・実装できます。
2. 目が覚めるような「鮮やかな明るさ(高輝度)」を実現
Arduino UNO R4 WiFiのLEDマトリックスが駆動電流の少ないマイコン端子直結駆動であるのに対し、本製品にはLED駆動用のDMOSアレイ(電流ドライバ)を搭載。各LEDに時分割駆動(ダイナミック表示)に適した十分な電流を供給できます。さらに、人間の目が最も明るさを感じやすい「緑色LED」を採用したことで、教室内はもちろん、明るい屋外でも、文字やグラフィックがクッキリと鮮明に視認できます。
3. 表現力が大幅向上:16×12ドット(計192画素)のワイド画面
Arduino UNO R4 WiFi(12×8ドット・96画素)の2倍の画素数を誇ります。アルファベットの滑らかなスクロール表示、簡単なミニゲーム(ブロック崩しやドット絵アニメーション)、日本語(漢字)や複雑なアイコンの表示など、学生の「作りたい!」という創作意欲を刺激する豊かな表現が可能です。
4. TRYGEARシリーズに最適化された拡張性
「XDML-FUNX」は、TRYGEARシリーズが持つ豊富な入出力を最大限に活用できるように設計されています。
・オンボード機能をそのまま併用可能: TRYGEARマイコン基板上のスイッチ、LED、圧電ブザー、RCサーボコネクタ、MicroSDカードソケットなどをそのまま利用できます。これらの豊富な入出力と「XDML-FUNX」の表示機能を組み合わせることで、多様なアプリケーション製作に取り組めます。
・外部回路やセンサの追加も容易: 「XDML-FUNX」を搭載した状態でも、マイコン基板の電源コネクタやアナログコネクタ(デジタル端子としても利用可能)、I2C端子が解放されています。様々な外部センサーや回路を拡張し、さらに応用分野を広げることが可能です。

※Arduino UNO R3との互換性を重視した「TRYGEAR-AVR(ATmega328P搭載)」では、MCUの端子数の制約により、表示が16×8ドットに制限され、利用可能なオンボード入出力は圧電ブザーのみとなります。
プログラミング
Arduinoでのプログラミング
ダイナミック点灯方式の基礎
まずは、ダイナミック点灯方式の基礎を学びましょう。
Frame[]の中はご自身でもう少し気の利いた絵に書き換えてください。
loop()の最後のdelay(1)の数値を100とかもっと大きくすると、LEDの全体が同時に光っているのではなく、1行ずつ順番に光っていることが確認できます。
#include "TRYGEAR.h"
// ==========================================
// 1. XDML-FUNX用 画面バッファと制御変数の定義
// ==========================================
#define LED_COLS 16 // 16列
#define LED_ROWS 12 // 12行
int row[LED_ROWS] = {XD1, XD0, XD2, XD3, XD4, XD5, XD6, XD7, TFT_SCK, TFT_MOSI, TFT_MISO, TFT_CS} ;
int col[] = {XD10, XD11, XD12, XD13} ;
void setup() {
for (int i = 0; i < 12; i++) {
pinMode(row[i], OUTPUT);
digitalWrite(row[i], HIGH) ;
}
for (int i = 0; i < 4; i++) {
pinMode(col[i], OUTPUT);
digitalWrite(col[i], LOW) ;
}
pinMode(XD8, OUTPUT);
digitalWrite(XD8, HIGH) ;
}
uint16_t Frame[LED_COLS] = {
0b101001000001,
0b010100100010,
0b101000010100,
0b010100001000,
0b101000111100,
0b010101000110,
0b101010001001,
0b010110000001,
0b101010001001,
0b010110000101,
0b101001001010,
0b010100111100,
0b101000001000,
0b010100010100,
0b101000100010,
0b010101000001,
} ;
void loop() {
for (int current_col = 0; current_col < 16; current_col++) {
// current_col(0〜15)に対応するFrameデータを取得
uint16_t line = ~Frame[current_col] ;
// 一旦、表示をOFFにしてゴーストを防止
digitalWrite(XD8, HIGH) ;
// 行データ(12ビット分)を対応するピンに出力
for (int j = 0; j < 12; j++) {
digitalWrite(row[j], (line >> j) & 1) ;
}
// 新しい列のコモンピンを設定する
digitalWrite(XD10, current_col&1);
digitalWrite(XD11, current_col&2);
digitalWrite(XD12, current_col&4);
digitalWrite(XD13, current_col&8);
// 表示をONに戻す
digitalWrite(XD8, LOW) ;
delay(1) ;
}
}タイマー割込みによるマトリックスの表示処理の自動化
タイマー割込みを使用して、マトリックスの表示処理を自動化しましょう。
これにより、画面の表示処理のことは忘れ、また、厳しいタイミングが要求される処理を考慮することなく、Frame[]を更新する処理をloop()の中に書けば、表示される絵がどんどん変わります。
Arduinoのコンパイラ・ライブラリ周りは、MCUごとに開発者が異なるため、MCUのハードウェアに近い処理のAPIが統一されておらず、異なるMCUで同じように書けないことがあります。割り込み処理もその一つで、使用しているMCUにより書き分ける必要があります。残念。
下記のプログラムは、RP2XXX系のMCUとESP32系のMCUに対応しています。
#include "TRYGEAR.h"
// ==========================================
// 1. XDML-FUNX用 画面バッファと制御変数の定義
// ==========================================
#define LED_COLS 16 // 16列
#define LED_ROWS 12 // 12行
int row[LED_ROWS] = {XD1, XD0, XD2, XD3, XD4, XD5, XD6, XD7, TFT_SCK, TFT_MOSI, TFT_MISO, TFT_CS} ;
int col[] = {XD10, XD11, XD12, XD13} ;
void setup() {
for (int i = 0; i < 12; i++) {
pinMode(row[i], OUTPUT);
digitalWrite(row[i], HIGH) ;
}
for (int i = 0; i < 4; i++) {
pinMode(col[i], OUTPUT);
digitalWrite(col[i], LOW) ;
}
pinMode(XD8, OUTPUT);
digitalWrite(XD8, HIGH) ;
// マイコンに応じたタイマー割り込みの開始
initTimerInterrupt();
}
uint16_t Frame[LED_COLS] = {
0b101001000001,
0b010100100010,
0b101000010100,
0b010100001000,
0b101000111100,
0b010101000110,
0b101010001001,
0b010110000001,
0b101010001001,
0b010110000101,
0b101001001010,
0b010100111100,
0b101000001000,
0b010100010100,
0b101000100010,
0b010101000001,
} ;
void loop() {
/* ここでは、今は何もしてなーい */
/* 実際には、Frameにお絵描きをするコードを書く */
/* 画面の表示処理は割り込みで1msごとに実施されているが、そのタイミングを気にせずにコードを書ける */
}
volatile uint8_t current_col = 0; // 現在表示中の列(0〜15)
// ==========================================
// 2. 「LED表示更新ロジックの中身」
// ==========================================
inline void processLEDRefresh() {
// current_col(0〜15)に対応するFrameデータを取得
uint16_t line = ~Frame[current_col];
// 一旦、表示をOFFにしてゴーストを防止
digitalWrite(XD8, HIGH) ;
// 行データ(12ビット分)を対応するピンに出力
for (int j = 0; j < 12; j++) {
digitalWrite(row[j], (line >> j) & 1) ;
}
// 新しい列のコモンピンを設定する
digitalWrite(XD10, current_col&1);
digitalWrite(XD11, current_col&2);
digitalWrite(XD12, current_col&4);
digitalWrite(XD13, current_col&8);
// 表示をONに戻す
digitalWrite(XD8, LOW) ;
// 次の割り込みのために列カウンターをインクリメント
current_col++;
if (current_col >= LED_COLS) {
current_col = 0; // 16列超えたら0に戻る
}
}
// ==========================================
// 3. MCUごとのタイマー割り込みエントリー
// ==========================================
#if defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_RP2350)
// ------------------------------------------
// RP2350 / RP2040 (Raspberry Pi コア用)
// ------------------------------------------
struct repeating_timer timer;
// 関数の前に直接「__not_in_flash_func」を付与します(コアの仕様通り)
bool __not_in_flash_func(pio_timer_callback)(struct repeating_timer *t) {
processLEDRefresh(); // 共通ロジックを呼び出し
return true;
}
void initTimerInterrupt() {
// 1ms = 1000マイクロ秒 ごとにコールバックを呼ぶ
add_repeating_timer_us(-1000, pio_timer_callback, NULL, &timer);
}
#elif defined(ESP32)
// ------------------------------------------
// ESP32-S3 (ESP32 コア用)
// ------------------------------------------
hw_timer_t * timer = NULL;
// 関数の前に直接「IRAM_ATTR」を付与します(ESP32の仕様通り)
void IRAM_ATTR esp_timer_callback() {
processLEDRefresh(); // 共通ロジックを呼び出し
}
void initTimerInterrupt() {
// ESP32 Arduino Core v3.xのAPI
timer = timerBegin(1000000);
timerAttachInterrupt(timer, &esp_timer_callback);
timerAlarm(timer, 1000, true, 0);
}
#elif defined(ARDUINO_ARCH_STM32)
// ------------------------------------------
// STM32H503 (STM32duino コア用)
// ------------------------------------------
// 使用するハードウェアタイマーのインスタンスポインタ
HardwareTimer *MyTim = NULL;
// STM32用のタイマーコールバック関数
void stm32_timer_callback(void) {
processLEDRefresh(); // 共通ロジックを呼び出し
}
void initTimerInterrupt() {
// TIM1を割り込み用に使用(競合する場合はTIM2やTIM3等に変更可能)
MyTim = new HardwareTimer(TIM1);
// 割り込み周期を1ms(1000Hz)に設定
MyTim->setOverflow(1000, HERTZ_FORMAT);
// コールバック関数を登録
MyTim->attachInterrupt(stm32_timer_callback);
// タイマー割り込みを開始
MyTim->resume();
}
#else
#error "未対応のマイコンアーキテクチャです。RP2350、ESP32-S3、またはSTM32H503を使用してください。"
#endifボールを跳ね回らせよう
タイマー割込みを使用してダイナミック点灯方式の更新処理を自動実行されるようにすると、LEDマトリックスのダイナミック表示を完全に忘れ、Frame[]に絵を描けばよいだけになります。
loop()内に、ボールが跳ねまわる絵をFrame[]に描く処理を書いてみましょう。
せっかくだから壁で跳ね返った時に音が出るようにしましょう。
#include "TRYGEAR.h"
// ==========================================
// 1. XDML-FUNX用 画面バッファと制御変数の定義
// ==========================================
#define LED_COLS 16 // 16列
#define LED_ROWS 12 // 12行
int row[LED_ROWS] = {XD1, XD0, XD2, XD3, XD4, XD5, XD6, XD7, TFT_SCK, TFT_MOSI, TFT_MISO, TFT_CS} ;
int col[] = {XD10, XD11, XD12, XD13} ;
void setup() {
for (int i = 0; i < 12; i++) {
pinMode(row[i], OUTPUT);
digitalWrite(row[i], HIGH) ;
}
for (int i = 0; i < 4; i++) {
pinMode(col[i], OUTPUT);
digitalWrite(col[i], LOW) ;
}
pinMode(XD8, OUTPUT);
digitalWrite(XD8, HIGH) ;
// マイコンに応じたタイマー割り込みの開始
initTimerInterrupt();
}
uint16_t Frame[LED_COLS] = {
0b101001000001,
0b010100100010,
0b101000010100,
0b010100001000,
0b101000111100,
0b010101000110,
0b101010001001,
0b010110000001,
0b101010001001,
0b010110000101,
0b101001001010,
0b010100111100,
0b101000001000,
0b010100010100,
0b101000100010,
0b010101000001,
} ;
void fillBuff(int v)
{
uint16_t dot = v ? 0xFFF: 0x000 ;
for (int i = 0; i < LED_COLS; i++) {
Frame[i] = dot ;
}
}
void plotBuff(int x, int y, int v)
{
if ((x >= LED_COLS) || (x < 0))
return ;
if ((y >= LED_ROWS) || (y < 0))
return ;
Frame[x] = 1 << y ;
}
float x = 5.0, y = 5.0;
float dx = 0.5, dy = 1.0 ;
void loop() {
// ダイナミック点灯方式の更新処理は割り込みで自動実行されるので、
// ここでは、ダイナミック表示を完全に忘れ、Frame[]に絵を描けばよい。
// せっかくだから音も出そうね。
fillBuff(0) ; // 画面消去
plotBuff((int)x, (int)y, 1) ;
x += dx;
y += dy;
if (x >= LED_COLS) {
x = LED_COLS - 1;
dx = -dx ;
tone(SPK, 1000) ;
}
if (y >= LED_ROWS) {
y = LED_ROWS - 1;
dy = -dy ;
tone(SPK, 500) ;
}
if (x <= 0) {
x = 0;
dx = -dx ;
tone(SPK, 1000) ;
}
if (y <= 0) {
y = 0;
dy = -dy ;
tone(SPK, 500) ;
}
delay(50);
noTone(SPK) ;
}
volatile uint8_t current_col = 0; // 現在表示中の列(0〜15)
// ==========================================
// 2. 「LED表示更新ロジックの中身」
// ==========================================
inline void processLEDRefresh() {
// current_col(0〜15)に対応するFrameデータを取得
uint16_t line = ~Frame[current_col];
// uint16_t line = 0;
// 一旦、表示をOFFにしてゴーストを防止
digitalWrite(XD8, HIGH) ;
// delayMicroseconds(900);
// 行データ(12ビット分)を対応するピンに出力
for (int j = 0; j < 12; j++) {
digitalWrite(row[j], (line >> j) & 1) ;
}
// 新しい列のコモンピンを設定する
digitalWrite(XD10, current_col&1);
digitalWrite(XD11, current_col&2);
digitalWrite(XD12, current_col&4);
digitalWrite(XD13, current_col&8);
// 表示をONに戻す
digitalWrite(XD8, LOW) ;
// 次の割り込みのために列カウンターをインクリメント
current_col++;
if (current_col >= LED_COLS) {
current_col = 0; // 16列超えたら0に戻る
}
}
// ==========================================
// 3. MCUごとのタイマー割り込みエントリー
// ==========================================
#if defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_RP2350)
// ------------------------------------------
// RP2350 / RP2040 (Raspberry Pi コア用)
// ------------------------------------------
struct repeating_timer timer;
// 関数の前に直接「__not_in_flash_func」を付与します(コアの仕様通り)
bool __not_in_flash_func(pio_timer_callback)(struct repeating_timer *t) {
processLEDRefresh(); // 共通ロジックを呼び出し
return true;
}
void initTimerInterrupt() {
// 1ms = 1000マイクロ秒 ごとにコールバックを呼ぶ
add_repeating_timer_us(-1000, pio_timer_callback, NULL, &timer);
}
#elif defined(ESP32)
// ------------------------------------------
// ESP32-S3 (ESP32 コア用)
// ------------------------------------------
hw_timer_t * timer = NULL;
// 関数の前に直接「IRAM_ATTR」を付与します(ESP32の仕様通り)
void IRAM_ATTR esp_timer_callback() {
processLEDRefresh(); // 共通ロジックを呼び出し
}
void initTimerInterrupt() {
// ESP32 Arduino Core v3.xのAPI
timer = timerBegin(1000000);
timerAttachInterrupt(timer, &esp_timer_callback);
timerAlarm(timer, 1000, true, 0);
}
#elif defined(ARDUINO_ARCH_STM32)
// ------------------------------------------
// STM32H503 (STM32duino コア用)
// ------------------------------------------
// 使用するハードウェアタイマーのインスタンスポインタ
HardwareTimer *MyTim = NULL;
// STM32用のタイマーコールバック関数
void stm32_timer_callback(void) {
processLEDRefresh(); // 共通ロジックを呼び出し
}
void initTimerInterrupt() {
// TIM1を割り込み用に使用(競合する場合はTIM2やTIM3等に変更可能)
MyTim = new HardwareTimer(TIM1);
// 割り込み周期を1ms(1000Hz)に設定
MyTim->setOverflow(1000, HERTZ_FORMAT);
// コールバック関数を登録
MyTim->attachInterrupt(stm32_timer_callback);
// タイマー割り込みを開始
MyTim->resume();
}
#else
#error "未対応のマイコンアーキテクチャです。RP2350、ESP32-S3、またはSTM32H503を使用してください。"
#endif回路図
