Skip to content

Feedforward speed control with shared battery compensation - #118

Draft
SaintSampo wants to merge 4 commits into
mainfrom
speedRefactor
Draft

Feedforward speed control with shared battery compensation#118
SaintSampo wants to merge 4 commits into
mainfrom
speedRefactor

Conversation

@SaintSampo

@SaintSampo SaintSampo commented Aug 10, 2026

Copy link
Copy Markdown
Member

Summary

Reworks the motor speed loop from a pure PI controller into feedforward + P with battery compensation, and cleans up the speed math along the way. Feedforward (kS to break stiction, kV per unit speed) does the bulk of the work.

Changes

Speed control law (encoded_motor.py)

  • Effort is now kS·sign(target) + kV·target + kP·error, scaled by battery voltage_scale. Integral dropped (with feedforward carrying the load, the residual is small and voltage comp absorbs droop).
  • kS/kV are per-motor attributes; kP is the injectable controller's kp (set_speed_controller still works).
  • Per-board defaults tuned on hardware: Beta kS=0.12, kV=0.02, kp=0.1; Nano kS=0.1, kV=0.00122, kp small (Nano's high gear ratio/resolution makes the P loop go unstable above ~0.03).

Battery compensation centralized on Board (board.py)

  • voltage_scale + update_voltage_compensation() now live on Board, measured once at construction and shared by every consumer. Removes the duplicate copies the drivetrain and motors each held.

Speed-math cleanup (encoded_motor.py, differential_drive.py)

  • _UPDATE_PERIOD_MS/_UPDATE_HZ constants replace the magic period=20 / 60*50 that had to agree by hand.
  • _rpm_to_counts_per_update / _counts_per_update_to_rpm hold the conversion in one place (get_speed/set_speed call them).
  • Renamed the misleading self.speed (counts-per-tick) to self._counts_per_update.
  • set_speed(0/None) now resets prev_speed so the direction-change reset can't key off a stale value.

API notes

  • DifferentialDrive.update_voltage_compensation() removed — call Board.get_default_board().update_voltage_compensation() instead (e.g. after a battery swap).
  • Board now measures the battery once at construction (one 8-sample read); on the Nano this leaves the shared VIN/RM2 pin restored to output, which is friendlier to BLE than before.

Centralize and clarify encoder timing and unit conversions. In encoded_motor.py introduce _UPDATE_PERIOD_MS/_UPDATE_HZ, conversion helpers (_counts_per_update_to_rpm, _rpm_to_counts_per_update), and store counts-per-update instead of ambiguous `speed`. Use the update period constant for the virtual timer and for rpm<->counts conversions. Update set_speed/get_speed/inner update logic to use the new helpers and names. In differential_drive.py rename cmpsToRPM to snake_case cmps_to_rpm for naming consistency. Improves readability and correctness of rpm/count calculations.
Move battery voltage measurement/compensation onto Board (nominal voltage per platform, update_voltage_compensation(), voltage_scale measured at init). DifferentialDrive now reads board.voltage_scale (removed its own redundant voltage logic). EncodedMotor switched to feedforward+P velocity control (kS/kV per platform), holds Board reference, applies voltage_scale to motor effort, and clamps effort to [-1,1]. Minor PID/integral changes to match the new feedforward approach.
Update EncodedMotor defaults: NanoXRP now uses zeroed feedforward (kS/kV = 0.00) with a P-only PID (kp=0.015). Other platforms use kS=0.12, kV=0.02 and a stronger P-only PID (kp=0.1). Also clear prev_speed (set to 0) when stopping so direction is forgotten when the controller is cleared.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant