Moving the Kart

Now that you have given TinyKart its proverbial eyes with the LiDAR, it's time to give it its proverbial legs (this sounded better on paper lol). Specifically, we will be going over how to actually make the kart steer and accelerate. Much like the LiDAR, you won't be writing all the driver code, but it's very important to understand how it all works, since the patters appear all over embedded programming.

Hardware

Before we can program the hardware, we first need to discuss, well, the hardware! In robotics, anything that can make a piece of a robot move is called an actuator. For example, the wheels on a Roomba, or the claw on Spot.

The Traxxas Slash has two main means of actuation:

  1. Steering
  2. Accelerating the rear wheels

Steering is achived with a servo motor:

Servos are motors that are designed to hold a particular position, rather than continuing to rotate. By adding another linkage, this rotational angle can be converted to a linear position, thus creating a linear actuator that can hold a particular distance. As it turns out, that's exactly how steering works! Essentially, mechanical linkages are geometrically connected to the servo such that each far end of the servos range will cause the wheels to reach their max turning angle.

On the other hand, accelerating is something that requires a continues axis, so a servo would make no sense. In this case, the RC car simply uses a brushed DC motor:

While discussion on brushed motors is better done by the more electrically inclined, a simple way to understand these things is that when you apply a voltage across the two leads, a magnetic field in the motor forms, moving the output shaft:

Critically, the polarity of the voltage across the leads must switch, or else the motor will just stall. Thankfully, there is dedicated hardware designed to do this for us, called a motor controller or ESC (Electronic Speed Control). The ESC on the Slash looks like this:

The ESC sits between the battery and the motor, controlling the polarity and power as required to reach some level of output speed. This means that when we want to move the motor, we need to interact with the ESC.

Interface

So how to we actually control these actuators? Conveniently, they actually use the same exact interface. That is Pulse Width Modulation (PWM).

PWM shows up all over electrical things, both as an effectively analog power source, and as a digital signal, as it is used here. The idea of PWM is to represent an analog range like 0-100% using a digital signal, which can naturally only be 0 or 1. While this could be done by sending binary integers, that would be extremely inefficient. Instead, PWM works by creating set periods of time. Inside these periods, the percentage of time the signal is high versus low is the analog value itself, called the duty cycle. For a visualisation:

The issue with this approach is that because the duty cycle is time on over period, the frequency of the PWM signal must be set in stone. For historical reasons, RC cars do not like this, and use a different approch, called Pulse Period Modulation.

In this formulation, 0-100% is not the duty cycle, but rather a range of time the signal is high versus low. For example, most servos use 1.0ms high for min angle, 1.5ms high for their midpoint, and 2.0ms for their max angle. This approach means that any frequency with a period greater than 2.0ms can control the servo, allowing it to work with very cheap hardware.

As it turns out, this is also exactly how the ESC works! The only difference being that 1.0ms is full reverse, and 2.0ms is full forward power.

Setting up the hardware

Alright, now it's time for you to set up your controller to connect to these actuators. To do this, we're going to have to do a tiny bit of wiring to get stuff setup. Each of the actuators has a connector like this:

For the servo these pins mean:

  • Red: 5V in, to power the servo
  • Black: Ground
  • White: PWM control signal in

For the ESC these pins mean:

  • Red: 5V out, to power the servo
  • Black: Ground, to battery
  • White: PWM control signal in

Our goals for wiring things up is to:

  1. Connect ESC 5V out to servo 5V in
  2. Connect ESC and servo PWM to pins on the controller
  3. Connect all three to common ground

To do this:

  1. Make sure the system is powered off
  2. Jump the ESC 5V to servo 5V
  3. Jump ESC and Servo ground together using a breadboard
  4. Jump a ground pin from the controller to the same breadboard lane as the other two grounds, creating a common ground
  5. Finally, jump the ESC and Servo PWM lines to these exact pins:

Purple being A0, and green being D0. (NOTE: In real life the A0 & D0 wires will be white)

Now, connect the battery to the ESC, and power to the controller. Now, press and hold the little blue button on the ESC to turn it on. Finally, reset the controller. You should see the kart do a little jump, and maybe move its wheels. This is good! This means that the ESC is armed, and ready to move.

The code

Now that things are hooked up, you can move the kart with commands on the TinyKart class:

tinyKart->set_forward(command.throttle_percent);
tinyKart->set_steering(command.steering_angle);

Feel free to explore the other methods on the class, as they allow for movement in other ways, such as in reverse.

While this high level API is all you will be working with for the autonomy, I want to briefly show how its implemented:

        analogWrite(pwm_pin, value);
    }

    /// Sets the esc to power forward with some 0.0-1.0 percent.
    void set_forward(float power) {
        assert(power <= 1.0 && power >= 0.0);

        this->was_forward = true;

As you can see, its actually rather simple, thanks to Arduino. I want to untangle that second line for you, since there's really two things going on here.

First, the value of that line is the value we are setting the hardware PWM peripheral's register to control the duty cycle. Because PWM is digital, we can only output duty cycle percents up to some resolution in bits. Think of these as buckets, and the more buckets we have the more we can slice the 0-100% range into finer pieces to uniformly distribute among the buckets. For example, Arduino defaults to an 8 bit PWM, which means that setting the register to 255 will be 100% duty cycle, 0 will be 0%, and 255//2 will be closeish to 50%.

In the case of our ST board, we have a 12-bit PWM, which means that we have 4092 "buckets". This means to select some percent duty cycle, you use the equation: \( power * 4092 \), where power is in range [0, 1].

Second, the analogWrite call sets duty cycle, not duty width, which is what we're looking for. This means that we need to map our desired percent first from percent to width in ms, then from ms to duty cycle, then duty cycle to register value. This is exactly what that one-liner is doing. From left to right:

  1. Our forward time range is 1.5-2.0ms, so divide our power by 2 and add it to 1.5, to get our desired percent in time domain.
  2. We need this time width in duty cycle, so we now need to find the percent of time that width takes out of the complete PWM period. To do this, we just divide by the period, which is just a constant set in setup().
  3. Finally, take this 0.0-1.0 duty cycle and multiply it by the max register value (4092 here) to get the value to write to the register.
  4. Write the value to the register using analogWrite.

The servo works exactly the same, except split over left vs right rather than reverse vs forwards.

Stop and think

Now I said this is important to understand, even if you won't be writing it. Now's the time to ponder how this setup could influence the autonomy. Before you see the explanation, please think this over for a moment, to make sure you have a good grasp on things.

An answer

I would say this setup has two main consequences upstream in the autonomy stack:

  1. Latency
  2. Precision

First, because PWM signals have a set period width, this limits the rate at which we can update the steering and speed, since we must wait for the prior PWM period to end before we can change the duty cycle. While this isn't of massive significance, It's something to consider as adding, ex. 10ms of latency at 100Hz (what the reference code you have is using) is actually quite a large value on the embedded world.

Second, 12-bits of resolution is quite high, but because we are stuck with only using small period ranges, we actually have very little resolution. This means that while you can still get rather specific with your steering angles ( certainly fine for our use), small angle increments can sometimes have no effect or a large effect depending on if it causes us to step into another "bucket".

While neither of these are really something you need to worry about, its worth taking the time to think about how your hardware will constrain your autonomous routines, as this is something that is far more pronounced on a full size vehicle.

Homework

Alright, now it's your turn to make the funny RC car move. Keep building off your last code, although you will need to hack it a bit.

Your challenge today is to make the kart slow proportionally to the distance in front of it. This will be very useful in the autonomous routines later, as it will help prevent the kart from sliding around. Doing this will require you to use distance values from the LiDAR again, but with it facing forward as is proper. Specifically, the kart should be able to avoid hitting an object with much speed, and preferably stop before hitting it at all. Consider experimenting with using reverse as a brake.

Start slow and build your way up in speed and see how fast you can go! This experience will come in handy later when testing autonomous things.

Note that when the kart boots, it starts in a paused state, indicated by the yellow LED. In this state, it rejects all commands. To allow it to move, press the blue button. Press the button again to stop the kart (this is what you were doing the first day!). If you want to use this functionality in your own code (like to stop if the kart hit something):

tinykart->pause(); // Stops
tinykart->unpause(); // Starts

Some notes before you start:

  • When running the kart in motion, make sure everything is well secured. Perhaps consult your friendly ME peers to see how to do this well. In particular, if ground is disconnected the kart goes wild, so be careful.
  • The controller can be powered off of mobile battery banks, not just from a laptop. Use this to make the kart cordless. The controller will boot to its last flashed code.
  • The kart is fast! On slippery floor especially it can really get away from you, so I recommend capping the speed at around 0.3 at most. It also tends to stall around 0.15. For grippier floors like carpet, move that range up a bit.

To set the karts max speed, modify the 3rd parameter in the TinyKart constructor:

    tinyKart = new TinyKart{STEERING_PIN, esc, 0.3, 4.5};