Description de l’espace-état d’un controleur

Note

Avant de poursuivre la lecture de ce didacticiel, il est recommandé aux lecteurs d’avoir pris connaissance du document Introduction au Contrôle de l’Espace-État.

L’objectif de ce didacticiel est de fournir des instructions de « bout à bout » sur la mise en œuvre dans l’espace d’état d’un contrôleur pour un volant d’inertie. En parcourant ce didacticiel, les lecteurs apprendront à:

  1. Créer un modèle espace-état précis d’un volant d’inertie à l’aide de l’identification de système ou d’un logiciel CAO.

  2. Implémenter un filtre de Kalman pour filtrer les mesures de vitesse de l’encodeur sans décalage.

  3. implémenter une commande LQR de rétroaction qui, lorsqu’elle est combinée avec le modèle de commande prédictive, générera les tensions d’entrée afin de conduire le volant d’inertie à la référence.

Ce tutoriel se veut être simple pour faciliter la tâche aux équipes n’ayant pas beaucoup d’expertise en programmation. Bien que la bibliothèque WPILib offre une flexibilité significative dans la manière dont ses fonctionnalités de contrôle dans l’espace d’état sont implémentées, parcourir attentivement l’implémentation décrite dans ce didacticiel devrait fournir aux équipes une structure de base qui peut être réutilisée pour une variété de systèmes espace-état.

The full example is available in the state-space flywheel (Java/C++) and state-space flywheel system identification (Java/C++) example projects.

Pourquoi utiliser le contrôle espace-état ?

Étant donné que la commande de l’espace-état se concentre sur la création d’un modèle précis de notre système, nous pouvons adéquatement prédire comment notre modèle va répondre aux entrées des commandes. Cela nous permet de simuler nos mécanismes sans accès à un robot physique, ainsi que de choisir facilement les gains que nous savons parfaitement fonctionnels. Avoir un modèle nous permet également de créer des filtres sans retard, comme les filtres de Kalman, pour filtrer de manière optimale les signaux obtenus à partir des capteurs.

Modélisation de notre volant d’inertie

Rappelons que les systèmes continus espace-état sont modélisés à l’aide du système d’équations suivant:

\[\begin{split}\dot{\mathbf{x}} &= \mathbf{A}\mathbf{x} + \mathbf{B}\mathbf{u} \\ \mathbf{y} &= \mathbf{C}\mathbf{x} + \mathbf{D}\mathbf{u}\end{split}\]

Where x-dot is the rate of change of the system’s state, \(\mathbf{x}\) is the system’s current state, \(\mathbf{u}\) is the input to the system, and \(\mathbf{y}\) is the system’s output.

Utilisons ce système d’équations pour modéliser notre volant d’inertie de deux manières différentes. Nous allons d’abord le modéliser en utilisant identification du système à l’aide de la suite d’outils SysId, puis le modéliser en fonction du moment d’inertie du moteur et du volant.

The first step of building up our state-space system is picking our system’s states. We can pick anything we want as a state – we could pick completely unrelated states if we wanted – but it helps to pick states that are important. We can include hidden states in our state (such as elevator velocity if we were only able to measure its position) and let our Kalman Filter estimate their values. Remember that the states we choose will be driven towards their respective references by the feedback controller (typically the Linear-Quadratic Regulator since it’s optimal).

For our flywheel, we care only about one state: its velocity. While we could chose to also model its acceleration, the inclusion of this state isn’t necessary for our system.

Ensuite, nous identifions les entrées dans notre système. Les entréss peuvent être considérées comme des paramètres que nous pouvons mettre « dans » notre système pour en changer l’état. Dans le cas du volant d’inertie (et de nombreux autres mécanismes à simple articulation en FRC®), nous n’avons qu’une seule entrée : la tension appliquée sur le moteur. En choisissant la tension comme entrée (plutôt qu’un paramètre comme le cycle de travail du moteur), nous pouvons compenser la réduction de la tension de la batterie à mesure que la charge de la batterie augmente.

Un système espace-état en temps continu écrit x-point, ou le taux de variation instantané de l’état du système, comme proportionnel à l’état actuel et ses entrées. Puisque notre état est la vitesse angulaire, \(\mathbf{\dot{x}}\) sera l’accélération angulaire du volant d’inertie.

Ensuite, nous allons modéliser notre volant d’inertie comme un système espace-état en temps continu. Le LinearSystem de WPILib convertira ce système en temps discret selon un mécanisme interne. Relisez la notation espace-état pour en savoir d’avantage sur les systèmes en temps continu et en temps discret.

Modélisation par identification du système

Pour reformuler ceci dans la notation espace-état en faisant usage de l’idenfication de système, en nous rappelant du volant d’inertie exemple de notation espace-état, où nous re-écrivons l’équation suivante en fonction de \(\mathbf{a}\).

\[\begin{split}V = kV \cdot \mathbf{v} + kA \cdot \mathbf{a}\\ \mathbf{a} = \mathbf{\dot{v}} = \begin{bmatrix}\frac{-kV}{kA}\end{bmatrix} v + \begin{bmatrix}\frac{1}{kA}\end{bmatrix} V\end{split}\]

\(\mathbf{v}\) est la vitesse du volant d’inertie, \(\mathbf{a}\) et \(\mathbf{\dot{v}}\) sont l’accélération de la roue d’inertie, et \(V\) est la tension. En reformulant ce qui précède selon la représentation conventionnelle \(\mathbf{x}\) pour le vecteur d’état et \(\mathbf{u}\) pour le vecteur d’entrée, nous trouvons:

\[\mathbf{\dot{x}} = \begin{bmatrix}\frac{-kV}{kA} \end{bmatrix} \mathbf{x} + \begin{bmatrix}\frac{1}{kA} \end{bmatrix} \mathbf{u}\]

La deuxième partie de la notation espace-état relie les termes actuels du système état et entrées à la sortie. Dans le cas d’un volant d’inertie, notre vecteur de sortie \(\mathbf{y}\) (ou toute autre grandeur que nos capteurs peuvent mesurer) est la vitesse de notre volant d’inertie, qui se trouve également être un élément de notre vecteur d’état \(\mathbf{x}\). Par conséquent, notre matrice de sortie est \(\mathbf{C} = \begin{bmatrix}1 \end{bmatrix}\), et notre matrice du système de la traversée est \(\mathbf{D} = \begin{bmatrix}0 \end{bmatrix}\). En reformulant ce qui précède selon la notation espace-état en temps continu conduit au résultat suivant.

\[\begin{split}\mathbf{\dot{x}} &= \begin{bmatrix}\frac{-kV}{kA} \end{bmatrix} \mathbf{x} + \begin{bmatrix}\frac{1}{kA} \end{bmatrix} \mathbf{u} \\ \mathbf{y} &= \begin{bmatrix}1\end{bmatrix} \mathbf{x} + \begin{bmatrix}0\end{bmatrix} \mathbf{u}\end{split}\]

La classe LinearSystem contient des méthodes permettant de créer facilement des systèmes espace-état identifiés à l’aide de la méthode d’identification de système. Cet exemple montre un modèle volant d’inertie avec un kV de 0.023 et un kA de 0.001 :

33  // Volts per (radian per second)
34  private static final double kFlywheelKv = 0.023;
35
36  // Volts per (radian per second squared)
37  private static final double kFlywheelKa = 0.001;
38
39  // The plant holds a state-space model of our flywheel. This system has the following properties:
40  //
41  // States: [velocity], in radians per second.
42  // Inputs (what we can "put in"): [voltage], in volts.
43  // Outputs (what we can measure): [velocity], in radians per second.
44  //
45  // The Kv and Ka constants are found using the FRC Characterization toolsuite.
46  private final LinearSystem<N1, N1, N1> m_flywheelPlant =
47      LinearSystemId.identifyVelocitySystem(kFlywheelKv, kFlywheelKa);

Modélisation à l’aide du moment d’inertie du volant d’inertie et de l’engrenage.

Un volant d’inertie peut également être modélisé sans avoir accès à un robot physique, en utilisant des informations sur les moteurs, l’engrenage et le volant d’inertie moment d’inertie. Une dérivation complète de ce modèle est présentée dans la Section 8.2.1 du document Controls Engineering in FRC.

La classe LinearSystem contient des méthodes pour créer facilement un modèle de volant d’inertie à partir de ses moteurs, de l’engrenage et du moment d’inertie. Le moment d’inertie peut être calculé à l’aide d’un logiciel CAO ou en utilisant la physique du système. Les exemples utilisés ici sont détaillés dans le projet d’exemple du volant d’inertie (Java/C++).

Note

Pour les classes espace-état de la WPILib, l’engrenage est modélisé par une fonction de sortie sur entrée – c’est-à-dire que si le volant d’inertie tourne plus lentement que les moteurs, ce nombre devrait être supérieur à un.

Note

La classe C++ LinearSystem utilise librairie d’unités C++ pour prévenir les mélanges des unités et se conformer à l’analyse dimensionnelle.

34  private static final double kFlywheelMomentOfInertia = 0.00032; // kg * m^2
35
36  // Reduction between motors and encoder, as output over input. If the flywheel spins slower than
37  // the motors, this number should be greater than one.
38  private static final double kFlywheelGearing = 1.0;
39
40  // The plant holds a state-space model of our flywheel. This system has the following properties:
41  //
42  // States: [velocity], in radians per second.
43  // Inputs (what we can "put in"): [voltage], in volts.
44  // Outputs (what we can measure): [velocity], in radians per second.
45  private final LinearSystem<N1, N1, N1> m_flywheelPlant =
46      LinearSystemId.createFlywheelSystem(
47          DCMotor.getNEO(2), kFlywheelMomentOfInertia, kFlywheelGearing);

Filtres de Kalman : Observation de l’état du volant d’inertie

Les filtres de Kalman sont utilisés pour filtrer nos mesures de vitesse à l’aide de notre modèle espace-état pour générer une estimation de l’état \(\mathbf{\hat{x}}\). Comme notre modèle de volant est linéaire, nous pouvons utiliser un filtre de Kalman pour estimer la vitesse du volant. Le filtre de Kalman fournie par la WPILib prend un système linéaire ou LinearSystem (que nous avons trouvé ci-dessus), ainsi que des écarts-types des mesures du modèle et des capteurs. Nous pouvons ajuster le « lissage » de notre de l’état estimé en ajustant ces poids. Les déviations standard plus importants de l’état feront en sorte que le filtre « se conforme moins » à notre estimation de l’état et favorisera plus fortement les nouvelles mesures, tandis que des valeurs plus grandes de déviations standard auront un effet contraire.

Dans le cas d’un volant d’inertie, nous commençons par un écart type d’état de 3 rad/s et un écart type de mesure de 0.01 rad/s. Le choix de ces valeurs est à la discrétion de l’utilisateur - ces poids ont produit un filtre qui était tolérant à certains bruits, mais dont l’estimation de l’état a rapidement réagi aux perturbations externes pour un volant d’inertie - et doivent être réglés pour créer un filtre qui soit conforme à votre volant spécifique. Les tracés des états, des mesures, des entrées, des références et des sorties au fil du temps sont un excellent moyen visuel de régler les filtres de Kalman.

L’effet d’un filtre Kalman, médian et RII sur certaines données du volant d’inertie.

Le graphique ci-dessus montre deux filtres de Kalman réglés différemment, ainsi qu’un filtre IIR simple-pôle et un Filtre médian. Ces données ont été recueillies avec un tireur sur ~5 secondes, et quatre balles ont été lancées à travers le tireur (comme on le voit dans les quatre creux de vitesse). Bien qu’il n’y ait pas de règles strictes sur le choix du bon état et des écarts-types des mesures, ils devraient en général être réglés pour se rapprocher suffisamment du modèle pour éliminer le bruit tout en réagissant rapidement aux perturbations externes.

Comme le contrôleur de rétroaction calcule l’erreur à l’aide du paramètre x-chapeau estimé par le filtre de Kalman, le contrôleur ne réagira aux perturbations qu’aussi rapidement que l’estimation d’état du filtre change. Dans le graphique ci-dessus, la courbe située en haut et à gauche (avec un écart type d’état de 3.0 et un écart type de mesure de 0.2) a produit un filtre qui réagit rapidement aux perturbations tout en rejetant le bruit, tandis que la courbe située en haut et à droite montre un filtre qui a été à peine affecté par les baisses de vitesse.

59  // The observer fuses our encoder data and voltage inputs to reject noise.
60  private final KalmanFilter<N1, N1, N1> m_observer =
61      new KalmanFilter<>(
62          Nat.N1(),
63          Nat.N1(),
64          m_flywheelPlant,
65          VecBuilder.fill(3.0), // How accurate we think our model is
66          VecBuilder.fill(0.01), // How accurate we think our encoder
67          // data is
68          0.020);

Étant donné que les filtres de Kalman utilisent notre modèle espace-état dans L’étape de prédiction, il est important que notre modèle soit aussi précis que possible. Une façon de vérifier cela est d’enregistrer la tension d’entrée et la vitesse d’entrée d’un volant d’inertie au fil du temps, et de rejouer ces données en appelant uniquement predict sur le filtre de Kalman. Ensuite, les gains kV et kA (ou moment d’inertie et autres constantes) peuvent être ajustés jusqu’à ce que le modèle corresponde étroitement aux données enregistrées.

Régulateurs linéaires-quadratiques et commande prédictive inverse de procédé

Le régulateur linéaire-quadratique trouve un contrôleur de rétroaction pour amener notre système volant d’inertie à sa référence. Parce que notre volant n’a qu’un seul état, la loi de commande choisie par notre LQR sera sous la forme \(\mathbf{u = K (r - x)}\)\(\mathbf{K}\) est une matrice 1x1; en d’autres termes, la loi de commande choisie par notre LQR est simplement un contrôleur proportionnel, ou un contrôleur PID avec seulement un gain P. Ce gain est choisi par notre LQR en fonction de l’excursion d’état et des efforts de contrôle que nous lui transmettons. En savoir plus sur le réglage des contrôleurs LQR, consultez LQR application example.

Tout comme le SimpleMotorFeedforward peut être utilisé pour générer des tensions d’entrée de la commande prédictive étant donnés les constantes kS, kV, et kA, la classe Plant Inversion Feedforward génère feedforward les tensions d’entrée étant donné le système espace-état. Les commandes de tension générées par la classe LinearSystemLoop sont la somme des entrées des commandes prédictive et de rétroaction.

60  // A LQR uses feedback to create voltage commands.
61  private final LinearQuadraticRegulator<N1, N1, N1> m_controller =
62      new LinearQuadraticRegulator<>(
63          m_flywheelPlant,
64          VecBuilder.fill(8.0), // qelms. Velocity error tolerance, in radians per second. Decrease
65          // this to more heavily penalize state excursion, or make the controller behave more
66          // aggressively.
67          VecBuilder.fill(12.0), // relms. Control effort (voltage) tolerance. Decrease this to more
68          // heavily penalize control effort, or make the controller less aggressive. 12 is a good
69          // starting point because that is the (approximate) maximum voltage of a battery.
70          0.020); // Nominal time between loops. 0.020 for TimedRobot, but can be
71  // lower if using notifiers.

En rassemblant le tout: LinearSystemLoop

LinearSystemLoop combine notre système, notre contrôleur et notre observateur que nous avons créés plus tôt. Le constructeur montré instancie également un PlantInversionFeedforward.

73  // The state-space loop combines a controller, observer, feedforward and plant for easy control.
74  private final LinearSystemLoop<N1, N1, N1> m_loop =
75      new LinearSystemLoop<>(m_flywheelPlant, m_controller, m_observer, 12.0, 0.020);

Une fois que nous avons obtenu notre LinearSystemLoop, la dernière chose à faire est de l’exécuter. Pour ce faire, nous mettrons périodiquement à jour notre filtre de Kalman avec nos nouvelles mesures de vitesse d’encodeur et lui appliquerons de nouvelles commandes de tension. Dans cette optique, nous avons d’abord défini la référence, puis corrigé avec la vitesse actuelle du volant d’inertie, prédit le filtre de Kalman dans le prochain pas de temps, et appliqué les entrées générées à l’aide de getU.

 96  @Override
 97  public void teleopPeriodic() {
 98    // Sets the target speed of our flywheel. This is similar to setting the setpoint of a
 99    // PID controller.
100    if (m_joystick.getTriggerPressed()) {
101      // We just pressed the trigger, so let's set our next reference
102      m_loop.setNextR(VecBuilder.fill(kSpinupRadPerSec));
103    } else if (m_joystick.getTriggerReleased()) {
104      // We just released the trigger, so let's spin down
105      m_loop.setNextR(VecBuilder.fill(0.0));
106    }
107
108    // Correct our Kalman filter's state vector estimate with encoder data.
109    m_loop.correct(VecBuilder.fill(m_encoder.getRate()));
110
111    // Update our LQR to generate new voltage commands and use the voltages to predict the next
112    // state with out Kalman filter.
113    m_loop.predict(0.020);
114
115    // Send the new calculated voltage to the motors.
116    // voltage = duty cycle * battery voltage, so
117    // duty cycle = voltage / battery voltage
118    double nextVoltage = m_loop.getU(0);
119    m_motor.setVoltage(nextVoltage);
120  }
121}

Angle Wrap with LQR

Mechanisms with a continuous angle can have that angle wrapped by calling the code below instead of lqr.Calculate(x, r).

var error = lqr.getR().minus(x);
error.set(0, 0, MathUtil.angleModulus(error.get(0, 0)));
var u = lqr.getK().times(error);