Motion Loop

Motion Control contains the main Finite State Machine, with these states:

The main loop of the motion controller. It is a FSM (Finite State Machine) which governs the state transitions.

There are following states present:

motion_control.hpp:

72typedef enum
73{
74  Unknown,
75  Idle,
76  Sitting,  // Sitting, torque off
77  Standing_up,
78  Standing,
79  Walking,
80  Sitting_down
81} tState;

State: Unknown

Default state. Turn off servo motors. This is a save state that should never be reached.

motion_control.cpp:

166// Loop in which the motions are coordinated
167void MotionControl::motion_loop(void)
168{
169  switch (hex_state) {
170    case Unknown:   // check in which state the robot is and go to idle
171      servo_driver.free_servos();
172      break;

State: Idle

Idle state is the default starting state.

State transitions

The robot waits for an input in form of the “start_cmd”. If the command is received the state changes to Sitting while the servos are turned on.

motion_control.cpp:

174    case Idle:
175      if (start_cmd) {
176        next_hex_state = Sitting;
177        servo_driver.turn_on_servos();
178      }
179      break;

State: Sitting

In the sitting state the servo motors move to the neutral sitting position with a reduced speed. This enables a smooth transition from unknown servo positions to the neutral sitting position. (In the visualization this step is unconstrained, meaning it will take 0 time to execute).

State transitions

The stand_up_cmd resets the servos to full speed again, while transitioning to the standing up state. If the start_cmd is reset the robot goes back to the idle state.

motion_control.cpp:

181    case Sitting:
182      current_height = sitting_height;
183      servo_driver.set_servo_speed(0.2);  // reduce speed to prevent jerking motion
184      for (int c = 0; c < NUMBER_OF_LEGS; c++) {
185        feet.foot[c].position.x = neutral_foot_pos_x[c];
186        feet.foot[c].position.y = neutral_foot_pos_y[c];
187        feet.foot[c].position.z = current_height;
188      }
189
190      if (stand_up_cmd) {
191        servo_driver.set_servo_speed(0);  // max speed
192        next_hex_state = Standing_up;
193      } else if (!start_cmd) {
194        next_hex_state = Idle;
195        servo_driver.free_servos();
196      }
197      break;

State: Standing_up

In the standing up state the height of the body is increased until it reaches the desired standing height.

State transitions

As soon as the standing height is reached the FSM transitions to the walking state.

motion_control.cpp:

199    case Standing_up:
200      current_height -= sit_step_height;
201      if (current_height <= -walking_height) {
202        next_hex_state = Walking;
203        current_height = -walking_height;
204      }
205
206      for (int c = 0; c < NUMBER_OF_LEGS; c++) {
207        // feet.foot[c].position.x = neutral_foot_pos_x[c];
208        // feet.foot[c].position.y = neutral_foot_pos_y[c];
209        feet.foot[c].position.z = current_height;
210      }
211      break;

State: Standing

In the standing state the robot only moves its body without moving the feet positions. This also serves as a proof of the inverse kinematics.

State transitions

If the stand_up_cmd is reset, the robot will sit down again. If the walking_cmd is set the robot goes to the Walking state.

motion_control.cpp:

213    case Standing:
214      body.orientation.pitch = 0.4 * cmd_vel_.linear.x;
215      body.orientation.roll = 0.5 * cmd_vel_.linear.y;
216      body.orientation.yaw = 0.5 * cmd_vel_.angular.z;
217
218      if (!stand_up_cmd) {
219        if (gait.is_standing()) {
220          next_hex_state = Sitting_down;
221        }
222      }
223
224      if (walking_cmd) {
225        next_hex_state = Walking;
226      }
227      break;

State: Walking

This state simply invokes the gait_cycle, which is where it starts walking according to the user input.

State transitions

If the stand_up_cmd is reset, the robot will sit down again. If the walking_cmd is reset the robot goes to the Standing state.

motion_control.cpp:

229    case Walking:
230      gait.gait_cycle(cmd_vel_, &feet);
231      /*
232      std::cerr << "[          ]      feet(x,y,z) G1: (" <<
233        feet.foot[0].position.x << ", " <<
234        feet.foot[0].position.y << ", " <<
235        feet.foot[0].position.z << ")" <<
236        std::endl;
237      */
238
239      if (!stand_up_cmd) {
240        if (gait.is_standing()) {
241          next_hex_state = Sitting_down;
242        }
243      }
244      if (!walking_cmd) {
245        if (gait.is_standing()) {
246          next_hex_state = Standing;
247        }
248      }
249      break;

State: Sitting_down

This is the reversed motion from standing up.

motion_control.cpp:

251    case Sitting_down:
252      current_height += sit_step_height;
253      if (current_height >= sitting_height) {
254        current_height = sitting_height;
255        next_hex_state = Sitting;
256      }
257
258      for (int c = 0; c < NUMBER_OF_LEGS; c++) {
259        // feet.foot[c].position.x = neutral_foot_pos_x[c];
260        // feet.foot[c].position.y = neutral_foot_pos_y[c];
261        feet.foot[c].position.z = current_height;
262      }
263      break;

Transmitting the Results

After the State logic the new states are assigned:

motion_control.cpp:

271hex_state = next_hex_state;

And the leg joint angles are translated into joint_states which are then transmitted to the servo driver:

motion_control.cpp:

275// transmit the servo positions
276if (hex_state != Idle) {
277  if (inverse_kinematics.calculate_ik(feet, body, &legs)) {
278    int i = 0;
279    for (int leg_index = 0; leg_index < NUMBER_OF_LEGS; leg_index++) {
280      joint_state_.position[i] = legs.leg[leg_index].coxa;
281      i++;
282      joint_state_.position[i] = legs.leg[leg_index].femur;
283      i++;
284      joint_state_.position[i] = legs.leg[leg_index].tibia;
285      i++;
286    }
287    servo_driver.transmit_servo_positions(joint_state_);
288  } else {
289    RCLCPP_WARN(rclcpp::get_logger("Motion Control"), "Out of Range");
290    feet = old_feet;  // Do not move any foot if one is out of range
291  }
292}
293
294old_feet = feet;

Functions

Not all functions are listed here, there are some more in the code, some of which are unused, obsolete or used for testing.

Sit Down/Stand Up

Commands used to trigger transitions. Only works if the start_cmd flag is set.

motion_control.cpp:

126void MotionControl::stand_up(void)
127{
128  if (start_cmd) {
129    RCLCPP_INFO(rclcpp::get_logger("Motion Control"), "Stand up");
130    stand_up_cmd = true;
131  }
132}
133
134void MotionControl::sit_down(void)
135{
136  if (start_cmd) {
137    RCLCPP_INFO(rclcpp::get_logger("Motion Control"), "Sit down");
138    stand_up_cmd = false;
139  }
140}

Start & Stop Command

This command first checks if the hexapod is sitting, if yes it sets the flag to either turn the servo motors on or off.

motion_control.cpp:

143void MotionControl::start_stop(void)
144{
145  if (stand_up_cmd) {return;}
146  if (start_cmd) {
147    RCLCPP_INFO(rclcpp::get_logger("Motion Control"), "Stop");
148    start_cmd = false;
149  } else {
150    RCLCPP_INFO(rclcpp::get_logger("Motion Control"), "Start");
151    start_cmd = true;
152  }
153}

Stand/Walk Command

This command sets the flag to either walk or move the body while standing.

motion_control.cpp:

155void MotionControl::stand_walk(void)
156{
157  if (!stand_up_cmd) {return;}
158  if (walking_cmd) {
159    RCLCPP_INFO(rclcpp::get_logger("Motion Control"), "Stand");
160    walking_cmd = false;
161  } else {
162    RCLCPP_INFO(rclcpp::get_logger("Motion Control"), "Walk");
163    walking_cmd = true;
164  }
165}

Update Velocity Command

Sets a new velocity if the robot is in the standing position.

motion_control.cpp:

 93void MotionControl::update_vel(geometry_msgs::msg::Twist cmd_vel)
 94{
 95  // if the sitdown commands arrives the velocity is set to 0.
 96  // So the Hexapod can enter a standing state
 97  if (stand_up_cmd) {
 98    cmd_vel_ = cmd_vel;
 99  } else {
100    cmd_vel_.linear.x = 0;
101    cmd_vel_.linear.y = 0;
102    cmd_vel_.angular.z = 0;
103  }
104}