After we have chosen the motors and motor drivers, we must connect them to a microcontroller and control their rotation. How do we do that?
The Microcontroller
In our particular case we used a microcontroller chip mounted on a custom-printed circuit board together with the motor drivers, which we ended up not using anyway. Thus, our life would have actually been easier if, instead of using that custom board, we would just buy some well-known pre-made microcontroller board. The popular choices included Arduino, Teensy, Baby Orangutan, Basic STAMP, ARM mbed, TI LaunchPad, ST Discovery, and anything else you might google up using the keyword “microcontroller development board“. Hence, for simplicity, I shall avoid details specific to our case and assume the set-up with such a separate board.
For those of you not familiar with microcontrollers, it is enough to understand that a microcontroller is a just a black-box chip with several pins (those are the metal “legs” of the chip).The microcontroller can be programmed to set output voltage on some of the pins to certain values (usually either 0V, corresponding to “bit zero” or 5V, corresponding to “bit one”). It is also possible to sense input voltage on some other pins. Those two basic operations allow the microcontroller to communicate with other devices connected to it.
So, we have to connect some output pins of the microcontroller to the motor driver. Then, by setting appropriate output voltages on those pins, we shall be telling the motor driver to operate the motor as necessary. How exactly should the motor driver chip be connected and operated is specific to each chip, but in principle the assembly for one motor might look as follows:
Controlling the Motor
In the example above (which corresponds to the driver we used), two pins (A and B) are used to tell the driver in which direction to rotate the motor. The whole code for specifying rotation direction is thus as simple as:
if (rotate_forward) { set_pin(PIN_A, 1); set_pin(PIN_B, 0); } else { set_pin(PIN_A, 0); set_pin(PIN_B, 1); }
Specifying rotation speed is just a bit trickier. For that we must produce a pulse width modulation (PWM) signal on pin X. This simply means quickly switching pin’s values between 0 and 1, so that on average the value “1” is kept a given fraction of time. A “full PWM” would mean keeping pin X at “1” all the time – in this case the motors would rotate with their maximum speed. A “null-PWM” means keeping “0” all the time. This corresponds to motors not rotating. All values inbetween are also possible. If pin X is at value 1 one third of a time, the motors will rotate with approximately a third of their maximum speed.
Most microcontrollers have built-in mechanisms for producing such a signal on some of the pins. We won’t delve into details of doing that – there are lots of tutorials out there already. For all practical purposes, the actual code for setting the motor speed boils down to
set_pwm_pin(PIN_X, speed); // speed = 0..255
Feedback
Are we done? No. Unfortunately, simply setting the motor rotation speed to a fixed number is not enough for precise control. For example, if we set the PWM speed inputs for both robot motors to the same value, despite all expectations, the robot will most probably not drive perfectly straight. Thus, to ensure exact control, we must constantly measure the actual rotation speed of the motor and adjust the PWM to keep the speed at a given value.
In order to measure motor rotation speed, our motor is equipped with a rotary encoder: a sensor, which generates pulses in accordance with the rotation. The faster the rotation – the more pulses are generated per second. To obtain this feedback information we first connect the motor encoder to an input pin of a microcontroller:
Next, we write a simple function that simply counts all pulses that come in on pin E:
ISR(INT0_vect) { pulse_count++; }
Finally, we set up a timer interrupt that will regularly examine the current pulse_count
value, compare it with some target value, and adaptively update the PWM signal on Pin X so that the target rotation speed is achieved:
ISR(TIMER0_COMPA_vect) { current_pulse_count = pulse_count; pulse_count = 0; // Reset pulse counter // Update pulse counter update_motor_speed(target_pulse_count, current_pulse_count); }
PID controller
So how do we use the feedback and choose the appropriate PWM value to set for the motor speed in order to keep up with the target pulse count? This simple question has a whole discipline dedicated to answer it, known as control theory. The easiest answer that control theory provides us here is the PID controller.
The idea of a PID controller is generic and simple: we first measure the discrepancy between the target pulse count and the actual measured pulse count (the error):
error = (target_pulse_count - current_pulse_count);
In addition, we keep track of the error, accumulated over multiple iterations of the algorithm, and the difference between the error of the previous iteration and this iteration:
error_i = error_i + error; error_d = error - prev_error;
The actual input to the motor driver is now computed as a linear combination of these three error values with some experimentally determined coefficients P
, I
and D
:
new_pwm = P*error + I*error_i + D*error_d;
If the parameters are chosen well, the PID controller will magically keep the motor turning at the necessary speed.
So, to summarize the above, the update_motor_speed
function may look approximately as follows:
void update_motor_speed(int target, int current) { error = (target - current); error_i = error_i + error; // Prevent the integral term from growing too large if (error_i > max_error_i) error_i = max_error_i; if (error_i < min_error_i) error_i = min_error_i; error_d = error - prev_error; prev_error = error; new_pwm = P*error + I*error_i + D*error_d; set_pwm_pin(PIN_X, new_pwm); }
Our actual function was somewhat more complicated and included a couple of specific hacks and improvements, but the gist is still there.
Summary
To summarize, here is what you need to do to properly operate a motor:
- Connect the microcontroller’s output pins to a motor driver, and the motor driver to a motor.
- Use pins “A” and “B” (or whatever the motor driver specification requires) to set motor rotation direction.
- Connect the motor’s rotary encoder sensor to an input pin “E” of a microcontroller.
- Make sure the microcontroller is counting the pulses on pin “E”.
- Set up a timer, that reads the counted pulses and updates the PWM parameters on pin “X” to match the “
target_pulse_count
“.
The same logic applies for the second motor.
But where do the motor direction and values of target_pulse_count
come from? Those are specified by the higher logic, running in the phone, of course. We’ll get to that eventually.