Motor control is one of the basic tasks in any robotics development. Pulse-Width-Modulation (PWM) is often used to control DC motors or servos. This post describes how to drive PWM signals from BeagleBone Black to control a DC Motor. Also, we’ll see how to use encoder readings to calculate speed & rpm ranges of the motor.
A better but expensive way to control motors is to use a smart actuator like Dynamixel. I’ll write about smart actuators in a different post. Smart actuators are finding more and more ground in robotics for their ease-of-integration and clutter-free designs. Having said that, most hobbyists still start with PWM control.
Adafruit BBIO
Adafruit BBIO is an extremely useful, thus popular, Python IO library for accessing BeagleBone Black IO pins. Install it.
ePWM Modules
TI-3358 Sitara microprocessor on BeagleBone Black has 3 instances of Pulse-Width Modulation SubSystem (PWMSS). Each PWMSS in turn has an instance each of ePWM (enhanced PWM) module and eQEP (enhanced Quadrature Encoder Pulse) module.
ePWM or eHRPWM (high-resolution) modules can be programmed to drive PWM signals on BeagleBone IO pins. The PWM IO pins are usually connected to a Motor Driver chip that in turn drives DC motor(s).
Each ePWM has two outputs – EPWMxA, EPWMxB where x is the ePWM instance id on the Sitara device. Those two outputs together is a PWM Channel. However, both outputs can be used independently. The only catch is that both should operate on the same PWM frequency (at least with BBIO).
PWM IO Pins
PWM Instance | Output-A | Output-B |
---|---|---|
ehrpwm0 | P9_22 | P9_21 |
ehrpwm1 | P9_14 | P9_16 |
ehrpwm2 | P8_19 | P8_13 |
eQEP Modules
The magnetic (wheel) encoders are fitted to the motor shaft and rotate along with the shaft. The circuit associated with encoder sends rotation count pulses to BeagleBone IO pins. eQEP modules on BBB can be programmed to count incoming pulses.
eQEP readings can then be used to calculate motor/wheel speed. Quadrature encoding requires two channels to determine the direction of the motor rotation. Hence, each eQEP requires two IO pins of BBB.
eQEP IO Pins
eQEP Instance | Input-A | Input-B |
---|---|---|
eQEP0 | P9_42 | P9_27 |
eQEP1 | P9_35 | P9_33 |
eQEP2 | P8_12 | P8_11 |
DRV-8833 H-Bridge Motor Driver

DRV-8833 is a dual H-bridge motor driver IC capable of bi-directional control of two DC motor simultaneously. It’s an ideal driver to Pulse-Width modulate low-voltage DC motors.
Inputs xIN1 & xIN2 are connected to PWM-A & PWM-B outputs of BBB. Outputs xOUT1 & xOUT2 are connected to the two motor leads. VIN of DRV-8833 is the supplied with rated motor voltage.
Below table from DRV-8833 data sheet shows how different input combinations control motor direction and H-bridge functioning:
xIN1 | xIN2 | Function |
---|---|---|
PWM | 0 | Forward PWM, fast decay |
1 | PWM | Forward PWM, slow decay |
0 | PWM | Reverse PWM, fast decay |
PWM | 1 | Reverse PWM, slow decay |
Example Connections
As an example, to control forward speed of a DC motor in fast decay mode (first configuration in above table) you will have to make connections as shown in table below:
BBB Pin | BBB Pin Config | Connected To | Purpose |
---|---|---|---|
P9_22 (Out) | pwm | DRV8833 BIN1 | Forward PWM, fast decay |
P9_21 (Out) | gpio/LOW | DRV8833 BIN2 | Forward PWM, fast decay |
P9_42 (In) | eqep | EncoderB-1 | Encoder Channel 1 |
P9_27 (In) | eqep | EncoderB-2 | Encoder Channel 2 |
P9_1 | gpio | gnd | Common Ground |
Also, connect VIN & GND pins of DRV-8833 to motor voltage source and ground respectively.
PWM Programming (Python)
In this section and the next, we’ll see how to use Adafruit BBIO library, BeagleBone Balck & DRV-8833 hardware to control speed of DC motor using PWM.
First, let’s implement some wrapper classes to facilitate PWM and eQEP operations.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | #!/bin/env python from Adafruit_BBIO import PWM from Adafruit_BBIO import GPIO from Adafruit_BBIO.Encoder import RotaryEncoder, eQEP0 import time import numpy as np from math import pi class pwm_ops: def __init__(self, pin_0, pin_1): self.pin_0 = pin_0 self.pin_1 = pin_0 def start(self, duty, freq, polarity = 0): PWM.start(self.pin_1, 0, freq) PWM.start(self.pin_0, duty, freq) def stop(self): PWM.stop(self.pin_0) def cleanup(self): PWM.cleanup() class eqep_ops: def __init__(self, eqep): self.encoder = RotaryEncoder(eqep) self.encoder.setAbsolute() self.encoder.enable() def read(self): return self.encoder.position def reset(self): self.encoder.zero() def __del__(self): self.encoder.disable() |
pwm_ops
class objects need two PWM IO pins. start()
function takes three arguments: duty cycle (0-100), PWM frequency and polarity (not used in this example). eqep_ops
class objects need eQEP module name. read()
function returns encoder position at the time of reading.
Now, let’s implement another class do a few interesting things:
- Drive a range of PWM frequencies at specific duty cycle.
- Sweep across a range of duty cycles at fixed frequency.
- Drive each PWM setting for a specified amount of time.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | class motor_pwm_sweep: # Wheel Info wheel_radius = 0.035 # meters # Wheel Encoder Info (PPR) ticks_per_rev = 198.6 data = None pwm = None eqep = None def __init__(self, pwm_pin0, pwm_pin1, eqep_inst): self.pwm = pwm_ops(pwm_pin0, pwm_pin1) self.eqep = eqep_ops(eqep_inst) def calculate_wheel_rpm(self, encoder_ticks, time_secs): revs = float(encoder_ticks / self.ticks_per_rev) rpm = (revs * 60) / time_secs return rpm def calculate_wheel_velocity(self, encoder_ticks, time_secs): revs = float(encoder_ticks / self.ticks_per_rev) dist_per_rev = float(2 * pi * self.wheel_radius) distance = float(revs * dist_per_rev) speed = float(distance/time_secs) return speed def sweep_pwm_freq(self, freq_min=1000, freq_max=4000, freq_step=100, duty=50, sample_time=3): print('%5s %8s %12s %7s %6s %12s %12s' % ('#', 'Freq', 'Enc Ticks', 'Speed', 'RPM', 'EncStart', 'EncEnd')) freqs = range(freq_min,freq_max+1,freq_step) data = np.zeros((len(freqs), 6)) for i in range(0, len(freqs)): enc_start = self.eqep.read() self.pwm.start(duty, freqs[i]) time.sleep(sample_time) self.pwm.stop() enc_end = self.eqep.read() enc_reading = enc_end - enc_start # populate data data[i][0] = freqs[i] data[i][1] = enc_reading data[i][2] = self.calculate_wheel_velocity(enc_reading, sample_time) data[i][3] = self.calculate_wheel_rpm(enc_reading, sample_time) data[i][4] = enc_start data[i][5] = enc_end print('%3d %8d %12d %6.1f %6d %12d %12d' % (i, data[i][0], data[i][1], data[i][2], data[i][3], data[i][4], data[i][5])) time.sleep(0.5) def sweep_pwm_duty(self, duty_min=0, duty_max=100, duty_step=5, freq=2000, sample_time=3 ): print('%5s %8s %12s %7s %6s %12s %12s' % ('#', 'Duty', 'Enc Ticks', 'Speed', 'RPM', 'EncStart', 'EncEnd')) duties = range(duty_min,duty_max+1,duty_step) data = np.zeros((len(duties), 6)) for i in range(0, len(duties)): enc_start = self.eqep.read() self.pwm.start(duties[i], freq) time.sleep(sample_time) self.pwm.stop() enc_end = self.eqep.read() enc_reading = enc_end - enc_start # populate data data[i][0] = duties[i] data[i][1] = enc_reading data[i][2] = self.calculate_wheel_velocity(enc_reading, sample_time) data[i][3] = self.calculate_wheel_rpm(enc_reading, sample_time) data[i][4] = enc_start data[i][5] = enc_end print('%3d %8d %12d %6.1f %6d %12d %12d' % (i, data[i][0], data[i][1], data[i][2], data[i][3], data[i][4], data[i][5])) time.sleep(0.5) def __del__(self): self.pwm.cleanup() |
PWM Frequency Sweep
Let’s control motor speed by changing PWM frequency from 100 Hz to 4000 Hz in steps of 100 Hz at 50% duty cycle. We’ll let the motor run for 4 seconds at each frequency.
1 2 3 | motor1 = motor_pwm_sweep('P9_21', 'P9_22', eQEP0) motor1.sweep_pwm_freq(100,4000,100,50,4) |

We can see that the speed of the motor actually decreases at higher PWM frequencies. This is because PWM signals at higher frequencies give lesser time for current to rise to its peak value.
PWM Duty-cycle Sweep
Another way to control motor using PWM is to change PWM duty-cycle at a fixed frequency. At a PWM frequency of 200 Hz, let’s sweep the duty-cycle range (0-100) in steps of 5 – again, each for 4 seconds.
1 2 3 | motor1 = motor_pwm_sweep('P9_21', 'P9_22', eQEP0) motor1.sweep_pwm_duty(0,100,5,200,4) |

As expected, motor runs fastest at 100% duty-cycle.
Bottom line, you should consider an RPM sweet-spot for your DC motor based on both above plots. In applications where acceleration/deceleration is permitted, use above plots to determine a decent velocity/rpm range for the motor. Further experiments could include:
- A different decay mode of DRV-8833.
- PWM control of forward and reverse motor directions.
- PWM control of a differential (dual) motor system.