Vous devez gérer deux problèmes:
- débordement arithmétique
- liquidation de l'intégrateur
Le débordement arithmétique est assez simple - chaque fois que vous faites des mathématiques entières, assurez-vous que vous utilisez des valeurs intermédiaires de plus grande largeur: par exemple, si a
et b
sont 16 bits, et que vous les ajoutez / soustrayez, utilisez un intermédiaire 32 bits et limitez-la à la plage d'une valeur de 16 bits (0 à 65535 pour non signé, -32768 à 32767 pour signé) avant de redescendre à 16 bits. Si vous êtes absolument sûr de ne jamais avoir de débordement, car vous êtes absolument sûr de la plage des variables d'entrée, vous pouvez ignorer cette étape, mais attention.
Le problème de liquidation de l'intégrateur est plus subtil. Si vous avez une erreur importante pendant une période prolongée, de sorte que vous atteigniez la limite de saturation de la sortie de votre contrôleur, mais que l'erreur est toujours non nulle, l'intégrateur continuera d'accumuler l'erreur, pouvant devenir beaucoup plus importante qu'elle ne devrait le faire régime permanent. Une fois que le contrôleur sort de la saturation, l'intégrateur doit redescendre, provoquant un retard inutile et éventuellement une instabilité dans la réponse de votre contrôleur.
D'un autre côté:
Je recommanderais fortement (ouais, je sais que cette question a 18 mois, donc vous avez probablement terminé votre tâche, mais pour le bénéfice des lecteurs, faisons comme si ce n'était pas le cas) que vous calculiez le terme intégral différemment: Au lieu de Ki * (erreur intégrée), calculer l'intégrale de (erreur Ki *).
Il y a plusieurs raisons à cela; vous pouvez les lire dans un article de blog que j'ai écrit sur la façon d'implémenter correctement les contrôleurs PI .
<stdint.h>
pouruint8_t
etuint16_t
, plutôt queunsigned int
etunsigned char
.unsigned
variables pour un contrôleur PI? Cela ajoute beaucoup de complexité à votre code; lesif/else
cas séparés ne sont pas nécessaires (sauf si vous utilisez des gains différents en fonction du signe d'erreur) Vous utilisez également la valeur absolue du dérivé, ce qui est incorrect.PID_derivative
affectation; vous obtenez la même valeur si vous changezPID_error
etPID_lastError
. Et d'ailleurs, vous avez déjà perduPID_error
le signe de: si la dernière foissetMotorSpeed =8
etcurrentMotorSpeed = 15
, et cette foissetMotorSpeed = 15
etcurrentMotorSpeed = 8
, alors vous obtiendrez unePID_derivative
valeur de 0, ce qui est faux.unsigned char
s'agit d'un type 8 bits et d'unsigned int
un type 16 bits: siPID_kd = 8
etPID_derivative = 32
, alors leur produit sera(unsigned char)256 == 0
, car en C, le produit de deux entiers du même type T est également de celui même type T. Si vous voulez faire une multiplication 8x8 -> 16, vous devez transtyper l'un des termes en un nombre non signé de 16 bits avant la multiplication, ou utiliser un compilateur intrinsèque (MCHP les appelle «intégrés») conçu pour vous donne une multiplication 8x8 -> 16.