Orion MAIN_MODE_FULL_AI_CONTROL の速度制御¶
この文書は MAIN_MODE_FULL_AI_CONTROL で Core/Src/state_func.c の maintaskRun() から呼ばれる速度制御系を、現行ソースコードに合わせて整理したものです。
主制御は TIM7 割り込み内で 500Hz(2ms 周期)実行されます。
速度指令から車輪出力までの制御ブロック図¶
MAIN_MODE_FULL_AI_CONTROL での 4 輪駆動を示します。矢印は値の流れで、Main 制御基板上の制御演算は 500 Hz 周期で実行します。最後の CAN 指令より先の BLDC 基板内部制御はこのリポジトリの対象外です。
flowchart TB
CMD["上位指令: 並進速度の大きさ・方向<br/>目標ヨー角・加速度/角速度制限"]
XY["極座標 → グローバル XY<br/>IMU yaw で機体ローカル XY へ変換<br/>X×1.1、Y×1.3"]
ACC["内部速度目標との差から加速度方向を決定<br/>加速度制限・条件付き boost"]
INT["加速度をグローバル座標で積分<br/>現在の内部速度目標をローカル座標へ戻す"]
THETA["目標ヨー角と IMU yaw の角度制御<br/>目標旋回速度と yaw 減衰を生成"]
KIN["4 輪オムニ逆運動学<br/>ローカル XY 速度 + 目標旋回速度 → 各輪目標 rps"]
ANG["各輪の目標 rps を積分 → 目標角度"]
CTRL["各輪の出力計算<br/>角度 P + rps 偏差項 + yaw 減衰 + rps FF"]
LIMIT["出力を ±60 に制限"]
CAN["CAN 指令<br/>右前・右後: CAN1 ID 0x100/0x101<br/>左後・左前: CAN2 ID 0x102/0x103"]
DRIVE["左右 BLDC 基板 → 4 輪モータ"]
FB["CAN フィードバック<br/>各輪の実 rps・エンコーダ角度"]
IMU["IMU yaw・yaw 角速度"]
CMD --> XY --> ACC --> INT --> KIN --> ANG --> CTRL --> LIMIT --> CAN
CMD --> THETA --> KIN
THETA --> CTRL
INT -->|現在の内部速度目標| ACC
IMU --> XY
IMU --> INT
IMU --> THETA
CAN --> DRIVE --> FB
FB --> CTRL
モータ高温時は加速度の boost を省略します。
各輪の出力は clamp(Kp × 目標角度と実角度の差, ±15) - Kd × (実 rps - 目標 rps) + ROBOT_RADIUS × yaw_rps_drag + 目標 rps です。omniMoveIndiv() がこれを ±60 に制限して CAN へ送ります。output.motor_voltage は変数名であり、BLDC 基板へ送る duty / 電圧相当の指令値です。実際のモータ端子電圧ではありません。
この図の並進速度ループは、内部速度目標と上位指令の差を使います。MAIN_MODE_FULL_AI_CONTROL では実並進速度を直接 PID していません。実 rps とエンコーダ角度は各輪の制御へ戻ります。
全体の流れ¶
MAIN_MODE_FULL_AI_CONTROL の maintaskRun() は、次の順序で指令を処理します。
- 上位指令をローカル速度目標へ変換する。
- 加速度制限値を決める。
- 現在の速度目標
target.local_vel_nowを加速度制限で追従させる。 - ヨー角目標から旋回速度
target.yaw_rpsを決める。 - 並進速度と旋回速度を各オムニホイールの目標 rps と目標角度へ変換する。
- ホイール角度差、実 rps、ヨー減衰を使って各モータ出力を計算する。
- 各輪の出力を制限して CAN へモータ指令を送る。
呼び出し順は以下です。
setLocalTargetSpeed()
setTargetAccel()
accelControl()
accelBoost() // モータ高温時は省略
speedControl()
thetaControl()
setTargetOmniAngle()
clearOmniRotationAngleErrorIfStopped()
omniAngleControl()
omniMoveIndiv()
入力と座標系¶
上位指令は RobotCommandV2 で受信します。
現在サポートされている制御モードは POLAR_VELOCITY_TARGET_MODE です。
target_global_velocity_r: グローバル座標での速度スカラーtarget_global_velocity_theta: グローバル座標での速度方向target_global_theta: グローバル座標での目標ヨー角acceleration_limit: 並進加速度制限linear_velocity_limit: 現状の速度制御レイヤでは直接使っていないangular_velocity_limit: ヨー速度制限
setLocalTargetSpeed() では、極座標の速度指令をグローバル XY に戻し、IMU yaw を使ってローカル XY に変換します。
その後、指令値段階の補正として X に 1.1 倍、Y に 1.3 倍を掛けています。
制御内部では以下の速度を分けて持ちます。
| 変数 | 意味 |
|---|---|
target.local_vel |
上位指令から得た最終的なローカル速度目標 |
target.local_vel_now |
加速度制限を通した現在のローカル速度目標 |
target.global_vel_now |
local_vel_now の積分をグローバル座標で保持したもの |
加速度制限¶
setTargetAccel() は上位指令の acceleration_limit を採用します。
ただし 3.0 未満または 20 超過の場合は 3.0 にフォールバックします。
accelControl() は target.local_vel - target.local_vel_now を速度誤差として見ます。
誤差方向へ、スカラー値 accel_target の加速度ベクトルを作ります。
ここでは速度誤差の大きさで加速度を弱めず、常に最大加速度を使います。
speedControl() は加速度をグローバル座標に変換してから target.global_vel_now に積分します。
ロボットが旋回していても、慣性をグローバル座標で扱うためです。
最後に target.global_vel_now をローカル座標へ戻し、次周期の target.local_vel_now とします。
速度誤差が 1 周期分の加速度以下になった場合は、目標速度へ直接スナップします。 これにより目標付近で小さな積分誤差が残るのを防いでいます。
accelBoost()¶
accelBoost() は加速度指令に X 方向の追加補正を掛ける層です。
- X 方向加速度比率が大きい場合、X 加速度へ最大 1.5 倍の boost を掛ける。
- boost gain が 1.0 未満または 2.0 超過になった場合は 1.0 に戻す。
maintaskRun() ではモータ高温時に accelBoost() を呼ばないため、温度が高い場合は加速補正を抑制します。
ヨー角制御¶
thetaControl() は target_global_theta と IMU yaw の角度差から target.yaw_rps を作ります。
- 角加速度上限は
8 * 2π rad/s^2。 - 目標角との差が 10deg 未満の範囲では、加速上限を半分にする。
- 1deg 未満はデッドゾーンとして扱う。
v = sqrt(2ax)の形で、目標角まで止まり切れるヨー速度を計算する。- 計算したヨー速度は
angular_velocity_limitで制限する。 target.yaw_rpsは 1 周期あたりの増分で制限し、急な立ち上がりを抑える。
また IMU の実ヨー角速度と目標ヨー速度との差から target.yaw_rps_drag を作ります。
この値は各ホイール出力に加算され、旋回方向の慣性を打ち消す減衰項として働きます。
ホイール目標生成¶
setTargetOmniAngle() は並進速度 target.local_vel_now と旋回速度 target.yaw_rps を 4 輪の目標 rps に変換します。
使用しているホイール角は以下です。
| index | 位置 | 角度 |
|---|---|---|
| 0 | Front Right | 30deg |
| 1 | Back Right | 315deg |
| 2 | Back Left | 225deg |
| 3 | Front Left | 150deg |
旋回成分は ROBOT_RADIUS * target.yaw_rps として各輪に同じ符号で足します。
ホイール 1 回転あたりの移動量は OMNI_DIAMETER * π です。
各輪では、目標 rps を 500Hz で積分して target.omni_angle[i].angle_rad を更新します。
この目標角度は、モータ基板から返る実角度との差分を使うための内部位相指令です。
ホイール角度制御¶
omniAngleControl() は各輪ごとに以下を計算します。
angle_diff = target angle - actual motor angle
rps_diff = actual rps - target rps
motor_voltage =
clamp(angle_diff * omni_angle_kp, 15)
- rps_diff * omni_angle_kd
+ ROBOT_RADIUS * yaw_rps_drag
+ current_target_rps
役割は以下です。
| 項 | 役割 |
|---|---|
angle_diff * kp |
目標角度へ追従する主フィードバック |
- rps_diff * kd |
rps 差を抑える速度フィードバック |
ROBOT_RADIUS * yaw_rps_drag |
ヨー方向の減衰補正 |
current_target_rps |
目標 rps のフィードフォワード |
target.omni_angle_kp と target.omni_angle_kd は起動時にそれぞれ 50 と 1 です。
LPUART1 デバッグ入力で q/a が kp、w/s が kd の倍率調整に使われます。
最終的な output.motor_voltage[i] は omniMoveIndiv() で OMNI_OUTPUT_VOLTAGE_LIMIT = 60.0 に制限され、CAN 経由で各 BLDC 基板へ送られます。
停止条件¶
MAIN_MODE_FULL_AI_CONTROL でも、以下のいずれかを満たす場合は omniStopAll() で 4 輪出力を 0 にします。
sys.stop_flagが true- 上位指令の
stop_emergencyが true - vision が無効
- vision の最終更新から 500ms 超過
sys.stop_flag が立っている周期には clearSpeedContrlValue() が呼ばれ、速度制御内部状態を実オドメトリ側へ寄せます。非常停止指令や vision 喪失のみでゼロ出力となった周期には、この関数は呼ばれません。
具体的には omni.local_odom_speed_mvf をグローバル速度へ変換し、target.global_vel_now に入れます。
これにより停止解除時の速度目標が実速度から再開し、内部速度状態だけが飛ぶのを避けます。
実速度・オドメトリとの関係¶
omniOdometryUpdate() はモータ角度差からローカル速度、グローバル速度、オドメトリを更新します。
速度制御の直接フィードバックには target.local_vel_now を使っており、MAIN_MODE_FULL_AI_CONTROL の加速度制御では実速度 omni.local_odom_speed_mvf を直接使っていません。
実速度が速度制御へ入る主な経路は以下です。
- 停止中または停止要求時の
clearSpeedContrlValue() - デバッグ表示と送信 telemetry
- オドメトリ / vision 統合位置推定
現在の速度制御は「実速度を直接 PID する」のではなく、「加速度制限済みの内部速度目標を作り、各ホイールの角度追従で実現する」構成です。
注意点¶
linear_velocity_limitはRobotCommandV2に存在しますが、現行のsetLocalTargetSpeed()では速度ベクトルの制限に使われていません。SPEED_SCALAR_LIMITはrobot_control.cに定義されていますが、現行実装では未使用です。accelBoost()内でaccel_scalarが 0 の場合、X 加速度比率の計算で 0 除算の可能性があります。現状は後段の gain 保護で大きな異常値を 1 に戻す意図に見えますが、将来修正するなら先にaccel_scalar <= 0を判定する方が明確です。omniMoveIndiv()の引数名はout_limitですが、実際にはモータ基板へ送る duty / 電圧相当の制限値として使われています。