nuckyのブログです。旅行記や食べ歩き、乗り鉄や電子工作、鉄道模型とかを紹介しています
1. DCCパケットモニタの使い方
2. 開発の工夫点・こだわり
まとめ
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パケット分の出力終了 } } }} |
Author:nucky
Web Nucky Blogへようこそ!
コメントの投稿