Lab 5:Linear PID control and Linear interpolation

Pre Lab

For this lab, we want to play with PID control, specifically position control for this week. We want to implement PID control for a certain amount of time (in my case, 12 seconds).
In terms of sending and receiving data, BLE is only used to trigger the starting and retrieve logged data. Code on python sends a start command START_P_RUN. Once the command is received, a boolean running is set to be true, while timestamp at this point is set to be the starting time. Inside the loop, I have the TOF measurements conducted which gives input for porportional control and are collected into arrays. My instincts made it to have motors taking in speed within the same if statement. However, checking TOF data ready takes time which caused motor cannot adjust speed according to distances quickly, so I placed helper function of motor taking action outside the if data-ready range.

File: Lab 5.ino

    void loop(void)
    { ... 
      if (running) {
        if (distanceSensorA.checkForDataReady()) {
          d_mm_A = distanceSensorA.getDistance();
          fresh_TOF = millis();
          distanceSensorA.clearInterrupt();
          Compute_P(d_mm_A);
          collect_TOF(d_mm_A);
        }
        else {
          Compute_P(d_mm_A);
        }
      motor_action(Output);
      ...}
      }
          

Data including timestamp, distance, PWM speed are not sent until time hits the boundary, which prevents a smaller loop frequency. After robot stops, arrays are sent by BLE. Similar to previous labs, each message of data is formatted into estring, which gets parsed by notification handler and stored into separate lists in preparation for analysis and plotting. Here are my helper functions: Compute_P, motor_action, and collect_TOF.

File: Lab 5.ino

      void Compute_P(double Input)
      {
      dis_error = Input - Setpoint; // calculate the distance from target postion
      /*Compute PID Output*/
      Output = kp * dis_error; // (+ ki * errSum + kd * dErr) P control only!
      speed = constrain(abs((int)Output), 50, 255) ; // constrain speed with the running PWM range tested from lab 4
      }
      
      void motor_action(double dis_diff){
        if (dis_diff > 10){ //leave a tolerence for TOF reading 
          forward(speed);
          //Serial.println("Too Far... I need to go closer!");  // Comment out print to react faster
        }
        else if (dis_diff < -10) {
          backward(speed);
          //Serial.println("Too close... I need to go farther!");
        }
        else {
          stop();
          //delay(1000);
        } 
      }

      void collect_TOF(float d_mm_A){
          if (log_index < LOG_SIZE) {
            time_arr[log_index] = fresh_TOF - start_time;
            dist_arr_A[log_index] = d_mm_A;
            motor_arr[log_index] = speed; 
            log_index ++;
          }
      }
          

I chose proportional control only because its quick response and ease for tuning. It only has one variable --proportional gain -- to adjust. It is relatively easy to hand on compared with PI control or PID control. I do recognize that it has some cons, such as it can only approach to setpoints to infinity in infinity time (error never zero); high Kp made it really easier to omit the setpoint and crash to the wall. It is okay to use at this point since the system/question is not complicated.

Task 1 Position Control

The deadband of motor is at 35 for PWM speed from lab 5. I set the boundary to be within [50, 255] to ensure the smooth running of thw car, which is also the range of my output after conversion. In consideration of a reasonable range of proportional control gain, the input value can be depending on the maximum value of long-distance mode of 4,000 mm. The resulting range can be very big. Through testing, the range is approximately from 0.05 to 0.2. Gain larger than 0.2 easily got car crash to the obstable regardless of intervention from setpoint.

data logged

There are certain periods in the time from measured displacement graph where car performed a linear speed. The maximum linear speed among them is 870.75 mm/s(511.25mm/s, 684.68mm/s, 870.75 mm/s), by using linear data and time stamp to calculate.

Watch the video of car performing position control on carpet floor with Kp of 0.15.
Watch the video of car performing position control on carpet floor with Kp of 0.2.
Watch the video of car performing position control on lab table with Kp of 0.1.
data logged

In terms of frequency, my TOF can collect 123 samples within 12 seconds consistently, so the frequency is approximately 10.25Hz. Compared with TOF, controller runs a higher frequency, which results in cases that proportional control and motor action updates every loop run using newest distance reading while TOF does not collect new reading.
Something that I did not get a chance to implement here is to have a braking motion. Now my car speed is really constrained in a range that car can run. It will only stop when times come to the end or it is right at the target position. Now the speed would like to bring car over the position and then it is activiated to run the opposite direction. It would be faster or more efficient to activate the-other-direction run as a way to brake. A more clear way to explain it is that you would tap on backward on remote control when you tried to do stunt on a forward-fast-running car.

Task 2 Linear Extrapolation

To fill in more data from linear extrapolation to estimate the location and take action based on that, I updated the code with following changes. The performance showed that car can actually stop without crashing to the wall with higher kp and bigger initial distance difference, and it spent less time adjusting near the setpoint/ target position. Besides, it achieved to collect far more data samples than before (1,000 > 123).

File: Lab 5.ino

      if (running) {
        if (distanceSensorA.checkForDataReady()) {
          last_tof = new_tof;   //mark the collected data as OLD
          last_tof_time = new_tof_time;

          new_tof = distanceSensorA.getDistance();
          new_tof_time = millis();

          d_mm_A = new_tof;
          fresh_TOF = new_tof_time;
          distanceSensorA.clearInterrupt();

          if (new_tof_time > last_tof_time) {
              tof_slope = (new_tof - last_tof) / (float)(new_tof_time - last_tof_time); // mm per ms
          }
          Compute_P(d_mm_A);
          collect_TOF(d_mm_A);
        }
        else {
          estimate_dis(d_mm_A);
          Compute_P(est_d_mm_A);
          collect_TOF(est_d_mm_A);
        }
      motor_action(Output);
      ...}

      void estimate_dis(double Input)
      {
      unsigned long t_now = millis();
      float dt = (float)new_tof_time - last_tof_time;
      est_d_mm_A = new_tof + tof_slope * dt;
      }

          
data logged for extrapolation Watch the video of car performing position control with extrapolation with kp of 0.25.

Reference

Thanks to Michelle Yang, Jack Long and Zoey Zhou for discussion about slow reaction/data logging rate.
I used chatGpt for clarifying questions, debugging such as finding missing parathesis, and brainstorming the structure to build pre-lab code.