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

Travis Farmer Mar 8, 2018

  1. Travis Farmer

    Travis Farmer TrainBoard Member

    352
    320
    14
    i finally ordered some(10) ATtiny85 from Digikey. this should really speed up development when they arrive. in the mean-time, i have to build myself a programmer. I am pretty sure i have a ZIF socket for DIP chips kicking around, and some perfboard. fairly easy to make, i just have to find my workbench again. :ROFLMAO:
    ~Travis
     
    Scott Eric Catalano and Atani like this.
  2. Travis Farmer

    Travis Farmer TrainBoard Member

    352
    320
    14
    I received my shipment of ATtiny85 (10) yesterday. i haven't found my workbench yet (quite a plethora of unfinished projects), but i hope to at some point this weekend. :oops:
    I have drawn up a rough schematic of proposed circuit connections, if i am able to attach a PDF. I haven't done much on the code yet due to work leaving me exhausted, but i hope to get some more work done on it this weekend, also.
    On the schematic, i have included an on-board transistor to provide the inverted complement to the DCC++ signal needed by some motor drivers.
    for those with motor driver shields, it may be worthwhile to build the circuit onto a UNO prototyping shield, and make the connections to whatever pins your motor driver shield requires.

    ~Travis
     

    Attached Files:

    Scott Eric Catalano likes this.
  3. WillemT

    WillemT TrainBoard Member

    55
    40
    7
    Glad you are getting somewhere. I had my order in for almost a week now, delivery is usually next day.

    I would use a 10K for R3 and probably try that for R2 as well, I like low currents when you can get away with it.

    I assume you will program the chip elsewhere? You could add a few pins for an ISP programmer to plug onto it, makes it a lot easier when you find you need to alter any fuse setting or upload new versions of the sketch.

    Looking forward to your findings. As soon as I get something, hopefully early next week, I will join in.

    Willem.
     
  4. Travis Farmer

    Travis Farmer TrainBoard Member

    352
    320
    14
    I updated the original attachment. thanks for the tip.

    i opted to remove the ATtiny85, and use an IC socket. that way, i can use two chips. one for a stable prototype, and one for development, using a separate programming board (with a ZIF socket).

    fortunately, i have a digital oscilloscope, as well as a logic analyzer, so i can directly monitor the output when checking for proper signal. that way i won't have to worry about "killing" my only DCC loco. ;)
    ~Travis
     
    Scott Eric Catalano likes this.
  5. Travis Farmer

    Travis Farmer TrainBoard Member

    352
    320
    14
    https://github.com/travisfarmer/tinyDCCpp
    ok, i will try and explain what i have done. the above link is for the Main track standalone ATtiny85 DCC++ generator. i haven't hardware tested it yet, as i have not yet had time to excavate room on my work bench.
    protocol wise, the ATtiny85 DCC++ generator received data as follows.
    Code:
    0xff 0x00 0x01 .......DATA
    1     2       3              4
    assuming it formats on your screen as i have typed it. ;)

    byte 1 and 2 command the start of the packet (the combination of a 0xff followed by a 0x00 should not occur at any other time).
    byte 3 is a single byte (0x00 to 0xff) that describes the length of the data (4). so if data is 4 bytes long, byte 3 would be 0x03.

    Structure of DATA bytes (follows above bytes)
    Set Throttle
    Code:
    0x01 0x00 0x00 0x00 0x00 0x00
    1       2        3       4        5        6
    byte 1 is equal to 0x01. this is the command identifier.
    byte 2 is the register byte.
    byte 3 and 4 is the loco ID, or Cab (high byte first, then lowbyte. if only lowbyte, highbyte is 0x00)
    byte 4 is the speed.
    byte 5 is the direction.

    Engine Decoder Functions
    (6 bytes used)
    byte 1 is equal to 0x02.
    byte 2 is the register byte.
    byte 3 and 4 is the loco ID.
    byte 5 is BYTE1.
    byte 6 is BYTE2.

    Accessory Decoder Functions
    (5 bytes used)
    byte 1 is equal to 0x03.
    byte 2 and 3 is the accessory address.
    byte 4 is the accessory number.
    byte 4 is enable (0x00 or 0x01).

    Track Power
    (2 bytes used)
    Byte 1 is equal to 0xfd.
    byte 2 is equal to 0x01 (on), or 0x00 (off).

    Read Track Current
    (1 byte used)
    Byte 1 is equal to 0xfe.

    hopefully i will find some time to hardware test it. it is embarrassing to provide untested code. :oops:

    ~Travis
     
    Scott Eric Catalano and WillemT like this.
  6. WillemT

    WillemT TrainBoard Member

    55
    40
    7
    I received my ATtiny85s, 2 x 8pin DIP and 4 x SMD.

    I installed one on a bread board along with an UNO as SPI programmer (connected LEDs to pins 7,8 and 9 - I like some blinking light feedback). Also added a LED to the Tiny's pin0 (physical pin 5). I then proceeded as follows:

    1. Using the factory fuse setting, which should be 1MHz, I uploaded a Blink type sketch. I set on/off to 500/500ms giving 1Hz at 50% duty. All worked as expected.

    2. Next I added code to set Timer1 to about 1Hz. 1MHz divided by 4096 (pre-scale values are binary) which gives 244.1406... Hz to the Timer. With a 244 Top and 122 Compare we get almost 1Hz at 50% duty. Connected a LED to pin1 (physical pin 6) and it worked by blinking at about 1Hz. (Slowly drifting compared to the pin0 LED).

    3. Decided to up the clock frequency. The max pre-scaler for Timer1 is 16K - that is 4x the 4096 value. So, seeing a 4MHz option I chose it and burned the fuses. This resulted in one dead ATtiny. Further investigation showed the 4MHz option is for an external crystal. Fortunately I do have 16MHz crystals. (The ATtine85s I have are 20DU and 20SU which allows up to 20MHz external clocks). With crystal and caps connected (the advantage of using a breadboard) I burned the fuses to 1MHz again. All back on line.

    4. Next I upped the internal clock to 8MHz - that is an internal option (I checked it this time). So, with the pre-scaler set to 16K and the clock at 8MHz the Timer1 LED pulsed at close to 2Hz. All seems to work.

    5. Added an interrupt routine for MatchA which clocks a further LED (pin3 - physical 2). I set the routine to clock the LED between on/off every second interrupt. The LED pulsed at 1Hz. All working.

    6. I then burned fuses for 16MHz, using the internal 8MHz clock and PLL. Changed the Timer1 pre-scaler to 16 and set Top to 200 and MatchA to 100. This is for a DCC++ zero as per post #24 above. That in theory should give us a 5KHz signal at 50% duty. I counted 2500 interrupts between clocking the LED between on and off. The result was a LED pulsing at 1Hz. All working as it should.

    I messed a bit using MatchB which outputs on pin4 (physical 3) to free pin1 to try and use serial comms. The extension I have for the ATTiny claims it uses pins 0 and 1. Enabling serial comms immediately interfered with the frequency on Timer1. It appears that most soft serial drivers use that timer. That makes it a non-option. What I did, however, find is that my ATtinys seem to still have the bug when using MatchB (at least the DIP ones do). I could not find anything on the web as to whether that was ever fixed. Fortunately there is an easy workaround and it worked fine.

    I did not get to trying I2C communications since I do not have any I2C chips other than a 4 digit 7-segment display. I did not feel like implementing that for a test at this stage. I will pick up some 23017 and 23008 chips tomorrow morning and then try that. I do have some ISP chips which I will also try.

    Sorry for the long post. Thought it might save you some trouble.

    Willem.
     
    Last edited: Apr 12, 2018
  7. Travis Farmer

    Travis Farmer TrainBoard Member

    352
    320
    14
    i planned on the DCC++ ATtiny85 being I2C slaves actualy. that way they can be controlled by most any choice of MCU, or even a Rpi. this leaves more headroom for the master MCU to handle I/O, and other tasks, without the risk of blocking code disrupting the DCC++ signal.

    good to know the tests worked. at least now i know that i am not writing code for a doomed project. ;)

    ~Travis
     
    Scott Eric Catalano likes this.
  8. WillemT

    WillemT TrainBoard Member

    55
    40
    7
    Yes. I realise that. However, it is much easier to initially check I2C working as a master. To test as slave I need to get a master going. I do not want to switch the UNO between programmer and master, too much work:D (only have one available). The biggest problem for me is checking if things are working - I do not have a scope ... yet. I need to switch LEDs to check things working.

    In any case, I got and connected a MCP23008. Set it up for 4 Input and 4 Output. Connected LEDs to GP4 and 6. Added a small toggle routine which switch pin status on the 23008 and called it from the blink loop as well as the Interrupt routine. All works.

    Hint: If you have problems getting the I2C working make sure you have pull-up resistors on the SCL SDA lines:barefoot:.

    Next I will test it as a slave. I will add a second ATtiny85 and run that as a slave driving it from with this one.

    How are you doing?

    Willem.
     
    Travis Farmer likes this.
  9. Travis Farmer

    Travis Farmer TrainBoard Member

    352
    320
    14
    amidst a variety of weekend "ToDo list" items, i regularly think longingly of this project. as i suspect i will be getting rain later this afternoon, i hope to sneak away to my "Man Cave", and solder up a prototype. I also plan to integrate in-circuit programming to save a little effort., though as the pins for programming, are also for other tasks, i will have to think of that when i work with it.

    what i plan on doing for testing, is to program an UNO to send some basic "messages" via I2C, in a debugging sequence, and i will monitor the output. i may even unpack my circular test track, and try the prototype live to visualize the results. as the debug controller will be programmed very simply, i should also consider supplying the code if it works, so others can debug the generator. key phrase, "if it works". ;)

    progress has been slow due to after-work exhaustion, but i hope to really get something done this weekend. providing the "ToDo list" doesn't spontaneously manifest new items. ;)

    i am aware. ;) i plan on using 4.7K for each.

    ~Travis
     
    Scott Eric Catalano likes this.
  10. WillemT

    WillemT TrainBoard Member

    55
    40
    7
    That is what I used (eventually).

    I also connected the UNO's SPI to the ATtiny's SPI as well as the I2C to the 23008 simultaneously. Both worked just fine. So I can upload a sketch and it responds immediately - no need to connect/disconnect.

    When I had the LEDs connected they also responded without disconnection the SPI from the programmer. Once the Wire functions are enabled the pins are reconfigured and are no longer available as digital I/O. Uploading a new sketch however resets the ATtiny so that SPI takes over again. Quite neat actually.

    Willem.
     
    Scott Eric Catalano likes this.
  11. Travis Farmer

    Travis Farmer TrainBoard Member

    352
    320
    14
    ok, not sure what i am doing wrong... allow me to state what i have done.
    after double checking the schematic and my "spaghetti wire, with bubble-gum solder" prototype, i connected my logic analyzer to the DCC++ pins (logic level, not the HV AC output), and proceeded to program the ATtiny85. i could see the programming data fly by on the DCC++ output pins, and the upload stated successful (via my USBasp programmer). after removing the programmer, and applying power, i watched the analyzer output, and the only thing that happened was the inverter output of the DCC++ signal was opposite value of the non-inverted output, as it should, but no signal.

    i suspect two possible causes. i forgot the pull-up on the reset, and possibly the 16Mhz internal clock was not activated.
    I seem to remember something about an internal reset pull-up, but that may be just the Arduino boards... i will check the datasheet.

    so, clearly an ATtiny85 isn't as easy to work with as an Arduino, for me anyway. :oops:
    perhaps a cup of coffee is in order.

    ~Travis
     
    Scott Eric Catalano likes this.
  12. Travis Farmer

    Travis Farmer TrainBoard Member

    352
    320
    14
    After re-reading the above, it occurred to me i never made those changes i don't think. so i changed:
    Code:
    #define DCC_ZERO_BIT_TOTAL_DURATION_TIMER1 3199
    #define DCC_ZERO_BIT_PULSE_DURATION_TIMER1 1599
    
    #define DCC_ONE_BIT_TOTAL_DURATION_TIMER1 1855
    #define DCC_ONE_BIT_PULSE_DURATION_TIMER1 927
    to now be:
    Code:
    #define DCC_ZERO_BIT_TOTAL_DURATION_TIMER1 200
    #define DCC_ZERO_BIT_PULSE_DURATION_TIMER1 100
    
    #define DCC_ONE_BIT_TOTAL_DURATION_TIMER1 116
    #define DCC_ONE_BIT_PULSE_DURATION_TIMER1 58
    at least that is how i interpreted it. in any case, i get square-wave output now on the DCC++ and DCC++ inverted pins, as seen from my logic analyzer output. (attached)

    I haven't sent any I2C data to it yet, but at least i have made progress since yesterday. it seems to be outputting a DCC idle packet anyway. i still have to code up the debugging controller (UNO/MEGA2560), then i will be able to function test the generator.
    I will update my GitHub copy of the code, as soon as i finish this post.
    EDIT: Updated -> https://github.com/travisfarmer/tinyDCCpp

    ~Travis
     

    Attached Files:

    Last edited: Apr 15, 2018
    Scott Eric Catalano likes this.
  13. WillemT

    WillemT TrainBoard Member

    55
    40
    7
    I can see what happened.

    Loading values greater than 255 into the 8-bit registers get their high bytes truncated. So, loading a zero using the values 3199:1599 end up as 127:63 when the high byte is truncated. This sets a period of 127us flipped at 63us (50%) and should have just given you a higher frequency. However loading a one with values 1855:927 ends up as 63:159. This is a problem since the Match is now later than the Top and, if I interpret the docs correctly, that will simple lock the output high. Having set the correct values will get you going.

    On my side I experimented with I2C comms. Normal Master seems to work fine to the 23008. I next added the second ATtiny and set it up as a slave. After a lot of finger trouble I got the Master to write to the Slave and the Slave accepting it. The read on the Slave, however, was a problem - I just did not seem to get the correct values. Not being able to see what you get and just using flashing LEDs for checking makes debugging hard.

    To try and see what is happening I decided to connect a 16x2 LCD panel to the Slave. That turned out to be a no-go, the LCD library does not work when Wire is initialised as a Slave. I then moved the LCD to the Master and checked what I am sending, that showed correct. The only way I could think of checking what is received by the Slave was to try and return the value to the Master. I added code to request a value from the Slave by the Master and then tried to get the Slave to return it. After quite a lot of frustration trying to get that going I made some changes as to how variables are handled by the Slave, and BANG - everything works.

    So I can send from the Master, receive by the Slave and return values from the Slave to the Master - two way comms all working. I can now proceed to the next stage.

    Let me explain my hang-up with the comms. I kind of accepted basic DC++ train control can be managed on the ATtiny after your initial work. That is pretty useless if we cannot control the Base Station. Also using more powerful, and expensive, MCUs to try and control the ATtiny is kind of counter productive - the thread title says is all. My idea, and hence joining in, is to get an inexpensive minimal DCC++ setup going with, say, maybe only two ATtinys.

    At this stage it seems we can use a Master ATtiny to read switches and a potentiometer on the ADC input, not enough pins to use an encoder, to send control instructions to the Slave ATtiny acting as a Base Station. We can read back from the Slave and probably display status and also maybe read CV's etc. This could be a small, relatively inexpensive selfcontained DCC++ controller. That is what I will actually build for simple connection and testing. When going further to Wi-Fi and NMRA control I feel we rather go to larger MCU's or move to the newer designs like the great work done using the ESP32 by Atani and others.

    I will now start looking at controlling the DCC++ part.

    Willem.
     
    Scott Eric Catalano likes this.
  14. WillemT

    WillemT TrainBoard Member

    55
    40
    7
    Kind of quiet here so I thought I will report on my progress.

    1. I gave up on the idea of using control streaming as per the original. The easiest routines to unpack the string, like scanf, have huge memory footprints. I also looked at the above approach, still very much streaming, and decided to go for a more package type approach. I created one struct for sending and one for receiving. I started with 4 byte and one int variables for sending - six bytes in total. However, something like a CV write to main needs two ints. So I changed two of the bytes to ints making it 8 bytes. Since throttle is the only instruction requiring all 5 variables I rearranged it and dropped one of the ints, ending with 4 variables - 6 bytes per message. Receiving has two bytes and one int - 4 bytes.

    2. After trying @Trusty's excellent library, and having trouble compiling it (too impatient me), I just stripped the original to the minimum required (for me). I added wire "onReceive" and "onRequest" routines which seem to work well. I initially used a simulator type Base Station stand-in to simplify getting the comms working.

    3. I used a 23008 to initially get I2C working providing 4 inputs and 4 outputs. I later swapped it for a 23017 since I needed another 3 ins and 4 outs for the keypad. I will keep the "Track Power" and "Loco Select" on buttons not knowing how robust and durable these membrane keypads are.

    4. After a lot of finger trouble, I really need to be more careful when programming, I got the following going: Track power On/Off, Throttle, Function 1 to 8, CV writing on the Main, Reading CV on program (even though it is the same track - more later) and Diagnostic (2000x slow down).

    5. I can get a rotary encoder working nicely using two pins but it stops when I enable wire or LCD. So i decided to stay with a potentiometer. I use it with centre 0, including a small dead band to hit 0 easier, and then +-125 (losing two due to the dead band). I am currently using a rotary trimmer on the breadboard but it is rather jittery - definitely need a quality pot here.

    Problem with using a pot is when changing locos. The pot is invariable not going to be at the new loco's setting causing speed jumps. I came up with some solution. When changing loco I "unlock" the pot from the loco throttle. I then use the difference to analogWrite to a PWM LED. As you turn the pot the LED dims until close to the required value when the pot is "locked" to the new loco. Using different LEDs for above or below makes it easy to know which direction to turn the pot to match. Will see how well that works in practice.

    6. I added my very generic cheapo dual motor board and a 9V battery to test output. Diagnostic showed the expected pulsing on the output LEDs. The problem with these motor boards is they have no current sense. I lifted pins 1 and 15, wired them together and added a 0.15Ohm resistor to ground (this setup will only use one side at a time). Build a small 11x op-amp breadboard friendly, breakout board (I do not have any THD op-amps but plenty SMD). Current sense now works.

    7. I did not want to connect my only DCC ready (not converted yet - I do have the 6-pin decoder) in case there are problems. I managed to get an older, discontinued decoder, Digitrax DZ125PS relatively cheap. The plug at the end of the cables seem to be the problem - it is too large to allow the shell to go down far enough when mounted in the intended locos. The plug is a 4x2 pin 0.1" spaced solid unit. I cut it along the middle, length wise, and now have a decoder with two 4x1, breadboard friendly plugs. Added it to the setup, which is getting rather dense with jumpers, and could turn on the headlights. Direction also changed with throttle direction. I added a 12V tail light bulb to test current but the 9V battery could not handle it.

    8. Added a 12V 25W power supple and all seems to be OK. I can even read CV's. Programming Cvs also work. Got brave and connected the motor outputs from the decoder to a length of flex track, placed my still DC loco on it and... it works. Tried some of my older (1970s vintage) and even they work. Need to take out the caps and inductors on those before upgrading to DCC.

    9. The code now needs cleaning up, I need a bit more memory (sitting at 8060KB) to sort ops and main selection, and get the display to work in a sensible way - it is messy at present. Then make a PC board for a self contained use-able unit.

    10. Attached is a picture, hope I got that right, of the current set-up.

    Sorry for the long post.
     

    Attached Files:

    Last edited: May 2, 2018
    Scott Eric Catalano, 4-4-0 and Atani like this.
  15. John W Zerbe

    John W Zerbe TrainBoard Member

    96
    59
    7
    Thank you for this! I was just planning out how I was going to go buy another arduino + motor shield, strip much of the dcc++ generation code out so that it would only respond to throttle control for a specific address to drive my dc track via pwm for older steam that I don't want to convert to dcc.
    The much simpler approach is to simply wire a mobile decoder's motor output wires to the track, hard wire the inputs to the "main" outputs on the dcc++ and then just use a dpdt switch to change one of the isolated tracks back and forth dc/dcc. I run N scale so I'm thinking pretty much any generic HO or N decoder would do it. Could even use the lighting outputs for something else on the layout.
     
    Scott Eric Catalano and WillemT like this.
  16. WillemT

    WillemT TrainBoard Member

    55
    40
    7
    Funny you would say this.

    I was thinking along the same lines. I also run N and already have this extra decoder. I have some very vintage Fleishmann and MiniTrix locos. They are real small with virtually no space to fit any decoders - definitely not this one I have been testing with. Connecting the decoder motor output to the tracks could be a neat way to run them - and it works.

    This discontinued Digitrax DZ125PS decoder, the replacement seems to be the DZ126.., only has motor, front and reverse lights. It is specked at max 1Amp/1.25Amp peak for the motor and 0.5 for the Functions. I forgot to check the current drawn when I ran my older locos. Will check at some stage and let you know. The lights can be split so that each is controlled by a different Function, so yes, those can be used for something else.

    Willem.
     
    Last edited: May 3, 2018
    Scott Eric Catalano likes this.
  17. John W Zerbe

    John W Zerbe TrainBoard Member

    96
    59
    7
    I've put a couple DZ126 decoders in some ConCor E8s. I may get another of those from my local hobby store for this.
    btw I started out with a 12v power supply and wasn't happy with it for top speed on some of the locos. I ended up getting a "RS-75-15 Mean Well Power Supply 15V 5A" from ebay and have been very happy with the results.
     
    Scott Eric Catalano and WillemT like this.
  18. WillemT

    WillemT TrainBoard Member

    55
    40
    7
    Sounds like good advice. It confirms what I have read in quite a few places, recommending 15V for N-gauge. I used the 12V unit because it was sitting on the desk right next to me - it is in fact a RS-25-12 Mean Well. I do have a RS-25-15 as well. It may be slightly light at 1.7Amp but will do for now. I will probably get the RS-35-15 which is probably the max that the L298 motor chips can handle.

    Mean Well seems to have an enormous range of power supplies at any imaginable power output. I mostly cannot purchase from ebay due to shipping, import, handling and a million other charges making it too expensive for me. There is a local component supplier with reasonable pricing carrying most of their units, so I just get it from them - 45 minute drive there and back.

    Willem.
     
    Scott Eric Catalano likes this.
  19. John W Zerbe

    John W Zerbe TrainBoard Member

    96
    59
    7
    The only reason I went with the 5amp version is "future proofing". ie if I grow the layout to be larger than my current plans, I can upgrade my h-bridge board to drive it. I would think that your 1.7amp version will do well for quite a while though.
     
    Scott Eric Catalano likes this.
  20. patrick_martin

    patrick_martin TrainBoard Member

    23
    27
    7
    So I have been quietly following your progress but I'll be working with the attiny841 with dual UARTs so Nextion can still interface.
     
    Scott Eric Catalano likes this.

Share This Page