Senior-Design / software / elevate / src / elevate_module.cpp
elevate_module.cpp
Raw
/**
 * @file elevate_module.cpp
 * 
 * @brief elevate leg module
 * 
 * @author Jonathan Lee
 * Contact: jonlee27@seas.upenn.edu
 */
#include "elevate_module.h"
#include "elevate_constants.h"
#include <Arduino.h>

uint32_t const ElevateModule::MOTOR_FREQUENCY = MOTOR_FREQUENCY_;
uint8_t const ElevateModule::MOTOR_RESOLUTION_BITS = MOTOR_RESOLUTION_BITS_;

float const ElevateModule::KP = KP_;
float const ElevateModule::KI = KI_;
float const ElevateModule::KD = KD_;
unsigned long const ElevateModule::PID_RATE_MS = PID_RATE_MS_;
int const ElevateModule::MINIMUM_OUTPUT = MINIMUM_OUTPUT_;
int const ElevateModule::MAXIMUM_OUTPUT = MAXIMUM_OUTPUT_;
long const ElevateModule::ERROR_THRESHOLD = ERROR_THRESHOLD_;

/**
 * Elevate Module constructor
 * 
 * @param pwm_pin                pwm pin
 * @param pwm_channel            pwm channel
 * @param direction_pin          direction pin
 */
ElevateModule::ElevateModule(uint8_t pwm_pin, uint8_t pwm_channel, uint8_t direction_pin) :
PWM_PIN(pwm_pin),
PWM_CHANNEL(pwm_channel),
DIRECTION_PIN(direction_pin),
pid_controller(KP, KI, KD, PID_RATE_MS, MINIMUM_OUTPUT, MAXIMUM_OUTPUT) {
  is_setup = false;
  state = STOPPED;
  status = FINE;
  height = 0;
  height_offset = 0;
  lower_limit_switch_pressed = false;
  upper_limit_switch_pressed = false;
}

/**
 * Set up module
 */
void ElevateModule::setup() {
  if (!is_setup) {
    pwm_setup(PWM_CHANNEL, PWM_PIN);
    pinMode(DIRECTION_PIN, OUTPUT);
    is_setup = true;
  }
}

/**
 * Get module state
 * 
 * @return module state
 */
ElevateState ElevateModule::get_state() const {
  return state;
}

/**
 * Get module status
 * 
 * @return module status
 */
ElevateStatus ElevateModule::get_status() const {
  return status;
}

/**
 * Update module status
 */
void ElevateModule::update_status() {
  if (upper_limit_switch_pressed && lower_limit_switch_pressed) {
    status = MALFUNCTION;
  } else if (upper_limit_switch_pressed) {
    status = UPPER_LIMITED;
  } else if (lower_limit_switch_pressed) {
    status = LOWER_LIMITED;
  } else {
    status = FINE;
  }
}

/**
 * Force stop the module
 */
void ElevateModule::hard_stop() {
  pid_controller.set_mode(OFF);
  set_speed(0);
  state = STOPPED;
}

/**
 * Command the module to smoothly stop
 * 
 * @param height height to stop at
 */
void ElevateModule::smooth_stop(long height) {
  static bool first_call = true;
  static unsigned long start_time = millis();
  if (millis() - start_time > 1.05 * STOP_SETTLE_TIME) {
    first_call = true;
  }
  if (first_call) {
    first_call = false;
    start_time = millis();
  }

  if (state == STOPPED) {
    hard_stop();
  } else if (abs(height - (this->height - this->height_offset)) < ERROR_THRESHOLD) {
    hard_stop();
  } else {
    if (millis() - start_time < STOP_SETTLE_TIME) {
      state = STOPPING;
      move(height);
    } else {
      hard_stop();
    }
  }
}

/**
 * Command the module to move
 * 
 * @param height height to move to
 */
void ElevateModule::move(long height) {
  pid_controller.set_mode(ON);
  set_speed(pid_controller.control(height, this->height - this->height_offset));
}

/**
 * Update module readings
 * 
 * @param height                     new module height from encoder MCU
 * @param lower_limit_switch_pressed whether or not lower limit switch is pressed
 * @param upper_limit_switch_pressed whether or not upper limit switch is pressed
 */
void ElevateModule::update(
    long height,
    bool lower_limit_switch_pressed,
    bool upper_limit_switch_pressed) {
  static unsigned long previous_time = millis();
  unsigned long time_elapsed = millis() - previous_time;
  long height_difference = height - this->height;

  switch (state) {
    case STOPPED:
      previous_time = millis();
      break;
    case STOPPING:
      previous_time = millis();
      break;
    case CALIBRATE:
      previous_time = millis();
      break;
    case MOVING_DOWN:
      if (time_elapsed > 1000) {
        if (height_difference > -(UNITS_PER_ROTATION / 2)) {
          this->height_offset += (time_elapsed / 1000) * UNITS_PER_ROTATION;
        }
      }
      previous_time = millis();
      break;
    case MOVING_UP:
      if (time_elapsed > 1000) {
        if (height_difference < (UNITS_PER_ROTATION / 2)) {
          this->height_offset -= (time_elapsed / 1000) * UNITS_PER_ROTATION;
        }
      }
      previous_time = millis();
      break;
  }

  this->height = height;
  this->lower_limit_switch_pressed = lower_limit_switch_pressed;
  this->upper_limit_switch_pressed = upper_limit_switch_pressed;
}

/**
 * Update the height offset
 */
void ElevateModule::update_offset() {
  height_offset = height;
}

/**
 * Set up the pwm pin of the module
 * 
 * @param channel pwm channel
 * @param pin     pwm pin
 */
void ElevateModule::pwm_setup(uint8_t channel, uint8_t pin) const {
  ledcSetup(channel, MOTOR_FREQUENCY, MOTOR_RESOLUTION_BITS);
  ledcAttachPin(pin, channel);
  ledcWrite(channel, 0);
}

/**
 * Set the speed of the module
 * 
 * @param speed speed to set the module at
 */
void ElevateModule::set_speed(int speed) {
  if (speed == 0) {
    ledcWrite(PWM_CHANNEL, 0);
    state = STOPPED;
  } else if (speed > 0) {
    if (status == UPPER_LIMITED) {
      ledcWrite(PWM_CHANNEL, 0);
      state = STOPPED;
    } else {
      digitalWrite(DIRECTION_PIN, HIGH);
      ledcWrite(PWM_CHANNEL, speed);
      state = MOVING_UP;
    }
  } else {
    if (status == LOWER_LIMITED) {
      ledcWrite(PWM_CHANNEL, 0);
      state = STOPPED;
    } else {
      digitalWrite(DIRECTION_PIN, LOW);
      ledcWrite(PWM_CHANNEL, -speed);
      state = MOVING_DOWN;
    }
  }
}