Mixing Code with Hobbies Part 3

August 12, 2019

This should be the last part for mixing my development work with my model train hobby. Picking up from the last part, I showed how I put together the hardware to run the motor. Now I'll go over the code necessary to make the two Micro:bit's work together to control the engine.

All the Code

The throttle:

let speed = 0;
let regulator = [-1023, -767, -511, -380, 0, 380, 511, 767, 1023];
let regulatorStep = 5; //5 is off.
radio.setGroup(1);
basic.forever(function () {
    input.onButtonPressed(Button.A, function () {
        speed = throttle('down');
        radio.sendValue('down', speed);
        printSpeed('down', speed, regulatorStep);
    })
    input.onButtonPressed(Button.B, function () {
        speed = throttle('up');
        radio.sendValue('up', speed);
        printSpeed('up', speed, regulatorStep);
    })
    input.onButtonPressed(Button.AB, function () {
        speed = throttle('stop');
        radio.sendValue('stop', speed);
        printSpeed('stop', speed, regulatorStep);
    })
});

function printSpeed(dir: string, n: number, r: number) {
    function upArrow() {
        basic.showArrow(0);
    }
    function downArrow() {
        basic.showArrow(4);
    }
    function stopDisplay() {
        basic.showLeds(`
            # . . . #
            . # . # .
            . . # . .
            . # . # .
            # . . . #
            `)
    }

    switch (dir) {
        case 'up':
            basic.clearScreen()
            upArrow()
            break;
        case 'down':
            basic.clearScreen()
            downArrow()
            break;
        case 'stop':
            basic.clearScreen()
            stopDisplay()
            break;
        default: ;
            basic.clearScreen()
            stopDisplay()
            break;
    }
}

function moveRegulator(b: boolean) {
    b ? regulatorStep++ : regulatorStep--;
}

function limiter(n: number) {
    if (regulatorStep > regulator.length() - 1) {
        regulatorStep = regulator.length() - 1;
        return regulator[regulatorStep];
    } else if (regulatorStep < 0) {
        regulatorStep = 0;
        return regulator[0];
    } else {
        return n;
    }
}

function throttle(dir: string) {
    switch (dir) {
        case 'up':
            moveRegulator(true);
            speed = regulator[regulatorStep];
            break;
        case 'down':
            moveRegulator(false);
            speed = regulator[regulatorStep];
            break;
        case 'stop':
            speed = regulator[4];
            regulatorStep = 4;
            break;
        default:
            speed = 0;
            break;
    }
    return speed = limiter(speed);
}

The driver:

let speed = 0
radio.setGroup(1)
function setMotorPins(m1: number, m2: number) {
    pins.analogWritePin(AnalogPin.P13, m1);
    pins.analogWritePin(AnalogPin.P14, m2);
}

function inverted(n: number) {
    return n * -1;
}

basic.forever(function () {
    radio.onReceivedValue(function (name: string, n: number) {
        speed = n;
        if (speed === 0) {
            setMotorPins(0, 0);
        } else if (speed < 0) {
            setMotorPins(inverted(speed), 0);
        } else {
            setMotorPins(0, speed);
        }
    });
    basic.showNumber(speed)
})

The Remote Control Micro:bit in Detail

To keep this build dead simple, I wanted to just use the two buttons on the Micro:bit be my input. This isn't to say I can't add a pot to have fine control over the pwm, I just didn't wanted to keep things to a bare minimum for this initial build.

let speed = 0;
let regulator = [-1023, -767, -511, -380, 0, 380, 511, 767, 1023];
let regulatorStep = 5; //5 is off.
radio.setGroup(1);

This is just setting up a few globally available values off the bat. I start the speed at 0. The regulator is an array that I store the pwm values that will be sent depending on button presses. the number regulatorStep is the middle off value of the regulator array. I found that 1023 was the bit's self imposed limit. The first notch of the regulator (380) was more of an experiment to find the engine's motor would have enough power to turn. Initially it had been lower, but all I'd get was a hum.

Finally I set what radio group/frequency with the built in radio.setGroup() method that the bit will be set at. If I'm ever around another bit with this, there will be interference, but for now, it is set to 1.

basic.forever(function () {
    input.onButtonPressed(Button.A, function () {
        speed = throttle('down');
        radio.sendValue('down', speed);
        printSpeed('down', speed, regulatorStep);
    })
    input.onButtonPressed(Button.B, function () {
        speed = throttle('up');
        radio.sendValue('up', speed);
        printSpeed('up', speed, regulatorStep);
    })
    input.onButtonPressed(Button.AB, function () {
        speed = throttle('stop');
        radio.sendValue('stop', speed);
        printSpeed('stop', speed, regulatorStep);
    })
});

The forever method call should tell you everything you need to know about how long the method is going to run. In the same vein as the loop() method from Processing, it's going to keep running as long as there's power.

  1. Pressing the left button (A), put the throttle into reverse one step, or slow the engine in forward 1 step.
  2. Pressing the right button (B), put the throttle into forward one step or slow the engine in reverse 1 step.
  3. Pressing both buttons puts the throttle to 0, stopping the engine.

Each press sets speed, broadcasts the speed as a kind of key/value pair object. So the recieving bit can be choosey about what event keys it needs to even pay attention for.

Finally, I call the printSpeed method so the bit can show me what direction the train should be moving.

function printSpeed(dir: string, n: number, r: number) {
    function upArrow() {
        basic.showArrow(0);
    }
    function downArrow() {
        basic.showArrow(4);
    }
    function stopDisplay() {
        basic.showLeds(`
            # . . . #
            . # . # .
            . . # . .
            . # . # .
            # . . . #
            `)
    }

    switch (dir) {
        case 'up':
            basic.clearScreen()
            upArrow()
            break;
        case 'down':
            basic.clearScreen()
            downArrow()
            break;
        case 'stop':
            basic.clearScreen()
            stopDisplay()
            break;
        default: ;
            basic.clearScreen()
            stopDisplay()
            break;
    }
}

The printScreen method checks the direction as a string then calls internal methods to use the Micro:bit's leds. Feels like a big block of stuff to ultimate make a left/right or an X. Again, there are already built-in methods for some of the icons I wanted to display through basic's showArrow and showLeds.

function moveRegulator(b: boolean) {
    b ? regulatorStep++ : regulatorStep--;
}

function limiter(n: number) {
    if (regulatorStep > regulator.length() - 1) {
        regulatorStep = regulator.length() - 1;
        return regulator[regulatorStep];
    } else if (regulatorStep < 0) {
        regulatorStep = 0;
        return regulator[0];
    } else {
        return n;
    }
}

The moveRegulator is just moving regulatorStep up or down based on a boolean. This works along with the limiter method which just checks to see if I tried to go too far with the regulator index. If it is -or attempts to- set it to either the last item or the 0 index. Otherwise return the number as valid.

function throttle(dir: string) {
    switch (dir) {
        case 'up':
            moveRegulator(true);
            speed = regulator[regulatorStep];
            break;
        case 'down':
            moveRegulator(false);
            speed = regulator[regulatorStep];
            break;
        case 'stop':
            speed = regulator[4];
            regulatorStep = 4;
            break;
        default:
            speed = 0;
            break;
    }
    return speed = limiter(speed);
}

The throttle method takes a string from the button presses in the direction (or stop). The cases move the regulator, then checks if it's still in the range of the regulator array and returns the current speed/pwm value.

The Motor Driver/Reciever in Detail

The reciever Micro:bit is a lot simpler compared to the controller. There's a lot less setup and a lot fewer methods necessary to run the engine.

let speed = 0
radio.setGroup(1)

In this case, setup is as easy as setting a default speed and again setting the radio group to 1.

function setMotorPins(m1: number, m2: number) {
    pins.analogWritePin(AnalogPin.P13, m1);
    pins.analogWritePin(AnalogPin.P14, m2);
}

function inverted(n: number) {
    return n * -1;
}

There are two methods that get a lot of use in the forever() method. These set pins 13 and 14 to the correct value depending on direction. If 13 has a value, 14 will have 0, and vis versa to change the motor's direction.

The inverted() method exists because when the speed value (from the regulator array) arrives over the airwaves, it's possible it's a negative number. This is great in that it tells me what direction the train should be going, but doesn't actually work to send a negative number to the output pins. This takes care of that.

basic.forever(function () {
    radio.onReceivedValue(function (name: string, n: number) {
        speed = n;
        if (speed === 0) {
            setMotorPins(0, 0);
        } else if (speed < 0) {
            setMotorPins(inverted(speed), 0);
        } else {
            setMotorPins(0, speed);
        }
    });
    basic.showNumber(speed)
})

In the forever method I have the bit watch through it's radio for values received on it's group (was seen in the key/value pair broadcast on the controller bit). If the speed is less than zero: invert it and set the motor pins. Otherwise set the motor pins in the opposite way. Unless of course it's 0. In which case the pins get set to 0 and 0. I also show the speed on the driver Micro:bit, but I've found I can't really see stuffed into the engine. But it helps to see that it's recieving.

So that's it. All that's left is to take the code and turn it into a hex file from the microbit code site. You can then take the hex file and -with the Micro:bit plugged into your computer- drag the file onto the bit.

Conclusions

With the power on, the hardware shoved into the engine, I can start pressing buttons and the engine will trundle back and forth.

It was exceedingly enjoyable to see not only the code work, but the hardware as well. This was a small project and it did help envigorate both my hobby and my coding work. I know that not every side-project gets done, but they don't always need to. Just that something should be learned. In this case, this one was completed and it was fun.