Description de
l’asservissement numérique mis en œuvre sur le PIC16F877
II. Description de l’ensemble asservissement + commande moteur
b.) Schéma simplifié de la carte :
III. L’asservissement numérique
IV. Etude du code assembleur de la carte 2001
2. Fonctionnalites specifiques au PIC
utilisées
6. Protocole de communication avec la
carte mère (bus SPI)
8. Modes de fonctionnement possibles
Un asservissement a pour but de désensibiliser un système à une perturbation de sortie.
Le principe repose sur un système dont on reboucle la sortie vers l’entrée. On parle alors de système bouclé.
On utilise généralement une représentation par schéma block :
Le correcteur introduit dans la chaîne directe sert à modifier le comportement du système selon les fréquences.
Un tel système est conçu de façon à respecter trois critères :
- Rapidité (début de la réponse à une consigne)
- Stabilité (fin du régime transitoire)
- Précision (régime établi)
Ces trois critères permettent de définir le comportement du système dans 3 zones de fréquences :
- Hautes fréquences : rapidité
- Moyennes fréquences : stabilité
- Basses fréquences : précision
L’analyse d’un système linéaire asservi repose sur l’étude de la transmittance de sa boucle ouverte (du signal d’erreur au signal de retour).
Dans ce cas, pour simplifier l’étude, on se placera de préférence dans le domaine pseudo-continu, par la transformée bilinéaire :
On remarque que la forme ressemble à celle d’un système continu. En effet, l’étude dans le domaine pseudo-continu peut se faire avec les même méthodes que dans le domaine continu.
![]() |
Ce bloc mécanique se compose d’un moteur avec son réducteur et d’un codeur incrémental.
Le codeur incrémental génère deux signaux carrés de fréquence proportionnelle à la vitesse du moteur.
La caractéristique essentielle du codeur incrémental est son nombre de pas par tour (chaque pas correspond au nombre de périodes de chaque signal pour un tour du codeur).
Ces deux signaux sont déphasés l’un par rapport à l’autre de 90° ou –90° selon le sens de rotation.
Ex :
![]() |
Le principe repose sur le fait que la vitesse d’un moteur est proportionnelle à la tension d’induit. De plus, un moteur étant généralement très inductif, il va se comporter comme un passe bas, et ne va donc être sensible qu’à la valeur moyenne de la tension si la première harmonique est de fréquence suffisamment haute.
![]() |
La commande du moteur se fait par l’intermédiaire des transistors T1 à T4 selon un montage dit en H.
Les transistors T1 T4 et T2 T3 sont commandés simultanément.
Les couples T1 T4 et T2 T3 sont commandés en alternance et exclusivement.
Le signal d’entrée est signal rectangulaire de rapport cyclique variable et de fréquence telle qu’elle soit filtrée par le moteur :
Le
signal de sortie est donc lui aussi de forme rectangulaire dont les niveaux
haut et bas correspondent aux alimentations.
La valeur moyenne vaut :
Où a
est le rapport cyclique (0<a<1).
Dans le cas où les alimentations sont symétriques :
Ce montage permet donc de contrôler la vitesse et le sens du moteur à partir du rapport cyclique du signal d’entrée.
Cette carte est centrée autour d’un microcontrôlleur PIC16F877.
Elle reçoit les ordres de la carte mère par l’intermédiaire d’un bus SPI et délivre en sortie les signaux nécessaires à la commande des cartes de puissance des deux moteurs.
Elle reçoit, de plus, les signaux en provenance des codeurs de chaque moteur.
![]() |
Sur la carte, apparaissent deux blocs logiques :
- le premier sert à modifier les signaux en provenance des codeurs en deux signaux mieux adaptés pour le PIC.
- le deuxième sert à générer les signaux pour commander chaque paire de transistor sur les cartes de puissance.
Le premier bloc logique a pour but de générer un signal d’horloge et un signal de sens, pour permettre de mieux exploiter les possibilités offertes par le PIC (utilisation des timer internes).
Sur les cartes 2000 et 2001, le signal d’horloge génère 500 fronts montants pour chaque tour.
Le deuxième bloc logique a deux fonctions. Il permet de générer deux signaux en opposition de phase mais en prenant soin de respecter un temps mort au moment des commutations pour empêcher la conduction des branches du pont H sur la carte de puissance.
Ce temps mort est nécessaire du fait des temps de blocage des transistor. On évite ainsi des provoquer un court-circuit entre les alimentations :
Un asservissement est une méthode de régulation d’une grandeur physique par l’intermédiaire d’un système numérique.
La majorité des systèmes numériques (microcontrolleurs processeurs…) sont cadencés à partir d’une horloge. Le temps est donc discrétisé. Il en résulte qu’une grandeur physique ne peut être mesurée qu’à certains instant. On parle alors d’échantillonnage.
De par même la nature d’une représentation numérique, on doit tenir compte du fait qu’un nombre a une limite inférieure et une limite supérieure.
Le temps étant discret, on se placera dans le domaine dit discret ou dans le domaine pseudo-continu et on utilisera les outils adéquats (transformée en Z, échantillonnage, transformée bilinéaire…).
L’implantation d’un correcteur numérique se fait à partir de sa transmittance discrète C(z-1) de la forme :
Cette
transmittance est le rapport du signal de commande U(z) et du signal d’erreur
e(z) :
D’où :
Soit encore :
Or, dans le domaine discret, U(z-n) représente la n-ième précédante de U. On en déduit ainsi une formule récurrente (avec A1=1) :
L’implantation d’un correcteur numérique consiste juste en une équation récurrente.
Pour synthétiser un correcteur numérique, il nous faut connaître la fonction de transfert du procédé, vu du système numérique, en l’occurrence le microcontrolleur dans notre cas.
La méthode qui permet de connaître toute la chaîne externe en s’affranchissant du plus de calcul, est l’identification :
On applique un échelon de commande, et on relève la grandeur de sortie. A partir de cette réponse, on en déduit le modèle. Dans le cas d’un moteur, le modèle est du premier ordre :
Pour connaître le modèle, il suffit de déterminer A et t.
Afin de s’affranchir des frottements secs qui perturberaient l’identification, on effectue l’échelon alors que le moteur tourne déjà :
De là, tous les calculs se feront à l’aide du logiciel TPA pour plus de simplicité.
Méthode :
![]() |
Sur la carte PIC, deux correcteurs sont implantés :
- une boucle de vitesse
- une boucle de position par dessus la boucle de vitesse
![]() |
![]() |
Le code se compose principalement d’une boucle principale effectuant le séquençage, à savoir, l’échantillonnage de la position, et le calcul des équations récurrentes de chaque boucle.
La gestion des codeurs, la génération des signaux pour les moteurs (PWM), la gestion des timer (horloge et codeurs) et la gestion de la communication se fait en interruptions.
Le code continu.asm se compose de plusieurs parties :
- Définitions
- Interruptions
- Initialisation
- Boucle principale
- Sous-routines
Le code bank23.asm comporte des sous fonctions :
- mise à jour des compteurs pour la position
- fonction de saturation pour les sorties PWM
- fonction d’envoie des commandes PWM
- gestion de la communication avec la carte mère (interprétation des ordres)
- fonctions d’effacage de la RAM
- autres fonctions (fonction « 2 puissance X », fonction d’attente)
La RAM (plus précisément, les registres) sont utilisés de facon a simplifier le code, et à le rendre générique pour les 2 moteurs.
En effet, les deux moteurs devant etre traites de facon semblable, la plupart des fonctions sont génériques et peuvent etre appelees indifferement pour les deux moteurs.
Il en résulte un « gaspillage » de la RAM (éternel dillemne taille RAM vs. Complexite du code).
C’est ainsi que les fonctions mathématiques (bibliotheque pour les calculs flottants) utilisent selon le contexte les même régions mémoires vu du début de chaque debut de banque RAM.
Les banques 0 et 2 seront plutot reservees pour les operations sur le moteur 1, tandis que les banques 1 et 3 seront plutot reservees pour le moteur 2.
De meme, les parametres ou toutes autres valeurs associes au moteur 1 seront dans les banques 0 et 2, et dans les banques 1 et 3 pour le moteur 2.
(voir mapping mémoire pages suivantes)
Dans le cas de la carte 2001, les fontions utilisées sont les suivantes :
- modules PWM (1 pour chaque moteur)
- module SPI (communication avec la carte mère
- compteurs TIMER0 et TIMER1 pour les codeurs
- compteur TIMER2 pour la base de temps (contraintes liées au module PWM à ne pas oublier)
- 2 broches sensibles à un changement d’état (utilisées pour connaître le sens du moteur)
Au total, il faut donc gérer plusieurs interruptions :
- 3 interruptions associées aux TIMER.
- L’interruption du changement d’état sur le port B
- L’interruption de la liaison SPI pour la communication
|
|
Bank0 (moteur
1) |
Bank1 (moteur
2) |
Bank2 (moteur
1) |
Bank3 (moteur
2) |
0 |
00 |
|
|
|
|
|
|
|
|
|
|
15 |
0F |
|
|
|
|
16 |
10 |
|
|
temp_asked_vit |
|
17 |
11 |
|
|
temp_asked_pos_l |
|
18 |
12 |
|
|
temp_asked_pos_m |
|
19 |
13 |
|
|
asked_pos_l |
|
20 |
14 |
|
|
asked_pos_m |
|
21 |
15 |
|
|
asked_pos_h |
|
22 |
16 |
|
|
asked_vit_l |
|
23 |
17 |
|
|
asked_vit_h |
|
24 |
18 |
|
|
commande_l |
|
25 |
19 |
|
|
commande_h |
|
26 |
1A |
|
|
commande_m |
|
27 |
1B |
|
|
vit_l |
|
28 |
1C |
|
|
vit_m |
|
29 |
1D |
|
|
vit_h |
|
30 |
1E |
|
|
increment_def_l |
|
31 |
1F |
|
|
increment_def_h |
|
32 |
20 |
|
|
|
|
|
|
|
|
|
|
51 |
33 |
|
|
|
|
52 |
34 |
consigne_pos |
|
commande_pos |
|
53 |
35 |
|
|
|
|
54 |
36 |
|
|
|
|
55 |
37 |
|
|
|
|
56 |
38 |
delta_t |
|
pos_reel |
|
57 |
39 |
|
|
|
|
58 |
3A |
|
|
|
|
59 |
3B |
|
|
|
|
60 |
3C |
consigne |
|
vit_reel |
|
61 |
3D |
|
|
|
|
62 |
3E |
|
|
|
|
63 |
3F |
|
|
|
|
64 |
40 |
|
|
vit_reel1 |
|
65 |
41 |
|
|
|
|
66 |
42 |
|
|
|
|
67 |
43 |
|
|
|
|
68 |
44 |
Temp1 |
Temp1b |
pos_reel1 |
|
69 |
45 |
var2 |
PCLATH_TEMP |
|
|
70 |
46 |
|
|
|
|
71 |
47 |
var1 |
INT_STATUS |
|
|
72 |
48 |
commande |
|
commande1 |
|
73 |
49 |
|
|
|
|
74 |
4A |
|
|
|
|
75 |
4B |
|
|
|
|
76 |
4C |
epsilon |
|
commande2 |
|
77 |
4D |
|
|
|
|
78 |
4E |
|
|
|
|
79 |
4F |
|
|
|
|
80 |
50 |
epsilon1 |
|
Pos_courante_l |
|
81 |
51 |
|
|
Pos_courante_m |
|
82 |
52 |
|
|
Pos_courante_h |
|
83 |
53 |
|
|
Pos0_l |
Pos1_l |
84 |
54 |
epsilon2 |
|
Pos0_m |
Pos1_m |
85 |
55 |
|
|
Pos0_h |
Pos1_h |
86 |
56 |
|
|
v_increment_l |
|
87 |
57 |
|
|
v_increment_h |
|
88 |
58 |
K1 |
|
Kpos_1 |
|
89 |
59 |
|
|
|
|
90 |
5A |
|
|
|
|
91 |
5B |
|
|
|
|
92 |
5C |
K2 |
|
Kpos_2 |
|
93 |
5D |
|
|
|
|
94 |
5E |
|
|
|
|
95 |
5F |
|
|
|
|
96 |
60 |
K3 |
|
epsilon_pos |
|
97 |
61 |
|
|
|
|
98 |
62 |
|
|
|
|
99 |
63 |
|
|
|
|
100 |
64 |
K4 |
|
epsilon1_pos |
|
101 |
65 |
|
|
|
|
102 |
66 |
|
|
|
|
103 |
67 |
|
|
|
|
104 |
68 |
K5 |
|
Kadj |
|
105 |
69 |
|
|
|
|
106 |
6A |
|
|
|
|
107 |
6B |
|
|
|
|
108 |
6C |
unite_temps |
|
|
|
109 |
6D |
|
|
|
|
110 |
6E |
|
|
|
|
111 |
6F |
|
|
|
|
112 |
70 |
BITS |
|
|
|
113 |
71 |
TIM1_G |
|
|
|
114 |
72 |
TIM1_H |
|
|
|
115 |
73 |
TIM1_L |
|
|
|
116 |
74 |
TIM0_G |
|
|
|
117 |
75 |
TIM0_H |
|
|
|
118 |
76 |
TIM0_L |
|
|
|
119 |
77 |
W_TEMP |
|
|
|
120 |
78 |
STATUS_TEMP |
|
|
|
121 |
79 |
SPI_BUF |
|
|
|
122 |
7A |
SPI_STATUS |
|
|
|
123 |
7B |
FPFLAGS |
|
|
|
124 |
7C |
add_temp |
|
|
|
125 |
7D |
TIME_l_old |
|
|
|
126 |
7E |
time_h |
|
|
|
127 |
7F |
time_l |
|
|
|
Avant de détailler le code, il existe un point délicat à propos des codeurs.
En effet, les TIMER 0 et 1 ne savent que compter, or, les moteurs peuvent aller dans les deux sens.
A partir de la, deux solutions étaient possibles. Soit les codeurs étaient gérés en temps réel, ce qui voulait dire interrompre le PIC sur chaque front, soit les codeurs étaient gérés de facon independante.
La premiere solution aurait solicite beaucoup le PIC (mais n’aurait pas forcement ete perturbante) mais était simple a mettre en œuvre. La deuxieme solution, a consiste a ruser pour utiliser au maximum les TIMER.
C’est la deuxieme solution qui a ete choisie (meme si elle est moins evolutive).
Cette ruse consiste a stocker la position courante en mémoire, et à utiliser les TIMER seulement pour compter des déplacements par rapport a la valeur en memoire.
Pour avoir la position instantannee, il faut donc ajouter ou retrancher a la position « courante » la valeur du TIMER, selon que le moteur tourne dans un sens ou dans l’autre.
Ainsi, tant que le moteur tourne dans le meme sens, la position « courante » ne bouge pas, mais le TIMER s’incrémente. On a donc la position reelle en ajoutant ou retranchant la valeur du TIMER.
Si le moteur change de sens a un moment (detecte grace aux interruptions sur changement d’etat sur le port B), on met alors a jour la position « courante » en ajoutant ou retranchant la valeur du TIMER selon le sens précédant du moteur.
Cette ruse, complique la gestion des codeurs et empeche notablement toute amelioration possible, mais à l’aventage de ne monopoliser que tres peu le pic du point de vue des interruptions.
D’autre part, pour ceux qui oseront s’aventurer dans cette partie du code, il faut signaler qu’il n’a pas été possible de mettre à jour la position reelle a chaque tour de boucle du programme principal.
En effet, et ceci pour une raison obscure le fait de remettre a zéro trop souvent le TIMER1 (le probléme ne survient apparement pas sur le TIMER0) inhibbe le TIMER1 pour une durée et un nombre d’impulsion de comptage aléatoire.
Avis, donc, à ceux qui pourront dire pourquoi, car le datasheet de microchip ne fait pas mention d’un tel phénomène.
Cette ruse mise à part, le reste du code est relativement simple a comprendre. Le detail du sequencage devrait permettre de comprendre le fonctionnement.
Le séquenceur, cadencé à une période de 13,12ms (imposé par le module PWM), gère le fonctionnement de l’asservissement.
La boucle principale se charge dans un premier temps de générer les consignes pour la boucle de position. Cette génération de consigne a été implantée en 2001 pour soulager la carte mère en lui évitant de générer la rampe de position.
Dans un deuxieme temps, on met a jour la position reelle (sans remettre a zero les TIMER donc) pour chaque moteur.
Dans un troisieme temps, on mémorise les anciennes valeurs utilisees pendant le précédant calcul de l’équation reccurente, puis on calcule la nouvelle valeur de la vitesse ainsi que les nouvelles valeurs qui seront utilisées pour l’équation reccurente. Dans cette étape, on vient donc de calculer toutes les valeurs nécessaires, aussi bien en binaire simple (complément à deux) qu’en flottant.
L’étape suivante, consiste donc à appliquer la (ou les, selon le type de boucle désirée) formule récurrente ou pas, si on se limite à une consigne en boucle ouverte.
La derniere etape consiste donc à envoyer au module PWM la commande ainsi déterminée.
Le protocole utilisé est un protocole simple, qui n’utilise aucune vérification dans les ordres de transmission, si ce n’est l’utilisation de deux commandes particulières pour vérifier si la communication est toujours valide.
Les ordres sont donc supposés bien recus sans place à une erreur de transmission.
Pour les non initiés, le principe d’échange d’octet mis en œuvre sur un bus SPI, peut troubler car très souvent, la moitié des données transmises sont perdues (tout au moins dans le cas qui nous concerne).
Dans notre cas, le PIC étant configuré en esclave, il ne peut donc jamais provoquer une communication.
La carte mère (configurée en maitre) peut seule initier un échange d’octet.
Trois types d’instructions sont implémentées :
- la lecture
- l’écriture
- l’ordre
Dans le cas d’une écriture, la carte mère procède à deux envois de données, selon l’ordre : operation demandée puis opérande. Dans ce cas, pour effectuer l’envoie d’une donnée plus longue qu’un octet, il faudra procéder à plusieurs écritures.
Dans le cas d’une lecture, la carte mère procède toujours à deux transmissions, mais le premier consiste en l’envoie d’un octet représentant la donnée a recuperer. La transmission suivante consistera donc a recuperer la donnée préparée par le PIC.
Dans le cas d’un ordre, la carte mère ne procéde qu’à une transmission. Tout se passe comme si on faisait une lecture ou une ecriture pour laquelle aucun argument n’est necessaire. L’octet transmis est interprete directement par le PIC.
Voici le jeu d’instruction implémenté sur le PIC.
On notera que dans le cas de l’envoie d’une valeur sur plusieurs octets, la valeur finale est supposée envoyée après réception (du coté du PIC) de l’octet de poids fort.
Ceci justifie les variables précédées de « temp » dans le code (et le mapping).
Pour l’écriture, l’octet correspond à « l’opération demandée »
vit mot1 l : 00000001 0x01
vit mot1 h : 00010001 0x11
vit mot2 l : 00100001 0x21
vit mot2 h : 00110001 0x31
pos mot1 l : 01000001 0x41
pos mot1 m : 01010001 0x51
pos mot1 h : 10000001 0x81
pos mot2 l : 01100001 0x61
pos mot2 m : 01110001 0x71
pos mot2 h : 10010001 0x91
w addresse 0/1 : 11000001 0xC1
w donnee : 11010001 0xD1
w addresse 2/3 : 11110001 0xF1
vitesse ass1 l : 10100001 0xA1
vitesse ass1 h : 10110001 0xB1
vitesse ass2 l : 10101001 0xA9
vitesse ass2 h : 10111001 0xB9
pos mot1 l : 00000010 0x02
pos mot1 m : 00010010 0x12
pos mot1 h : 01000010 0x42
pos mot2 l : 00100010 0x22
pos mot2 m : 00110010 0x32
pos mot2 h : 01010010 0x52
vit mot1 l : 01100010 0x62
vit mot1 m : 01110010 0x72
vit mot1 h : 10010010 0x92
vit mot2 l : 10100010 0xA2
vit mot2 m : 10110010 0xB2
vit mot2 h : 11000010 0xC2
ping1 : 10000010 0x82
ping2 : 11100010 0xE2
r donnee : 11010010 0xD2
r interrupts : 11110010 0xF2 -> (7 ,6 ,5 ,4 ,3 ,fin_2 ,fin_1 ,boucle_asserv )
Mode pwm : 00110100
Mode asserv : 00100100
Asserv vit : 00000100
Asserv pos : 00010100
Reboot : 10000100
Reset positions : 00001100
Arret urgence : 11110100
On pourra remarquer la présence d’instruction particulières, telles que « w adresse » ou « w/r donnée ».
Ces instructions permettent de pouvoir directement lire ou écrire toute la RAM (registres) du PIC.
Ces instructions peuvent donc être très utiles pour debugger le fonctionnement d’un programme.
On notera aussi la présence de « ping 1 » et « ping 2 » qui permettent de vérifier si la communication se fait bien, et par la même occasion de vérifier si le PIC réagit.
Grâce aux ordres transmissibles au PIC, il est possible de fonctionner dans différents mode pour l’asservissement.
Par défaut, le PIC est en boucle ouverte et est commandé à partir de consignes en « vitesse » qui correspondent au rapport cyclique des sorties PWM.
Le PIC peut être passé en boucle fermée à l’aide de l’odre « mode asserv », et sera par defaut un asservissement en vitesse (consignes en nombre de tour par minute).
Si on désire une boucle de position il faut, AVANT de passer en boucle fermée, envoyer l’odre « asserv pos », mais attention, les ordres qu’il recevra, seront des consignes relatives en nombre de pas à effectuer, le PIC générant de lui meme la rampe (vitesse constante) nécessaire.
Une amélioration possible serait de pouvoir choisir aussi s’il doit générer tout seul la rampe de position, sachant qu’il est assez delicat de générer autre chose qu’une rampe, ou si la carte mère lui envoie une rampe (et non pas des échelons).
A noter, qu’un reset soft est possible, mais est à n’utiliser que rarement mis à part en tant qu’initialisation (avant de passer en boucle de position au démarrage par exemple).