DCC++Arduino: Bare-bones DCC++ generator

Travis Farmer Mar 8, 2018

  1. Antonio Fiol

    Antonio Fiol New Member

    9
    1
    1
    Thanks @WillemT ! This code may be exactly what I need, and your explanation looks like a brilliant starting point. I will start my tests soon.

    About the schematic, it looks great as well. A bit too full of components for my taste ;-) because I'm always thinking of my "times N" before adding anything to the circuit.

    I'm using inverter chips instead of 2N2222 and will only be using half of the L298N, so I won't need the switching.
    I will select a current sense resistor so I can measure using the 1.1V scale on the ATTiny85 and avoid the amplifier.
    And will definitely not use a GPIO expander in this part of the circuit.

    I might use two ATTiny85, one for the main track(s) and one for the programming track.

    Here's a very basic question about DCC: Is current sensing needed on the main track, other than to cut the power in case of short circuit? I understand that current sensing is needed on the programming track, in order to read the CVs, but I am considering NOT using current sensing for the main track at all, except for (fully analog) overcurrent protection.
     
  2. WillemT

    WillemT TrainBoard Member

    55
    40
    7
    Remember, That schematic is a fully self contained controller with dual motor driver for both main and programming tracks. I am using DCC to actually avoid the "times N" part. Isolated sections only required if you use a lot of occupation detection (I use sensors for position detection when needed). My layout is quite small with only a few locos. No real need for multi districts - only for the reverse loop sections.

    I used the 2N2222s to avoid having to add another IC for inverters since I already had the gates, and used them all, to control both LEDs for the display and to switch between Main/Program. I used the SMD version, they are small and takes very little space.

    Only used for the keypad and other control switches for the controller part - not needed for just the DCC part.

    And I thought you like to reduce the number of components :LOL:.

    No. If you are sure you do not need short circuit protection - not needed. I use it to read and continuously display the current drawn. I find if you (read that as "I") have any derailments (wrong setting for a trailing turnout) you will get a short - so I am happy with the extra protection.

    If you are only going to use half of a L298N why not look at say the Allegro A4953 (the A4952 has fault indication as well but I cannot get it here) or similar. It has built in over current protection, is small and works great for DCC. It is also cheaper (over here at least). I mount it on aluminium backed PC Board for cooling and had no problems.

    The ATtiny85 system was only an experimental exercise. My final version will use multiple ATtiny84 DCC units (I think they are great for that) and small drivers running like boosters off the main DCC signal with a central controller capable of being controlled with Wi-Fi (most likely) remote controls. I also run a separate DCC buss for accessories. We like to dream and plan.

    Willem.
     
    Last edited: Mar 17, 2019
  3. Antonio Fiol

    Antonio Fiol New Member

    9
    1
    1
    I've powered up the ATtiny85 with your code, unmodified, and hooked a cheap 24MHz logic analyzer to pin 6, looking forward to a clean DCC signal.
    It is fine most of the time, but something weird happens in that signal.
    As far as I understood, it should be all ones and zeros, longer pulses and shorter pulses, but never losing the symmetry. Captura de pantalla de 2019-03-17 20-00-40.png

    Captura de pantalla de 2019-03-17 22-51-18.png
     
  4. WillemT

    WillemT TrainBoard Member

    55
    40
    7
    I do not have a scope or logic analyser so I never looked at the result for this, or the original on an UNO. The basic data stream is correct for an idle package (which is what it will be sending). There seems to be a change in timing whenever the code bits change from a 0 to 1 (Actually there seems to be a problem the other way as well)

    I did not change anything to the actual code generating all this, except change the timer registers used - and obviously the duration to produce the 1's and 0s. The basic timing seems to be correct since the logic analyser correctly recognises the 1s and 0s. I do not know if it can be a particular thing with the ATtiny85 - I doubt that. I am going to have a careful look to what the interrupt routine does at that moment.

    The funny thing is that my controller seems to work correctly with throttle and function changes. I am running locos on my layout with it. I also used it to feed the DCC signal via a opto decoupler to a processor running the NMRA library, without problems. I cannot compare it to my accessory development since the ATtiny84 uses the exact same code as the UNO.

    Do you have an UNO, or a 328P you can substitute, and run the original code to see what we get there?

    Willem
     
    Last edited: Mar 18, 2019
  5. WillemT

    WillemT TrainBoard Member

    55
    40
    7
    Forget about the 328P.

    I have a suspicion as what is happening. I made a change based on a wild guess. Since I do not have an analyser and I am right now too lazy to upload it to my DCC++ unit (need to move it from the layout room to the PC etc) and try it first, I hope you will be willing to try the attached file and see if the data looks better or worse on your analyser.

    Just replace the original with this one, compile and upload.

    Thanks in advance.

    Willem
     

    Attached Files:

  6. Antonio Fiol

    Antonio Fiol New Member

    9
    1
    1
    Your hunch was correct. Thank you very much @WillemT for taking the time to check it. It would have taken me ages to get down to the timing level of the code.
    The idle DCC signal is now correct according to the analyzer, and it even decodes the packets (surprise!).

    Did you somehow swap the low and high levels of the signal in the process? Every bit is now "low" first, then "high", for the same amount of time. I thought it was "high" then "low". Not really important, I guess, since the loco might be on the track either way.

    Captura de pantalla de 2019-03-18 20-10-48.png Captura de pantalla de 2019-03-18 20-08-19.png

    I'm attaching the logic analyzer capture as well. You can open it with PulseView. It will decode the DCC if you add https://github.com/littleyoda/sigrok-DCC-Protocoll and then select the data line D1 and the right polarity.

    I sense you may now go to amazon and buy one of these sub-$12 usb logic analyzers (don't bother getting the one with the test hooks unless you want to solder them, as they don't fit the provided cable).
     

    Attached Files:

    Atani likes this.
  7. WillemT

    WillemT TrainBoard Member

    55
    40
    7
    The problem was that I made certain assumptions as to timer 1 register behaviour. Registers OCM1A (Output Compare Match 1A) and OCM1B are double buffered. That means they can be changed at anytime but only gets logged when the counter goes from TOP to BOTTOM. OCM1C actually holds the TOP. I assumed that one was double buffered as well. It appears as if that is not the case, hence its value changed the moment it was set (at the 50% duty cycle point). This changed the remaining time for the current bit to the following bit's period. The effect only shows when changing from a 1 to a 0 or the other way. I hate to think what normal data looked like. I also do not know how my DCC++ unit actually can work.

    What I did was to write the period's time into a temp variable at the 50% compare match and used the overflow interrupt to update the OCRC1 after the full period. My initial testing of this type of timer was on the 32U4 - I will now have to go and check if that works as expected. On the UNO we use the 16bit counter where you can set OCRA1 to the TOP and all works fine. If this did not work the only other option would have been to use timer 0, but that changes the system timer and effects delay() etc. Glad this seems to work.

    Yes it now is correct, must have changed with the above fix. I use COM1A1:COM1A0 = 1:1 -> Set on match (50% duty), clear at TOP (so the next bit starts low). Could use 0:1 which is the other way round. As you say it does not matter. The loco direction on the track does not matter at all in DCC - it as a pseudo AC signal. The decoder is instructed as to direction. You can have two locos on the same track, facing the same way, going in opposite directions.

    I will update my DCC++ unit tomorrow and see how that works. I did have some funnies with it. Will keep you updated.

    Yes I definitely need something. I would appreciate it if you have a link to those amazon units.

    Willem.
     
    Last edited: Mar 18, 2019
  8. Antonio Fiol

    Antonio Fiol New Member

    9
    1
    1
  9. WillemT

    WillemT TrainBoard Member

    55
    40
    7
  10. Atani

    Atani TrainBoard Member

    1,469
    1,756
    37
  11. WillemT

    WillemT TrainBoard Member

    55
    40
    7
    OK. I uploaded the updated code to my DCC++ unit and found a problem. It no longer read the current and hence no over current or short circuit protection. After some searching I found the problem.

    I screwed up. The annoying part is that I made the exact same mistake before, when I implemented the mode change between Operations and Programming (age must be getting at me). Only the required bits have to be changed in the TIMSK register. By first clearing it I killed Timer_0 and hence millis().

    Please make the following changes to your code. In the file "DCCpp_ATtiny85.ino", change the last statement in "setup()" as follows:

    Code:
      mainRegs.loadPacket(1,RegisterList::idlePacket,2,0);    // load idle packet into register 1  
         
    //  bitSet(TIMSK, 6);       // enable interrupt vector for Timer 1 Output Compare A Match (OCR1A)  
    //  bitSet(TIMSK, 2);       // enable interrupt vector for Timer 1 OVL (OCR1C)  
      TIMSK |= (1<<OCIE1A) | (1<<TOIE1);       // enable interrupt vectors for Timer_1 Output Compare A Match (OCR1A) and Timer_1 Overflow (OCR1C)
    
    } // setup
    
    You can delete the commented instructions.

    Then in "PacketRegister.cpp" change the "RegisterList::setSystem" routine (it is the last one in the file) to read as follows:

    Code:
    void RegisterList::setSystem(struct I2Cdata *Data) volatile  {
      // set current operating system
      currentSystem = Data->byteData;
    
      if ( currentSystem == 0 )  {  // Only required when changing to programming mode
    
    //    bitClear(TIMSK,6);          // Disable interrupt vector for Timer 1 Output Compare A Match (OCR1A)
        TIMSK &= ~((1<<OCIE1A) | (1<<TOIE1));     // clear interrupt vectors for Timer_1 Output Compare A Match (OCR1A) and Timer_1 Overflow (OCR1C)
       
        //Clear and initialize all registers
        currentReg=reg;
        regMap[0]=reg;
        maxLoadedReg=reg;
        nextReg=NULL;
        currentBit=0;
        nRepeat=0;
       
        loadPacket(1,RegisterList::idlePacket,2,0);    // load idle packet into register 1  
       
    //    bitSet(TIMSK, 6);       // enable interrupt vector for Timer 1 Output Compare A Match (OCR1A)  
        TIMSK |= (1<<OCIE1A) | (1<<TOIE1);        // enable interrupt vectors for Timer_1 Output Compare A Match (OCR1A) and Timer_1 Overflow (OCR1C)
      }
    }
    Again you can delete the commented code.

    You said you are not going to switch between Ops and Programming, but just in case you change your mind.

    Once updated as above everything seems to work smooth and as designed. My trains run fine. I also made some changes to the Controller code while I had it all connected to the PC, all related to the user interface. If ever you want that, let me know.

    Sorry for the problems.

    Willem
     
    Last edited: Mar 20, 2019
  12. FlightRisk

    FlightRisk TrainBoard Member

    548
    237
    14
    If you guys are still following this thread, these are 5v analyzers, right? What are you connecting them to? Just wondering if you are running them for testing with a 5v supply or have something like a voltage divider if you are connecting them to the rails with a 15V supply.
     
  13. Atani

    Atani TrainBoard Member

    1,469
    1,756
    37
    They will operate on a range of voltages but I have mostly used them in the 3v3-5v range.

    I have directly fed it from the direction pin that goes to the motor shield with great success. For reading from the track you would likely need to use a DCC decoder style circuit to have a usable lower voltage signal line.
     
  14. FlightRisk

    FlightRisk TrainBoard Member

    548
    237
    14
    Are you noticing that the DCC protocol decoder doesn't work for some signals? In particular, it won't read accessory packets. I'll test with other packets tonight. One thing I noticed is that I have irregular pulse duration on some bits. In the DCC plugin I see <1><0><0><1><100/40/60><0><0><0> And looking at the pulses, I can see they are shorter than 60ms. So I assume this is telling me something like it sees a 40ms pulse and was expecting a 60ms one? I don't know if it is my Arduino, the sketch, the protocol decoder, the cheap logic analyzer, Pulseview, etc. I am sampling at 50kHz and will try lower and higher. What I do see it decodes a lot of idle packets. Several at at time, then just 1's and 0s, then another group of idle packets, etc. Is there a way to fix this?
     
  15. Atani

    Atani TrainBoard Member

    1,469
    1,756
    37
    It is telling you that the fifth bit was malformed, the first half had a 40usec part and second was 60usec.

    I usually start at 500kHz or 1MHz. Too low of a sample frequency will result in malformed reads/decodes.
     
  16. WillemT

    WillemT TrainBoard Member

    55
    40
    7
    Hi @FlightRisk,

    What you should see:

    At startup register one is loaded with the idle packet. Register one is then continuously sent till you replace the idle packet with a throttle instruction to register 1.
    The initial pulse sequence is as follows: DCC++ sends a preamble consisting of 22 ones (NMRA specifies at least 14 - if I remember correctly the NMRA library looks for 12 ones then start looking for a zero). Next follows a zero (signalling the start of an instruction) then the idle packet, consisting of 8 ones. This is followed by a zero and then another 8 ones - the second set of 8 is the checksum (in the case of the idle packet it will also be 8 ones).

    This set of pulses is repeated till the instruction in register one is replaced.

    Once a throttle instruction is sent to register one it will replace the idle packet and will consist of more than one block of 8 bits (each block of 8 separated by a zero), again followed by a zero and the checksum. If throttle instructions are written to further registers each register will be sent in sequence. Throttle instruction are only sent once but continuously repeated - in sequence.

    When an instruction other than a throttle is sent, it is immediately sent using register 0 and repeated for a specified number of times (NMRA usually recommendeds 5 times). Things then revert back to the throttle register instruction sequence.

    Remember that each and every instruction (including each of those in the throttle sequence) is precede by the 22 bit preamble. The first zero after the preamble signifies the start of an instruction.

    Hope that helps.

    Willem.
     
  17. FlightRisk

    FlightRisk TrainBoard Member

    548
    237
    14
    Well, the issue is that the MAIN track is not as clean as the PROG track. I tried switching out inputs and cables on the analyzer. I tried different versions of DCC++. Despite 50kHz sampling rate, which is is more 2.5x oversampling, it doesn't work. Everything on the track works, the decoders work, the protocol decoder just doesn't work if the 1 bits are less than 60ms. There are no short duration pulses on the program track. So I upped the sample rate to 100kHz and it works fine now. I am going to test a Mega instead of an Uno and at some point use an optoisolator to read directly from the motorboard output. I am reading off the signal pins feeding the motorboard. Thanks for the help and comments.
     
  18. Atani

    Atani TrainBoard Member

    1,469
    1,756
    37
    Very odd, it should have in theory worked on 50kHz, were you sampling both OPS and PROG simultaneously? If so that may explain it a bit, I don't know the devices will capture concurrently for multiple channels reliably at lower frequencies.
     
  19. FlightRisk

    FlightRisk TrainBoard Member

    548
    237
    14
    @Atani No. I disconnected all the leads except ground and main. Strange. I'll maybe change drivers too. I'm using the one saelea installed when I installed I installed their software. I see there is a program to run from Sigrok that installs a generic driver for the chip inside this thing. But I get perfect idle packets on the program track.
     
  20. Atani

    Atani TrainBoard Member

    1,469
    1,756
    37
    probably because it is a canned packet. If you try and read a CV it may change since it is not an idle packet.

    Well that kinda rules out the multiple channel theory.

    Something else to explore on the signal quality is this, I haven't flashed it yet but I plan on it soon. Another option is using an ESP8266 to capture it: https://gist.github.com/Beherith/9d090f64437d6ca721a76e70a097a664

    and a nano version: https://gist.github.com/Beherith/c26fc758f54f43f4029f0fa94655cb33 (uses MynaBay decoder)
     
    Last edited: May 14, 2020
    FlightRisk likes this.

Share This Page