Z-Scale model train controller, part II

Welcome to part 2 of this series! In our last article, we described an electronic circuit that is able to drive a small motor and provides an easy way to measure the freewheeling voltage generated by the motor, which gives us an indication of its speed. Now it is time to move on to a microcontroller firmware that will drive this circuit.

This article will not focus on how the Arduino receives commands from the serial port, as this will be covered by a future article, for now let’s just assume we know at what speed we want to run our motor.

PWM Motor driving

If we want to drive our motor using two PWM signals, a best practice is to define constants at the beginning of the arduino sketch, so that we can reallocate pins at a later stage if we want to, as well as make the code more readable:


//////////////////////////////////////////////////////////////////
// Hardware pinout info
//////////////////////////////////////////////////////////////////

// How the L293D H Bridge is connected to the Arduino
const int fwdpwm = 9; // Used for PWM, L293D EN1
const int bckpwm = 10; // Used for PWM, L293D EN2
const int pin1a = 2; // Pin 1A
const int pin4a = 3; // Pin 4A
const int bemfpin = 0; // Analog0 Pin connected to BEMF measurement point

We can then setup our AVR’s Timer 1 with the PWM frequency we want – 60 hertz in our case. Using the Timer1 library on Arduino enables us to do this very easily:

void setup() {

pinMode(pin1a,OUTPUT);
pinMode(pin4a,OUTPUT);

Timer1.initialize(1e6/PWMFREQUENCY);
// enable timer compare interrupt
TIMSK1 |= (1 << OCIE1A);
TIMSK1 |= (1 << OCIE1B);
&#91;/code&#93;

In the code above, we first make sure our motor is off, then we use the "initialize" call on Timer1 to setup the PWM frequency. Last, we alto setup Timer1 interrupts, which we'll use as triggers for BEMF measurement.

With this, we can now easily switch train direction back and forth. The train will be going forward like this (pwm_rate being the current train speed):

&#91;code lang="cpp"&#93;

// Setup L293D as follows:
 // - Y1: High/HiZ dependingon EN1 (PWM)
 // - Y4: Low (gnd)
 digitalWrite(pin1a,HIGH);
 digitalWrite(pin4a,LOW);
 Timer1.disablePwm(bckpwm);
 digitalWrite(bckpwm,HIGH);
 Timer1.pwm(fwdpwm,pwm_rate);
 return;

&#91;/code&#93;

and it will go backwards with this:

&#91;code lang="cpp"&#93;

// Setup L293D as follows:
 // - Y4: High/HiZ dependingon EN2 (PWM)
 // - Y1: Low (gnd)
 digitalWrite(pin1a,LOW);
 digitalWrite(pin4a,HIGH);
 Timer1.disablePwm(fwdpwm);
 digitalWrite(fwdpwm, HIGH);
 Timer1.pwm(bckpwm,pwm_rate);
 return;

&#91;/code&#93;

<h2>Measuring Back-EMF</h2>

As mentioned above, we want to measure back-EMF while the PWM signal is low. It is easy to do using Timer 1 interrupts:



ISR(TIMER1_COMPA_vect){
 if (current_direction != FWD)
 return;
 if (!bitRead(PINB,1))
 { // Only trigger when output goes low
 delayMicroseconds(1300); // Wait for the filter curve to be past us.
#ifdef DEBUG
 setpin(PORTD,7); // Just a debug signal for my scope to check
// how long it takes for the loop below to complete
#endif
 // Now read our analog pin (average over several samples, it is very noisy):
 int bemf = analogRead(bemfpin);
 for (int i=0; i<7;i++) {
     bemf += analogRead(bemfpin);
 }
 ring_buffer&#91;idx&#93; = bemf; // No overflow to fear, analog read is 0-1023.
 idx = (idx+1)%BUFFER_SIZE;
#ifdef DEBUG
 clearpin(PORTD,7);
#endif
 }
}
&#91;/code&#93;

There might be ways to setup Timer 1 interrupts in a more efficient manner, as I still need to check whether the output signal is high of low (I trigger the interrupt whenever Timer1 passes the PWM ratio value, which happens at each transition from low to high and high to low). One issue is that the Timer1 library also plays with timer register configurations, so it is easy to break things in unexpected manners there. The implementation above works fine.

This interrupt routine simply makes 8 ADC measurements which it sums and stores into a ring buffer. This is a very primitive way of coping with measurement noise - we can call this oversampling if we want to be pedantic. We don't do the averaging in the interrupt, so that we don't waste time.

Why not waste time? Because we cannot spend too long in the interrupt. In particular, if we want to use the Arduino's hardware serial port, this serial port's UART has a (hardware) buffer of maximum two characters that can be received asynchronously. The Arduino serial subroutine therefore needs to run often enough so that no more than two characters are received between each check, and this serial subroutine is interrupt driven. On the ATMega 328, there is no sophisticated interrupt tree and while we're in our interrupt no other interrupt will run, and the hardware serial buffer will fill fast.

In short: if our serial port runs at 9600 bauds, we should aim at keeping the interrupt length under 2*(10/9600)s, or roughly 2ms. Take a new look at our scope capture: we can see on the purple signal, that this is the case:

<a href="http://www.aerodynes.fr/wp-content/uploads/2013/03/13-commented-copy.png"><img src="http://www.aerodynes.fr/wp-content/uploads/2013/03/13-commented-copy-300x157.png" alt="" title="Driving a motor with BEMF measurement" width="300" height="157" class="aligncenter size-medium wp-image-401" /></a>

We now have a regular interrupt triggered whenever the PWM signal changes, and uses the low period to do a quick measurement of the motor's freewheeling tension.

So the next question is, what do we do with it ?

<h2>Using Back-EMF to regulate speed</h2>

The key to speed regulation - and regulation of pretty much any mechanical process, whether it is speed, temperature, or any process where you can modulate the output by changing an input - is what is called a feedback loop: in our case, this is the formula that reads the train speed, and modulates the throttle of the train to reach the target speed.

The most usual way of regulating such processes is through the use of PID or Proportional-Integral-Derivative feedback control loops, which, when tuned properly, are great at doing this kind of job. Again, lots of literature has been written on PID control, and if you are interested to learn mode, you should refer to <a href="http://en.wikipedia.org/wiki/PID_controller" title="Wikipedia">Wikipedia</a> as a first source, which will then give you tons of good bibliographic material.

In our particular case, we have all we need to implement PID control of train speed: a value that is proportional to train speed (back-EMF voltage) and a throttle value in the form of PWM duty cycle. And even better: someone wrote a very good Arduino PID library already! You can find it on the <a href="http://playground.arduino.cc/Code/PIDLibrary" title="Arduino playground">Arduino Playground</a>. And icing on the cake: the author of this library wrote a <a href="http://brettbeauregard.com/blog/2011/04/improving-the-beginners-pid-introduction/" title="great series of articles">great series of articles</a> explaining in detail how this library works.

In our controller Arduino sketch, this translates into the following bits:

First of all, in the variables definition part of the sketch (even before "setup()" function), we just need to define the PID controller:


double Kp=0.40, Ki=1.45, Kd=0;
double pwm_rate, measured_rpm, target,rpm;
int sampleTime = 80;
/**
 * Arguments are myPID(Input,Output,Setpoint,kp,ki,kd,mode);
 *   Input : The variable we're trying to control -> Measured speed of the train
 *   Output: The variable that will be adjusted by the pid -> pwm_rate
 * Setpoint: The value we want to Input to maintain -> target_rpm
 */
PID myPID(&measured_rpm, &pwm_rate, &target_rpm,Kp,Ki,Kd, DIRECT);

Then in 'setup()' we initialize it:

   //turn the PID on
   myPID.SetSampleTime(sampleTime);
   myPID.SetOutputLimits(0,MAX_PWM);
   myPID.SetMode(AUTOMATIC);

And the last step, of course, is to regularly do PID calculations when in the main 'loop()':

  ///////
  // PID calculations
  ///////  
   measured_rpm = moving_avg();
   myPID.Compute(); // Most important part!
   pwm(pwm_rate);

One word of caution there: it is a good idea to measure the standard run length of your main loop and make sure the PID sample time is consistent with it. A sample time that is too low compared to the main loop will not do any good and will lead to worse results. In our case, we are at a 80ms calculation time, which gives us time to send up to 40 characters at 9600 bps in each loop in the serial line - and we'll do much less in general as you'll see.

A better way of doing those PID calculations would probably be to use interrupts in order to make sure the loop is computed at strictly regular intervals, but it is actually overkill unless for some reason you need a really really fast running PID control loop...

PID tuning

PID tuning is an art in itself: the values I came up with as default values were the result of a lot of trial and error, and standard good practices - raise P until unstable, lower a bit, raise I until nearly unstable, lower a bit, raise D carefully and not too much. Experiment with those values, see what works best for you!

This is actually a nice transition to our conclusion, because you probably don't want to have to recompile your sketch every time you want to change a single PID settings value!

Conclusion

This concludes part II of the design: we now have a basic motor controller that is able to drive a train in both directions at a constant speed. As mentioned just above, this is all nice, but so far, we have not talked about how this controller can send and receive commands and readings! You don't want to have to recompile your sketch every time you want to change a setting, in particular. But before talking about the serial interface, let's finish the hardware design, and part III will describe how we drive turnouts and solenoid accessories.

Tagged on:     

4 comments on “Z-Scale model train controller, part II

  • February 18, 2015 at 11:54
    Permalink

    Would it be possible to use PID control and back-EMF on an arduino measurement at the same time of those “electronic track cleanere” like the Gaugemaster HF-1 ?

    Reply
    • February 19, 2015 at 08:11
      Permalink

      … probably not – I removed my HF-1 when I designed this. The upside is that since the trains are driven with PWM, the pulsed power makes the locomotives run really smoothly, even better than with the HF-1, in my experience.

      Reply
  • December 2, 2016 at 20:00
    Permalink

    Thanks for such an amazing project. Hope to try it this winter.
    Some of the code snippets have lines such as ” [/code]”
    I don’t understand those lines. Perhaps as I learn more about arudino
    programming. ? Merry Christmas!

    Reply
  • December 2, 2016 at 20:04
    Permalink

    The & # 91 as well as the code & # was parsed out of my comment.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *