Permalink
Browse files

Seperated hardware-specific stuff from the rest. Improved support for…

… short and long addresses to permit the use of long addresses < 127.
  • Loading branch information...
1 parent d5664af commit e5b4039968f8efb32fc20b1974e262cc2d0ceac2 Railstars committed Sep 17, 2012
Showing with 359 additions and 316 deletions.
  1. +199 −0 DCCHardware.c
  2. +17 −0 DCCHardware.h
  3. +16 −14 DCCPacket.cpp
  4. +15 −14 DCCPacket.h
  5. +94 −268 DCCPacketScheduler.cpp
  6. +18 −20 DCCPacketScheduler.h
View
@@ -0,0 +1,199 @@
+#include "Arduino.h"
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include "DCCHardware.h"
+
+/// An enumerated type for keeping track of the state machine used in the timer1 ISR
+/** Given the structure of a DCC packet, the ISR can be in one of 5 states.
+ *dos_idle: there is nothing to put on the rails. In this case, the only legal thing
+ to do is to put a '1' on the rails. The ISR should almost never be in this state.
+ *dos_send_premable: A packet has been made available, and so we should broadcast the preamble: 14 '1's in a row
+ *dos_send_bstart: Each data uint8_t is preceded by a '0'
+ *dos_send_uint8_t: Sending the current data uint8_t
+ *dos_end_bit: After the final uint8_t is sent, send a '0'.
+*/
+typedef enum {
+ dos_idle,
+ dos_send_preamble,
+ dos_send_bstart,
+ dos_send_uint8_t,
+ dos_end_bit
+} DCC_output_state_t;
+
+DCC_output_state_t DCC_state = dos_idle; //just to start out
+
+/// The currently queued packet to be put on the rails. Default is a reset packet.
+uint8_t current_packet[6] = {0,0,0,0,0,0};
+/// How many data uint8_ts in the queued packet?
+volatile uint8_t current_packet_size = 0;
+/// How many uint8_ts remain to be put on the rails?
+volatile uint8_t current_uint8_t_counter = 0;
+/// How many bits remain in the current data uint8_t/preamble before changing states?
+volatile uint8_t current_bit_counter = 14; //init to 14 1's for the preamble
+/// A fixed-content packet to send when idle
+//uint8_t DCC_Idle_Packet[3] = {255,0,255};
+/// A fixed-content packet to send to reset all decoders on layout
+//uint8_t DCC_Reset_Packet[3] = {0,0,0};
+
+
+/// Timer1 TOP values for one and zero
+/** S 9.1 A specifies that '1's are represented by a square wave with a half-period of 58us (valid range: 55-61us)
+ and '0's with a half-period of >100us (valid range: 95-9900us)
+ Because '0's are stretched to provide DC power to non-DCC locos, we need two zero counters,
+ one for the top half, and one for the bottom half.
+
+ Here is how to calculate the timer1 counter values (from ATMega168 datasheet, 15.9.2):
+ f_{OC1A} = \frac{f_{clk_I/O}}{2*N*(1+OCR1A)})
+ where N = prescalar, and OCR1A is the TOP we need to calculate.
+ We know the desired half period for each case, 58us and >100us.
+ So:
+ for ones:
+ 58us = (8*(1+OCR1A)) / (16MHz)
+ 58us * 16MHz = 8*(1+OCR1A)
+ 58us * 2MHz = 1+OCR1A
+ OCR1A = 115
+
+ for zeros:
+ 100us * 2MHz = 1+OCR1A
+ OCR1A = 199
+
+ Thus, we also know that the valid range for stretched-zero operation is something like this:
+ 9900us = (8*(1+OCR1A)) / (16MHz)
+ 9900us * 2MHz = 1+OCR1A
+ OCR1A = 19799
+
+*/
+
+uint16_t one_count=115; //58us
+uint16_t zero_high_count=199; //100us
+uint16_t zero_low_count=199; //100us
+
+/// Setup phase: configure and enable timer1 CTC interrupt, set OC1A and OC1B to toggle on CTC
+void setup_DCC_waveform_generator() {
+
+ //Set the OC1A and OC1B pins (Timer1 output pins A and B) to output mode
+ //On Arduino UNO, etc, OC1A is Port B/Pin 1 and OC1B Port B/Pin 2
+ //On Arduino MEGA, etc, OC1A is or Port B/Pin 5 and OC1B Port B/Pin 6
+#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_AT90CAN128__) || defined(__AVR_AT90CAN64__) || defined(__AVR_AT90CAN32__)
+ DDRB |= (1<<DDB5) | (1<<DDB6);
+#else
+ DDRB |= (1<<DDB1) | (1<<DDB2);
+#endif
+
+ // Configure timer1 in CTC mode, for waveform generation, set to toggle OC1A, OC1B, at /8 prescalar, interupt at CTC
+ TCCR1A = (0<<COM1A1) | (1<<COM1A0) | (0<<COM1B1) | (1<<COM1B0) | (0<<WGM11) | (0<<WGM10);
+ TCCR1B = (0<<ICNC1) | (0<<ICES1) | (0<<WGM13) | (1<<WGM12) | (0<<CS12) | (1<<CS11) | (0<<CS10);
+
+ // start by outputting a '1'
+ OCR1A = OCR1B = one_count; //Whenever we set OCR1A, we must also set OCR1B, or else pin OC1B will get out of sync with OC1A!
+ TCNT1 = 0; //get the timer rolling (not really necessary? defaults to 0. Just in case.)
+
+ //finally, force a toggle on OC1B so that pin OC1B will always complement pin OC1A
+ TCCR1C |= (1<<FOC1B);
+
+}
+
+void DCC_waveform_generation_hasshin()
+{
+ //enable the compare match interrupt
+ TIMSK1 |= (1<<OCIE1A);
+}
+
+/// This is the Interrupt Service Routine (ISR) for Timer1 compare match.
+ISR(TIMER1_COMPA_vect)
+{
+ //in CTC mode, timer TCINT1 automatically resets to 0 when it matches OCR1A. Depending on the next bit to output,
+ //we may have to alter the value in OCR1A, maybe.
+ //to switch between "one" waveform and "zero" waveform, we assign a value to OCR1A.
+
+ //remember, anything we set for OCR1A takes effect IMMEDIATELY, so we are working within the cycle we are setting.
+ //first, check to see if we're in the second half of a uint8_t; only act on the first half of a uint8_t
+ //On Arduino UNO, etc, OC1A is digital pin 9, or Port B/Pin 1
+ //On Arduino MEGA, etc, OC1A is digital pin 11, or Port B/Pin 5
+#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_AT90CAN128__) || defined(__AVR_AT90CAN64__) || defined(__AVR_AT90CAN32__)
+ if(PINB & (1<<PINB6)) //if the pin is low, we need to use a different zero counter to enable streched-zero DC operation
+#else
+ if(PINB & (1<<PINB1)) //if the pin is low, we need to use a different zero counter to enable streched-zero DC operation
+#endif
+
+ {
+ if(OCR1A == zero_high_count) //if the pin is low and outputting a zero, we need to be using zero_low_count
+ {
+ OCR1A = OCR1B = zero_low_count;
+ }
+ }
+ else //the pin is high. New cycle is begining. Here's where the real work goes.
+ {
+ //time to switch things up, maybe. send the current bit in the current packet.
+ //if this is the last bit to send, queue up another packet (might be the idle packet).
+ switch(DCC_state)
+ {
+ /// Idle: Check if a new packet is ready. If it is, fall through to dos_send_premable. Otherwise just stick a '1' out there.
+ case dos_idle:
+ if(!current_uint8_t_counter) //if no new packet
+ {
+// Serial.println("X");
+ OCR1A = OCR1B = one_count; //just send ones if we don't know what else to do. safe bet.
+ break;
+ }
+ //looks like there's a new packet for us to dump on the wire!
+ //for debugging purposes, let's print it out
+// if(current_packet[1] != 0xFF)
+// {
+// Serial.print("Packet: ");
+// for(uint8_t j = 0; j < current_packet_size; ++j)
+// {
+// Serial.print(current_packet[j],HEX);
+// Serial.print(" ");
+// }
+// Serial.println("");
+// }
+ DCC_state = dos_send_preamble; //and fall through to dos_send_preamble
+ /// Preamble: In the process of producing 14 '1's, counter by current_bit_counter; when complete, move to dos_send_bstart
+ case dos_send_preamble:
+ OCR1A = OCR1B = one_count;
+// Serial.print("P");
+ if(!--current_bit_counter)
+ DCC_state = dos_send_bstart;
+ break;
+ /// About to send a data uint8_t, but have to peceed the data with a '0'. Send that '0', then move to dos_send_uint8_t
+ case dos_send_bstart:
+ OCR1A = OCR1B = zero_high_count;
+ DCC_state = dos_send_uint8_t;
+ current_bit_counter = 8;
+// Serial.print(" 0 ");
+ break;
+ /// Sending a data uint8_t; current bit is tracked with current_bit_counter, and current uint8_t with current_uint8_t_counter
+ case dos_send_uint8_t:
+ if(((current_packet[current_packet_size-current_uint8_t_counter])>>(current_bit_counter-1)) & 1) //is current bit a '1'?
+ {
+ OCR1A = OCR1B = one_count;
+// Serial.print("1");
+ }
+ else //or is it a '0'
+ {
+ OCR1A = OCR1B = zero_high_count;
+// Serial.print("0");
+ }
+ if(!--current_bit_counter) //out of bits! time to either send a new uint8_t, or end the packet
+ {
+ if(!--current_uint8_t_counter) //if not more uint8_ts, move to dos_end_bit
+ {
+ DCC_state = dos_end_bit;
+ }
+ else //there are more uint8_ts…so, go back to dos_send_bstart
+ {
+ DCC_state = dos_send_bstart;
+ }
+ }
+ break;
+ /// Done with the packet. Send out a final '1', then head back to dos_idle to check for a new packet.
+ case dos_end_bit:
+ OCR1A = OCR1B = one_count;
+ DCC_state = dos_idle;
+ current_bit_counter = 14; //in preparation for a premable...
+// Serial.println(" 1");
+ break;
+ }
+ }
+}
View
@@ -0,0 +1,17 @@
+#ifndef __DCCHARDWARE_H__
+#define __DCCHARDWARE_H__
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+void setup_DCC_waveform_generator(void);
+void DCC_waveform_generation_hasshin(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //__DCCHARDWARE_H__
View
@@ -1,55 +1,57 @@
#include "DCCPacket.h"
-DCCPacket::DCCPacket(unsigned int decoder_address) : address(decoder_address), kind(idle_packet_kind), size_repeat(0x40) //size(1), repeat(0)
+DCCPacket::DCCPacket(uint16_t new_address, uint8_t new_address_kind) : kind(idle_packet_kind), size_repeat(0x40) //size(1), repeat(0)
{
+ address = new_address;
+ address_kind = new_address_kind;
data[0] = 0x00; //default to idle packet
data[1] = 0x00;
data[2] = 0x00;
}
-byte DCCPacket::getBitstream(byte rawbytes[]) //returns size of array.
+uint8_t DCCPacket::getBitstream(uint8_t rawuint8_ts[]) //returns size of array.
{
int total_size = 1; //minimum size
if(kind == idle_packet_kind) //idle packets work a bit differently:
// since the "address" field is 0xFF, the logic below will produce C0 FF 00 3F instead of FF 00 FF
{
- rawbytes[0] = 0xFF;
+ rawuint8_ts[0] = 0xFF;
}
- else if(address <= 127) //addresses of 127 or higher are 14-bit "extended" addresses
+ else if(!address_kind) // short address
{
- rawbytes[0] = (byte)address;
+ rawuint8_ts[0] = (uint8_t)address;
}
else //we have a 14-bit extended ("4-digit") address
{
- rawbytes[0] = (byte)((address >> 8)|0xC0);
- rawbytes[1] = (byte)(address & 0xFF);
+ rawuint8_ts[0] = (uint8_t)((address >> 8)|0xC0);
+ rawuint8_ts[1] = (uint8_t)(address & 0xFF);
++total_size;
}
- byte i;
+ uint8_t i;
for(i = 0; i < (size_repeat>>6); ++i,++total_size)
{
- rawbytes[total_size] = data[i];
+ rawuint8_ts[total_size] = data[i];
}
- byte XOR = 0;
+ uint8_t XOR = 0;
for(i = 0; i < total_size; ++i)
{
- XOR ^= rawbytes[i];
+ XOR ^= rawuint8_ts[i];
}
- rawbytes[total_size] = XOR;
+ rawuint8_ts[total_size] = XOR;
return total_size+1;
}
-byte DCCPacket::getSize(void)
+uint8_t DCCPacket::getSize(void)
{
return (size_repeat>>6);
}
-byte DCCPacket::addData(byte new_data[], byte new_size) //insert freeform data.
+uint8_t DCCPacket::addData(uint8_t new_data[], uint8_t new_size) //insert freeform data.
{
for(int i = 0; i < new_size; ++i)
data[i] = new_data[i];
View
@@ -3,7 +3,7 @@
#include "Arduino.h"
-typedef unsigned char byte;
+typedef unsigned char uint8_t;
//Packet kinds
// enum packet_kind_t {
@@ -31,24 +31,25 @@ typedef unsigned char byte;
class DCCPacket
{
private:
- //A DCC packet is at most 6 bytes: 2 of address, three of data, one of XOR
- unsigned int address;
- byte data[3];
- byte size_repeat; //a bit field! 0b11000000 = 0xC0 = size; 0x00111111 = 0x3F = repeat
- byte kind;
+ //A DCC packet is at most 6 uint8_ts: 2 of address, three of data, one of XOR
+ uint16_t address;
+ uint8_t address_kind;
+ uint8_t data[3];
+ uint8_t size_repeat; //a bit field! 0b11000000 = 0xC0 = size; 0x00111111 = 0x3F = repeat
+ uint8_t kind;
public:
- DCCPacket(unsigned int decoder_address=0xFF);
+ DCCPacket(uint16_t decoder_address=0xFF, uint8_t decoder_address_kind=0x00);
- byte getBitstream(byte rawbytes[]); //returns size of array.
- byte getSize(void);
+ uint8_t getBitstream(uint8_t rawuint8_ts[]); //returns size of array.
+ uint8_t getSize(void);
inline unsigned int getAddress(void) { return address; }
inline void setAddress(unsigned int new_address) { address = new_address; }
- byte addData(byte new_data[], byte new_size); //insert freeform data.
- inline void setKind(byte new_kind) { kind = new_kind; }
- inline byte getKind(void) { return kind; }
- inline void setRepeat(byte new_repeat) { size_repeat = ((size_repeat&0xC0) | (new_repeat&0x3F)) ;}
- inline byte getRepeat(void) { return size_repeat & 0x3F; }//return repeat; }
+ uint8_t addData(uint8_t new_data[], uint8_t new_size); //insert freeform data.
+ inline void setKind(uint8_t new_kind) { kind = new_kind; }
+ inline uint8_t getKind(void) { return kind; }
+ inline void setRepeat(uint8_t new_repeat) { size_repeat = ((size_repeat&0xC0) | (new_repeat&0x3F)) ;}
+ inline uint8_t getRepeat(void) { return size_repeat & 0x3F; }//return repeat; }
};
#endif //__DCCPACKET_H__
Oops, something went wrong.

0 comments on commit e5b4039

Please sign in to comment.