FC2ブログ

Entries

CH32V003でDCCデコーダを作る(5) — DCCパケットモニタの製作

 今回は、デコーダ開発の土台として作成した「DCCパケットモニタ(v2.1.0)」の機能と、実装におけるこだわりの工夫点をご紹介します。

2026-04-11.png

 ただ信号を見るだけではなく、PCのキーボードから操作できる「対話型(インタラクティブ)」なツールに仕上げました。

1. DCCパケットモニタの使い方
 DCCデコーダ開発基板のシリアル端子にTTL-232R-5Vを使用してPCにUSB接続し、TeraTerm等のターミナルソフトでシリアル(115200bps)で接続すると、線路上のDCCパケットがリアルタイムにデコードされ、見やすいテキストで流れていきます。さらに、ターミナルソフト上で以下のキーボード入力をすることで、表示をリアルタイムに制御できます 。

  • [I] キー:Idleパケットの表示切替
 線路に常に流れている「Idle(待機)パケット」の表示/非表示をワンタッチで切り替えます 。
  • [A] キー:特定アドレスのフィルタリング
 Aを押した後に「3」などの数値を入力してEnterを押すと、その車両(またはアクセサリ)宛てのパケットだけを抽出して表示します 。複数の車両が向けのパケットが送出されている状態でのデバッグに絶大な威力を発揮します。
  • [R] キー:フィルタのリセット
 設定した制限を解除し、すべてのパケットを表示するデフォルト状態に戻します 。
  • [H] キー:ヘルプメニューの表示
 現在のフィルタ設定状態やコマンド一覧を確認できます 。

2. 開発の工夫点・こだわり
 PICマイコンで製作していた旧バージョンから、CH32V003(RISC-V)の能力を引き出すために、ソフトウェアの構造を根本から見直しました。

工夫①:EXTI(外部割り込み)とTIM2によるエッジ検出
 PIC時代は「CPUがひたすらピンの状態を監視する(ポーリング)」方式でしたが、今回はCH32V003のEXTI(外部割り込み)を採用しました 。
 DCC信号が変化した瞬間(立ち上がり・立ち下がり)だけ割り込み処理が走り、1μs精度に設定したTIM2(タイマー)で直前のエッジからの経過時間を計算します 。経過時間が80μs以下なら「1」、それ以上なら「0」と判定するロジックです 。これによりCPUの負荷が激減し、UART通信などの別処理を並行して行う余裕が生まれました。

工夫②:「見やすさ」を求めた処理
 シリアル出力されるHEX(16進数)生データの長さに応じて、その後のテキスト表示位置がガタガタずれるのは、開発を進めるなかで非常にストレスでした。
 そこで今回のv2.1.0では、生データ表示エリアを「最大6バイト幅」で完全固定しました 。データが短い場合は、足りない部分を空白(スペース3文字分)でパディング(穴埋め)しています 。これにより、区切り文字である | の位置が縦一直線に美しく揃い、ログの視認性が飛躍的に向上しました。
 また、パケットの内容について16進数を翻訳した内容を右側に表示するようにしました。これにより、どのような順番で、どのようなパケットがコマンドステーションから送出されているのかが、一目瞭然とすることができました。

工夫③:「フィルタリング」機能
 アドレスフィルタ([A]キー)の処理順序にもこだわっています。
 パケットの受信が完了し、エラーチェック(XOR)を通った直後、シリアルに出力する「前」の段階でアドレスを計算・判定しています 。
 車両(Loco)だけでなく、計算が複雑なアクセサリ(9ビットアドレス)もしっかり判定し 、もしターゲット外のアドレスであれば continue で処理をスキップさせます 。これにより、不要なHEXデータすら画面に一切表示されない、フィルタリングを実現しました 。

まとめ
 ハードウェアのタイマー割り込みによる確実な受信と、RISC-Vマイコンの処理能力を活かしたUI処理。DCCデコーダを作るための「裏方ツール」ですが、これだけでもDCC電子工作の題材として十分に楽しい開発でした。

 特に一概にDCC信号とは言っても、コマンドステーションによって送出するパケットのクセが随分と異なることが分かります。

 目に見えないDCC信号が、自分の思い通りに美しく整理されて画面に流れていく快感はたまりません。最強のデバッグ環境が整いましたし、何と言っても難しいDCC信号の読み取りに成功したので、あとは、ワンコインデコーダでこれまで培った技術を活かして、次はいよいよDCCデコーダの機能実装へと進んでいきます!ご期待ください!!

 最後に開発したDCCパケットモニタのソースコードを参考に貼っておきますね。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
/**
 * ============================================================================
 * CH32V003 DCC Packet Analyzer (v2.1.0)  2026.4.14  Copyright(c)Nucky
 * ============================================================================
 * [更新履歴]
 * v2.1.0:
 * - HEX生データの表示を最大6バイト幅で固定パディングし、'|'の位置を揃えた。
 * - アドレスフィルタの判定をメインループの先頭に移動し、ターゲット以外のパケット
 * (HEXデータを含む)を完全に非表示にするよう修正。アクセサリにも対応。
 * * [ハードウェア接続]
 * CH32V003 PD4 (DCC_PIN)  <--- 外部割り込み(EXTI)で信号のエッジを検出
 * CH32V003 PD5 (UART_TX)  ---> PC (Tera Term等へ 115200bpsで出力)
 * CH32V003 PD6 (UART_RX)  <--- PC (キーボードからのフィルタ操作を受信)
 * * [License Information]
 * 本ソースコードは GPL (General Public License) に準拠して公開します。
 * - 複製・改変・再配布を行う際は、必ず著作者のクレジット表記をお願いします。
 * - 事前連絡のない無断での商用利用は固く禁止いたします。
 * ============================================================================
 */
 
#include "ch32v00x.h"
 
// ============================================================================
//  1. 定数・グローバル変数定義
// ============================================================================
 
// --- ハードウェアピン設定 ---
#define DCC_PORT GPIOD
#define DCC_PIN  GPIO_Pin_4
#define DCC_BUF_SIZE 8      // DCCパケットを格納する最大バイト数(通常最大6バイト)
 
// --- DCC受信ステートマシン用の状態定義 ---
typedef enum {
    DCC_STATE_PREAMBLE,   // プリアンブル(10個以上の'1'の連続)を待っている状態
    DCC_STATE_START_BIT,  // バイト間の開始/終了ビット('0'なら継続, '1'なら終了)を待つ状態
    DCC_STATE_DATA_BYTE   // 8ビットのデータを受信中の状態
} DCC_State_t;
 
// --- 割り込み処理(ISR)内で変更されるため、必ず volatile を付ける ---
volatile DCC_State_t dcc_state = DCC_STATE_PREAMBLE; // 現在の受信状態
volatile uint16_t preamble_count = 0;   // 連続して受信した'1'の数
volatile uint8_t  bit_count = 0;        // 1バイト(8ビット)中の受信済みビット数
volatile uint8_t  current_byte = 0;     // 現在組み立て中の1バイトデータ
volatile uint8_t  byte_index = 0;       // パケットバッファ内の書き込み位置(0~7)
volatile uint8_t  packet_buffer[DCC_BUF_SIZE]; // 受信したDCCデータを格納する配列
volatile uint8_t  packet_ready = 0;     // 1: パケット受信完了(メインループで処理待ち)
volatile uint16_t last_edge_time = 0;   // 直前のエッジ変化時のTIM2カウント値(1us単位)
 
// --- UI・フィルタリング用変数 ---
volatile uint8_t  filter_show_idle = 0;   // 0: Idleパケット(0xFF)を隠す, 1: 表示する
volatile uint16_t filter_address = 0;     // 0: 全パケット表示, 1-9999: 指定アドレスのみ表示
volatile uint8_t  ui_input_mode = 0;      // 0: 通常コマンド待機, 1: アドレス数値入力モード
uint16_t ui_buffer_val = 0;               // UARTから入力された数値を一時的に計算・保持するバッファ
uint8_t  current_page = 1;                // サービスモード(Paged)において、直前に指定されたページ番号を保持
 
// ============================================================================
//  2. UART入出力・ヘルパー関数
// ============================================================================
 
// 1文字送信関数
void UART_PutChar(char c) {
    // 送信データレジスタが空(TXE)になるまで待機してから送信
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    USART_SendData(USART1, c);
}
 
// 文字列送信関数 (ヌル文字 '\0' に達するまで1文字ずつ送信)
void UART_Print(const char *s) {
    while(*s) UART_PutChar(*s++);
}
 
// 10進数出力関数 (割り算を使用して各桁を算出)
void UART_PrintDec(uint16_t num) {
    char buf[6]; int i = 0;
    if (num == 0) { UART_PutChar('0'); return; }
    while (num > 0) { buf[i++] = (num % 10) + '0'; num /= 10; }
    while (--i >= 0) UART_PutChar(buf[i]); // 下の位から配列に入ったので逆順に出力
}
 
// 16進数(HEX)2桁出力関数 (例: 255 -> "FF")
void UART_PrintHex(uint8_t data) {
    const char hex_chars[] = "0123456789ABCDEF";
    UART_PutChar(hex_chars[data >> 4]);   // 上位4ビット
    UART_PutChar(hex_chars[data & 0x0F]); // 下位4ビット
}
 
// ファンクション状態(ON/OFF)の出力関数 (例: F1*, F2_)
void UART_PrintFuncState(uint8_t start_f, uint8_t data, uint8_t count) {
    for(uint8_t i = 0; i < count; i++) {
        UART_Print(" F");
        UART_PrintDec(start_f + i); // F番号の出力
        // 指定ビットが1なら'*'(ON)、0なら'_'(OFF)を出力
        UART_PutChar((data & (1 << i)) ? '*' : '_');
    }
}
 
// ============================================================================
//  3. 解析ロジック (NMRA規格に基づくパケットデコード)
// ============================================================================
 
// サービスモード(プログラミングトラックでのCV読み書き)の解析
// buf[0]の先頭が 0111 (0x70) のパケット
void Analyze_Service_Mode(uint8_t *buf, uint8_t len) {
    UART_Print("| SVC ");
    if (len == 3) {
        if (buf[0] & 0x08) { // Paged Mode (0111 1xxx)
            uint8_t reg = (buf[0] & 0x03) + 1; // 下位2bitがレジスタ(1~4)
            if (reg == 2) {
                current_page = buf[1]; // レジスタ2はページ設定。値を記憶する。
                UART_Print("[PageReg W:"); UART_PrintDec(current_page); UART_Print("]");
            } else {
                // 記憶したページ番号を用いて実際のCV番号を計算: (Page-1)*4 + Reg
                uint16_t cv = (current_page > 0) ? (uint16_t)(current_page - 1) * 4 + reg : reg;
                UART_Print("[CV"); UART_PrintDec(cv);
                UART_Print((buf[0] & 0x04) ? " Write:" : " Verify:"); // Bit2で読み書き判定
                UART_PrintDec(buf[1]); UART_Print("]");
            }
        } else { // Address Mode (CV1専用の古いモード)
            UART_Print("[CV1 ");
            UART_Print((buf[0] & 0x04) ? "Write:" : "Verify:");
            UART_PrintDec(buf[1]); UART_Print("]");
        }
    } else if (len == 4) { // Direct Mode (現在の主流: 0111 11CC)
        // 上位アドレス(buf[0]の下位2bit)と下位アドレス(buf[1])を結合
        uint16_t cv = ((uint16_t)(buf[0] & 0x03) << 8 | buf[1]) + 1;
        UART_Print("[Direct CV"); UART_PrintDec(cv);
        UART_Print((buf[0] & 0x04) ? " Write:" : " Verify:");
        UART_PrintDec(buf[2]); UART_Print("]");
    }
}
 
// アクセサリデコーダ(ポイントや信号機)の解析
// buf[0]の先頭が 10 (0x80) のパケット
void Analyze_Accessory(uint8_t *buf) {
    // 複雑な9ビットアドレス計算: buf[1]の上位3bitを反転させ、buf[0]の下位6bitと結合
    uint16_t addr_9bit = ((uint16_t)((~buf[1]) & 0x70) << 2) | (buf[0] & 0x3F);
    uint8_t pair = (buf[1] >> 1) & 0x03; // ペア番号(0~3)
    // 1~2048 の一般的な表示アドレスに変換
    uint16_t display_addr = (addr_9bit > 0) ? ((addr_9bit - 1) * 4) + pair + 1 : pair + 1;
 
    UART_Print("| Acc:"); UART_PrintDec(display_addr);
    if (buf[1] & 0x80) { // 基本アクセサリ (Bit7 == 1)
        UART_Print(" [P:"); UART_PrintDec(pair);
        UART_Print((buf[1] & 0x01) ? " Dir:1" : " Dir:0"); // Bit0: 方向
        UART_Print((buf[1] & 0x08) ? " (ON)]" : " (OFF)]"); // Bit3: コイル通電
    } else { // 拡張アクセサリ (信号機の現示など)
        UART_Print(" [Extended]");
    }
}
 
// 車両デコーダ(Loco)および走行中プログラミング(OPS)の解析
void Analyze_Loco(uint8_t *buf) {
    uint16_t loco_addr;
    uint8_t inst, d4 = 0, d5 = 0;
 
    // アドレスの長さに応じて、命令(Instruction)が入っているバイト位置を調整
    if ((buf[0] & 0xC0) == 0xC0) { // ロングアドレス (14bit)
        loco_addr = ((uint16_t)(buf[0] & 0x3F) << 8) | buf[1];
        inst = buf[2]; d4 = buf[3]; d5 = buf[4];
    } else { // ショートアドレス (7bit)
        loco_addr = buf[0];
        inst = buf[1]; d4 = buf[2]; d5 = buf[3];
    }
 
    UART_Print("| Loco:"); UART_PrintDec(loco_addr);
 
    // インストラクション(inst)の上位ビットパターンでコマンドを分類
    if ((inst & 0xF0) == 0xE0) { // OPS Mode (POM) 走行中のCV書き込み
        uint16_t cv = ((uint16_t)(inst & 0x03) << 8 | d4) + 1;
        UART_Print(" [OPS] CV"); UART_PrintDec(cv);
        if (inst & 0x08) {
            if (inst & 0x04) UART_Print(" Write:"); else UART_Print(" Reserved");
        } else UART_Print(" Verify:");
        UART_PrintDec(d5);
    }
    else if ((inst & 0xC0) == 0x00) { // 特殊命令群
        if (inst == 0x3F) { // 128スピードステップ
            UART_Print(" [128Spd] ");
            UART_Print((d4 & 0x80) ? "FWD " : "REV "); // 最上位ビットが方向
            UART_PrintDec(d4 & 0x7F);                  // 下位7ビットが速度(0-127)
        } else UART_Print(" [AdvCmd]"); // 重連(Consist)等
    }
    else if ((inst & 0xE0) == 0x40) { // 14/28スピードステップ
        UART_Print(" [Speed] ");
        UART_Print((inst & 0x20) ? "FWD " : "REV ");
        UART_PrintDec(inst & 0x1F);
    }
    else if ((inst & 0xE0) == 0x80) { // ファンクション F0-F4
        UART_Print(" [F0-F4]");
        UART_Print((inst & 0x10) ? " F0*" : " F0_"); // F0だけBit4に配置されている特殊仕様
        UART_PrintFuncState(1, inst, 4); // F1-F4はBit0-3
    }
    else if ((inst & 0xF0) == 0xA0) { UART_Print(" [F9-F12]"); UART_PrintFuncState(9, inst, 4); }
    else if ((inst & 0xF0) == 0xB0) { UART_Print(" [F5-F8]"); UART_PrintFuncState(5, inst, 4); }
    else if (inst == 0xDE || inst == 0xDF || (inst >= 0xD8 && inst <= 0xDC)) { // 拡張ファンクション F13-F68
        // インストラクションのHEX値から開始ファンクション番号を逆算
        uint8_t start_f = (inst == 0xDE) ? 13 : (inst == 0xDF) ? 21 : 29 + (inst - 0xD8) * 8;
        UART_Print(" [F"); UART_PrintDec(start_f); UART_Print("-F"); UART_PrintDec(start_f+7); UART_Print("]");
        UART_PrintFuncState(start_f, d4, 8);
    }
}
 
// ============================================================================
//  4. UI状態管理・UART受信割り込み
// ============================================================================
 
// ヘルプメニューの表示
void Show_Help(void) {
    UART_Print("\r\n--- CH32V003 DCC Analyzer v2.1.0 ---\r\n");
    UART_Print("Current Status:\r\n");
    UART_Print(" [I] Show Idle   : "); UART_Print(filter_show_idle ? "Yes\r\n" : "No\r\n");
    UART_Print(" [A] Target Addr : ");
    if (filter_address == 0) UART_Print("All\r\n"); else { UART_PrintDec(filter_address); UART_Print("\r\n"); }
    UART_Print("------------------------------------\r\n");
    UART_Print("Commands: [I]Toggle Idle, [A]Set Filter Addr, [R]Reset Filters, [H]Help\r\n\r\n");
}
 
// UART受信割り込みハンドラ (PCからのキーボード入力を処理)
void USART1_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void USART1_IRQHandler(void) {
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        char c = USART_ReceiveData(USART1);
 
        if (ui_input_mode) {
            // [A]キー押下後の数値入力モード
            if (c >= '0' && c <= '9') {
                ui_buffer_val = ui_buffer_val * 10 + (c - '0'); // 桁を繰り上げて加算
                UART_PutChar(c); // エコーバック
            } else if (c == '\r' || c == '\n') { // Enterキーで確定
                filter_address = ui_buffer_val;
                ui_input_mode = 0;
                UART_Print("\r\nOK: Target Address Set (");
                if(filter_address == 0) UART_Print("All"); else UART_PrintDec(filter_address);
                UART_Print(")\r\n");
            }
        } else {
            // 通常コマンド入力モード
            if (c == '\r' || c == '\n') return; // 空のEnterは無視
             
            if (c == 'i' || c == 'I') {
                filter_show_idle = !filter_show_idle; // フラグの反転
                UART_Print("\r\nIdle Display Toggled.\r\n");
            } else if (c == 'a' || c == 'A') {
                UART_Print("\r\nEnter Target Address (0-9999) + Enter: ");
                ui_buffer_val = 0;
                ui_input_mode = 1; // 数値入力モードへ遷移
            } else if (c == 'r' || c == 'R') {
                filter_address = 0;
                filter_show_idle = 0;
                UART_Print("\r\nFilters Reset to Default.\r\n");
            } else if (c == 'h' || c == 'H') {
                Show_Help();
            }
        }
    }
}
 
// ============================================================================
//  5. メインロジック・DCCエンジン
// ============================================================================
 
// DCCビット(1 or 0)を受け取り、ステートマシンでパケットを組み立てる
void Process_DCC_Bit(uint8_t bit) {
    switch (dcc_state) {
        case DCC_STATE_PREAMBLE:
            if (bit == 1) {
                if (preamble_count < 255) preamble_count++; // オーバーフロー防止
            } else {
                if (preamble_count >= 10) {
                    // '1'が10回以上続いた後の'0'はパケット開始ビット
                    dcc_state = DCC_STATE_DATA_BYTE;
                    bit_count = 0; current_byte = 0; byte_index = 0;
                }
                preamble_count = 0; // 10回未満ならノイズと見なしてリセット
            }
            break;
 
        case DCC_STATE_DATA_BYTE:
            // 受信したビットを左にシフトしながらバイトに組み立てる
            current_byte = (current_byte << 1) | bit;
            if (++bit_count == 8) {
                // 8ビット揃ったらバッファに保存し、次の区切りビットを待つ
                if (byte_index < DCC_BUF_SIZE) packet_buffer[byte_index++] = current_byte;
                dcc_state = DCC_STATE_START_BIT;
            }
            break;
 
        case DCC_STATE_START_BIT:
            if (bit == 0) {
                // '0'ならまだ次のバイトが続く
                dcc_state = DCC_STATE_DATA_BYTE;
                bit_count = 0; current_byte = 0;
            } else {
                // '1'ならエンドビット(パケット終了)
                packet_ready = 1; // メインループに解析処理を依頼
                dcc_state = DCC_STATE_PREAMBLE;
                preamble_count = 1; // この'1'は次のプリアンブルの1ビット目になる可能性がある
            }
            break;
 
        default:
            dcc_state = DCC_STATE_PREAMBLE; preamble_count = 0; break;
    }
}
 
// 外部割り込みハンドラ (PD4ピンの変化エッジで発動)
void EXTI7_0_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void EXTI7_0_IRQHandler(void) {
    if(EXTI_GetITStatus(EXTI_Line4) != RESET) {
        EXTI_ClearITPendingBit(EXTI_Line4); // 割り込みフラグクリア
 
        uint16_t now = TIM2->CNT; // 1us単位で動くタイマーの現在値を取得
        // 直前のエッジからの経過時間を計算 (16bit符号なし演算なのでオーバーフローも自然に処理される)
        uint16_t elapsed = (uint16_t)(now - last_edge_time);
         
        if (elapsed < 20) return; // 20us未満の変動はチャタリング/ノイズとして無視
        last_edge_time = now;
 
        // Lowレベル(0V)に落ちた瞬間=パルス(High期間)の終わり
        if (GPIO_ReadInputDataBit(DCC_PORT, DCC_PIN) == Bit_RESET) {
            if (elapsed > 10000) {
                // 10ms以上無信号だった場合はタイムアウトとし、ステートを初期化
                dcc_state = DCC_STATE_PREAMBLE;
                preamble_count = 0; return;
            }
            // 経過時間で 1/0 を判定 (80us以下なら'1', それ以上なら'0')
            Process_DCC_Bit((elapsed <= 80) ? 1 : 0);
        }
    }
}
 
// --- メイン関数 ---
int main(void) {
    // 割り込み優先度グループ設定
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    SystemCoreClockUpdate(); // システムクロック(48MHz)の更新
 
    // 1. 周辺機能のクロック有効化 (GPIOD, USART1)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_USART1, ENABLE);
     
    // 2. UARTのピン設定 (PD5:TX 出力, PD6:RX 入力)
    GPIO_InitTypeDef gi_tx = {GPIO_Pin_5, GPIO_Speed_50MHz, GPIO_Mode_AF_PP};
    GPIO_Init(GPIOD, &gi_tx);
    GPIO_InitTypeDef gi_rx = {GPIO_Pin_6, GPIO_Speed_50MHz, GPIO_Mode_IN_FLOATING};
    GPIO_Init(GPIOD, &gi_rx);
     
    // 3. USART1 初期化 (115200bps, 8bit, 1stop, No Parity)
    USART_InitTypeDef ui = {115200, USART_WordLength_8b, USART_StopBits_1, USART_Parity_No, USART_Mode_Tx | USART_Mode_Rx, USART_HardwareFlowControl_None};
    USART_Init(USART1, &ui);
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 受信割り込み許可
    USART_Cmd(USART1, ENABLE);
     
    // UART用NVIC(割り込みコントローラ)設定
    NVIC_InitTypeDef ni_uart = {USART1_IRQn, 0, 0, ENABLE};
    NVIC_Init(&ni_uart);
 
    // 4. TIM2 初期化 (1usの分解能を得るための設定)
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    // Prescaler = 48MHz/1000000 - 1 = 47. これによりTIM2は1MHz(1us)単位でカウントアップする
    TIM_TimeBaseInitTypeDef tb = {(uint16_t)((SystemCoreClock / 1000000) - 1), TIM_CounterMode_Up, 0xFFFF, TIM_CKD_DIV1, 0};
    TIM_TimeBaseInit(TIM2, &tb);
    TIM_Cmd(TIM2, ENABLE);
     
    // 5. EXTI(外部割り込み) 初期化 (PD4ピン)
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOD, GPIO_PinSource4);
    // 立ち上がり・立ち下がりの両方のエッジで割り込みを発生させる
    EXTI_InitTypeDef ei = {EXTI_Line4, EXTI_Mode_Interrupt, EXTI_Trigger_Rising_Falling, ENABLE};
    EXTI_Init(&ei);
     
    // EXTI用NVIC設定
    NVIC_InitTypeDef ni_dcc = {EXTI7_0_IRQn, 1, 0, ENABLE};
    NVIC_Init(&ni_dcc);
 
    // 起動時の初期画面出力
    Show_Help();
    uint8_t local_buf[DCC_BUF_SIZE]; // メインループ処理用のローカルバッファ
 
    // --- メインループ ---
    while(1) {
        // パケット受信完了フラグが立つまで待機
        if (packet_ready) {
            // 受信バッファのコピー中は、データの破損を防ぐため一時的にDCC割り込みを禁止
            NVIC_DisableIRQ(EXTI7_0_IRQn);
            uint8_t len = byte_index;
            for(uint8_t i = 0; i < len; i++) local_buf[i] = packet_buffer[i];
            packet_ready = 0;
            byte_index = 0;
            NVIC_EnableIRQ(EXTI7_0_IRQn); // コピー完了後、割り込み再開
 
            // エラーチェック (XOR計算)
            // 最終バイト(エラー訂正バイト)を除いた全バイトをXORする
            uint8_t ck = 0;
            for (uint8_t i = 0; i < len - 1; i++) ck ^= local_buf[i];
             
            // 計算結果と最終バイトが一致していれば正常なパケット
            if (ck == local_buf[len - 1]) {
                 
                uint8_t skip_packet = 0; // 1ならこのパケットの印字をスキップ
 
                // --- フィルタリング処理 ---
                // 1. Idleパケットのフィルタ
                if (local_buf[0] == 0xFF && !filter_show_idle) skip_packet = 1;
 
                // 2. 指定アドレスフィルタ
                if (!skip_packet && filter_address != 0) {
                    uint16_t target = 0xFFFF; // 仮の無効アドレス
                    // Idle、Reset、Service Mode 以外のパケットからアドレスを抽出
                    if (local_buf[0] != 0xFF && local_buf[0] != 0x00 && (local_buf[0] & 0xF0) != 0x70) {
                        if ((local_buf[0] & 0xC0) == 0x80) { // Accessoryアドレス計算
                            uint16_t addr_9bit = ((uint16_t)((~local_buf[1]) & 0x70) << 2) | (local_buf[0] & 0x3F);
                            uint8_t pair = (local_buf[1] >> 1) & 0x03;
                            target = (addr_9bit > 0) ? ((addr_9bit - 1) * 4) + pair + 1 : pair + 1;
                        } else { // Locoアドレス計算
                            if ((local_buf[0] & 0xC0) == 0xC0) target = ((uint16_t)(local_buf[0] & 0x3F) << 8) | local_buf[1];
                            else target = local_buf[0];
                        }
                    }
                    if (target != filter_address) skip_packet = 1; // ターゲット不一致ならスキップフラグを立てる
                }
                 
                // フィルタ条件に引っかかった場合はここで処理を打ち切り、次のパケットを待つ
                if (skip_packet) continue;
 
                // --- シリアル出力処理 ---
                // 生データ(HEX)の出力。表示幅を最大6バイト分に固定し、空白でパディングする
                for (uint8_t i = 0; i < 6; i++) {
                    if (i < len) {
                        UART_PrintHex(local_buf[i]);
                        UART_PutChar(' ');
                    } else {
                        // データが6バイト未満の場合、区切り文字 '|' の位置を合わせるための空白(3文字分)
                        UART_Print("   ");
                    }
                }
 
                // パケットの意味を解析して出力
                if (local_buf[0] == 0xFF) UART_Print("| Idle");
                else if (local_buf[0] == 0x00) UART_Print("| Reset");
                else if ((local_buf[0] & 0xF0) == 0x70) Analyze_Service_Mode(local_buf, len);
                else if ((local_buf[0] & 0xC0) == 0x80) Analyze_Accessory(local_buf);
                else Analyze_Loco(local_buf);
 
                UART_Print("\r\n"); // 1パケット分の出力終了
            }
        }
    }
}
この記事にトラックバックする(FC2ブログユーザー)
https://webnucky.blog.fc2.com/tb.php/365-395d7466

トラックバック

コメント

コメントの投稿

コメントの投稿
管理者にだけ表示を許可する