Skip to content

Commit c6d0540

Browse files
megarubbervirtuald
andauthored
Add StateSpaceFlywheelSysId example (#99)
Issue #49 Original example [is here](https://github.com/wpilibsuite/allwpilib/tree/main/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/examples/statespaceflywheelsysid) --------- Co-authored-by: Dustin Spicuzza <[email protected]>
1 parent d7df94c commit c6d0540

File tree

2 files changed

+112
-0
lines changed

2 files changed

+112
-0
lines changed

StateSpaceFlywheelSysId/robot.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright (c) FIRST and other WPILib contributors.
4+
# Open Source Software; you can modify and/or share it under the terms of
5+
# the WPILib BSD license file in the root directory of this project.
6+
#
7+
8+
import math
9+
import wpilib
10+
import wpimath
11+
import wpimath.units
12+
import wpimath.controller
13+
import wpimath.system
14+
import wpimath.system.plant
15+
import wpimath.estimator
16+
17+
kMotorPort = 0
18+
kEncoderAChannel = 0
19+
kEncoderBChannel = 1
20+
kJoystickPort = 0
21+
kSpinupRadPerSec = wpimath.units.rotationsPerMinuteToRadiansPerSecond(500.0)
22+
23+
# Volts per (radian per second)
24+
kFlywheelKv = 0.023
25+
26+
# Volts per (radian per second squared)
27+
kFlywheelKa = 0.001
28+
29+
30+
class MyRobot(wpilib.TimedRobot):
31+
"""
32+
This is a sample program to demonstrate how to use a state-space controller to control a
33+
flywheel.
34+
"""
35+
36+
def robotInit(self) -> None:
37+
# The plant holds a state-space model of our flywheel. This system has the following properties:
38+
#
39+
# States: [velocity], in radians per second.
40+
# Inputs (what we can "put in"): [voltage], in volts.
41+
# Outputs (what we can measure): [velocity], in radians per second.
42+
#
43+
# The Kv and Ka constants are found using the FRC Characterization toolsuite.
44+
self.flywheelPlant = (
45+
wpimath.system.plant.LinearSystemId.identifyVelocitySystemRadians(
46+
kFlywheelKv, kFlywheelKa
47+
)
48+
)
49+
50+
# The observer fuses our encoder data and voltage inputs to reject noise.
51+
self.observer = wpimath.estimator.KalmanFilter_1_1_1(
52+
self.flywheelPlant,
53+
[3], # How accurate we think our model is
54+
[0.01], # How accurate we think our encoder data is
55+
0.020,
56+
)
57+
58+
# A LQR uses feedback to create voltage commands.
59+
self.controller = wpimath.controller.LinearQuadraticRegulator_1_1(
60+
self.flywheelPlant,
61+
[8], # Velocity error tolerance
62+
[12], # Control effort (voltage) tolerance
63+
0.020,
64+
)
65+
66+
# The state-space loop combines a controller, observer, feedforward and plant for easy control.
67+
self.loop = wpimath.system.LinearSystemLoop_1_1_1(
68+
self.flywheelPlant, self.controller, self.observer, 12.0, 0.020
69+
)
70+
71+
# An encoder set up to measure flywheel velocity in radians per second.
72+
self.encoder = wpilib.Encoder(kEncoderAChannel, kEncoderBChannel)
73+
74+
self.motor = wpilib.PWMSparkMax(kMotorPort)
75+
76+
# A joystick to read the trigger from.
77+
self.joystick = wpilib.Joystick(kJoystickPort)
78+
79+
# We go 2 pi radians per 4096 clicks.
80+
self.encoder.setDistancePerPulse(2 * math.pi / 4096)
81+
82+
def teleopInit(self) -> None:
83+
self.loop.reset([self.encoder.getRate()])
84+
85+
def teleopPeriodic(self) -> None:
86+
# Sets the target speed of our flywheel. This is similar to setting the setpoint of a
87+
# PID controller.
88+
if self.joystick.getTriggerPressed():
89+
# We just pressed the trigger, so let's set our next reference
90+
self.loop.setNextR([kSpinUpRadPerSec])
91+
92+
elif self.joystick.getTriggerReleased():
93+
# We just released the trigger, so let's spin down
94+
self.loop.setNextR([0])
95+
96+
# Correct our Kalman filter's state vector estimate with encoder data.
97+
self.loop.correct([self.encoder.getRate()])
98+
99+
# Update our LQR to generate new voltage commands and use the voltages to predict the next
100+
# state with out Kalman filter.
101+
self.loop.predict(0.020)
102+
103+
# Send the new calculated voltage to the motors.
104+
# voltage = duty cycle * battery voltage, so
105+
# duty cycle = voltage / battery voltage
106+
nextVoltage = self.loop.U()
107+
self.motor.setVoltage(nextVoltage)
108+
109+
110+
if __name__ == "__main__":
111+
wpilib.run(MyRobot)

run_tests.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ BASE_TESTS="
4545
Solenoid
4646
StatefulAutonomous
4747
StateSpaceFlywheel
48+
StateSpaceFlywheelSysId
4849
SwerveBot
4950
TankDrive
5051
TankDriveXboxController

0 commit comments

Comments
 (0)