How to create square waves for DCC signal (model railroad standard) on Teensy 3.2

Status
Not open for further replies.

jay-g

New member
Hi there,

First, I would like to thank you for this awesome resource here.

I would like to program an algorithm that creates the DCC signal which is used by model railroads. 220px-DCCsig.png

I am wondering which function to use to create this signal. I will be using the Teensy 3.2 together with an H-bridge LMD18200T. This means I need to somehow create a square wave on one pin. The signal out of the Teensy will be composed of either 1 or 0. The 1 being a 58 microseconds long square wave and 58 microseconds long pause; the 0 should be a 100 microseconds long square wave with a 100 microseconds long pause (in fact it doesnt really matter whether the pause is before or after the wave). Also there is some tolerance: the 1 signal can be between 55 and 61 us long and 0 between 95 and 9900(!) us.

The LMD18200T will then use this signal from the Teensy to invert the input voltage from an external power supply accordingly and put it on the rails.
At this point I am not concerned with the algorithm itself, but the specific function to use for the 1 or 0 wave. As long as there is no new command (like a new speed setting) it should repeat all the old commands (in case an engine lost power for a brief moment so it does continue after it receives the repeated command).
Later I also want to put commands into arduino ide's serial monitor and have the Teensy put those commands onto the rails.
Reading posts about it I got the idea that PWM would be a good way of creating accurate signals.


TLDR: Which function in Teensy 3.2 is both easy to use and accurate enough to create a digital signal composed of 58 us and 100 us half square waves?

Edit: I do not know how to use the PWM (analogWrite?) function properly. All i could come up with myself was to use digitalWrite for the signal and elapsedMicros for timing. But I am not sure how this would turn out in a program that is also doing a lot of other stuff at the same time like waiting for input on serial, controlling led's and possibly a CAN bus later down the road.
 
Last edited:
This is an almost perfect application of the ws2812 drivers. You could control up to 8 sections of track with one teensy.
 
Scratch that, it's only for outputting constant (repeating) waves, not one-shot.

This is better: https://www.pjrc.com/teensy/td_digital.html

Code:
int zeroDelay = 100;
int oneDelay = 58;
int pin = PIN_D6;   // or whatever PWM pin you want to use

// 0
digitalWrite(pin, HIGH);
delayMicroseconds(zeroDelay);
digitalWrite(pin, LOW);

// 1
digitalWrite(pin, HIGH);
delayMicroseconds(oneDelay);
digitalWrite(pin, LOW);

If the signal has to be generated to an accuracy of +- a microsecond or two, you should be fine.

WS2812Serial is a much more complex way of doing this, as it has to be, as the WS281x series devices require pulse widths of 400 nanoseconds +- 10%. Difficult to do without resorting to either assembly language (as is done on Atmel AVR) or using the DMA engine (which is how it's done on more capable chips, like Teensy 3.x and above, and Raspberry Pi.)

If you go the DMA route, you will be creating the waveforms in RAM (chunks of 100 bits) and using DMA to shift them out a PWM pin, so you will need to set the right clock divider frequency. I think there is a tool for that around here somewhere, but I don't remember what it's called.

The above code should be good enough for a proof-of-concept, assuming the timing isn't super-critical. It might be off by a few tens of nanoseconds, but considering how huge the difference is between a 1 and a 0 I think you'll be okay.

If you need something non-blocking (don't have to wait for transmission to complete) then go with DMA, as it runs independently of the CPU.
 
Last edited:
For a non-blocking solution, I would use a timer to generate 58 uS and 100uS pulses. Set the timer up to generate an interrupt. In the interrupt service routine (ISR) for the timer you set the next timer period up and toggle the output pin. To start it off, you have a routine that sets up the timer for the first pulse and sets the output pin high. This would be extremely low overhead and allow your mainline code to continue while sending.

Here's a tutorial on interrupts for the arduino which will be fairly similar on a teensy. Though, there are many tutorials out there.
 
Thank you everyone for your input!
@boxxofrobots I had a look at the WS2812 library and it does indeed look like a very similar signal. When I opened the actual library files I was a bit overwhelmed though, as my programming learning curve just began a couple weeks ago (I am taking David Malan's CS50 course). So i will keep it in mind in case I won't make any progress.

@Pilot The signal does not have to be that accurate. Theres a tolerance of 3 us for the length of one half pulse. So the digitalwrite or even better digitalwritefast function will absolutely be appropriate. Its the delay I was unhappy with. As far as I understand my program would then do nothing until the delay is over. Because the delay is as long as one half pulse it would be 50% of the time, right? So what happens if I send a command via serial monitor during a delay? Will it ignore my command or would it get buffered somehow and then executed when the delay is over?

@PhilB This might be my path for now. I found the digitaltogglefast command which supposedly toggles the pin as fast as a direct register command like GPIOC_PTOR. I cobbled this code together today, but the signal is not correct (it would be a 58 us half pulse, then a 100 us break and so on). I would use the delaymicroseconds function to create the proper wave length, but again am unsure if that would make my program unresponsive for input from the serial monitor and other functions id love to add later like CAN bus, lighting and so on. Just so that were talking about the same thing: the ISR in this case would be my toggle() function?

Code:
IntervalTimer myTimer;
volatile int whichbittosend = 0;

void setup()
{
    pinMode(13, OUTPUT);
    myTimer.begin(toggle, 1000000);
}

void loop()
{

}

void toggle()
{
    if (whichbittosend == 0)
    {
        digitalToggleFast(13);
        myTimer.update(1000000);
        whichbittosend = 1;
    }

    else if (whichbittosend == 1)
    {
        digitalToggleFast(13);
        myTimer.update(100000);
        whichbittosend = 0;
    }
    
}

(I used pin 13/built in led and used a 1 s and 0.1 s interval for the 0 / 1 signal to make it easy to debug visually)
 
If you use intervalTimer like this, it shouldn't block except when running the code in that toggle() function, and that is probably going to finish in hundreds of nanoseconds. That's a vague guess based on running a profiler on code compiled in C on x86, and it's "probably" right. In any case, I would not expect that code to take longer than a microsecond, and it sounds like you have way more than that much "slop" allowed in the digital signal.

If something is received over serial while that function is running (which you should definitely expect to happen sooner or later, and perhaps routinely) it should be okay because there is a buffer on the Teensy 3.2. I don't know how long the buffer is, though. I've seen people around here say it's 256 bytes, others say they only got 128 bytes out of it. Sorry I don't know for sure.
 
Status
Not open for further replies.
Back
Top