DCC Paket Dekoder

Das Format wurde von der NMRA definiert und ein Paket kann wie folgt aussehen:

Das Bild stammt aus dem NMRA Dokument: S 9.2 vom Juli 2004
Wenn man das Bild betrachtet, dann fällt einem die 'darunterliegende' Statemachine auf wie man das Stück Software kodieren könnte.
Der Standard besagt, dass die Zentrale ein bipolares Signal mit den Spannungen von +-8 bis +- 22 Volt liefern muss. Lenz liefert  ab Zentrale +-12 Volt welche mit einem RS232 Level Shifter Board wieder auf Prozessor genehme TTL Werte von 5 Volt reduziert. Mit dem TX Signal geht es dann direkt auf den Capture und Compare Eingang des PIC's.

Der Platine mit dem PIC Prozessor macht das was der Titel schon sagt, das DCC Signal wird ab Zentrale dekodiert und zwar so, dass man es auch lesen kann:

3;60;63
d;60;6d
3;90;93
d;80;8d
3;60;63

Den Datenstrom kann man mit dem Programm Hyperterminal anschauen, d.h. die Daten kommen in hexadezimaler und als Text über die Schnittstelle.
Alternativ könnten auch die Bitzeiten der Halbbits übertragen werden, dann würde das folgende C# Programm die NMRA Pakete extrahieren:

using System;
using System.IO;
using System.IO.Ports;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Text;
namespace SerialPortExample
{
    class SerialPortProgram
    {
	// Create the serial port with basic settings
	private SerialPort m_port = new SerialPort("COM1", 115200);
	// The streamwriter to save the trace information to the disk
	private StreamWriter m_streamwriter = new StreamWriter("Lenz_cu.txt");
        [STAThread]
        static void Main(string[] args)
        { 
          // Instantiate this class
          new SerialPortProgram();
        }
        private SerialPortProgram()
        {
            try
            {
		m_streamwriter.AutoFlush = true;
		Console.WriteLine("Avaliable ports::");
                foreach (string s in SerialPort.GetPortNames())
			Console.WriteLine(s);
                m_port.ReceivedBytesThreshold = 1;
                m_port.Handshake = Handshake.None;
                Console.WriteLine("Incoming Data:");
                // Attach a method to be called when there is data waiting in the port's buffer
                m_port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
                // Begin communications
                m_port.Open();
                // Enter an application loop which keeps this thread alive
                Application.Run();
            }
            catch (Exception ex)
            {
                MessageBox.Show( ex.Message);
            }
        }
        private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                // Show all the incoming data in the port's buffer
		//string Ls_string = m_port.ReadLine();
		//m_streamwriter.WriteLine(Ls_string);
		//Console.WriteLine(Ls_string);
                SerialPort port = (SerialPort)sender;
		// binary mode 
		int bytesAvailable = m_port.BytesToRead;
		byte[] data = new byte[bytesAvailable];
		m_port.Read(data, 0, data.Length);
		for (int n = 0; n < bytesAvailable; n++)
		{
			// extract the bits from the stream...
			int Li_time = data[n];
			string Ls_string = " " + Li_time.ToString() + " ";
			Console.Write( Ls_string );
			m_streamwriter.Write(Ls_string);
			eDCC_BitState L_bit_state = ExtractBit(Li_time);
			Insert_dcc_bit(L_bit_state);
		}
		}
		catch (IOException ex)
		{
			m_streamwriter.WriteLine(ex.Message);
			Console.WriteLine("IOException: " + ex.Message);
		}
		catch (Exception ex)
        	{
			m_streamwriter.WriteLine(ex.Message);
			Console.WriteLine(ex.Message);
        	       	//MessageBox.Show(ex.Message);
           	}
        }
	// half bit times
	private int m_bit1_min = 51;
	private int m_bit1_max = 65;
	private int m_bit0_min = 95;
	private int m_bit0_max = 9900;
	// not yet used, the max bit streching time allowed
	//private int m_0_duration = 12000;
	private enum ePacket_decoding_state
	{
		eExtract_preamble,		// each half bit will be counted
		eExtract_data			// only full and valid bits will be processed
	}
		
	private enum eDCC_BitState // Named bit state
	{
		eUndefined_bit_state,		// undefined
		eSpike_bit,			// a spike or other error situation from the cu
		eZero_bit,			// a valid zero bit time
		eOne_bit			// a valid one bit time
	}
	private byte[] m_dcc_command = new byte[] { 0, 0, 0, 0, 0, 0, 0 };
	private eDCC_BitState			m_last_bit_state = eDCC_BitState.eUndefined_bit_state;
	private ePacket_decoding_state	m_decoding_state = ePacket_decoding_state.eExtract_preamble;
	private int		m_half_bit_count = 0;
	private int		m_byte_count = 0;
	private int		m_preamble_count = 0;
	private int		m_packet_bit_count = 0;
        	// xtract the bits from the nmra bit stream
		private eDCC_BitState ExtractBit(int i_bit_time)
        	{
			eDCC_BitState L_bit_state = eDCC_BitState.eUndefined_bit_state;
			if (m_bit1_min <= i_bit_time && m_bit1_max >= i_bit_time)
			{
				// a bit '1' catched
				L_bit_state = eDCC_BitState.eOne_bit;
			}
			else if (m_bit0_min <= i_bit_time && m_bit0_max >= i_bit_time)
			{
				// a bit '0' catched
				L_bit_state = eDCC_BitState.eZero_bit;
			}
			else
			{
				// a spike or a faulty bit from the command station
				L_bit_state = eDCC_BitState.eSpike_bit;
				Console.WriteLine( "???: " + i_bit_time.ToString() );
			}
			return L_bit_state;
		}
		private void Insert_dcc_bit( eDCC_BitState i_bit_state )
		{
            		// process only valid bist
			if (i_bit_state == eDCC_BitState.eOne_bit || i_bit_state == eDCC_BitState.eZero_bit)
			{
				switch (m_decoding_state)
				{
					case ePacket_decoding_state.eExtract_preamble:
 						InsertPreamble(i_bit_state);
						break;
					case ePacket_decoding_state.eExtract_data:
						m_half_bit_count++;
						// now we process the dcc "half bits"
						switch (m_half_bit_count)
						{
							case 1:
								m_last_bit_state = i_bit_state;
                                				// after the second half bit we know what to do
								return;
							case 2:
								// do the normal processing later on...
								m_half_bit_count = 0;
								break;
						}
						// matching bit pair
						if ( m_last_bit_state == i_bit_state )
							InsertPacketBit(i_bit_state);
						else
						{
							//Console.WriteLine("Error decoding state: bit pairs do not match!");
							Reset_packet_decoder();
						}
						break;
					default:
						Console.WriteLine("Error decoding state: " + m_decoding_state.ToString());
						// Reset state engine
						Reset_packet_decoder();
						break;
				}
				m_streamwriter.WriteLine(i_bit_state.ToString());
				//Console.WriteLine(i_bit_state.ToString());
			}
			else
			{
				Console.WriteLine( "Error bit: " + i_bit_state.ToString());
				// Reset state engine
				Reset_packet_decoder();
			}
		}
		private void InsertPreamble(eDCC_BitState i_bit_state)
		{
			if (i_bit_state == eDCC_BitState.eOne_bit)
			{
				// normal preamble bit
				m_preamble_count++;
			}
			else
			{
				// we are not counting half bits
				if (m_preamble_count > 10)
				{
					// sync on the last zero bit pair
					if ( m_last_bit_state == i_bit_state )
						m_decoding_state = ePacket_decoding_state.eExtract_data;
					m_last_bit_state = i_bit_state;
				}
				else
				{
					// not enough preamble bits received!
					//Console.WriteLine("Error decoding state: not enough preamble bits received!");
					Reset_packet_decoder();
				}
			}
		}
		private void InsertPacketBit(eDCC_BitState i_bit_state)
		{
			switch (m_packet_bit_count)
			{
				case 0:
				case 1:
				case 2:
				case 3:
				case 4:
				case 5:
				case 6:
				case 7:
					// small optimisation
					// process only only bits which are set
					if (i_bit_state == eDCC_BitState.eOne_bit)
					{
						double Ld_x = 2;
						// we receive the high bit first
						double Ld_y = 7 - m_packet_bit_count;
						double Ld_val = Math.Pow(Ld_x, Ld_y);
						byte Lb_val = (byte) Ld_val;
						m_dcc_command[m_byte_count] |= Lb_val;
					}
					m_packet_bit_count++;
					break;
				case 8:
					m_byte_count++;
					// the packet byte end bit has to a 'zero'
					if (i_bit_state == eDCC_BitState.eZero_bit)
					{
						// next packet byte please
						m_packet_bit_count = 0;
					}
					else
					{
						// a valid dcc packet has been received
						Print_dcc_packet();
						Reset_packet_decoder();
					}
					break;
				default:
					Console.WriteLine("Error decoding state: invalid packet bit counter!");
					break;
			}
		}
		private void Print_dcc_packet()
		{
			string Ls_string = "DCC packet received: ";
			Console.Write(Ls_string);
			m_streamwriter.Write(Ls_string);
			for (int Li_loop = 0; Li_loop < m_byte_count; Li_loop++)
			{
				byte packet_byte = m_dcc_command[Li_loop];
				Console.Write("{0} ", packet_byte);
				m_streamwriter.Write("{0} ", packet_byte);
			}
			Console.WriteLine();
			m_streamwriter.WriteLine();
		}
		// initial preamble counting state
		private void Reset_packet_decoder()
		{
			int Li_elems = m_dcc_command.Length;
			for (int Li_loop = 0; Li_loop < Li_elems; Li_loop++)
			{
				m_dcc_command[Li_loop] = 0;
			}
			m_half_bit_count = 0;
			m_byte_count = 0;
			m_preamble_count = 0;
			m_packet_bit_count = 0;
			m_decoding_state = ePacket_decoding_state.eExtract_preamble;
			m_last_bit_state = eDCC_BitState.eUndefined_bit_state;
		}
	}
}