Topic: tech dcc pc src prev next
tech dcc pc src > dcc.c
#include <avr/interrupt.h>
#include <stddef.h>
#include "dcc.h"
#include "debounce.h"
#include "millis.h"
#include "pins.h"
volatile bool g_request_transmit = false;
volatile bool g_transmitting_packet = false;
unsigned char g_packet[DCC_PACKET_LENGTH];
size_t g_packet_length;
unsigned char g_transmit_bit;
bool g_next_bit = false;
// Allow mirroring of the DCC signal.
bool g_phase = false;
unsigned char g_transmit_repeat = 0;
void DCC_Setup()
{
HERMES_DDR_SIGNAL = (unsigned char)(
HERMES_DDR_SIGNAL |
((1 << HERMES_DD_BLOCK) | (1 << HERMES_DD_COMMON))
);
HERMES_PORT_SIGNAL = (unsigned char)(
HERMES_PORT_SIGNAL &
~((1 << HERMES_BIT_BLOCK) | (1 << HERMES_BIT_COMMON))
);
}
void DCC_SignalUpdateIsr()
{
TCNT2 = 0;
// The NMRA specifies that each half of a zero bit should be at least 100us
// and that each half of a one bit should be between 55 and 61us (nominally
// 58us).
static const unsigned char ZERO_TIME = 112;
static const unsigned char ONE_TIME = 57;
HERMES_PORT_SIGNAL = (unsigned char)(
HERMES_PORT_SIGNAL ^
((1 << HERMES_BIT_COMMON) | (1 << HERMES_BIT_BLOCK))
);
OCR2A = g_next_bit ? ONE_TIME : ZERO_TIME;
if(HERMES_PORT_SIGNAL & (g_phase ? (1 << HERMES_BIT_COMMON) : (1 << HERMES_BIT_BLOCK)))
{
// Common signal is high.
// Second half of bit. Set g_next_bit.
if(g_transmitting_packet && g_transmit_bit > g_packet_length)
{
// Finish this packet.
g_next_bit = false;
--g_transmit_repeat;
if(g_transmit_repeat == 0)
// Stop transmitting data.
g_transmitting_packet = false;
else
// Retransmit the packet.
g_transmit_bit = 0;
}
else if(g_transmitting_packet)
{
// Continue this packet.
const size_t byte_index = g_transmit_bit / 8;
const size_t bit_index = g_transmit_bit % 8;
g_next_bit = (g_packet[byte_index] >> (7 - bit_index)) & 0b00000001;
++g_transmit_bit;
}
else if(g_request_transmit)
{
// Hack: mirror the DCC signal for alternate packets.
// Some locomotives won't respond to packets if they are railed in
// the wrong direction. Mirroring the packet solves this, but each
// packet must be broadcast twice.
g_phase = !g_phase;
// Start transmitting a packet.
g_transmit_bit = 0;
g_next_bit = true;
g_transmitting_packet = true;
g_request_transmit = false;
}
}
}
void DCC_Start()
{
cli();
HERMES_DDR_SIGNAL |= (unsigned char)(1 << HERMES_DD_COMMON);
HERMES_DDR_SIGNAL |= (unsigned char)(1 << HERMES_DD_BLOCK);
HERMES_PORT_SIGNAL |= (unsigned char)(1 << HERMES_BIT_COMMON);
HERMES_PORT_SIGNAL &= (unsigned char)~(1 << HERMES_BIT_BLOCK);
TCNT2 = 0;
// Set OC2A on compare match.
TCCR2A = (unsigned char)((1 << COM2A1) | (1 << COM2A0));
// Set the prescaler to /8.
TCCR2B = (unsigned char)(1 << CS21);
// Clear the timer 2 compare interrupts.
TIFR2 = (unsigned char)(TIFR2 & ~(1 << OCF2A));
TIFR2 = (unsigned char)(TIFR2 & ~(1 << OCF2B));
// Enable timer 2 compare A interrupt (switching DCC signal).
TIMSK2 = (unsigned char)(TIMSK2 | (1 << OCIE2A));
/*PORTB = (unsigned char)(PORTB | (1 << PB1));*/
sei();
}
void DCC_Stop()
{
DCC_AwaitCompletion();
cli();
TCNT2 = 0;
TIFR2 = (unsigned char)(TIFR2 & ~((1 << OCF2A) | (1 << OCF2B)));
// Cancel timer 2 compare interrupts.
TIMSK2 = (unsigned char)(TIMSK2 & ~((1 << OCIE2A) | (1 << OCIE2B)));
HERMES_PORT_SIGNAL = (unsigned char)(
HERMES_PORT_SIGNAL &
~((1 << HERMES_BIT_COMMON) | (1 << HERMES_BIT_BLOCK))
);
// Make PB1 and PB2 high-impedence.
/*HERMES_DDR_SIGNAL = (unsigned char)(*/
/*HERMES_DDR_SIGNAL &*/
/*~((1 << HERMES_DD_COMMON) | (1 << HERMES_DD_BLOCK))*/
/*);*/
sei();
}
void DCC_Transmit(const packet p)
{
DCC_TransmitRepeat(p, 2);
}
void DCC_TransmitRepeat(const packet p, unsigned char repeat)
{
// Wait for transmission of a previous packet to end.
while(g_transmitting_packet);
g_transmit_repeat = repeat;
// Clear the outgoing packet buffer.
for(int i = 0; i < DCC_PACKET_LENGTH; ++i)
g_packet[i] = 0;
// Set each bit in the outgoing buffer, up to the packet length.
Packet_RawData(g_packet, &p);
g_packet_length = Packet_RawLength(&p);
// Signal to the interrupt routine that a new packet is ready.
g_request_transmit = true;
// Wait for the interrupt routine to accept the packet, setting
// g_transmitting_packet. Otherwise, this routine could be called,
// attempting to transmit a packet while one is waiting.
while(g_request_transmit);
}
void DCC_AwaitCompletion()
{
while(g_transmitting_packet);
}
void DCC_TransmitPagePresetSequence()
{
for(unsigned char i = 0; i < 5; ++i)
DCC_Transmit(Packet_CreateReset());
for(unsigned char i = 0; i < 15; ++i)
DCC_Transmit(Packet_CreatePagePreset());
}
void DCC_AutoCvWrite(cv_type cv, unsigned char value)
{
if(cv > 0 && cv < 5)
DCC_RegisterCvWrite((unsigned char)cv, value);
if(cv == 29)
DCC_RegisterCvWrite(5, value);
if(cv >= 5)
DCC_PagedCvWrite(cv, value);
}
void DCC_DirectCvWrite(cv_type address, unsigned char data)
{
// Move address to the 0-1023 address space.
address -= 1;
DCC_Start();
// Idle packets, decoder power on time.
// NMRA specs require 20 packets.
DCC_TransmitRepeat(Packet_CreateIdle(), 100);
// 3 or more reset packets, required by NMRA specs.
DCC_TransmitRepeat(Packet_CreateReset(), 15);
// 5 or more writes to a single CV, followed by 6 or more identical write
// or reset packets, required by NMRA specs.
DCC_TransmitRepeat(
Packet_CreateServiceMode3(
(unsigned char)(0b01111100 | ((address >> 8) & 0b00000011)),
(unsigned char)(address & 0b11111111),
data
),
15
);
DCC_TransmitRepeat(Packet_CreateReset(), 15);
// Allow the decoder plenty of time to interpret the instruction.
DCC_TransmitRepeat(Packet_CreateIdle(), 100);
DCC_Stop();
}
void DCC_PagedCvWrite(cv_type address, unsigned char value)
{
address -= 1;
DCC_Start();
DCC_TransmitRepeat(Packet_CreateIdle(), 100);
unsigned char page = (unsigned char)((address / 4) + 1);
unsigned char reg = (unsigned char)(address % 4);
DCC_TransmitRepeat(Packet_CreateReset(), 5);
DCC_TransmitRepeat(Packet_CreateServiceMode2(0b01111101, page), 15);
DCC_TransmitRepeat(Packet_CreateReset(), 15);
DCC_TransmitRepeat(Packet_CreateServiceMode2(0b01111000 | reg, value), 15);
DCC_AwaitCompletion();
// Allow the decoder plenty of time to interpret the instruction.
DCC_TransmitRepeat(Packet_CreateIdle(), 100);
DCC_Stop();
}
void DCC_RegisterCvWrite(unsigned char reg, unsigned char value)
{
reg = (unsigned char)(reg - 1);
DCC_Start();
DCC_TransmitRepeat(Packet_CreateIdle(), 100);
DCC_TransmitPagePresetSequence();
DCC_TransmitRepeat(Packet_CreateReset(), 5);
DCC_TransmitRepeat(
Packet_CreateServiceMode2(
(unsigned char)(0b01111000 | (reg & 0b00000111)),
value
),
15
);
DCC_AwaitCompletion();
// Allow the decoder plenty of time to interpret the instruction.
DCC_TransmitRepeat(Packet_CreateIdle(), 100);
DCC_Stop();
}
void DCC_HardReset()
{
/*
* The NMRA specs state that the decoder should reset when a value of 8 is
* written to CV #8. This is equivalent to the instruction 0b01111111,
* 0b00001000.
*
* On older decoders, setting another CV will reset the decoder.
*/
DCC_RegisterCvWrite(8, 8);
}