先日、このブログで記事にした通り、pioasmでパルス幅を計測するルーチンを作った後、DCCパルスの羅列が得られたので、それをNMRA DCC Libraryのパケットデコーダ部分を改造して、流し込めるようにしました。大したことはしてません。割り込みになっている部分をビット情報を流し込むように改造し、halfbitなどの認識処理を全て削除してシンプルにしただけです。もっとシンプルに直せますが、そこまでするならNMRA DCC Libraryを使わずに、独自のライブラリを作った方が良いので、ここまでとしました。
void PushDCCbit (byte inBit)
{
uint8_t DccBitVal;
static byte DCC_IrqRunning, preambleBitCount;
DccBitVal = inBit;
#ifdef ALLOW_NESTED_IRQ
DCC_IrqRunning = true;
interrupts(); // time critical is only the micros() command,so allow nested irq's
#endif
#ifdef DCC_DEBUG
DccProcState.TickCount++;
#endif
switch (DccRx.State)
{
case WAIT_PREAMBLE:
// We don't have to do anything special - looking for a preamble condition is done always
SET_TP2;
break;
#ifndef SYNC_ALWAYS
case WAIT_START_BIT_FULL:
// wait for startbit without level checking
if (!DccBitVal)
{
// we got the startbit
CLR_TP2;
CLR_TP1;
DccRx.State = WAIT_DATA ;
CLR_TP1;
// initialize packet buffer
DccRx.PacketBuf.Size = 0;
/*for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ )
DccRx.PacketBuf.Data[i] = 0;*/
DccRx.PacketBuf.PreambleBits = preambleBitCount;
DccRx.BitCount = 0 ;
DccRx.chkSum = 0 ;
DccRx.TempByte = 0 ;
//SET_TP1;
}
break;
#endif
case WAIT_START_BIT:
// we are looking for first half "0" bit after preamble
if (DccBitVal)
{
// "1"
preambleBitCount++;
}
else
{
// "0"
DccRx.State = WAIT_DATA ;
DccRx.PacketBuf.Size = 0;
/*for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ )
DccRx.PacketBuf.Data[i] = 0;*/
DccRx.PacketBuf.PreambleBits = preambleBitCount;
DccRx.BitCount = 0 ;
DccRx.chkSum = 0 ;
DccRx.TempByte = 0 ;
}
break;
case WAIT_DATA:
CLR_TP2;
DccRx.BitCount++;
DccRx.TempByte = (DccRx.TempByte << 1) ;
if (DccBitVal)
DccRx.TempByte |= 1 ;
if (DccRx.BitCount == 8)
{
if (DccRx.PacketBuf.Size == MAX_DCC_MESSAGE_LEN) // Packet is too long - abort
{
DccRx.State = WAIT_PREAMBLE ;
DccRx.BitCount = 0 ;
}
else
{
DccRx.State = WAIT_END_BIT ;
DccRx.PacketBuf.Data[ DccRx.PacketBuf.Size++ ] = DccRx.TempByte ;
DccRx.chkSum ^= DccRx.TempByte;
}
}
break;
case WAIT_END_BIT:
SET_TP2;
CLR_TP2;
DccRx.BitCount++;
if (DccBitVal) // End of packet?
{
CLR_TP3;
SET_TP4;
DccRx.State = WAIT_PREAMBLE ;
DccRx.BitCount = 0 ;
SET_TP1;
if (DccRx.chkSum == 0)
{
// Packet is valid
DccRx.PacketCopy = DccRx.PacketBuf ;
DccRx.DataReady = 1 ;
// SET_TP2; CLR_TP2;
preambleBitCount = 0 ;
}
else
{
// Wrong checksum
CLR_TP1;
#ifdef DCC_DBGVAR
DB_PRINT ("Cerr");
countOf.Err++;
#endif
}
SET_TP3;
CLR_TP4;
}
else // Get next Byte
{
// KGW - Abort immediately if packet is too long.
if (DccRx.PacketBuf.Size == MAX_DCC_MESSAGE_LEN) // Packet is too long - abort
{
DccRx.State = WAIT_PREAMBLE ;
DccRx.BitCount = 0 ;
}
else
{
DccRx.State = WAIT_DATA ;
DccRx.BitCount = 0 ;
DccRx.TempByte = 0 ;
}
}
}
// unless we're already looking for the start bit
// we always search for a preamble ( ( 10 or more consecutive 1 bits )
// if we found it within a packet, the packet decoding is aborted because
// that much one bits cannot be valid in a packet.
if (DccRx.State != WAIT_START_BIT)
{
if (DccBitVal)
{
preambleBitCount++;
//SET_TP2;
if (preambleBitCount > 10)
{
CLR_TP2;
#ifndef SYNC_ALWAYS
if (DccRx.chkSum == 0)
{
// sync must be correct if chksum was ok, no need to check sync
DccRx.State = WAIT_START_BIT_FULL;
}
else
{
#endif
DccRx.State = WAIT_START_BIT ;
//CLR_TP1;
#ifndef SYNC_ALWAYS
}
#endif
}
}
else
{
CLR_TP1;
preambleBitCount = 0 ;
// SET_TP2; CLR_TP2;
}
}
#ifdef ALLOW_NESTED_IRQ
DCC_IrqRunning = false;
#endif
//CLR_TP1;
CLR_TP3;
}
受信処理のスケッチは以下のようなものを書いて動作確認しました。先日のスケッチに、NMRA DCC Libaryの処理を追加した程度です。
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "dcc_pulse_dec.h"
#include "DccCV.h"
#include "NmraDcc_pio.h"
#define PIN_DCC 2
// pioasmを使って、dccpulse.pioからdcc_pulse_dec.hを生成しています。
// https://wokwi.com/tools/pioasm
PIO gPio;
uint gPio_SM;
unsigned long gRecvPulseWidth[8];
byte gRecvPulseBit[8] = {0,0,0,0,0,0,0,0};
//使用クラスの宣言
NmraDcc Dcc;
DCC_MSG Packet;
struct CVPair {
uint16_t CV;
uint8_t Value;
};
CVPair FactoryDefaultCVs [] = {
{CV_MULTIFUNCTION_PRIMARY_ADDRESS, DECODER_ADDRESS}, // CV01
{CV_ACCESSORY_DECODER_ADDRESS_MSB, 0}, // CV09 The LSB is set CV 1 in the libraries .h file, which is the regular address location, so by setting the MSB to 0 we tell the library to use the same address as the primary address. 0 DECODER_ADDRESS
{CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB, 0}, // CV17 XX in the XXYY address
{CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB, 0}, // CV18 YY in the XXYY address
{CV_29_CONFIG, 0x10 },
{CV_28_CONFIG, 1 },
{CV_VSTART, 16},
{CV_ACCRATIO, 60},
{CV_DECCRATIO, 30},
{CV_VMIDDLE, 125},
{CV_VMAX, 250},
{CV_BEMFcoefficient, 64},
{CV_PI_P, 18},
{CV_PI_I, 96},
{CV_BEMFCUTOFF,0},
{CV63_MASTERVOL,128},
{CV65_KICKSTART,16},
{CV_ST1, 1}, //0 SpeedTable CV67-94 28
{CV_ST2, 6}, //1
{CV_ST3, 12}, //2
{CV_ST4, 16}, //3
{CV_ST5, 20}, //4
{CV_ST6, 24}, //5
{CV_ST7, 28}, //6
{CV_ST8, 32},
{CV_ST9, 36},
{CV_ST10, 42},
{CV_ST11, 48},
{CV_ST12, 54},
{CV_ST13, 60},
{CV_ST14, 69},
{CV_ST15, 78},
{CV_ST16, 85},
{CV_ST17, 92},
{CV_ST18, 105},
{CV_ST19, 118},
{CV_ST20, 127},
{CV_ST21, 136},
{CV_ST22, 152},
{CV_ST23, 168},
{CV_ST24, 188},
{CV_ST25, 208},
{CV_ST26, 219},
{CV_ST27, 240},
{CV_ST28, 255},//27
};
uint8_t FactoryDefaultCVIndex = sizeof(FactoryDefaultCVs) / sizeof(CVPair);
void setup()
{
//PIOのch 0を確保
gPio = pio0;
//アセンブラのPIOプログラムへのアドレス
uint offset = pio_add_program(gPio, &dcc_pulse_dec_program);
// 空いているステートマシンの番号を取得
gPio_SM = pio_claim_unused_sm(gPio, true);
// PIOプログラムを初期化
dcc_pulse_dec_init(gPio, gPio_SM, offset, PIN_DCC);
//デバッグ用シリアル
Serial.begin(115200);
// Setup which External Interrupt, the Pin it's associated with that we're using, disable pullup.
//Dcc.pin(2, 2, 0); // RS Pi Pico GP2をDCC信号入力端子に設定
// Call the main DCC Init function to enable the DCC Receiver
Dcc.init( 140, 1, FLAGS_MY_ADDRESS_ONLY , 0 );
}
void loop()
{
// You MUST call the NmraDcc.process() method frequently from the Arduino loop() function for correct library operation
Dcc.process();
//PIO RX FIFO check
if( pio_sm_is_rx_fifo_full(gPio, gPio_SM) == true)
{
//FIFOから速く抜き取る(抜かないと処理が止まる)
for( int i = 0; i < 8; i++)
{
gRecvPulseWidth[i] = pio_sm_get(gPio, gPio_SM);
}
//中身の表示
//Serial.println("RECV:");
for( int i = 0; i < 8; i++)
{
//print32bits(gRecvPulseWidth[i]);
byte aDCCBits = 0;
for( int j = 0 ; j < 8; j++)
{
unsigned long aBitPulse = getBit((gRecvPulseWidth[i] >> (j * 4)) & 15);
aDCCBits = aDCCBits + ( aBitPulse << j);
PushDCCbit(aBitPulse);
}
//Little Endian
gRecvPulseBit[i] = aDCCBits;
//Serial.println(aDCCBits, BIN);
}
}
else
{
//Serial.print(".");
}
}
void print32bits(unsigned long inBuf)
{
Serial.print( getUS(inBuf & 15) );
Serial.print(" ");
Serial.print( getUS((inBuf >> 4) & 15) );
Serial.print(" ");
Serial.print( getUS((inBuf >> 8) & 15) );
Serial.print(" ");
Serial.print( getUS((inBuf >> 12) & 15) );
Serial.print(" ");
Serial.print( getUS((inBuf >> 16) & 15) );
Serial.print(" ");
Serial.print( getUS((inBuf >> 20) & 15) );
Serial.print(" ");
Serial.print( getUS((inBuf >> 24) & 15) );
Serial.print(" ");
Serial.println( getUS((inBuf >> 28) & 15) );
}
unsigned long getUS(unsigned long inVal)
{
return (15 - inVal) * 14;
}
byte getBit(unsigned long inVal)
{
byte aRet = 0;
switch(15 - inVal)
{
case 3:
case 4:
case 5:
aRet = 1;
break;
default:
aRet = 0;
break;
}
return aRet;
}
//---------------------------------------------------------------------
//DCC速度信号の受信によるイベント
// SppedSteps 127で,Speed 127。MAX aSpeedRefは127:65019
//---------------------------------------------------------------------
extern void notifyDccSpeed (uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t Speed, DCC_DIRECTION Dir, DCC_SPEED_STEPS SpeedSteps)
{
int32_t aSpeedRef = 0;
//速度値の正規化(65535(16bit)を100%とする処理)
if( Speed >= 1 )
{
aSpeedRef = ((Speed - 1) * 65535) / SpeedSteps;
} else {
//緊急停止信号受信時の処理 //Nagoden comment 2016/06/11
#ifdef DEBUG
Serial.println("***** Emagency STOP **** ");
#endif
aSpeedRef = 0;
}
// デバッグメッセージ
Serial.print("Speed - ADR: ");
Serial.print(Addr);
Serial.print(", AddrType: ");
Serial.print(AddrType);
Serial.print(", SPD: ");
Serial.print(Speed);
Serial.print(", DIR: ");
Serial.print(Dir);
Serial.print(", SpeedSteps: ");
Serial.print(SpeedSteps);
Serial.print(", aSpeedRef: ");
Serial.println(aSpeedRef);
}
//---------------------------------------------------------------------------
//ファンクション信号受信のイベント
//---------------------------------------------------------------------------
extern void notifyDccFunc(uint16_t Addr, DCC_ADDR_TYPE AddrType, FN_GROUP FuncGrp, uint8_t FuncState)
{
// デバッグメッセージ
Serial.println("notifyDccFunc()");
Serial.print("Addr:");
Serial.print(Addr);
Serial.print(", AddrType:");
Serial.print(AddrType);
Serial.print(", FuncGrp: ");
Serial.print(FuncGrp,HEX);
Serial.print(", FuncState: ");
Serial.println(FuncState,HEX);
}
一式を以下にzipで固めておきました。
このプログラムによって、NMRA DCC Library標準では高頻度な外部割込の影響で処理の遅延や定時間性の保証が難しかったのですが、それが解消されるはずです。RP2040でしか使えないのですが、RP2040はATMEGA328Pの後継ともいうべきマイコンなので、その辺は電子工作ユーザーにとっては問題にならないでしょう。
なお注意事項として、本ページに書かれた内容についてサポートは一切受け付けません。ご自分の責任・判断で自由にご利用ください。NMRA DCC Libraryの部分や私がNMRA DCC Libraryを改造した部分については、NMRA DCC Libraryのライセンス(LGPL2)に従ってください。