Open loop DC motor control with a PS3 controller

1-IMG_5063The next stage of my robot project is to make software talk to hardware. Previously I mentioned how I translate PS3 controller data into something useful for a robot. Before getting into complex real-world control, I wanted to check that my example 12V DC motor would respond to my Arduino firmware and controller with simple open loop messages.

What makes it open loop? A system is considered closed loop when the output is measured by a sensor input and fed back into the control system to reach a target setpoint. An example of that is a PID control scheme, which I’ll be building next. Compare that to how this system will work. The joystick position is mapped proportionally against the available motor output but there is no check about the motor’s actual speed. If the motor was powering something under load, the operator of the joystick would need to compensate for it to reach the desired speed. If a PID controller was used, the joystick position would map to a specific speed and the controller would make sure it actually turns at that speed, regardless of the load attached.

Here’s a list of the systems in use:

ROS node

My ROS system handles message communication and in this case it takes joy messages from the PS3 controller node, converts them into left-right motor magnitudes and sends them to the Arduino via serial. In the last node I wrote, the output from the PS3 controller was a Twist message: linear motion in the x direction and angular motion around the z axis. Those real-world values (m/s and rad/s) are great for actual robots but because I only have a single motor connected a breadboard, I changed the method so that the controller’s X/Y axes map from -255 to 255 (the Arduino’s PWM output range).

class JoyOpenLoop
		void joyCallback(const sensor_msgs::Joy::ConstPtr &msg);
		void updateParameters();
		float constrain(float value, float min, float max);
		void timerCallback(const ros::TimerEvent& e);
		void publishZeroMessage();
		ros::Subscriber joySub;
		ros::Publisher motorPub;
		ros::NodeHandle nh;
		ros::Timer timeout;
		int motorScale;
		int deadmanButton, xAxisIndex, yAxisIndex;
		int prevLeftMotor, prevRightMotor;
		bool canMove, startedZero;

JoyOpenLoop::JoyOpenLoop() {
	motorScale = 255;
	canMove = false;
	joySub = nh.subscribe("/joy", 10, &JoyOpenLoop::joyCallback, this);
	motorPub = nh.advertise<yardbot_msgs::LeftRightInt32>("/arduino/motors", 10);

void JoyOpenLoop::updateParameters() {
	// update the parameters for processing the joystick messages	
	if (!nh.getParam("deadman_button", deadmanButton))
		deadmanButton = 11;
	if (!nh.getParam("linear_axis", yAxisIndex))
		yAxisIndex = 1;
	if (!nh.getParam("angular_axis", xAxisIndex))
		xAxisIndex = 0;

void JoyOpenLoop::joyCallback(const sensor_msgs::Joy::ConstPtr &msg) {
	// process and publish
	yardbot_msgs::LeftRightInt32 motorMsg;
	// check deadman switch
	bool switchActive = (msg->buttons[deadmanButton] == 1);
	float x = msg->axes[xAxisIndex];
	float y = msg->axes[yAxisIndex];
	bool zeroed = (x == 0 && y == 0);
	// switch pressed and was moving
	if (switchActive && !canMove)
		startedZero = zeroed;
	if (switchActive && startedZero) {	
		// keep the math simple and just add the axis components		
		float left = y - x;
		float right = y + x;
		left = constrain(left, -1, 1);
		right = constrain(right, -1, 1);
		int leftMotor = motorScale*left;
		int rightMotor = motorScale*right;
		if (leftMotor != prevLeftMotor || rightMotor != prevRightMotor) {    
			motorMsg.left = leftMotor;
			motorMsg.right = rightMotor;
		prevLeftMotor = leftMotor;
		prevRightMotor = rightMotor;
	} else if (canMove) {
	canMove = (switchActive && startedZero);	
	// reset the timeout timer
	if (timeout) {
	timeout = nh.createTimer(ros::Duration(2), &JoyOpenLoop::timerCallback, this, true);

void JoyOpenLoop::timerCallback(const ros::TimerEvent& e) {

void JoyOpenLoop::publishZeroMessage() {
	yardbot_msgs::LeftRightInt32 msg;
	msg.left = 0;
	msg.right = 0;

float JoyOpenLoop::constrain(float value, float min, float max) {
	value = std::min(value, max);
	value = std::max(value, min);
	return value;

int main(int argc, char** argv) {
	ros::init(argc, argv, "openloop_joy_node");
	JoyOpenLoop joy_openloop_node;	
	return 0;

This is a pretty standard ROS C++ node object. First I define the class header, along with the different public and private methods and variables. Then is the object initializer. Here I set some variable values and create the joystick subscriber and motor publisher, then the parameter method is defined.

Next is the method that does the heavy lifting: the joystick callback. It’s much the same as the previous post, except this time I’ve added a zero-out function for the deadman button. This means that if the joystick is non-zero and the deadman button is released, nothing is sent until the joystick is zeroed out. This behaviour prevents the motor from taking off when the switch is pressed again.

At first I thought merging the two axes together would require complicated math but then the more I thought about it, I realized that since the axes are bound between -1 and 1, it’s a simple matter of adding the axes together so that pushing the stick left/right will add or subtract from the forward motion. Ultimately the left/right axis is about the relative speed between left and right motors. The end result is that moving the stick to the left or right extremes with 0 forward power will spin the robot in place.

The remaining methods offer some utility of constraining output and sending the zero speed message when no joystick data is received after a timeout.

Motor controller

Motor controller on breadboard
The motor controller I’m using is an L298D model from Sparkfun. It’s connected to an Arduino MEGA and 58RPM 12V DC motor. Like most H-bridge controllers, it uses 2 pins to control direction and another for speed using PWM output. In order to make that easier to use, I made a library that takes an integer between -255 and 255 and determines the direction pins automatically. Now the motor callback just needs to feed the message values directly to the library.

// assign the required pins
#define ENA_PIN  49
#define ENB_PIN  48
#define SPEED_PIN  9

// receive the motor callback
void motorCallback(const yardbot_msgs::LeftRightInt32 &msg) {
   // read the value of the left and right motors

void setRightMotorOutput(int value) {
  // write the requested motor value
  currentRightMotor = value;
  // no motor connected to the right side

void setLeftMotorOutput(int value) {
  // write the requested motor value
  currentLeftMotor = value;

The ROS message passed in is custom and is essentially a better labelled 2D vector (left and right).

iPhone monitor

Motor monitor animationWhile not being required for a robot, I thought it would be cool to have an iPhone app to monitor the different robot systems. One of the functions I’ve made is an animated view of the motor inputs. The left and right motors have arrows that show forward and reverse direction and the length of the arrow indicates the Arduino output to the motor. With this view, you can clearly see that moving the joystick to the extreme left and right positions means one motor is full speed forward and the other is full reverse.

The app uses my custom RBManager library, which in turn requires rosbridge to run on the ROS host. The phone is connected via websocket, like a mobile game might be. I hope to make a more fully featured iPad version soon and potentially offer it to the ROS community.

This is only an intro for full motor control but it’s been good to actually see hardware and software play nice together. Next up: closed loop feedback control!

Tagged with: , , , , , ,

Leave a Reply

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