Cours:Elen4 TNS TP TpsReel : Différence entre versions
(→PID numérique avec un filtre numérique linéaire) |
|||
| (17 révisions intermédiaires par le même utilisateur non affichées) | |||
| Ligne 2 : | Ligne 2 : | ||
<center> | <center> | ||
| − | '''<big>TP6 | + | '''<big>TP6 - Temps réel : bloc Gnuradio en Python / démodulation / PID Numérique</big>''' |
</center> | </center> | ||
| + | Le but de ce TP est double : | ||
| + | * réaliser des implémentations temps réel de filtre IIR en créant un bloc Gnuradio qui execute un traitement codé en Python | ||
| + | * et étudier l'implémentation d'un PID numérique sous forme d'un filtre IIR. | ||
| + | |||
| + | Dans les deux cas, un système temps réel sera considéré : | ||
| + | * à intervalles de temps (réguliers ou non), une valeur ''x(n)'' rentre dans le système | ||
| + | * le travail consiste à calculer ''y(n)'' avec l'équation au différences du filtre, à partir de la valeur ''x(n)'' et de ses versions précédentes ''x(n-1)'' et ''x(n-2)'' (et aussi ''y(n-1)'', ''y(n-2)'') qu'il est donc nécessaire de stocker. | ||
| + | * La notion de ''temps réel'' implique que ''y(n)'' doit être calculer avant qu'une nouvelle valeurs de ''x(n)'' ne rentre dans le système. | ||
| + | |||
| + | === Bloc Gnuradio en Python === | ||
| + | |||
| + | Nous allons implémenter un filtre IIR dans gnuradio, en écrivant le traitement dans un bloc Python. | ||
| + | |||
| + | ==== Filtre moyenneur==== | ||
| + | |||
| + | {{q| a)}} Dans un premier temps, prendre connaissance du tutoriel suivant : [https://wiki.gnuradio.org/index.php?title=Creating_Your_First_Block wiki Gnuradio - ''Creating Your First Block''] | ||
| + | |||
| + | {{q| b)}} Nous allons écrire un filtre simple, qui implémente un simple moyenneur : | ||
| + | <source lang="Python"> | ||
| + | |||
| + | import numpy as np | ||
| + | from gnuradio import gr | ||
| + | |||
| + | class blk(gr.sync_block): | ||
| + | def __init__(self): # Pas de paramètres | ||
| + | gr.sync_block.__init__( | ||
| + | self, | ||
| + | name="Filtre moyenneur", | ||
| + | in_sig=[np.float32], | ||
| + | out_sig=[np.float32] | ||
| + | ) | ||
| + | self.xn1 = 0 # initialisation d'un attribut pour stocker x(n-1) | ||
| + | |||
| + | def work(self, input_items, output_items): | ||
| + | xn = input_items[0][0] # On ne "consomme" qu'un seul échantillon en entrée | ||
| + | yn = (xn + self.xn1) / 2 | ||
| + | self.xn1 = xn | ||
| + | output_items[0][0] = yn # On ne produit donc également qu'un seul échantillon en sortie | ||
| + | return 1 # On retourne le nombre de valeurs consomées | ||
| + | </source> | ||
| + | |||
| + | ==== Filtre IIR : démodulation ==== | ||
| + | |||
| + | En reprenant le signal modulé en amplitude et la fonction de transfert du filtre passe-bas du TP précédent : | ||
| + | |||
| + | * Obtenir l'équation aux différence du filtre, ''ie'' écrire ''y(n)='' en fonction de ''x(n)'', ''x(n-1)'', ''x(n-2)'', ''y(n-1)'' et ''y(n-2)''. | ||
| + | * Créer un nouveau bloc Python dans le module <code>monModule</code> : | ||
| + | ** de type <code>sync</code> | ||
| + | ** une entrée en <code>np.float32</code> | ||
| + | ** une sortie en <code>np.float32</code> | ||
| + | * Écrire le code permettant d'obtenir ''y(n)'' dans la méthode <code>work()</code> : | ||
| + | ** ''x(n)'' est donné par <code>in[0][0]</code> | ||
| + | ** il sera nécessaire de stocker dans des '''attributs''' les valeurs de ''x(n-1)'', ''x(n-2)'', ''y(n-1)'' et ''y(n-2)''. | ||
| + | |||
| + | {{q| a)}} Réaliser cette implémentation du passe-bas et contrôler que la démodulation est identique à celle obtenue au [[Cours:Elen4_TNS_TP_TraitAudioEtDemodulation|TP 5]]. | ||
| + | |||
| + | {{q| b)}} Modifier votre code pour implémenter le filtre complet de démodulation. | ||
| + | |||
| + | === Approximation de PID par filtre numérique linéaire === | ||
| + | |||
| + | Cette partie sera travaillée dans un notebook Jupyter. | ||
| + | |||
| + | ==== Rappels PID ==== | ||
| + | |||
| + | * Principe | ||
| + | |||
| + | [[Fichier:PIDprincipe.png|400px]] | ||
| + | |||
| + | [[Fichier:PIDdetail.png|400px]] | ||
| + | |||
| + | * Définition mathématique, la sortie du controleur PID ''u(t)'' est donnée par: | ||
| + | |||
| + | [[Fichier:EqPID.png|400px]] | ||
| + | |||
| + | ==== PID numérique "classique" ==== | ||
| + | |||
| + | On considère que le temps est discrétisé : ''t = n.''d''t'' où d''t'' est l'intervalle de temps entre deux valeurs. | ||
| + | |||
| + | * En approximant grossièrement l'intégrale et la dérivée <br/>[[Fichier:PIDderivative.png|400px]] [[Fichier:PIDintegral.png|400px]] | ||
| + | ** La dérivée est calculée par ''D = (e(n)-e(n-1))/''d''t'' | ||
| + | ** L'intégrale est approximée en cumulant progressivement : ''I = I + e(n).''d''t'' | ||
| + | C'est ainsi que vous avez implémenté vos PID jusqu'ici. | ||
| + | |||
| + | Voici un notebook exemple à explorer sur deux modélisations : asservissement de 1) température et de 2) position ''z'' d'un drone : [[Media:TNS TP6 starter.ipynb.zip|TNS_TP6_starter.ipynb.zip]] | ||
| + | |||
| + | {{q | a)}} ''Ki = Kd = 0'' => chercher le meilleur ''Kp'' => erreur statique. | ||
| + | |||
| + | {{q | b)}} ''Kd = 0'' => l'introduction du terme integral permet de corriger cette erreur statique. | ||
| + | |||
| + | {{q | c)}} Le terme dérivatif n'est pas nécessaire pour la température | ||
| + | {{q | d)}} Mais l'est pour asservir la position ''z'' du drone. | ||
| + | ==== PID numérique avec un filtre numérique linéaire ==== | ||
| − | Le | + | Nous allons reprendre le PID, par sa fonction de transfert qui exprime un filtre linéaire continu, et construire un filtre linéaire numérique qui l'approxime. |
| − | * | + | |
| − | * | + | |
| + | {{q | a)}} Cherchons la fonction de transfert du filtre continu d'un PID. | ||
| + | <br/>En partant de l'équation différentielle | ||
| + | <br/>[[Fichier:EqPID.png|500px]] | ||
| + | <br/>On applique la transformée de Laplace (dérivée => multiplication par ''s'' ; intégrale => division par ''s'') : | ||
| + | <br/>[[Fichier:eqFxTransfertPID.png|130]] | ||
| + | <br/>Le terme dérivatif étant très sensible au bruit, on lui applique généralement un filtre passe bas (''w0/(w0+s)'') : | ||
| + | <br/>[[Fichier:eqFxTransfertPIDetPB.png|130]] | ||
| + | |||
| + | {{q | b)}} Il s'agit maintenant de transformer la variable ''s'' en une variable ''z'' : | ||
| + | * par transformée bilinéaire : <br/> ''s = (2/Ts)*(z-1)/(z+1)'' (voir le cours) | ||
| + | * ou par ''backward euler'' (version un peu plus simple) : <br/>''s = (z-1)/(Ts*z)''. | ||
| + | |||
| + | En injectant le changement de variable dans la fonction de transfert ''H(s)'', obtenir la fonction de transfert ''H(z)'' du filtre linéaire numérique correspondant (dans un premier temps avec ''backward Euler'') : | ||
| + | * soit "à la main" | ||
| + | * ou en exploitant [https://www.sympy.org/en/index.html Sympy] (calcul symbolique). Un début : | ||
| + | <source lang="Python"> | ||
| + | import sympy | ||
| + | from sympy import symbols, Poly, Eq | ||
| + | Ki, Kd, Kp, N, Ts, z, s = symbols('K_i K_d K_p N T_s z s') | ||
| + | |||
| + | Hs = Kp + (Ki/s) + Kd*s * (N/(N+s)) | ||
| + | |||
| + | Hz = Hs.subs(s, (z-1)/(Ts*z)) | ||
| + | |||
| + | # ... | ||
| + | </source> | ||
| + | Dans les deux cas, il sera nécessaire d'obtenir ''H(z)'' sous la forme d'une fraction rationnelle. | ||
| + | |||
| + | {{q | c)}} Implémentation du filtre : classe <code>FilterController</code> dont voici le squelette | ||
| + | <source lang="Python"> | ||
| + | class FilterController: | ||
| + | |||
| + | def mfilter(self, e): | ||
| + | xn = e | ||
| + | # TODO | ||
| + | return yn | ||
| + | |||
| + | def __init__(self, K_p, K_i, K_d, N, T_s, set_point): | ||
| + | self.set_point = set_point | ||
| + | # A COMPLÉTER | ||
| + | |||
| + | def get_control(self, measurement, dt): | ||
| + | e = self.set_point - measurement | ||
| + | u = self.mfilter(e) | ||
| + | return u | ||
| + | </source> | ||
| + | {{q | d)}} Appliquer ce contrôleur à la simulation d'asservissement de drone et vérifier qu'un comportement proche de celui de la partie précédente (PID numérique "classique") est obtenu. | ||
| − | + | {{q | e)}} Implémenter le filtre obtenu à l'aide de la transformée bilinéaire. | |
| − | + | <!-- | |
| + | === Archive : codage de bloc Gnuradio en C++ === | ||
Ce guide est inspirée du guide [https://wiki.gnuradio.org/index.php?title=Creating_C%2B%2B_OOT_with_gr-modtool Creating an OOT (C++ block example)] en l'adaptant pour nos besoins : | Ce guide est inspirée du guide [https://wiki.gnuradio.org/index.php?title=Creating_C%2B%2B_OOT_with_gr-modtool Creating an OOT (C++ block example)] en l'adaptant pour nos besoins : | ||
| Ligne 358 : | Ligne 498 : | ||
{{Todos| Modifier votre code pour implémenter le filtre complet de démodulation}} | {{Todos| Modifier votre code pour implémenter le filtre complet de démodulation}} | ||
| − | + | --> | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
Version actuelle datée du 1 juin 2026 à 15:03
TP6 - Temps réel : bloc Gnuradio en Python / démodulation / PID Numérique
Le but de ce TP est double :
- réaliser des implémentations temps réel de filtre IIR en créant un bloc Gnuradio qui execute un traitement codé en Python
- et étudier l'implémentation d'un PID numérique sous forme d'un filtre IIR.
Dans les deux cas, un système temps réel sera considéré :
- à intervalles de temps (réguliers ou non), une valeur x(n) rentre dans le système
- le travail consiste à calculer y(n) avec l'équation au différences du filtre, à partir de la valeur x(n) et de ses versions précédentes x(n-1) et x(n-2) (et aussi y(n-1), y(n-2)) qu'il est donc nécessaire de stocker.
- La notion de temps réel implique que y(n) doit être calculer avant qu'une nouvelle valeurs de x(n) ne rentre dans le système.
Sommaire
Bloc Gnuradio en Python
Nous allons implémenter un filtre IIR dans gnuradio, en écrivant le traitement dans un bloc Python.
Filtre moyenneur
a) Dans un premier temps, prendre connaissance du tutoriel suivant : wiki Gnuradio - Creating Your First Block
b) Nous allons écrire un filtre simple, qui implémente un simple moyenneur :
import numpy as np
from gnuradio import gr
class blk(gr.sync_block):
def __init__(self): # Pas de paramètres
gr.sync_block.__init__(
self,
name="Filtre moyenneur",
in_sig=[np.float32],
out_sig=[np.float32]
)
self.xn1 = 0 # initialisation d'un attribut pour stocker x(n-1)
def work(self, input_items, output_items):
xn = input_items[0][0] # On ne "consomme" qu'un seul échantillon en entrée
yn = (xn + self.xn1) / 2
self.xn1 = xn
output_items[0][0] = yn # On ne produit donc également qu'un seul échantillon en sortie
return 1 # On retourne le nombre de valeurs consomées
Filtre IIR : démodulation
En reprenant le signal modulé en amplitude et la fonction de transfert du filtre passe-bas du TP précédent :
- Obtenir l'équation aux différence du filtre, ie écrire y(n)= en fonction de x(n), x(n-1), x(n-2), y(n-1) et y(n-2).
- Créer un nouveau bloc Python dans le module
monModule:- de type
sync - une entrée en
np.float32 - une sortie en
np.float32
- de type
- Écrire le code permettant d'obtenir y(n) dans la méthode
work():- x(n) est donné par
in[0][0] - il sera nécessaire de stocker dans des attributs les valeurs de x(n-1), x(n-2), y(n-1) et y(n-2).
- x(n) est donné par
a) Réaliser cette implémentation du passe-bas et contrôler que la démodulation est identique à celle obtenue au TP 5.
b) Modifier votre code pour implémenter le filtre complet de démodulation.
Approximation de PID par filtre numérique linéaire
Cette partie sera travaillée dans un notebook Jupyter.
Rappels PID
- Principe
- Définition mathématique, la sortie du controleur PID u(t) est donnée par:
PID numérique "classique"
On considère que le temps est discrétisé : t = n.dt où dt est l'intervalle de temps entre deux valeurs.
- En approximant grossièrement l'intégrale et la dérivée
- La dérivée est calculée par D = (e(n)-e(n-1))/dt
- L'intégrale est approximée en cumulant progressivement : I = I + e(n).dt
C'est ainsi que vous avez implémenté vos PID jusqu'ici.
Voici un notebook exemple à explorer sur deux modélisations : asservissement de 1) température et de 2) position z d'un drone : TNS_TP6_starter.ipynb.zip
a) Ki = Kd = 0 => chercher le meilleur Kp => erreur statique.
b) Kd = 0 => l'introduction du terme integral permet de corriger cette erreur statique.
c) Le terme dérivatif n'est pas nécessaire pour la température
d) Mais l'est pour asservir la position z du drone.
PID numérique avec un filtre numérique linéaire
Nous allons reprendre le PID, par sa fonction de transfert qui exprime un filtre linéaire continu, et construire un filtre linéaire numérique qui l'approxime.
a) Cherchons la fonction de transfert du filtre continu d'un PID.
En partant de l'équation différentielle
On applique la transformée de Laplace (dérivée => multiplication par s ; intégrale => division par s) :
Le terme dérivatif étant très sensible au bruit, on lui applique généralement un filtre passe bas (w0/(w0+s)) :
b) Il s'agit maintenant de transformer la variable s en une variable z :
- par transformée bilinéaire :
s = (2/Ts)*(z-1)/(z+1) (voir le cours) - ou par backward euler (version un peu plus simple) :
s = (z-1)/(Ts*z).
En injectant le changement de variable dans la fonction de transfert H(s), obtenir la fonction de transfert H(z) du filtre linéaire numérique correspondant (dans un premier temps avec backward Euler) :
- soit "à la main"
- ou en exploitant Sympy (calcul symbolique). Un début :
import sympy
from sympy import symbols, Poly, Eq
Ki, Kd, Kp, N, Ts, z, s = symbols('K_i K_d K_p N T_s z s')
Hs = Kp + (Ki/s) + Kd*s * (N/(N+s))
Hz = Hs.subs(s, (z-1)/(Ts*z))
# ...
Dans les deux cas, il sera nécessaire d'obtenir H(z) sous la forme d'une fraction rationnelle.
c) Implémentation du filtre : classe FilterController dont voici le squelette
class FilterController:
def mfilter(self, e):
xn = e
# TODO
return yn
def __init__(self, K_p, K_i, K_d, N, T_s, set_point):
self.set_point = set_point
# A COMPLÉTER
def get_control(self, measurement, dt):
e = self.set_point - measurement
u = self.mfilter(e)
return u
d) Appliquer ce contrôleur à la simulation d'asservissement de drone et vérifier qu'un comportement proche de celui de la partie précédente (PID numérique "classique") est obtenu.
e) Implémenter le filtre obtenu à l'aide de la transformée bilinéaire.