Elektro Pioneer – remotely controlled motorized baby stroller

Some time ago, a life-changing event influenced the ways I had been organizing my free time. The focus has been moved from the home laboratory towards two young creatures. That was one of the reasons I haven’t been much active with my hobby. I haven’t fully given up on the small projects, just reduced the time I was spending working on them and publishing the results.

Nevertheless, the parenting can bring different ideas to the mind of an engineer, and what I would like to show in this post is one of those ideas I actually went through materializing. As some of you (parents) might know, the really important item in any house with babies is the stroller. Zillion different types, a lot of time invested in selecting the right one, but eventually the stroller quickly becomes old news.

Elektro Pioneer

Somewhere around that period I got bored with the traditional use of the strollers and decided to try to motorize it and make it remotely controllable. The prototype has been finalized in mid 2013, and in the rest of this post I will describe the whole process.

Preparation

The first thing I did was to determine the necessary power needed for driving the stroller. In order to this so, I conducted a small experiment: I put my son in the stroller, and used the hand baggage scale to measure the force needed to drive the stroller. I did the experiment on different types of the ground. Using basic physics, I have calculated that the minimum torque of 10 Nm is needed to drive the stroller. I decided to use two motors, each one giving power to one back wheel. Note: In this step I had made a mistake, which I realized at the later stage of the project and growing up of my son. I will refer to this later in this post.

Maxy Cosi Elea

Mechanical part

In order to transfer the power from the motors to the wheels I have constructed wooden gears based on the calculated transmission ratio. I used my elementary school skills with plywood and saw and was really happy with the result. As a final touch I painted the gears and applied some light lubrication in order to reduce the resistance and the loss of power due to the transmission.

Building gears

Gears

One pair of the gears had been mounted directly to the wheels; the second pair of the gears directly to the motors. In order to keep all this tight and together, I used the custom made frame that was fixed to the stroller. On the same frame I fixed the motors as well. With the gear ratio 1:2 and the wheel diameter of 15 cm, in order to achieve the normal walking speed of ~3 km/h I had to use motors that had 200 rpm.

Wheels, gears and motors

Electronics part

Based on the requirements (total torque of 10 Nm and rotation speed of 200 rpm) I found and ordered two 12V DC gear motors with integrated gearbox that had those characteristics. In order to properly drive the motors, I used Dual H Bridge DC motor driver (L298N). This driver provides enough current to drive both motors in both directions.

Driver and motors

For controlling the motors I used Arduino Pro Mini. By providing variable PMW signals to 2 driver inputs, I was able to control the speed of the motors and the direction of the wheels rotation. Since the whole setup had to be mobile, I used 20Ah LiPo battery that provided 12V DC power to the whole system. The motor driver was powered directly from the battery, and I used Arduino’s on-board power regulator to power it.

LiPo battery

The final stroller electronics were packed into the box:

Stroller box

Steering

Initially the idea was to use a joystick to drive the stroller; soon after playing some RC Plane simulator on my phone I have decided to do the steering in a much user friendlier way – using a remote controller with a gyroscope! On the stroller part of the setup, I connected RFM12B as a receiver and on remote controller part JeeNode (Block) with MPU6050 gyroscope and RFM12B as a transmitter. Information about the remote controller position has been calculated and sent every 200 ms from the remote controller to the stroller. Surprisingly, this gave really natural feeling of the controlling the stroller, almost like in video games 🙂 With the “safety” feature described bellow, this was definitely a nice touch to the whole setup.

Remote Controller Box

The electric diagrams of the stroller and the remote controller parts are shown on the schemes bellow.

Stroller schema
Remote control schema

Software part

“Safety” first

Two concept were introduced to make the driving experience smooth and safe. First, in the case stroller is getting far away or the remote control connection is down (remote control signal is not received more than 1 s), the speed of both wheels is gradually reduced to zero. This is the safety feature in order not to “loose” the stroller and the kid (a promise I had to make to my wife). Second, in the case of a change of the velocity and the driving direction from the controller, the speed of the both wheels is adapted and gradually changed – all this making the whole ride really smooth. This is the code for the stroller part:

#include <jeelib.h>

#define LED_PIN 3

// Motor Control
//left
#define ENA 6 // speed (PWM)
#define IN1 A3 // direction
#define IN2 7 // direction
//right
#define ENB 5 // speed (PWM)
#define IN3 A2 // direction
#define IN4 4 // direction

// RF
#define NodeEPF 17
#define NodeEP  15

struct {
    int l;
    int r;
} command;

// Motor control
void SetupMotors()
{
  pinMode(ENA,OUTPUT);
  pinMode(ENB,OUTPUT);
  pinMode(IN1,OUTPUT);
  pinMode(IN2,OUTPUT);
  pinMode(IN3,OUTPUT);
  pinMode(IN4,OUTPUT);

  MotorL(0);
  MotorR(0);
}

void MotorL(int velocity)
{
  if (velocity == 0)
  {
    analogWrite(ENA,0);
    digitalWrite(IN1,LOW); 
    digitalWrite(IN2,LOW);
    return;    
  }
  
  bool dir = velocity>=0;
  int velocity_ = abs(velocity);
  if (velocity_>100) velocity_=100;
  
  byte vel = 55+2*abs(velocity_);
  if (velocity_==0) vel=0;
  
//  Serial.print("Left motor  - Velocity = ");Serial.print(velocity_);Serial.print(" - Direction = ");Serial.print(dir ?"+":"-");Serial.print(" - "); Serial.println(vel);
  analogWrite(ENA,vel);
  digitalWrite(IN1,dir ? LOW : HIGH); 
  digitalWrite(IN2,dir ? HIGH : LOW);
}

void MotorR(int velocity)
{
  if (velocity == 0)
  {
    analogWrite(ENB,0);
    digitalWrite(IN3,LOW); 
    digitalWrite(IN4,LOW);    
    return;
  }
  
  bool dir = velocity>=0;
  int velocity_ = abs(velocity);
  if (velocity_>100) velocity_=100;
  
  byte vel = 55+2*abs(velocity_);
  if (velocity_==0) vel=0;
  
//  Serial.print("Right motor - Velocity = ");Serial.print(velocity_);Serial.print(" - Direction = ");Serial.print(dir ?"+":"-");Serial.print(" - "); Serial.println(vel);
  analogWrite(ENB,vel);
  digitalWrite(IN3,dir ? LOW : HIGH); 
  digitalWrite(IN4,dir ? HIGH : LOW);
}

void setup() {
  
  Serial.begin(57600);
  Serial.println(); Serial.println();
  Serial.println("Elektro Pioneer v1.0");
  
  Serial.println("Initializing motors...");
  SetupMotors();
  
  Serial.println("Initializing RF device...");
  rf12_initialize(NodeEP, RF12_868MHZ, 10);
  rf12_control(0xC686);
  rf12_control(0x94C2);
  rf12_control(0x9820);  
  
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, HIGH);
  delay(500);
  digitalWrite(LED_PIN, LOW);
    
  Serial.println("....................");    
}

int velL = 0, velLt = 0;
int velR = 0, velRt = 0;
unsigned long velTS=0;

void loopRF()
{
 int velL_ = velL;
  int velR_ = velR;  
  
  if ((rf12_recvDone() && rf12_crc == 0) &&
      ((int)(rf12_hdr & RF12_HDR_MASK) == NodeEP) && (rf12_len == sizeof(command)))
  {
    memcpy(&command, (void *)rf12_data , sizeof(command));
    rf12_sendStart(RF12_ACK_REPLY, 0, 0);
    
    digitalWrite(LED_PIN, HIGH);
    delay(20);
    digitalWrite(LED_PIN, LOW);
    
    velL_ = command.l;
    velR_ = command.r;
    velTS = millis();      
  }
  
  if ((velL_ != velL) || (velR_ != velR))
  {
      if (velL_ != velL)
      {
        velL = velL_;
      }
      if (velR_ != velR)
      {
        velR = velR_;
      }    
      Serial.print("(RF) L=");Serial.print(velL);Serial.print("   R=");Serial.print(velR); Serial.println();
  }  
}

void loopAutoStop()
{
  // auto stop if there was no
  // remote command in last 2 seconds
  if ((velR!=0 || velL!=0) && (((long)millis()-(long)velTS) > 2000))
  {
    velL = 0;
    velR = 0;
    Serial.print("(AS) L=");Serial.print(velL);Serial.print("   R=");Serial.print(velR); Serial.println();
  }  
}

unsigned long ascTS = 0;

void loopAdaptiveSpeedControl()
{
  long ascTD = millis()-ascTS;
  if ((ascTD > 125) && ((velR != velRt) || (velL != velLt)))
  {
    int dR = (abs(velR-velRt) < 5) ? (velR-velRt) : (5*sgn(velR-velRt)); // only by 5
    velRt = velRt + dR;
    int dL = (abs(velL-velLt) < 5) ? (velL-velLt) : (5*sgn(velL-velLt)); // only by 5
    velLt = velLt + dL;
    ascTS = millis();
    Serial.print("     L=");Serial.print(velLt);Serial.print("   R=");Serial.print(velRt);Serial.println();            
  }  
}

void loop() 
{
  int velLt_ = velLt;
  int velRt_ = velRt;
  
  loopRF(); // check remote commands
  loopAutoStop(); // safety auto stop
  loopAdaptiveSpeedControl(); // avarage speed increase and descrease
 
   if (velLt_ != velLt) MotorL(velLt);
   if (velRt_ != velRt) MotorR(velRt);
}

static inline int sgn(int val) {
  if (val < 0) return -1;
  if (val==0) return 0;
  return 1;
}

This is the code for the remote controller part:

#include "Wire.h"
#include "I2Cdev.h"
#include "MPU6050.h"
#include <jeelib .h>

MPU6050 accelgyro;

int16_t ax, ay, az;
int16_t gx, gy, gz;

#define LED_PIN 8

#define NodeEPF 17
#define NodeEP  15

struct {
    int l;
    int r;
} command;

#define H_PIN A1
#define V_PIN A0
#define K_PIN 13

int ReadJoystickH()
{
  int h = analogRead(H_PIN);
  h = h-495;
  if (h>0) h=100.0*h/528.0;
  if (h<0) h=100.0*h/495.0;
  h = -h;
  return h;
}

int ReadJoystickV()
{
  int v = analogRead(V_PIN);
  v = v-521;
  if (v>0) v=100.0*v/502.0;
  if (v<0) v=100.0*v/521.0;
 return v;
}

static inline int sgn(int val) {
  if (val < 0) return -1;
  return 1;
}

void setup() {
    Wire.begin();
    Serial.begin(57600);
    Serial.println(); Serial.println();
	Serial.println("Elektro Pioneer v1.0");

    Serial.println("Initializing MPU6050...");
    accelgyro.initialize();
    Serial.println(accelgyro.testConnection() ? "MPU6050 connection successful" : "MPU6050 connection failed");

    Serial.println("Initializing RF device...");
    rf12_initialize(NodeEPF, RF12_868MHZ, 10);
    rf12_control(0xC686);
    rf12_control(0x94C2);
    rf12_control(0x9820);    
    
    pinMode(LED_PIN, OUTPUT);
    digitalWrite(LED_PIN, HIGH);
	
	Serial.println("....................");    	
}

int ver = 0, ver_d = 0, hor = 0, hor_d = 0;

void loop() {
    accelgyro.getAcceleration(&ax, &ay, &az);

    ver = ax/120+ver_d;
    hor = ay/120+hor_d;
    
    if (millis() < 3000)
    {
        ver_d = ver_d-ver;
        hor_d = hor_d-hor;
    }
    
    if (ver>100) ver = 100;
    if (ver< -100) ver = -100;

    if (hor>100) hor = 100;
    if (hor< -100) hor = -100;

    int velL = -0.3*hor*sgn(ver) + ver;
    int velR = 0.3*hor*sgn(ver) + ver;

    if (velL>100) velL = 100;
    if (velL< -100) velL = -100;

    if (velR>100) velR = 100;
    if (velR< -100) velR = -100;
    
    velL = velL/5 * 5;
    velR = velR/5 * 5;
    
    command.l = velL;
    command.r = velR;

    Serial.print(millis());Serial.print("\t");
    Serial.print("H=");Serial.print(hor);Serial.print("\t");Serial.print("V=");Serial.print(ver);Serial.print("\t");
    Serial.print("[");Serial.print(command.r);Serial.print(",");Serial.print(command.l);Serial.print("]\t");
        
    while (!rf12_canSend()) rf12_recvDone();
 
    byte header = RF12_HDR_ACK; // 0 for No ACK
    header |= RF12_HDR_DST | NodeEP;
    
    rf12_sendStart(header, &command, sizeof(command));
    
    bool acked = false;
    
    long ackStartTime =  millis();
    long ackTime = 0;
    while (!acked && (ackTime < 100))
    {
	ackTime = millis() - ackStartTime;
        acked = (rf12_recvDone() && rf12_crc == 0 && (((int)rf12_hdr-128) == NodeEP));
    }
       
    Serial.print(acked ? "ACKED":"NO RESPONSE");
    
    Serial.println();

    if (acked)
    {
     digitalWrite(LED_PIN, LOW);
     delay(25);
     digitalWrite(LED_PIN, HIGH);
     delay(75);     
    }
    delay(100);
}

The first (unsuccessful) try

Once all was put together, the whole family headed for a nearby playground to give the new toy a maiden ride.

First we have tested only the steering, electronics, motors and gears. The test was OK.

Then we went on with the empty stroller. The test was OK.

Although everything was working as intended, once we put a child in the stroller, it failed to move 🙁 After spending some time debugging the whole thing, it hit me: the motors were not giving enough torque to move the stroller with the kid inside. Kids at this age (1st year) have a strange tendency to gain their weight rapidly; my son gained additional kilograms from the moment I did the initial calculation, and the motor power was simply not enough.

I have disassembled the whole thing and went back to the lab / home.

The second (successful) try

In meanwhile we bought a much lighter and smaller stroller. It was a super light stroller from Quinny, which at this point I would highly recommend to the all parents to be.

Quinny Yezz

A good thing was that I could just use the existing electronics and the motors; a bad thing was that I had to throw away the self-made gears. Luckily for me, the wheel diameter of the new strollers was half of the diameter of the "old" stroller, and by fitting the motors directly to the gears, I didn't need to change anything else.

Retrofit - Wheel
Retrofit - Stroller

On the final test day, I went alone with my son to the playground. First we tried the empty stroller:

I noticed that the new setup was more powerful and more agile then the previous one. This was due to the overall smaller weight of the stroller with the child in addition to no power loss in the wooden self-made gearbox.

Then, the big test was with the child inside:

We were driving and playing around for more than half an hour, and finally it was the time to go back home:

With the successful prototype finished, I haven't proceed further in developing the Elektro Pioneer. Some of the further improvements for the next versions (maybe in the case I get another child) could include:

  • Some battery monitoring mechanism - I didn't dig deeper in investigating power consumption.
  • Using a phone with integrated gyroscope to drive the stroller (either with Bluetooth or WiFi on board)
  • GPS control - possibility to follow the predefined path
  • GPS control - possibility to have "follow me" mode

In the later months and years I have seen numerous projects that have been addressing similar topics. Here are just some of them:

© 2015 - 2021, Quo Vadis ?. All rights reserved.

Loading

6 thoughts on “Elektro Pioneer – remotely controlled motorized baby stroller”

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.