PIC/MOSFET PWM Model Train Controller

Having been unable to resist buying some old Hornby OO Gauge bits from the second hand cabinet in a model shop, justification came from the educational value it would offer my son if I could make a speed controller, perhaps adding a sensor or two – the essence of industrial control and feedback mechanisms. Being three and a half, he just wanted to make the train fly off the track, but at least he enjoyed it.

This is a project to create a model train speed controller using the Pulse Width Modulation (PWM) output of a PIC16F690 microcontroller, to drive a MOSFET that ultimately controls the voltage on the tracks. The train will automatically switch into reverse when the control is turned anti-clockwise through the zero point.

The input to control the voltage is a potentiometer to the PIC’s Analogue to Digital Converter (ADC), and two LEDs (forward and backward) give feedback on the direction of travel. The polarity is switched using a double-pole double-throw (DPDT) relay.

The software on the PIC reads the ADC value and converts this to either a positive or negative voltage on the rails, depending on the position of the pot, with the mid-point giving zero volts (train stopped). A dead-zone is included around the mid-point to make it easier to stop the train (i.e. without it immediately heading off in the opposite direction).

The Schematic

The following schematic was drawn in Cadsoft Eagle, and while I routed a board, I’ve decided not to make it into a PCB yet, because I still want to add sensors and perhaps an LCD display with buttons to allow programming of sequences. So it’s still on the breadboard for now.

Model train controller using a PIC for PWM motor control.

The MOSFET is an IRF540A (Maplin code N10AH). The relay is an RSB-5-S (Maplin code N18AW). Equivalent parts could easily be chosen, but bear in mind that the Rds of the MOSFET must be low, and the Vgs should be logic-level (the lower the better, the IRF540A is 4V maximum, as I recall).

I added a heat-sink to the 7805 voltage regulator, since the input voltage is relatively high. I don’t think that the power requirement on the 5V rail is ever likely to take the device to dangerous temperatures, but I was concerned for curious little fingers poking around, and so erred on the side of caution.

The Code

The C code below compiles on the Hi-Tech C compiler, but it’s simple enough that it should easily convert to other variants.

The PWM is running at around 100Hz, and getting this low requires running the PIC’s internal oscillator no higher than 1MHz. Pulsing at low frequencies reduces motor noise and helps keep the MOSFET cool.

The DEADBAND and DIVISOR defines are used to implement the dead-zone and also to limit the output voltage (i.e. limit the train speed) depending on your power-supply (mine is 15V, and I don’t want to run the train at such a high voltage).

The dead-band has two states – completely off, and direction LED active – this means that an LED will come on in the dead-band prior to crossing into a voltage state.

The code for the dead-band is about to be cleaned up, so check back later if you want an update – it’s not as clear as it should be because it was ‘tinkered’ with while testing the intuitiveness of the controller. It should be more readable after the clean-up!

 * Train Speed Controller, PIC16F690.
 * (C) Kevin Sangeelee, released under the terms of the GNU GPL V2
 * Use at your own risk or not at all.
#include <pic.h>

#define _XTAL_FREQ    1000000

#define PS1        0
#define PS4        1
#define PS16       2

#define DEADBAND   30
#define DIVISOR    2.0f

    //& PWRTDIS
    //& BORDIS
    //& IESODIS
    //& FCMDIS    
    & WDTDIS
    //& HS

// Mainline
void main(void)
    // All ports low on initialisation
    PORTA = 0;
    PORTB = 0;
    PORTC = 0;

    TRISA = 0b00011111;        // Initialise Port A (RA0-4:In, RA5:Out)
    TRISB = 0b11111111;        // Initialise Port B (RB0-7:In)
    TRISC = 0b11010100;        // Initialise Port C (RC0-1,3,5:Out, RC2,4,6-7:In)

    OSCCON    = 0b01000000;        // Set oscillator to 1Mhz

    ANSEL = 0b01000000;    // Pot on RC2/AN6
    ANSELH = 0;
    ADCON0 = 0b00011001; // Left Justified, Ref=Vdd, Channel AN6, ADON
    ADCON1 = 0b01100000; // Fosc/32

    // Set PWM Period
    PR2 = 0b10011011; // Period Register of Timer 2

    // Set Timer 2 Prescaler and switch on
    T2CON = 4 | PS16; // 0=PS1, 1=PS4, 2 or 3=PS16

    // PWM Duty cycle (8 MSbs. Note 2 LSbs in CCP1CON)
    CCPR1L = 0b00000000;

    // 00 00 1100 (Configure CCP1 peripheral for PWM)
    CCP1CON = 0b00001100;

    int adc, pwm, abspwm;
    char forward;

    while (1)

        adc = ADRESH;
        adc <<= 1;
        adc |= ADRESL & 0x80 ? 1 : 0;
        adc <<= 1;
        adc |= ADRESL & 0x40 ? 1 : 0;

        // Re-range the result to -512..511
        pwm = adc - 0x200;

        // Redefine as a direction and magnitude
        if(pwm < 0) {
            forward = 0;
            abspwm = pwm * -1;
        } else {
            forward = 1;
            abspwm = pwm;

        // Limit the speed by limiting the PWM duty cycle.
        abspwm = (int)(abspwm / DIVISOR);

        if(abspwm > (DEADBAND * DIVISOR)) {
            RA5 = !forward;
            RC0 = forward;
            RC1 = !forward;
        } else if(abspwm <= (DEADBAND * DIVISOR) / 2) {
            abspwm = 0;
            RC0 = 0;
            RC1 = 0;
        } else {
            abspwm = 0;

        CCPR1L = abspwm >> 2; // 8 MSbs of Duty Cycle
        DC1B1 = abspwm & 0x02 ? 1 : 0;    // CCP1CON<5>
        DC1B0 = abspwm & 0x01 ? 1 : 0;    // CCP1CON<4>


The Photos

The case was just an old ABS enclosure I had been given – it has space for an LCD display so I’m duty bound to add one eventually (whether it needs one or not). The control knob is the lid from a kid’s smoothie pouch, fixed onto the pot with polymorph (low melting-point plastic).

The train controller is housed in an ABS box with a smoothie lid for a control knob.

The circuit is still on a breadboard. The power connector is on the left hand side, fixed with hot-melt glue. I used an old laptop charger that can deliver 15V and 2,5A. In practice, the train runs on a few hundred milliamps, but start-up current can approach 1A, I believe.

Hot melt glue is amazing stuff. Even more useful than BluTac.

Future Bits

I’ll post the hex for the controller firmware and some oscilloscope shots of the PWM pulses – they’re pretty smooth, no nasty spikes.

There will probably be some sensors added to detect when a train either passes a point or when it’s close to the buffers in a siding (optical break or proximity sensor).

Add I2C Slave or UART code to allow a computer such as a Raspberry Pi to override the pot and take control of the train.


The following sites were either useful while developing this circuit, or helpful to me afterwards when I wanted to check that my design wasn’t going to burn out train motors.

Review of Train Controllers – comprehensive review of existing train controllers by Jonathan Scott. The oscilloscope traces he’s published helped me decide the standard to aim for (and the problems to avoid).

Mosfets & Mosfet Drivers – a clear and well written article on MOSFET characteristics, in particular how they behave during switching transitions.

Using MOSFETs as General Switches – an article on using MOSFETs with lots of tips and explanations.

MOSFET Basics – Fairchild Semiconductor Application Notes on MOSFETs. A very detailed and comprehensive introduction, though goes way beyond our simple requirements of an N Channel MOSFET.

PIC16F690 – datasheet for the PIC microcontroller used in this project. This is quite a versatile chip with a good range of peripherals supported.

IRF540A MOSFET – datasheet for the MOSFET I chose. It switches with 4V and has a low Rds, so at the switching speeds of the controller, it doesn’t generate a lot of heat (it remains only warm to touch).

A Computer Driven Train Controller – some detailed information on driving model train motors specifically, including how characteristics of the controller circuitry affects the ideal motion of the train.

PWM DC Motor Controller – if you’re looking for general motor control, rather than model train control, then this would be a better option. In any case, it’s quite a comprehensive article and is well worth a read.

PIC PWM Calculator – a utility to calculate the register values required for a specific pulse frequency, given the microcontroller clock speed and the required frequency.


5 thoughts on “PIC/MOSFET PWM Model Train Controller

  1. Thank you for your work I am playing with it I am going to set it up to use a pic kit 2 low pin count board I tried the code but it had errors godone vs go_ done what version of hi tech compiler do u have and where in the code does it point the pwm to pin 5
    I am sorry I am new to pics and to hitech

    1. Hi, I’m using the PIC10/12/16 compiler V9.70. I did a search for GO_DONE, and it might be that the headers have changed in a more recent version. You could change the code to suit your version or #define _LEGACY_HEADERS (according to a post on the net).

      Looking at the datasheet for the PIC16F690, pin 5 has P1A. Register bits CCP1CON<7:6> define which pins are modulated in PWM mode. These bits are configured with ’00′ in the code, which means P1A is the modulated pin. (See definition of CCP1CON in chapter 11).

  2. i am thinking of trying it at a higher freq for the pwm like 3k and put a low pass filter and a cap to turn it into dc to drive the motor i am looking into it more its just what i have pictured in my head what do u think

    1. I’d be interested to know what you learn, in particular if it can remove motor ‘squealing’ with high-frequencies (though a DC motor is itself a bit like a low-pass R-C filter)

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>