Need help to build Balance platform by using arduino and two stepper motors

Hi all,

Just a newbie here,I need to build a 2-axis balance plate (motorized ) to balance a sensor (MPU6050) and implement PID controller.

Like balance a plate for a ball, i need similar help to build a 2 axis stepper motor based PID controller.

What have you tried? What exact parts do you have to work with?

I built a MPU9250 X/Y stabilized platform. I used the parts from a robotic am, bought through a web site, and wrote some code.

You can get some good MPU code from this site: https://os.mbed.com/users/onehorse/.

You should, in the least, use uSeconds to torque the servos. I use clock ticks, ESP32, as there are ~3 clock ticks per uS.

When you mount the shoehorn to the servo, send the servo to 90 degrees to find the offset. Shoehorns are off by 25uS. 90 degrees with a shoe horn is not 1500uS but either 1475uS or 1525uS. I send a continuous stream of correction info to the servo and let the servo routine do any needed delays; like this:

void fTweakServoX( void * parameter )
{
  struct stu_XYZ_INFO pxXYZ;
  TickType_t ServoDelay =  pdMS_TO_TICKS( 50 ); // limits degrees per second
  int Max = usToTicks( 1747 );
  int Min = usToTicks( 1349 );
  while (1)
  {
    xEventGroupWaitBits (eg, evtTweakServoX, pdTRUE, pdTRUE, portMAX_DELAY) ;
    xSemaphoreTake ( sema_LIDAR, xSemaphoreTicksToWait );
    xQueueReceive ( xQ_XYZ_INFO, &pxXYZ, QueueReceiveDelayTime );
    pxXYZ.iX = usToTicks( pxXYZ.iX );
    if ( ( pxXYZ.iX > Min ) && ( pxXYZ.iX < Max ) )
    {
      ledcWrite (Channel_X, pxXYZ.iX );
    }
    else
    {
      ledcWrite (Channel_X, usToTicks(iX_Posit90) );
    }
    xSemaphoreGive ( sema_LIDAR );
    vTaskDelay( ServoDelay );
    xSemaphoreGive( sema_X ); // <-- this semaphore prevents retriggering during torque and settle times.
  }
  vTaskDelete( NULL );
}

The semaphore prevents the constant incoming stream from overwhelming the servo and gives the servo time to get into position.

Use metal geared servo’s. My experience with plastic geared servos is they last about 200hours. My metal geared servos been running for more than a year continuous. You should give the servos their own, well regulated, power supply.

I have arduino UNO, i have not selected the stepper motor and driver yet but it would be A4988 or easy driver for enable micro stepping.

The sensor is not exactly MPU but it is based on laser gyros. That part is already built.
I need to build a stabilized plaform to do mapping of objects on a car.
For this purpose, PID based arduino control stabilzed platform is reqired. I have watched several videos on youtube and even google, but dint find the complete soulution or DIY.
Kindly help.

shanijo92:
Hi all,

Just a newbie here,I need to build a 2-axis balance plate (motorized ) to balance a sensor (MPU6050) and implement PID controller.

Like balance a plate for a ball, i need similar help to build a 2 axis stepper motor based PID controller.

A stepper motor is a very power hungry and prone to (unsurprizingly) a lot of vibration. Simple DC motor
can serve the job perfectly well if you already are implementing a PID loop to drive it.

Can you have some examples? how to implement PID for 2 axis stabilized platform.

Here is some example code that I use to create torque values to maintain a X/Y platform:

void fGetIMU( void *pvParameters )
{
  float _ax, _ay, _az;
  float _gx, _gy, _gz;
  float _hx, _hy, _hz;
  ////
  mpu9250.fInit_MPU9250( false, false );
  struct stu_XYZ_INFO pxXYZ;
    boolean DidBias = false;
  // boolean DidBias = true;
  TickType_t TimePast = xTaskGetTickCount();
  TickType_t TimeNow = xTaskGetTickCount();
  TickType_t xLastWakeTime;
  TickType_t xFrequency = 70;
  for (;;)
  {
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
     // read the sensor
    if ( mpu9250.isMPU9250_OK() && mpu9250.isAK8963_OK() )
    {
      if ( DidBias )
      {
        TimeNow = xTaskGetTickCount();
        deltat = ( (float)TimeNow - (float)TimePast) / 1000.0f;
        ////
        mpu9250.getAdata();
        _ax = mpu9250.getAxScaled() + axBias;
        _ay = mpu9250.getAyScaled() + ayBias;
        _az = mpu9250.getAzScaled() + azBias;
        mpu9250.getGdata();
        _gx = mpu9250.getGxScaled() + gxBias;
        _gy = mpu9250.getGyScaled() + gyBias;
        _gz = mpu9250.getGzScaled() + gzBias;
        mpu9250.getMdata();
        _hx = mpu9250.getMxScaled() + mxBias;
        _hy = mpu9250.getMyScaled() + myBias;
        _hz = mpu9250.getMzScaled() + mzBias;
        // h value multiplied by 10 converts to mG from uT
        MahonyQuaternionUpdate ( _ax, _ay, _az, _gx, _gy, _gz, _hx * 10.0f, _hy * 10.0f, _hz * 10.0f );
                float yaw   = atan2f(2.0f * (q[1] * q[2] + q[0] * q[3]), q[0] * q[0] + q[1] * q[1] - q[2] * q[2] - q[3] * q[3]);
                float pitch = -asinf(2.0f * (q[1] * q[3] - q[0] * q[2]));
                float roll  = atan2f(2.0f * (q[0] * q[1] + q[2] * q[3]), q[0] * q[0] - q[1] * q[1] - q[2] * q[2] + q[3] * q[3]);
                roll = roll * 5.54f; // calculate magnitude of servo torque
                pxXYZ.iX = iX_Posit90 - ( (int)roll );
                pitch = pitch * 5.54f;
                pxXYZ.iY = iY_Posit90 - ( (int)pitch );
                if ( xSemaphoreTake( sema_X, xZeroTicksToWait ) == pdTRUE ) // grab semaphore, do not wait
                {
                  xQueueOverwrite( xQ_XYZ_INFO, (void *) &pxXYZ );
                  xEventGroupSetBits( eg, evtTweakServoX );
                }
                if ( xSemaphoreTake( sema_Y, xZeroTicksToWait ) == pdTRUE ) // grab semaphore, do not wait
                {
                  xQueueOverwrite( xQ_XYZ_INFO, (void *) &pxXYZ );
                  xEventGroupSetBits( eg, evtTweakServoY );
                }
                TimePast = TimeNow;
      } // if ( DidBias )
      else
      {
        // MPU calibration
        axBias = 0.0;
        ayBias = 0.0;
        azBias = 0.0;
        gxBias = 0.0;
        gyBias = 0.0;
        gzBias = 0.0;
        mxBias = 0.0;
        myBias = 0.0;
        mzBias = 0.0;
        fCalculateM_offset( mxBias, myBias, mzBias, gxBias, gyBias, gzBias, axBias, ayBias, azBias );
        axBias = -(axBias);
        ayBias = -(ayBias);
        azBias = -(azBias);
        gxBias = -(gxBias);
        gyBias = -(gyBias);
        gzBias = -(gzBias);
        mxBias = -(mxBias);
        myBias = -(myBias);
        mzBias = -(mzBias);
        Serial.print( "calibration values ");
        Serial.print( ", ax ");
        Serial.print ( axBias, 6 );
        Serial.print( ", ay ");
        Serial.print (ayBias, 6 );
        Serial.print( ", az ");
        Serial.print ( azBias, 6 );
        Serial.print( ", gx ");
        Serial.print ( gxBias, 6 );
        Serial.print( ", gy ");
        Serial.print ( gyBias, 6 );
        Serial.print( ", gz ");
        Serial.print ( gzBias, 6 );
        Serial.print( ", mx ");
        Serial.print ( mxBias, 6 );
        Serial.print( ", my ");
        Serial.print ( myBias, 6 );
        Serial.print( ", mz ");
        Serial.print ( mzBias, 6 );
        Serial.println();
        DidBias = true;
        TimePast = xTaskGetTickCount();
         xEventGroupSetBits( eg, evtLIDAR_ServoAspectChange ); // start the LIDAR scanning
      }
    }
    xLastWakeTime = xTaskGetTickCount();
  } //  for (;;)
  vTaskDelete( NULL );
} // void fGetIMU( void *pvParameters )