ATtiny412を使った DCCポイントデコーダーを更に発展させて、
車両にも使えるように取り組んでいるのですが、
まだまだかかりそうなので一旦途中のソースコードを公開したいと思います。
無保証です。
VSCode+PlatformIOでプロジェクトを作成して、
platformio.iniファイルが下のコメントの内容になるように設定して、
main.cppファイルにコピペすればビルドできると思います。
(私は極力一つのファイルにまとめたいタイプなのです)
Atmel Studioでビルドすると余計なライブラリが含まれてしまうのか、
コードサイズが増えてしまいます。減らす方法を知りたい…。
◆ VSCode+PlatformIO
RAM: [====== ] 62.9% (used 161 bytes from 256 bytes)
Flash: [======== ] 76.6% (used 3139 bytes from 4096 bytes)
◆ Atmel Studio 7.0 (AVR/GNU C++ Compiler flags: -std=gnu++11)
Program Memory Usage : 3846 bytes 93.9 % Full
Data Memory Usage : 161 bytes 62.9 % Full
■ 2021/06/07(月) 19:45版
サービスモードをプリアンブルのビット数のみで判定していたりと、
手抜きな実装になっています。
DCCパケットパーサーの関数はもう少しスマートにしたかったのですが中々難しいですね。
あと、通知関数はNmraDccライブラリに似せて作っています(互換性はありません)。
//------------------------------------------------------------------------------
// Model Railway DCC Decoder
// Copyright (C) 2021 lonetrip
// https://ameblo.jp/lonetrip
//
// PlatformIO Project Configuration (platformio.ini)
// [env:ATtiny412]
// platform = atmelmegaavr
// board = ATtiny412
// framework = arduino
// upload_protocol = xplainedmini_updi
// monitor_speed = 115200
//
// Microchip ATtiny412-SSN (8-Pin SOIC)
// Pin Signal Function | Pin Signal Function
// 1: VDD +5V | 8: GND GND
// 2: PA6 DAC DAC0.OUT | 7: PA3 DCC_CMD PORT.IN
// 3: PA7 OUT_REV PORT.OUT/TCA0.WO0 | 6: PA0 UPDI UPDI
// 4: PA1 OUT_FWD PORT.OUT/TCA0.WO1 | 5: PA2 DCC_ACK PORT.OUT
//
// TOSHIBA TB67H450FNG (8-Pin SOIC)
// Pin Signal | Pin Signal
// 1: GND GND | 8: OUT2 MOT_FWD
// 2: IN2 OUT_FWD | 7: RS RS
// 3: IN1 OUT_REV | 6: OUT1 MOT_REV
// 4: VREF DAC | 5: VM +12V
//
//------------------------------------------------------------------------------
#define F_CPU (20000000UL) // CLK_CPU=20MHz
//#include <Arduino.h>
#ifdef Arduino_h
#include <EEPROM.h>
#endif
#include <avr/io.h>
#include <avr/eeprom.h>
#include <util/atomic.h>
#include <util/delay.h>
#define PIN_DCC_CMD_bm PIN3_bm // PORTA_PIN3
#define PIN_DCC_ACK_bm PIN2_bm // PORTA_PIN2
#define PIN_OUT_FWD_bm PIN1_bm // PORTA_PIN1
#define PIN_OUT_REV_bm PIN7_bm // PORTA_PIN7
#define PIN_OUT_DAC_bm PIN6_bm // PORTA_PIN6
//#define DEBUG_PORT
#ifdef DEBUG_PORT
#define DEBUG_PORT_LOOP
#define DEBUG_PORT_BIT
#define DEBUG_PORT_INTERRUPT
#endif
//#define DEBUG_UART
#ifdef DEBUG_UART
#define DEBUG_UART_PACKET
#endif
//-----------------------------------------------------------------------------
// Alternative Routine to Arduino
#ifdef Arduino_h
#define MILLIS_ADJUST(ms) (ms)
#else
#define MILLIS_ADJUST(ms) ((ms) * 4 + ((ms) + 9) / 10) // k=1/(4096Hz/1000Hz-4)
void setup();
void loop();
static uint8_t EEMEM EEPROM_DATA[EEPROM_SIZE];
class EEPROMClass {
public:
static const int16_t length() {
return EEPROM_SIZE;
}
uint8_t read(uint8_t address) {
eeprom_busy_wait();
return eeprom_read_byte(&EEPROM_DATA[address]);
}
void update(uint8_t address, uint8_t data) {
eeprom_busy_wait();
eeprom_update_byte(&EEPROM_DATA[address], data);
}
} EEPROM;
uint16_t millis() {
while (RTC.STATUS & RTC_CNTBUSY_bm);
return RTC.CNT;
}
void delay(uint16_t ms) {
uint16_t time = millis();
uint16_t target = MILLIS_ADJUST(ms);
while (((uint16_t)millis() - time) < target);
}
int main() {
// CLKCTRL (Clock Controller)
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, (0 << CLKCTRL_PEN_bp));
// RTC (Real Time Counter)
while (RTC.STATUS != 0);
RTC.CTRLA = RTC_PRESCALER_DIV8_gc | RTC_RTCEN_bm; // f=4096Hz, T=16s
setup();
sei();
while (true) {
loop();
}
return 0;
}
#endif
//-----------------------------------------------------------------------------
// Debug Output Class
class DebugOutput {
static const uint8_t PIN_DBG_bm = PIN_DCC_ACK_bm;
public:
inline void forLoop() {
#ifdef DEBUG_PORT_LOOP
PORTA.OUTTGL = PIN_DBG_bm;
#endif
}
inline void forBit(bool bit) {
#ifdef DEBUG_PORT_BIT
if (bit) {
PORTA.OUTSET = PIN_DBG_bm;
} else {
PORTA.OUTCLR = PIN_DBG_bm;
}
#endif
}
inline void forInterrupt(bool bit) {
#ifdef DEBUG_PORT_INTERRUPT
if (bit) {
PORTA.OUTSET = PIN_DBG_bm;
} else {
PORTA.OUTCLR = PIN_DBG_bm;
}
#endif
}
} m_debug;
//-----------------------------------------------------------------------------
// Serial Communication Class
class SerialCommunication {
private:
#ifdef DEBUG_UART
class ByteQueue {
private:
static const uint8_t SIZE = 8;
uint8_t m_data[SIZE];
uint8_t m_head;
uint8_t m_tail;
public:
void init() {
m_head = 0;
m_tail = 0;
}
bool empty() {
return (m_head == m_tail);
}
bool full() {
return (m_head == ((m_tail + 1) % SIZE));
}
void push(uint8_t data) {
if (full()) {
return;
}
m_data[m_tail] = data;
m_tail = (m_tail + 1) % SIZE;
}
uint8_t pop() {
if (empty()) {
return 0;
}
uint8_t data = m_data[m_head];
m_head = (m_head + 1) % SIZE;
return data;
}
} m_txd;
#endif
public:
void setup(uint32_t speed = 0) {
#ifdef DEBUG_UART
m_txd.init();
// PORT
PORTA.OUTSET = PIN1_bm;
PORTA.DIRSET = PIN1_bm;
PORTMUX.CTRLB |= PORTMUX_USART0_bm; // USART0.TXD=PA1
// USART (Universal Synchronous and Asynchronous Receiver and Transmitter)
if (speed) { // Speed,8,N,1
uint32_t baud = (64 * F_CPU) / (16 * speed);
USART0.BAUD = (uint16_t)(baud * (1024 + SIGROW.OSC20ERR5V) / 1024);
} else {
USART0.BAUD = 1;
}
USART0.CTRLA = 0;
USART0.CTRLB = USART_TXEN_bm;
USART0.CTRLC = USART_SBMODE_1BIT_gc | USART_CHSIZE_8BIT_gc;
#endif
}
void process() {
#ifdef DEBUG_UART
if (!m_txd.empty()) {
if (USART0.STATUS & USART_DREIF_bm) {
USART0.TXDATAL = m_txd.pop();
}
}
#endif
}
void write(const uint8_t data) {
#ifdef DEBUG_UART
while (m_txd.full()) {
process();
}
m_txd.push(data);
#endif
}
} m_serial;
//------------------------------------------------------------------------------
// General Timer Class
class GeneralTimer {
private:
bool m_run;
uint16_t m_time;
uint16_t m_target;
public:
GeneralTimer() {
stop();
}
void start(uint16_t ms = 0) {
m_run = true;
m_time = millis();
m_target = MILLIS_ADJUST(ms);
}
void stop() {
m_run = false;
}
bool process() {
if (m_run) {
return (((uint16_t)millis() - m_time) >= m_target);
}
return false;
}
};
//------------------------------------------------------------------------------
// DCC Decoder Class
class DccDecoder {
private:
static const uint8_t HALFBIT_INVALID = -1;
enum State : uint8_t {
STATE_PREAMBLE,
STATE_START_BIT,
STATE_DATA_BYTE,
STATE_END_BIT,
} m_state;
struct Packet {
static const uint8_t SIZE = 6;
uint8_t preamble;
uint8_t data[SIZE];
uint8_t size;
} m_packet;
uint8_t m_halfbit;
uint8_t m_byte;
uint8_t m_bit;
uint8_t m_xor;
class PacketQueue {
private:
static const uint8_t SIZE = 8;
Packet m_data[SIZE];
uint8_t m_head;
uint8_t m_tail;
public:
void init() {
m_head = 0;
m_tail = 0;
}
bool empty() {
return (m_head == m_tail);
}
bool full() {
return (m_head == ((m_tail + 1) % SIZE));
}
void push(Packet &packet) {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
if (full()) {
return;
}
m_data[m_tail] = packet;
m_tail = (m_tail + 1) % SIZE;
}
}
void pop(Packet &packet) {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
if (empty()) {
packet = {};
return;
}
packet = m_data[m_head];
m_head = (m_head + 1) % SIZE;
}
}
} m_queue;
struct Configuration {
uint16_t addressMultiFunction;
uint16_t addressAccessory;
bool cv29_addressing_method;
bool cv29_fl_location;
} m_config;
enum DCC_ACK_MODE : uint8_t {
DCC_ACK_BASIC,
DCC_ACK_ADVANCED,
};
protected:
enum DCC_ADDR_TYPE : uint8_t {
DCC_ADDR_SHORT,
DCC_ADDR_LONG,
};
enum DCC_DIRECTION : uint8_t {
DCC_DIR_REV,
DCC_DIR_FWD,
};
enum DCC_SPEED_STEPS : uint8_t {
SPEED_STEP_14 = 14,
SPEED_STEP_28 = 28,
SPEED_STEP_128 = 126,
};
enum FN_GROUP : uint8_t {
FN_0_4,
FN_5_8,
FN_9_12,
FN_13_20,
FN_21_28,
FN_0,
};
private:
void reset() {
m_state = STATE_PREAMBLE;
m_packet = {};
m_halfbit = HALFBIT_INVALID;
m_byte = 0;
m_bit = 0;
m_xor = 0;
}
void processPulse(uint16_t width) {
uint8_t halfbit = HALFBIT_INVALID;
// NMRA S-9.1 Electrical Standards for Digital Command Control
// A: Technique for Encoding Bits
// "1" Half Bit: Nominal 58us (52-64us)
if (((52 - 6) <= width) && (width <= (64 + 6))) {
halfbit = 1;
} else
// "0" Half Bit: Nominal 116us (90-10000us)
if ((90 <= width) && (width <= 10000)) {
halfbit = 0;
} else
// Timeout
if (12000 <= width) {
reset();
return;
}
// Check Valid Bit
if ((halfbit == m_halfbit) && (halfbit != HALFBIT_INVALID)) {
m_debug.forBit(halfbit);
processBit(halfbit);
halfbit = HALFBIT_INVALID;
}
m_halfbit = halfbit;
}
void processBit(uint8_t bit) {
// NMRA S-9.2 Communications Standards for Digital Command Control
// A: General Packet Format
switch (m_state) {
// Preamble
case STATE_PREAMBLE:
if (bit) {
m_packet.preamble++;
if (m_packet.preamble >= 12) {
m_state = STATE_START_BIT;
}
} else {
m_packet.preamble = 0;
}
break;
// Packet Start Bit
case STATE_START_BIT:
if (bit) {
m_packet.preamble++;
} else {
m_state = STATE_DATA_BYTE;
m_packet.size = 0;
m_bit = 0;
m_xor = 0;
}
break;
// Data Byte
case STATE_DATA_BYTE:
m_byte = (m_byte << 1) | bit;
m_bit++;
if (m_bit == 8) {
m_state = STATE_END_BIT;
if (m_packet.size < Packet::SIZE) {
m_packet.data[m_packet.size] = m_byte;
}
m_packet.size++;
m_bit = 0;
m_xor ^= m_byte;
}
break;
// Data Byte Start Bit & Packet End Bit
case STATE_END_BIT:
if (bit) {
// Check Error Detection
if (m_xor == 0) {
// Check Packet Size
if ((3 <= m_packet.size) && (m_packet.size <= Packet::SIZE)) {
m_queue.push(m_packet);
}
}
m_state = STATE_PREAMBLE;
m_packet.preamble = 0;
} else {
m_state = STATE_DATA_BYTE;
}
break;
}
}
void parsePacket(Packet &packet) {
// Check Mode
if (packet.preamble < 20) {
parseOperationModePacket(packet);
} else {
parseServiceModePacket(packet);
}
}
void parseOperationModePacket(Packet &packet) {
uint8_t address = packet.data[0];
uint8_t command = packet.data[1];
bool hasNoData = (packet.size == 3);
// NMRA S-9.2.1 Extended Packet Formats for Digital Command Control
// A: Address Partitions
// Address 0: Broadcast address
// Address 1-127: Multi-Function decoders with 7 bit addresses
if ((0 <= address) && (address <= 127)) {
parseMultiFunctionDecoderPacket(packet, DCC_ADDR_SHORT);
} else
// Address 128-191: Basic Accessory Decoders with 9 bit addresses
// and Extended Accessory Decoders with 11-bit addresses
if ((128 <= address) && (address <= 191)) {
parseAccessoryDecoderPacket(packet);
} else
// Address 192-231: Multi-Function Decoders with 14 bit addresses
if ((192 <= address) && (address <= 231)) {
parseMultiFunctionDecoderPacket(packet, DCC_ADDR_LONG);
} else
// Address 232-254: Reserved for Future Use
if ((232 <= address) && (address <= 254)) {
// No implement
} else
// Address 255: Idle Packet
if (address == 255) {
if (hasNoData) {
if (command == 0x00) {
// NMRA S-9.2 Communications Standards for Digital Command Control
// B: Baseline Packets
// Digital Decoder Idle Packet For All Decoders
// {Preamble} 0 11111111 0 00000000 0 11111111 1
notifyDccIdle();
}
}
}
}
void parseMultiFunctionDecoderPacket(Packet &packet, DCC_ADDR_TYPE type) {
uint16_t address = ((type == DCC_ADDR_SHORT)
? packet.data[0] : (((packet.data[0] & 0x3F) << 8) | packet.data[1]));
uint8_t offset = ((type == DCC_ADDR_SHORT) ? 0 : 1);
uint8_t *data = &packet.data[offset];
uint8_t command = data[1];
uint8_t data1 = data[2];
uint8_t data2 = data[3];
bool hasNoData = (packet.size == (3 + offset));
bool hasData1 = (packet.size == (4 + offset));
bool hasData2 = (packet.size == (5 + offset));
// Check Address
if ((address != m_config.addressMultiFunction) && (address != 0)) {
return;
}
// NMRA S-9.2.1 Extended Packet Formats for Digital Command Control
// B: Broadcast Command for Multi Function Digital Decoders
// {Preamble} 0 00000000 0 {instruction-bytes} 0 EEEEEEEE 1
// C: Instruction Packets for Multi Function Digital Decoders
// {Preamble} 0 0AAAAAAA 0 CCCDDDDD (0 DDDDDDDD (0 DDDDDDDD)) 0 EEEEEEEE 1
// {Preamble} 0 11AAAAAA 0 AAAAAAAA 0 CCCDDDDD (0 DDDDDDDD (0 DDDDDDDD)) 0 EEEEEEEE 1
switch ((command & 0xE0) >> 5) {
// CCC=000: Decoder and Consist Control Instruction
case 0b000:
switch ((command & 0x10) >> 4) {
// 0: Decoder Control
// {instruction byte} = 0000CCCF (DDDDDDDD)
case 0b0:
switch ((command & 0x0E) >> 1) {
// 000: Reset
case 0b000:
if (hasNoData) {
// NMRA S-9.2 Communications Standards for Digital Command Control
// B: Baseline Packets
// Digital Decoder Reset Packet For All Decoders
// {Preamble} 0 00000000 0 00000000 0 00000000 1
// D=0: Digital Decoder Reset
// D=1: Hard Reset
notifyDccReset(command & 0x01);
}
break;
// 001: Factory Test Instruction
// 011: Set Decoder Flags
// 101: Set Advanced Addressing
// 111: Decoder Acknowledgment Request (D=1)
// Else: Reserved for future use
}
break;
// 1: Consist Control
// {instruction byte} = 0001CCCC 0AAAAAAA
case 0b1:
break;
}
break;
// CCC=001: Advanced Operation Instructions
// {instruction byte} = 001CCCCC DDDDDDDD
case 0b001:
switch (command & 0x1F) {
// 11111: 128 Speed Step Control
// {instruction byte} = 00111111 DSSSSSSS
// D: Direction (1:Forward, 0:Reverse)
// S: Speed (0:Stop, 1:Emergency stop, 2-127:Speed)
case 0b11111:
if (hasData1) {
uint8_t speed = (data1 & 0x7F);
if (speed < 2) {
// Swap 0:Stop and 1:Emergency stop
speed ^= 1;
}
DCC_DIRECTION dir = ((data1 & 0x80) ? DCC_DIR_FWD : DCC_DIR_REV);
// speed (0:Emergency stop, 1:Stop, 2-127:Speed)
notifyDccSpeed(address, type, speed, dir, SPEED_STEP_128);
}
break;
// 11110: Restricted Speed Step Instruction
// {instruction byte} = 00111110 F0DCSSSS
case 0b11110:
break;
// 11101: Analog Function Group
// {instruction byte} = 00111101 VVVVVVVV DDDDDDDD
case 0b11101:
break;
}
break;
// CCC=010: Speed and Direction Instruction for reverse operation
// CCC=011: Speed and Direction Instruction for forward operation
// {instruction byte} = 01DCSSSS
case 0b010: // FALLTHROUGH
case 0b011:
if (hasNoData) {
uint8_t speed = (command & 0x0F);
uint8_t fl = ((command & 0x10) >> 4);
if (speed < 2) {
// Swap 0:Stop and 1:Emergency stop
speed ^= 1;
}
DCC_DIRECTION dir = ((command & 0x20) ? DCC_DIR_FWD : DCC_DIR_REV);
// NMRA S-9.2 Communications Standards for Digital Command Control
// B: Baseline Packets
// Speed and Direction Packet For Locomotive Decoders
// {Preamble} 0 0AAAAAAA 0 01DCSSSS 0 EEEEEEEE 1
// Digital Decoder Broadcast Stop Packets For All Decoders
// {Preamble} 0 00000000 0 01DC000S 0 EEEEEEEE 1
// for CV29[1]=1: 28 Speed Step
// speed (0:Emergency stop, 1:Stop, 2-29:Speed)
if (m_config.cv29_fl_location) {
if (speed >= 2) {
speed = ((speed << 1) | fl) - 2;
}
notifyDccSpeed(address, type, speed, dir, SPEED_STEP_28);
} else
// for CV29[1]=0: 14 Speed Step
// speed (0:Emergency stop, 1:Stop, 2-15:Speed)
{
notifyDccSpeed(address, type, speed, dir, SPEED_STEP_14);
notifyDccFunc(address, type, FN_0, fl);
}
}
break;
// CCC=100: Function Group One Instruction
// {instruction byte} = 100DDDDD
case 0b100:
if (hasNoData) {
notifyDccFunc(address, type, FN_0_4, (command & 0x1F));
}
break;
// CCC=101: Function Group Two Instruction
// {instruction byte} = 101SDDDD
case 0b101:
if (hasNoData) {
FN_GROUP fng = ((command & 0x10) ? FN_5_8 : FN_9_12);
notifyDccFunc(address, type, fng, (command & 0x0F));
}
break;
// CCC=110: Future Expansion
// {instruction byte} = 110CCCCC DDDDDDDD (DDDDDDDD)
case 0b110:
switch (command & 0x1F) {
// 00000: Binary State Control Instruction long form
case 0b00000:
break;
// 11101: Binary State Control Instruction short form
case 0b11101:
break;
// 11110: F13-F20 Function Control
case 0b11110:
if (hasData1) {
notifyDccFunc(address, type, FN_13_20, data1);
}
break;
// 11111: F21-F28 Function Control
case 0b11111:
if (hasData1) {
notifyDccFunc(address, type, FN_21_28, data1);
}
break;
}
break;
// CCC=111: Configuration Variable Access Instruction
case 0b111:
switch ((command & 0x10) >> 4) {
// 1: Configuration Variable Access Instruction - Short Form
// {instruction byte} = 1111CCCC DDDDDDDD
case 0b1:
break;
// 0: Configuration Variable Access Instruction - Long Form
// {instruction byte} = 1110CCVV VVVVVVVV DDDDDDDD
// {instruction byte} = 1110CCVV VVVVVVVV 111CDBBB
case 0b0:
if (hasData2) {
command = ((command & 0x0C) >> 2);
uint16_t cva = (((command & 0x03) << 8) | data1) + 1;
uint8_t cvd = data2;
parseCVInstruction(command, cva, cvd, DCC_ACK_ADVANCED);
}
break;
}
break;
}
}
void parseAccessoryDecoderPacket(Packet &packet) {
uint8_t command = packet.data[1];
uint16_t accDecAddress = (packet.data[0] & 0x3F)
| ((uint16_t)(~packet.data[1] & 0x70) << 2);
uint8_t power = ((command & 0x08) >> 3);
uint8_t index = ((command & 0x06) >> 1);
uint8_t direction = (command & 0x01);
uint16_t accOutAddress = ((((accDecAddress - 1) << 2) | index) + 1);
// Check Address
if (m_config.cv29_addressing_method) {
if (accOutAddress != m_config.addressAccessory) {
return;
}
} else {
if ((accDecAddress != m_config.addressAccessory) && (accDecAddress != 511)) {
return;
}
}
// NMRA S-9.2.1 Extended Packet Formats for Digital Command Control
// D: Accessory Digital Decoder Packet Formats
// Basic Accessory Decoder Packet Format (9-bit address)
// {Preamble} 0 10AAAAAA 0 1aaaCDDd 0 EEEEEEEE 1
// Broadcast Command for Basic Accessory Decoders
// {Preamble} 0 10111111 0 1000CDDd 0 EEEEEEEE 1
// A: Address bit
// a: Address bit (ones complement)
// C: Power (0:Deactivate, 1:Activate)
// D: Index (0-3:Turnout Number)
// d: Direction (0:Straight, 1:Diverging)
if (packet.size == 3) {
if ((command & 0x80) == 0x80) {
// for CV29[6]=1: Output Address method
// DecAddress 1, Index 0 1 2 3 -> OutAddress 1 2 3 4
// DecAddress 2, Index 0 1 2 3 -> OutAddress 5 6 7 8
// DecAddress 511, Index 0 1 2 3 -> OutAddress 2041 2042 2043 2044
if (m_config.cv29_addressing_method) {
notifyDccAccTurnoutOutput(accOutAddress, direction, power);
} else
// for CV29[6]=0: Decoder Address method
{
notifyDccAccTurnoutBoard(accDecAddress, index, direction, power);
}
}
} else
// Extended Accessory Decoder Control Packet Format (11-bit address)
// {Preamble} 0 10AAAAAA 0 0aaa0AA1 0 000XXXXX 0 EEEEEEEE 1
// Broadcast Command for Extended Accessory Decoders
// {Preamble} 0 10111111 0 00000111 0 000XXXXX 0 EEEEEEEE 1
if (packet.size == 4) {
if ((command & 0x89) == 0x01) {
notifyDccSigOutputState(accOutAddress, packet.data[2]);
}
} else
// Accessory Decoder Configuration Variable Access Instruction
// Basic Accessory Decoder Packet address for operations mode programming
// {Preamble} 0 10AAAAAA 0 1aaaCDDd 0 1110CCVV 0 VVVVVVVV 0 DDDDDDDD 0 EEEEEEEE 1
// Extended Decoder Control Packet address for operations mode programming
// {Preamble} 0 10AAAAAA 0 0aaa0AA1 0 1110CCVV 0 VVVVVVVV 0 DDDDDDDD 0 EEEEEEEE 1
if (packet.size == 6) {
if (((command & 0x80) == 0x80) || ((command & 0x89) == 0x01)) {
if ((packet.data[2] & 0xF0) == 0xE0) {
command = ((packet.data[2] & 0x0C) >> 2);
uint16_t cva = (((packet.data[2] & 0x03) << 8) | packet.data[3]) + 1;
uint8_t cvd = packet.data[4];
parseCVInstruction(command, cva, cvd, DCC_ACK_ADVANCED);
}
}
}
}
void parseServiceModePacket(Packet &packet) {
// NMRA S-9.2.3 Service Mode for Digital Command Control
// E: Service Mode Instruction Packets
switch ((packet.data[0] & 0xF0) >> 4) {
case 0b0111:
// Service Mode Instruction Packets for Direct Mode
// {Long-preamble} 0 0111CCAA 0 AAAAAAAA 0 DDDDDDDD 0 EEEEEEEE 1
// {Long-preamble} 0 011110AA 0 AAAAAAAA 0 111KDBBB 0 EEEEEEEE 1
if (packet.size == 4) {
uint8_t command = ((packet.data[0] & 0x0C) >> 2);
uint16_t cva = (((packet.data[0] & 0x03) << 8) | packet.data[1]) + 1;
uint8_t cvd = packet.data[2];
parseCVInstruction(command, cva, cvd, DCC_ACK_BASIC);
}
break;
}
}
void parseCVInstruction(uint8_t command, uint16_t cva, uint8_t cvd, DCC_ACK_MODE ack) {
// NMRA S-9.2.1 Extended Packet Formats for Digital Command Control
// Configuration Variable Access Instruction - Long Form
// NMRA S-9.2.3 Service Mode for Digital Command Control
// Instructions packets using Direct CV Addressing
switch (command) {
// CC=11: Write Byte
// {instruction byte} = xxxxCCAA AAAAAAAA DDDDDDDD
case 0b11:
if (notifyCVWrite(cva, cvd) == cvd) {
performAck(ack);
}
break;
// CC=01: Verify Byte
// {instruction byte} = xxxxCCAA AAAAAAAA DDDDDDDD
case 0b01:
if (notifyCVRead(cva) == cvd) {
performAck(ack);
}
break;
// CC=10: Bit Manipulation
// {instruction byte} = xxxxCCAA AAAAAAAA 111KDBBB
case 0b10:
if ((cvd & 0xE0) == 0xE0) {
uint8_t mask = (1 << (cvd & 0x07));
uint8_t bit = (((cvd & 0x08) >> 3) * mask);
switch ((cvd & 0x10) >> 4) {
// K=1: Write Bit
case 0b1:
cvd = (notifyCVRead(cva) & ~mask) | bit;
if ((notifyCVWrite(cva, cvd) & mask) == bit) {
performAck(ack);
}
break;
// K=0: Verify Bit
case 0b0:
if ((notifyCVRead(cva) & mask) == bit) {
performAck(ack);
}
break;
}
}
break;
}
}
void performAck(DCC_ACK_MODE ack) {
switch (ack) {
case DCC_ACK_BASIC:
notifyCVAck();
break;
case DCC_ACK_ADVANCED:
notifyAdvancedCVAck();
break;
}
}
protected:
// NMRA S-9.2.2 Configuration Variables for Digital Command Control
enum CV_INDEX {
// L:Multi-Function(Locomotive), A:Accessory, C:Common
// M:Mandatory, R:Recommended, O:Optional
// Common
CV07_CM_VERSION = 7, // Manufacturer Version Number
CV08_CM_MANUFACTURER_ID = 8, // Manufacturer ID
CV29_CM_CONFIG_DATA = 29, // Configuration Data
// Multi-Function Decoder
CV01_LM_PRIMARY_ADDRESS = 1, // Primary Address (1-127)
CV02_LR_VSTART = 2, // Vstart
CV03_LR_ACCEL_RATE = 3, // Acceleration Rate
CV04_LR_DECEL_RATE = 4, // Deceleration Rate
CV05_LO_VHIGH = 5, // Vhigh
CV06_LO_VMID = 6, // Vmid
CV11_LR_PACKET_TIMEOUT = 11, // Packet Time-Out Value
CV17_LO_EXT_ADDRESS_MSB = 17, // Extended Address (MSB) (192-231)
CV18_LO_EXT_ADDRESS_LSB = 18, // Extended Address (LSB) (0-255)
// Accessory Decoder
CV01_AM_ADDRESS_LSB = 1, // Decoder Address (LSB)
CV02_AO_AUX_ACTIVATION = 2, // Auxiliary Activation
CV03_AO_TIME_ON_F1 = 3, // Time On for Functions F1
CV04_AO_TIME_ON_F2 = 4, // Time On for Functions F2
CV05_AO_TIME_ON_F3 = 5, // Time On for Functions F3
CV06_AO_TIME_ON_F4 = 6, // Time On for Functions F4
CV09_AM_ADDRESS_MSB = 9, // Decoder Address (MSB)
};
enum CV08_MANUFACTURER_ID {
CV08_C_PUBLIC_DOMAIN = 0x0D, // Public Domain & Do-It-Yourself Decoders
};
enum CV29_CONFIG_DATA {
CV29_L_LOCO_DIRECTION = 0b00000001, // [0] Locomotive Direction (0:Normal, 1:Reversed)
CV29_L_FL_LOCATION = 0b00000010, // [1] FL location (0:14 Speed Step, 1:28 Speed Step)
CV29_L_APS = 0b00000100, // [2] Power Source Conversion (0:NMRA, 1:CV#12)
CV29_C_BIDI_ENABLE = 0b00001000, // [3] Bi-Directional Communications (0:Disabled, 1:Enabled)
CV29_L_SPEED_TABLE = 0b00010000, // [4] Speed Table (0:CV#2,5,6, 1:CV#66-95)
CV29_L_EXT_ADDRESSING = 0b00100000, // [5] Addressing Type (0:One byte, 1:Two byte)
CV29_A_DECODER_TYPE = 0b00100000, // [5] Decoder Type (0:Basic, 1:Extended Accessory)
CV29_A_ADDRESSING_METHOD = 0b01000000, // [6] Addressing Method (0:Decoder Address, 1:Output Address)
CV29_C_ACCESSORY_DECODER = 0b10000000, // [7] Accessory Decoder (0:Multi-Function, 1:Accessory)
CV29_L_MFUNCTION_DECODER = 0b00000000, // [7]
};
// Declaration for Notification Functions
virtual void notifyDccReset(uint8_t hardReset) {}
virtual void notifyDccIdle() {}
virtual void notifyDccSpeed(uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t Speed, DCC_DIRECTION Dir, DCC_SPEED_STEPS SpeedSteps) {}
virtual void notifyDccFunc(uint16_t Addr, DCC_ADDR_TYPE AddrType, FN_GROUP FuncGrp, uint8_t FuncState) {}
virtual void notifyDccAccTurnoutBoard(uint16_t BoardAddr, uint8_t OutputPair, uint8_t Direction, uint8_t OutputPower) {}
virtual void notifyDccAccTurnoutOutput(uint16_t Addr, uint8_t Direction, uint8_t OutputPower) {}
virtual void notifyDccSigOutputState(uint16_t Addr, uint8_t State) {}
virtual uint8_t notifyCVRead(uint16_t CV) { return 0; }
virtual uint8_t notifyCVWrite(uint16_t CV, uint8_t Value) { return 0; }
virtual void notifyCVResetFactoryDefault(uint8_t value = 0x08) {}
virtual void notifyCVAck() {}
virtual void notifyAdvancedCVAck() {}
void setConfiguration() {
uint8_t cv29 = notifyCVRead(CV29_CM_CONFIG_DATA);
m_config.addressMultiFunction = -1;
m_config.addressAccessory = -1;
m_config.cv29_addressing_method = (cv29 & CV29_A_ADDRESSING_METHOD);
m_config.cv29_fl_location = (cv29 & CV29_L_FL_LOCATION);
// Accessory Decoder
if (cv29 & CV29_C_ACCESSORY_DECODER) {
// Output Address method
// CV01,CV09: 76543210 .....A98 (CV01=0-255, CV09=0-7)
// Packet: ..765432 .A98.10. (ADRS=1-2044)
if (cv29 & CV29_A_ADDRESSING_METHOD) {
m_config.addressAccessory =
((notifyCVRead(CV09_AM_ADDRESS_MSB) & 0x07) << 8)
| notifyCVRead(CV01_AM_ADDRESS_LSB);
} else
// Decoder Address method
// CV01,CV09: ..543210 .....876 (CV01=0-63, CV09=0-7)
// Packet: ..543210 .876.... (ADRS=0-511)
{
m_config.addressAccessory =
((notifyCVRead(CV09_AM_ADDRESS_MSB) & 0x07) << 6)
| (notifyCVRead(CV01_AM_ADDRESS_LSB) & 0x3F);
}
} else
// Multi-Function Decoder
{
// Two byte addressing (Extended addressing)
// CV17,CV18: ..DCBA98 76543210 (CV17=0-39/192-231, CV18=0-255)
// Packet: ..DCBA98 76543210 (ADRS=128-9999)
if (cv29 & CV29_L_EXT_ADDRESSING) {
m_config.addressMultiFunction =
((notifyCVRead(CV17_LO_EXT_ADDRESS_MSB) & 0x3F) << 8)
| notifyCVRead(CV18_LO_EXT_ADDRESS_LSB);
} else
// One byte addressing
// CV01: .6543210 (CV01=1-127)
// Packet: .6543210 (ADRS=1-127)
{
m_config.addressMultiFunction =
(notifyCVRead(CV01_LM_PRIMARY_ADDRESS) & 0x7F);
}
}
}
public:
void setup() {
reset();
m_queue.init();
// PORT
PORTA.DIRCLR = PIN_DCC_CMD_bm;
PORTA.OUTCLR = PIN_DCC_ACK_bm;
PORTA.DIRSET = PIN_DCC_ACK_bm;
// EVSYS (Event System)
EVSYS.ASYNCUSER0 = EVSYS_ASYNCUSER0_ASYNCCH0_gc; // TCB0
EVSYS.ASYNCCH0 = EVSYS_ASYNCCH0_PORTA_PIN3_gc; // PA3 (PIN_DCC_CMD)
// TCB (16-bit Timer/Counter Type B)
TCB0.CTRLB = TCB_CNTMODE_PW_gc; // Pulse Width Measurement Mode
TCB0.EVCTRL = TCB_CAPTEI_bm | TCB_FILTER_bm;
TCB0.INTCTRL = TCB_CAPT_bm;
TCB0.CTRLA = TCB_ENABLE_bm | TCB_CLKSEL_CLKDIV2_gc; // f=fCLK_PER/2
// Configuration
if (notifyCVRead(CV08_CM_MANUFACTURER_ID) != CV08_C_PUBLIC_DOMAIN) {
notifyCVResetFactoryDefault();
}
setConfiguration();
}
void interrupt() {
uint16_t width = TCB0.CCMP;
PORTA.PIN3CTRL ^= PORT_INVEN_bm; // PA3 (PIN_DCC_CMD)
m_debug.forInterrupt(1);
processPulse(width / (F_CPU / 1000000UL / 2)); // T=6553us
m_debug.forInterrupt(0);
}
void process() {
Packet packet;
m_queue.pop(packet);
if (packet.size > 0) {
#ifdef DEBUG_UART_PACKET
m_serial.write(packet.preamble);
m_serial.write(packet.size);
for (int i = 0; i < packet.size; i++) {
m_serial.write(packet.data[i]);
}
#endif
parsePacket(packet);
}
}
};
//------------------------------------------------------------------------------
// Motor Driver Class
class MotorDriver {
private:
static const bool MOTOR_DRIVER_USE_PWM = true;
static const uint16_t PWM_PER = 199; // 100kHz=20MHz/1/(PER+1)
static const uint16_t PWM_LOW = 0;
static const uint16_t PWM_HIGH = PWM_PER + 1;
public:
enum Mode {
MODE_STOP,
MODE_FORWARD,
MODE_REVERSE,
MODE_BREAK,
};
enum Limit {
LIMIT_OFF = 0,
LIMIT_ACK = 1000,
LIMIT_LOCO = 1500,
LIMIT_TURNOUT = 1500,
};
void setup() {
// PORT
PORTA.OUTCLR = PIN_OUT_FWD_bm | PIN_OUT_REV_bm;
PORTA.DIRSET = PIN_OUT_FWD_bm | PIN_OUT_REV_bm;
// DAC (Digital-to-Analog Converter)
VREF.CTRLA = VREF_DAC0REFSEL_4V34_gc;
DAC0.CTRLA |= DAC_OUTEN_bm;
DAC0.DATA = 0;
DAC0.CTRLA |= DAC_ENABLE_bm;
// TCA (16-bit Timer/Counter Type A)
if (MOTOR_DRIVER_USE_PWM) {
PORTMUX.CTRLC |= PORTMUX_TCA00_ALTERNATE_gc;
TCA0.SINGLE.CTRLA &= ~TCA_SINGLE_ENABLE_bm;
TCA0.SINGLE.CTRLD &= ~TCA_SINGLE_SPLITM_bm;
TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1_gc;
TCA0.SINGLE.CTRLB = TCA_SINGLE_WGMODE_SINGLESLOPE_gc |
TCA_SINGLE_CMP1EN_bm | // TCA0.WO1=PA1 (PIN_OUT_FWD)
TCA_SINGLE_CMP0EN_bm; // TCA0.WO0=PA7 (PIN_OUT_REV)
TCA0.SINGLE.CTRLC = 0;
TCA0.SINGLE.PER = PWM_PER; // f=fCLK_PER/N/(PER+1)
TCA0.SINGLE.CMP1 = PWM_LOW;
TCA0.SINGLE.CMP0 = PWM_LOW;
TCA0.SINGLE.CTRLA |= TCA_SINGLE_ENABLE_bm;
}
}
void setLimit(uint16_t millivolt) {
uint16_t data = (millivolt + 8) / 17; // volt=4.34V*data/0xFF
DAC0.DATA = ((data <= 0xFF) ? data : 0xFF);
}
void setMode(Mode mode, uint8_t duty = 1, uint8_t period = 1) {
switch (mode) {
case MODE_STOP:
if (MOTOR_DRIVER_USE_PWM) {
TCA0.SINGLE.CMP1 = PWM_LOW;
TCA0.SINGLE.CMP0 = PWM_LOW;
} else {
PORTA.OUTCLR = PIN_OUT_FWD_bm | PIN_OUT_REV_bm;
}
setLimit(LIMIT_OFF);
break;
case MODE_FORWARD:
if (MOTOR_DRIVER_USE_PWM) {
TCA0.SINGLE.CMP1 = PWM_HIGH;
TCA0.SINGLE.CMP0 = PWM_HIGH * (period - duty) / period;
} else {
PORTA.OUTSET = PIN_OUT_FWD_bm;
PORTA.OUTCLR = PIN_OUT_REV_bm;
}
break;
case MODE_REVERSE:
if (MOTOR_DRIVER_USE_PWM) {
TCA0.SINGLE.CMP0 = PWM_HIGH;
TCA0.SINGLE.CMP1 = PWM_HIGH * (period - duty) / period;
} else {
PORTA.OUTSET = PIN_OUT_REV_bm;
PORTA.OUTCLR = PIN_OUT_FWD_bm;
}
break;
case MODE_BREAK:
if (MOTOR_DRIVER_USE_PWM) {
TCA0.SINGLE.CMP1 = PWM_HIGH;
TCA0.SINGLE.CMP0 = PWM_HIGH;
} else {
PORTA.OUTSET = PIN_OUT_FWD_bm | PIN_OUT_REV_bm;
}
break;
}
}
} m_driver;
//------------------------------------------------------------------------------
// My DCC Decoder Class
class MyDccDecoder : public DccDecoder {
private:
struct CVSet {
uint16_t index;
uint8_t data;
};
enum CV_INDEX_OPTION {
CV33_AO_MU_MODE = 33, // Manufacturer Unique: Decoder Mode
CV34_AO_MU_DELAY = 34, // Manufacturer Unique: Trigger Delay
};
enum CV33_MU_MODE {
CV33_L_MAGNETIC_TURNOUT = 0, // Magnetic force drive Turnout mode
CV33_L_MAGNETIC_TURNOUT_E = 1, // Magnetic force drive Turnout mode
CV33_L_MOTOR_LIGHT_ACC = 255, // Motor rotating force drive Turnout mode, Lighting accessory mode
};
// Factory Default for Multi-Function Decoder
// (referred to Rokuhan A053 DCC Decoder General Purpose type)
const CVSet m_cvMultiFunctionDefault[12] = {
{ CV01_LM_PRIMARY_ADDRESS, 3 }, // Primary address (2 digit address)
{ CV02_LR_VSTART, 0 }, // Starting voltage
{ CV03_LR_ACCEL_RATE, 0 }, // Acceleration Rate
{ CV04_LR_DECEL_RATE, 0 }, // Deceleration Rate
{ CV05_LO_VHIGH, 160 }, // Highest voltage
{ CV06_LO_VMID, 80 }, // Middle voltage
{ CV07_CM_VERSION, 1 }, // Decoder version
{ CV08_CM_MANUFACTURER_ID, CV08_C_PUBLIC_DOMAIN },
{ CV11_LR_PACKET_TIMEOUT, 0 }, // Packet time-out Time
{ CV17_LO_EXT_ADDRESS_MSB, 192 }, // Extended address (Upper) (4 digit address)
{ CV18_LO_EXT_ADDRESS_LSB, 128 }, // Extended address (Lower) (4 digit address)
{ CV29_CM_CONFIG_DATA, CV29_L_MFUNCTION_DECODER | CV29_L_FL_LOCATION },
};
// Factory Default for Accessory Decoder
// (referred to Rokuhan A060 DCC Accessory Decoder (Turnout & Lighting))
const CVSet m_cvAccessoryDefault[9] = {
{ CV01_AM_ADDRESS_LSB, 1 }, // Decoder address (Lower)
{ CV02_AO_AUX_ACTIVATION, 0 }, // Auxiliary input
{ CV03_AO_TIME_ON_F1, 30 }, // Running time
{ CV07_CM_VERSION, 1 }, // Decoder version
{ CV08_CM_MANUFACTURER_ID, CV08_C_PUBLIC_DOMAIN },
{ CV09_AM_ADDRESS_MSB, 0 }, // Decoder address (Upper)
{ CV29_CM_CONFIG_DATA, CV29_C_ACCESSORY_DECODER | CV29_A_ADDRESSING_METHOD },
{ CV33_AO_MU_MODE, 0 }, // Decoder mode
{ CV34_AO_MU_DELAY, 0 }, // Function delay
};
GeneralTimer m_turnout_delay;
GeneralTimer m_turnout_width;
uint8_t m_turnout_dir;
protected:
void notifyDccReset(uint8_t hardReset) override {
m_driver.setMode(MotorDriver::MODE_STOP);
}
void notifyDccSpeed(uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t Speed, DCC_DIRECTION Dir, DCC_SPEED_STEPS SpeedSteps) override {
if (Speed < 2) {
m_driver.setMode(MotorDriver::MODE_STOP);
return;
}
m_driver.setLimit(MotorDriver::LIMIT_LOCO);
if (Dir == DCC_DIR_FWD) {
m_driver.setMode(MotorDriver::MODE_FORWARD, Speed - 1, SpeedSteps);
} else {
m_driver.setMode(MotorDriver::MODE_REVERSE, Speed - 1, SpeedSteps);
}
}
void notifyDccAccTurnoutOutput(uint16_t Addr, uint8_t Direction, uint8_t OutputPower) override {
if (OutputPower) {
m_turnout_dir = Direction;
uint8_t cv34 = notifyCVRead(CV34_AO_MU_DELAY);
m_turnout_delay.start(cv34 * 100UL); // CV34 x 100ms
}
}
uint8_t notifyCVRead(uint16_t CV) override {
if (CV >= (uint16_t)EEPROM.length()) {
return 0;
}
return EEPROM.read(CV);
}
uint8_t notifyCVWrite(uint16_t CV, uint8_t Value) override {
if (CV >= (uint16_t)EEPROM.length()) {
return 0;
}
bool updateCV = true;
bool updateConfig = false;
switch (CV) {
case CV08_CM_MANUFACTURER_ID:
notifyCVResetFactoryDefault(Value);
updateCV = false;
updateConfig = true;
break;
case CV07_CM_VERSION:
updateCV = false;
break;
case CV29_CM_CONFIG_DATA:
updateConfig = true;
break;
case CV01_LM_PRIMARY_ADDRESS | CV01_AM_ADDRESS_LSB:
case CV17_LO_EXT_ADDRESS_MSB:
case CV18_LO_EXT_ADDRESS_LSB:
case CV09_AM_ADDRESS_MSB:
updateConfig = true;
break;
}
if (updateCV) {
EEPROM.update(CV, Value);
}
if (updateConfig) {
setConfiguration();
}
return EEPROM.read(CV);
}
void notifyCVResetFactoryDefault(uint8_t value = 0x08) override {
int n = 0;
const CVSet *cvSet = NULL;
switch (value) {
case 0x08:
n = sizeof(m_cvAccessoryDefault) / sizeof(CVSet);
cvSet = m_cvAccessoryDefault;
break;
case 0x09:
n = sizeof(m_cvMultiFunctionDefault) / sizeof(CVSet);
cvSet = m_cvMultiFunctionDefault;
break;
}
for (int i = 0; i < n; i++) {
EEPROM.update(cvSet[i].index, cvSet[i].data);
}
}
void notifyCVAck() override {
m_driver.setLimit(MotorDriver::LIMIT_ACK);
m_driver.setMode(MotorDriver::MODE_FORWARD);
PORTA.OUTSET = PIN_DCC_ACK_bm;
delay(6);
PORTA.OUTCLR = PIN_DCC_ACK_bm;
m_driver.setMode(MotorDriver::MODE_STOP);
}
public:
void setup() {
DccDecoder::setup();
m_driver.setup();
}
void process() {
DccDecoder::process();
if (m_turnout_delay.process()) {
m_turnout_delay.stop();
MotorDriver::Mode mode = (m_turnout_dir
? (MotorDriver::MODE_FORWARD) : (MotorDriver::MODE_REVERSE));
uint8_t cv03 = notifyCVRead(CV03_AO_TIME_ON_F1);
switch (notifyCVRead(CV33_AO_MU_MODE)) {
case CV33_L_MAGNETIC_TURNOUT: // FALLTHROUGH
case CV33_L_MAGNETIC_TURNOUT_E:
m_driver.setLimit(MotorDriver::LIMIT_TURNOUT);
m_driver.setMode(mode);
m_turnout_width.start(cv03); // CV03 x 1ms
break;
case CV33_L_MOTOR_LIGHT_ACC:
m_driver.setLimit(MotorDriver::LIMIT_TURNOUT);
m_driver.setMode(mode);
if ((cv03 == 0) || (cv03 == 255)) {
// No timer (Always ON)
} else {
m_turnout_width.start(cv03 * 20UL); // CV03 x 20ms
}
break;
}
}
if (m_turnout_width.process()) {
m_turnout_width.stop();
m_driver.setMode(MotorDriver::MODE_STOP);
}
}
} m_decoder;
//------------------------------------------------------------------------------
// Main Routine
ISR(TCB0_INT_vect) {
m_decoder.interrupt();
}
void setup() {
m_serial.setup();
m_decoder.setup();
}
void loop() {
m_serial.process();
m_decoder.process();
m_debug.forLoop();
}
以上です。