Skip to content

Controls Tutorial for Motion Profiling #2589

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Jan 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
db5426b
first pass new page
gerth2 Mar 1, 2024
5e569f0
wip2
gerth2 Mar 1, 2024
b42d94d
wip wip wip now I have to go make dinner
gerth2 Mar 1, 2024
1bd3d16
moar wip
gerth2 Mar 7, 2024
a4cb2dd
many things but it be movin
gerth2 Mar 8, 2024
8318e26
WIP getting profiling done
gerth2 Mar 8, 2024
09bfc71
WIP adding profiling
gerth2 Mar 8, 2024
82d3f95
basic profiling working. Including AI-assisted code translation.
gerth2 Mar 8, 2024
54fe002
Further tweaks for tunability and clerer explanation
gerth2 Mar 10, 2024
261cb8d
lint and spelling
gerth2 Mar 10, 2024
05759ed
Fixing setpoint nonresponsive bug
gerth2 Mar 10, 2024
963760d
Furhter typos and bugfixes
gerth2 Mar 10, 2024
8139418
reworded setpoint
gerth2 Mar 10, 2024
c90cb53
reworked naming of signals
gerth2 Mar 10, 2024
8d0cefc
bonk
gerth2 Oct 9, 2024
7e16a4c
Merge remote-tracking branch 'upstream/main' into gerth2_elevator_mot…
gerth2 Oct 9, 2024
cff4af3
Merge remote-tracking branch 'upstream/main' into gerth2_elevator_mot…
gerth2 Nov 22, 2024
403fad8
Merge remote-tracking branch 'origin/gerth2_elevator_motion_profiling…
gerth2 Nov 22, 2024
641a312
Merge branch 'wpilibsuite:main' into gerth2_elevator_motion_profiling
gerth2 Dec 19, 2024
f1c2556
rewrote trapezoid profile
gerth2 Dec 19, 2024
3a3adf9
Air resistance is now less arbitrary
gerth2 Jan 2, 2025
f7352ef
Update source/docs/software/advanced-controls/introduction/tuning-ele…
gerth2 Jan 3, 2025
f879c2f
Update source/docs/software/advanced-controls/introduction/tuning-ele…
gerth2 Jan 3, 2025
6f6757c
Update source/docs/software/advanced-controls/introduction/tuning-ele…
gerth2 Jan 3, 2025
9178c61
Update source/docs/software/advanced-controls/introduction/tuning-ele…
gerth2 Jan 3, 2025
3802f13
Update source/docs/software/advanced-controls/introduction/tuning-ele…
gerth2 Jan 3, 2025
3777398
Update source/docs/software/advanced-controls/introduction/tuning-ele…
gerth2 Jan 3, 2025
0b78585
Cleanups from Jason's requests
gerth2 Jan 3, 2025
0b6f7e9
lint
gerth2 Jan 3, 2025
b5b0823
Update source/docs/software/advanced-controls/introduction/tuning-ele…
gerth2 Jan 4, 2025
816e710
Added list of links for all tutorials
gerth2 Jan 4, 2025
5739ca0
oops
gerth2 Jan 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions source/_extensions/controls_js_sim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_js_file("pid-tune.js")
app.add_css_file("pid-tune.css")

print("Done.")

return {
"parallel_read_safe": True,
"parallel_write_safe": True,
Expand Down
7 changes: 5 additions & 2 deletions source/_extensions/controls_js_sim/base/base-visualization.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class BaseVisualization {

window.addEventListener("resize", this.updateSize.bind(this));
window.addEventListener("load", this.updateSize.bind(this));

this.position = 0;
this.positionPrev = 0;
}

updateSize() {
Expand All @@ -43,8 +46,8 @@ class BaseVisualization {
this.timeS = timeS;
}

setCurPos(positionRad) {
this.positionRad = positionRad;
setCurPos(position) {
this.position = position;
}

setCurSetpoint(setpoint) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
class VerticalElevatorPlant {
constructor(TimestepS, heightM) {
this.TimestepS = TimestepS;


this.mass = 20.0; // in kg

// Gains estimated by ReCalc (http://reca.lc) with the specs below
// motor: 1x REV Neo
// gearing: 3:1
// Pulley Diameter: 1 in
// efficiency: 75
// moving mass: 20 kg
this.kGVolts = 2.28;
this.kVVoltSecondPerM = 3.07;
this.kAVoltSecondSquaredPerM = 0.41;

//Factor for loosely-modeled air resistance for some unmodeled disturbance
// See https://www.grc.nasa.gov/www/k-12/VirtualAero/BottleRocket/airplane/falling.html
let r = 1.225; //Air density at sea level
let Cd = 1.42; // Drag Coefficient for a hollow hemisphere pointed the "wrong way" into the wind
let A = 1.5; //Exposed Area (m^2)
this.airResistanceCoef = Cd * r * A / 2.0;


//Maximum height the elevator travels to
this.heightM = heightM;

this.state = [0, 0];

this.systemNoise = false;
// Simulate half volt std dev system noise at sim loop update frequency
this.gaussianNoise = gaussian(0, 0.5);
}

acceleration([posM, velMPerS], inputVolts) {
if (this.systemNoise) {
inputVolts += this.gaussianNoise();
}

const gravityAcceleration = -this.kGVolts / this.kAVoltSecondSquaredPerM;
const EMFAcceleration = -this.kVVoltSecondPerM * velMPerS / this.kAVoltSecondSquaredPerM;
const controlEffortAcceleration = inputVolts / this.kAVoltSecondSquaredPerM;
const airResistanceAccel = -1.0 * Math.sign(velMPerS) * Math.pow(velMPerS, 2) * this.airResistanceCoef / this.mass;

var springAccel = 0.0;
var dashpotAccel = 0.0;

//Apply hard-stops as a combo spring + dashpot
if(posM > this.heightM){
//Top limit
springAccel = (posM - this.heightM) * -100000.0;
dashpotAccel = -100.0 * velMPerS;
} else if(posM < 0.0){
//Bottom limit
springAccel = (posM) * -100000.0;
dashpotAccel = -100.0 * velMPerS;
}

const accelMPerSSquared = gravityAcceleration + EMFAcceleration + controlEffortAcceleration + airResistanceAccel + springAccel + dashpotAccel;


return [velMPerS, accelMPerSSquared]
}

reset() {
this.state = [0, 0];
}

update(inputVolts) {
// Simulate system noise
if (this.systemNoise && inputVolts > 0) {
// apply system noise
inputVolts += this.gaussianNoise();
}

this.state =
secondOrderRK4((state, inputVolts) => this.acceleration(state, inputVolts),
this.state,
inputVolts,
this.TimestepS);
}

getPositionM() {
return this.state[0];
}

setSystemNoise(enabled) {
this.systemNoise = enabled;
}
}
154 changes: 154 additions & 0 deletions source/_extensions/controls_js_sim/sim/vertical-elevator-sim.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
class VerticalElevatorSim extends BaseSim {
constructor(divIdPrefix) {
super(divIdPrefix, "M", -0.5, 2.5);

this.simDurationS = 5.0;
this.simulationTimestepS = 0.005;
this.controllerTimestepS = 0.02;

this.elevHeightM = 1.0;

this.plant = new VerticalElevatorPlant(this.simulationTimestepS, this.elevHeightM);

this.visualization = new VerticalElevatorVisualization(
this.visualizationDrawDiv,
this.simulationTimestepS,
this.elevHeightM,
() => this.iterationCount - 1,
setpoint => this.setSetpointM(setpoint),
() => this.begin()
);
this.visualization.drawStatic();

this.timeSinceLastControllerIteration = 0.0;

this.accumulatedError = 0.0;
this.previousPositionError = 0.0;

//User-configured feedback
this.kP = 0.0;
this.kI = 0.0;
this.kD = 0.0;

//User-configured Feed-Forward
this.kG = 0.0;
this.kV = 0.0;
this.kA = 0.0;

//User-configured Profiling
this.maxVelMps = 0.0;
this.maxAccelMps2 = 0.0;

this.inputVolts = 0.0;

//Default starting setpoint
this.goal = new ProfileState(0.0, 0.0, 0.0);

// Reset at least once right at the start
this.resetCustom();
}

setSetpointM(setpoint) {
this.goal = new ProfileState(setpoint, 0.0, 0.0);
document.getElementById(this.divIdPrefix + "_setpoint").value = setpoint;
}

resetCustom() {
this.plant.reset();
this.timeS = Array(this.simDurationS / this.simulationTimestepS)
.fill()
.map((_, index) => {
return index * this.simulationTimestepS;
});

this.visualization.setCurPos(0.0);
this.visualization.setCurTime(0.0);
this.visualization.setCurSetpoint(0.0);
this.visualization.setCurControlEffort(0.0);

this.accumulatedError = 0.0;
this.previousPositionError = 0.0;
this.inputvolts = 0.0;
this.iterationCount = 0;

this.makeProfile()

this.positionDelayLine = new DelayLine(13); //models sensor lag

}

makeProfile(){
var constraints = new ProfileConstraints(this.maxVelMps, this.maxAccelMps2);
this.profile = new TrapezoidProfile(constraints)
this.start = new ProfileState(0.0,0.0,0.0);
this.setpoint = this.start;
this.profile.init(this.start, this.goal)
}

iterateCustom() {

this.curSimTimeS = this.timeS[this.iterationCount];

let measuredPositionM = this.positionDelayLine.getSample();

// Update controller at controller freq
if (this.timeSinceLastControllerIteration >= this.controllerTimestepS) {
this.setpoint = this.profile.calculate(this.curSimTimeS, this.setpoint)
this.inputVolts = this.updateController(this.setpoint, measuredPositionM);
this.timeSinceLastControllerIteration = this.controllerTimestepS;
} else {
this.timeSinceLastControllerIteration = this.timeSinceLastControllerIteration + this.simulationTimestepS;
}

this.plant.update(this.inputVolts);

this.positionDelayLine.addSample(this.plant.getPositionM());

this.visualization.setCurPos(this.plant.getPositionM());
this.visualization.setCurTime(this.curSimTimeS);
this.visualization.setCurSetpoint(this.setpoint.pos);
this.visualization.setCurControlEffort(this.inputVolts);

this.procVarActualSignal.addSample(new Sample(this.curSimTimeS, this.plant.getPositionM()));
this.procVarDesiredSignal.addSample(new Sample(this.curSimTimeS, this.setpoint.pos));
this.voltsSignal.addSample(new Sample(this.curSimTimeS, this.inputVolts));

this.iterationCount++;

if (this.iterationCount >= this.timeS.length) {
this.end();
}
}

updateController(setpoint, measurement) {

// Calculate error, error derivative, and error integral
let positionError = setpoint.pos - measurement;

this.accumulatedError += positionError * this.controllerTimestepS;

const derivativeError =
(positionError - this.previousPositionError) / this.controllerTimestepS;

// PID + gravity/Profiled feed-forward control law
let controlEffortVolts =
this.kG +
this.kV * setpoint.vel +
this.kA * setpoint.accel +
this.kP * positionError +
this.kI * this.accumulatedError +
this.kD * derivativeError;

// Cap voltage at max/min of the physically possible command
if (controlEffortVolts > 12) {
controlEffortVolts = 12;
} else if (controlEffortVolts < -12) {
controlEffortVolts = -12;
}

this.previousPositionError = positionError;

return controlEffortVolts;
}

}
Loading