Fabrication industrielle
Internet des objets industriel | Matériaux industriels | Entretien et réparation d'équipement | Programmation industrielle |
home  MfgRobots >> Fabrication industrielle >  >> Manufacturing Technology >> Processus de fabrication

astuces pour contrôler les moteurs à courant continu

Composants et fournitures

Arduino Due
En fait, vous pouvez utiliser n'importe quelle carte Arduino.
× 1
Makeblock Me TFT LCD
Ceci est un composant facultatif. Vous pouvez utiliser un autre type d'affichage, ou préférer n'en utiliser aucun.
× 1
Résistance 4.75k ohm
Les résistances peuvent varier en fonction de votre implémentation.
× 4
Potentiomètre rotatif (générique)
3 d'entre eux sont facultatifs (juste pour ajuster les coefficients du contrôleur).
× 4
Transistor à usage général PNP
Les transistors à utiliser peuvent varier en fonction de votre implémentation.
× 4
Moteur à courant continu (générique)
× 1
Commutateur à glissière
Ceci est utilisé pour la sélection de la direction.
× 1
Capteur de vitesse photoélectrique HC-020K
× 1
Planche à pain (générique)
× 1
Câbles de raccordement (générique)
× 1

Applications et services en ligne

Arduino IDE

À propos de ce projet

Contrôle de la vitesse et de la direction des moteurs à courant continu par contrôleur PID et sorties PWM

Introduction

Presque sur tous les projets disponibles, les créateurs aimeraient contrôler les vitesses et la direction des moteurs ensemble, mais ils préfèrent principalement envoyer directement le PWM aux moteurs à courant continu, même via un circuit de commande de moteur. Mais une telle méthode échoue toujours si vous devez faire correspondre la vitesse exactement comme vous le souhaitez à cause des frères jumeaux appelés « friction » et « inertie ».

(Veuillez ne jamais blâmer les jumeaux. Peu importe et quand vous souhaitez prendre des mesures avec ou sans quelque chose, les jumeaux viennent immédiatement et agissent juste pour vous aider à tout garder sous contrôle. Tandis que l'inertie laisse les choses « penser » avant d'agir, La friction limite leur accélération et leur vitesse. Et la "puissance" n'est "rien" si elle n'est pas sous contrôle total.)

Ainsi, si vous essayez de contrôler la vitesse d'un moteur directement en envoyant votre entrée en tant que signal PWM à la sortie, la vitesse réelle ne correspondra jamais à votre point de consigne et il y aura une différence significative (erreur), comme on peut le voir sur l'image juste au-dessus. Ici, nous avons besoin d'un autre moyen, appelé "contrôle PID".

Contrôleur PID

Qu'est-ce que le contrôle PID ? Imaginez comment conduire votre voiture :pour démarrer à partir de l'arrêt complet, vous devez appuyer sur la pédale d'accélérateur plus que pendant la croisière normale. Lors d'un déplacement à vitesse (presque) constante, vous n'avez pas besoin d'appuyer trop sur la pédale d'accélérateur, mais vous récupérez simplement la perte de vitesse lorsque cela est nécessaire. D'ailleurs, vous le relâchez légèrement si l'accélération est supérieure à vos besoins. C'est aussi la voie de la "conduite efficace".

Ainsi, le contrôleur PID fait exactement la même chose :le contrôleur lit la différence « Signal d'erreur (e) » entre le point de consigne et la sortie réelle. Il a 3 composants différents appelés "Proportionnel", "Intégral" et "Dérivé" ; le nom du contrôleur apparaît donc après la première lettre de chacun. La composante proportionnelle définit simplement la pente (accélération) de la sortie du contrôleur par rapport au signal d'erreur réel. La partie intégrale additionne les signaux d'erreur dans le temps afin de minimiser l'erreur finale. Et le composant dérivé surveille l'accélération du signal d'erreur et effectue un « ajustement ». Je ne donnerai pas ici de détails plus longs et plus longs, veuillez rechercher sur Internet si vous êtes intéressé.

Sur mon programme Arduino, le contrôleur PID est écrit sous la forme d'une fonction illustrée ci-dessous :

float controllerPID(float _E, float _Eprev, float _dT, float _Kp, float _Ki, float _Kd){ float P, I, D ; /* Formule de base :U =_Kp * ( _E + 0.5*(1/_Ki)*(_E+_Eprev)*_dT + _Kd*(_E-_Eprev)/_dT ); */ P =_Kp * _E; /* Composante proportionnelle */ I =_Kp * 0.5 * _Ki * (_E+_Eprev) * _dT; /* Composant Intégral */ D =_Kp * _Kd * (_E-_Eprev) / _dT; /* Composant Dérivé */ return (P+I+D);} 

Ensuite, la valeur de sortie finale est déterminée simplement en ajoutant la valeur de sortie actuelle et la sortie du contrôleur PID. C'est la section suivante du programme principal avec le calcul du signal d'erreur et de la sortie du contrôleur PID :

/* Signal d'erreur, sortie du contrôleur PID et sortie finale (PWM) vers le moteur */E =RPMset - RPM;float cPID =controllerPID(E, Eprev, dT, Kp, Ki, Kd);if ( RPMset ==0 ) OutputRPM =0 ; sinon OutputRPM =OutputRPM + cPID ; if ( OutputRPM <_minRPM ) OutputRPM =_minRPM;  

Circuit d'alimentation du moteur à courant continu

Bien sûr, il n'est jamais recommandé de piloter un moteur à courant continu directement à partir de la sortie d'Arduino ou d'une carte de commande similaire. Les moteurs à courant continu ont besoin d'une quantité de courant importante par rapport à ceux qui ne peuvent pas être fournis par les sorties des cartes de contrôleur. Vous devez donc piloter des bobines de relais. Mais ici un autre problème se pose :les relais ont des pièces mécaniques et ils peuvent tomber en panne à moyen ou long terme. Nous avons besoin d'un autre composant ici, des transistors.

En fait, les moteurs à courant continu sont entraînés par le courant, pas par la tension. Donc en utilisant ce principe, j'ai décidé d'utiliser des transistors. Mais vous devez choisir le bon transistor capable de supporter le courant du moteur. Au début, faites fonctionner le moteur directement en le connectant à l'alimentation et mesurez le courant dans les conditions de fonctionnement maximales, ou reportez-vous aux spécifications du fabricant.

Après avoir fait cela, j'ai décidé d'utiliser quatre transistors à jonction bipolaire BC307A PNP sur un "pont" pour déterminer la direction du courant à travers les bobines du moteur (en fait, un ensemble NPN BC337 fonctionnerait mieux en raison de sa capacité à résister à des courants de collecteur nettement plus élevés, mais je ne l'ai pas fait je ne les ai pas à l'époque).

Étant donné que les courants du moteur doivent passer par le chemin émetteur-collecteur du transistor, il est nécessaire d'utiliser des transistors avec approximativement les mêmes coefficients de gain de courant continu (hfe). Pour le vérifier, vous pouvez utiliser le circuit suivant et collecter des transistors vous donnant approximativement la même lecture de courant sur l'ampèremètre. Pour concevoir ces circuits préliminaires, vous devez tenir compte des éléments suivants :

  • Recherchez « Base-Emetteur sous tension ” (VBEon ) de transistor. C'est la tension minimale à appliquer à la base pour allumer le transistor.
  • Trouvez le « gain de courant continu typique » ” (hfe ) du transistor aux alentours du courant du collecteur proche du courant du moteur. Typiquement, c'est le rapport entre Collector Current (IC ) et Courant de base (IB ), hfe =IC / IB .
  • Rechercher « Courant continu maximal du collecteur ” de transistors (ICmax ). Le courant continu du moteur ne doit jamais dépasser cette valeur en termes de valeurs absolues. Je peux utiliser BC307 car le moteur que j'utilise a besoin de 70 mA tandis que le transistor a ICmax(abs) =100 mA.

Vous pouvez maintenant déterminer la valeur de la résistance à connecter à la base :au début, vous devez prendre en compte les limites de la sortie de votre carte contrôleur et essayer de maintenir le courant de base aussi bas que possible (il est donc recommandé de sélectionner le gain de courant continu des transistors au maximum Prenez la tension nominale de la sortie sur la carte contrôleur comme « Trigger Voltage ” (VT ), et recherchez le courant de base requis (IBreq ) en divisant le courant du moteur (IM ) au gain de courant continu (hfe ) de transistor :IBreq =IM/hfe .

Déterminez ensuite la tension à chuter à travers la résistance (VR ), en soustrayant la Base-Emitter On-Voltage (VBEon ) à partir de la Tension de déclenchement :VR =VT - VBEon .

Enfin, divisez la tension à supprimer sur la Résistance (VR ) à Courant de base requis (IBreq ) pour trouver la Valeur de résistance (R ):R =VR / IBreq .

[ Formulation combinée :R =( VT - VBEon ) * hfe / IM ]

Sur mon cas :

  • Courant du moteur :IM =70 mA
  • Paramètres BC307A :ICmax =100 mA, hfe =140 (j'ai mesuré approximativement), VBEon =0,62 V
  • Tension de déclenchement : VT = 3,3 V (sortie PWM d'Arduino Due)
  • R =5360 ohms (j'ai donc décidé d'utiliser 4900 ohms fabriqués par un 2K2 et un 2K7, pour assurer une couverture complète de la plage de RPM et le circuit aspire juste ~ 0,6 mA de la sortie PWM - une conception appropriée.)

Inverser la direction et notes importantes

Pour inverser la direction d'un moteur à courant continu, il suffit d'inverser le flux de courant. Pour ce faire, nous pouvons simplement faire un circuit en pont avec quatre ensembles de transistors. Sur le schéma; La sortie PWM n°2 active T1A et T1B tandis que la sortie PWM n°3 active T2A et T2B, de sorte que le courant traversant le moteur est modifié.

Mais ici, nous devons considérer un autre problème :lorsqu'ils viennent juste de démarrer, les moteurs électriques aspirent un courant de démarrage transitoire nettement supérieur au courant nominal que vous lisez pendant le fonctionnement normal/continu (les fabricants ne donnent que des courants nominaux). Le courant de démarrage peut être d'environ 130 % de la valeur nominale pour les moteurs de petite puissance et augmente en fonction de la puissance du moteur. Ainsi, si vous alimentez le moteur directement à partir d'une source de tension et inversez immédiatement la polarité pendant le fonctionnement, le moteur aspire des niveaux de courant extrêmes car il n'est pas complètement arrêté. Enfin, cela peut entraîner l'explosion de la source d'alimentation ou la combustion des bobines du moteur. Cela peut ne pas être si important et ressenti pour les moteurs de très petite puissance, mais devient important si les niveaux de puissance sur lesquels vous travaillez augmentent. Mais si vous alimentez le moteur via un transistor ou un ensemble de transistors (comme le couple Darlington), vous n'avez pas ce problème puisque les transistors limitent déjà le courant.

Quoi qu'il en soit, j'ai envisagé une petite routine sur le programme :lorsque la sélection de direction est modifiée pendant l'exécution, le programme met d'abord les deux sorties de commande à zéro et attend le moteur jusqu'à l'arrêt complet. Ensuite, il termine son devoir et redonne tout le contrôle à la routine principale.

if ( Direction !=prevDirection ) { /* Tuer les deux sorties PWM vers le moteur */ analogWrite(_chMotorCmdCCW,0) ; analogWrite(_chMotorCmdCW,0) ; /* Attendre que la vitesse du moteur diminue */ do { RPM =60*(float)readFrequency(_chSpeedRead,4)/_DiscSlots; } while ( RPM> _minRPM ); }  

Lecture rapide

Sur mon application, j'ai utilisé un capteur de vitesse HC-020K bon marché. Il envoie des impulsions au niveau de sa tension d'alimentation, et la fiche technique indique que la tension d'alimentation est de 5V. Cependant, ma carte est Arduino Due et elle ne peut pas l'accepter. Je l'ai donc alimenté directement à partir de la sortie 3,3 V de Due, et oui, cela a fonctionné. Et la fonction suivante est écrite pour lire la fréquence et la sortie HC-020K.

int readFrequency(int _DI_FrequencyCounter_Pin, float _ReadingSpeed) { pinMode(_DI_FrequencyCounter_Pin, INPUT); octet _DigitalRead, _DigitalRead_Previous =0 ; _Time long non signé =0, _Time_Init ; float _Frequency =0; if ( (_ReadingSpeed<=0) || (_ReadingSpeed>10) ) return (-1); else { _Time_Init =micros(); do { _DigitalRead =digitalRead(_DI_FrequencyCounter_Pin); if ( (_DigitalRead_Previous==1) &&(_DigitalRead==0) ) _Frequency++; _DigitalRead_Previous =_DigitalRead; _Temps =micros(); } while ( _Time <(_Time_Init + (1000000/_ReadingSpeed)) ); } return (_ReadingSpeed ​​* _Frequency); }  

Notez que la roue du HC-020K a 20 emplacements, la fréquence de lecture doit simplement être divisée par 20 afin d'obtenir un tour par seconde comme fréquence. Ensuite, le résultat doit être multiplié par 60 pour obtenir RPM.

RPM =60 * (float)readFrequency(_chSpeedRead,4) / _DiscSlots ; 

Retouches graphiques

Afin d'afficher les entrées et les résultats, j'ai utilisé un écran LCD TFT Makeblock Me et écrit la fonction CommandToTFT() pour lui envoyer des commandes. En fait, la raison de cette fonction est simplement de changer le point de connexion série sur une seule ligne du programme, si nécessaire.

Les fonctions Cartesian_Setup(), Cartesian_ClearPlotAreas() et Cartesian_Line() sont écrites pour préparer la zone de traçage graphique, effacer la zone de traçage lorsqu'elle est atteinte à la fin de l'axe horizontal (ici c'est le « temps ») et tracer des graphiques respectivement. Référez-vous au manuel Makeblock Me TFT LCD pour plus de détails si vous vous intéressez aux fonctions graphiques ici, car je ne les expliquerai pas ici car elles sont en fait hors de la portée de ce blog.

Fin

Ici, je copie le programme avec et sans fonctions graphiques séparément, afin que vous puissiez revoir la mise en œuvre du contrôle de vitesse et de direction de manière autonome ou avec des graphiques. En outre, sur les codes, vous pouvez trouver d'autres explications sur les fonctions programmées.

Enfin, normalement, vous ne pouvez pas réduire la vitesse d'un moteur à courant continu en dessous de 10 à 20 % de sa vitesse nominale, même s'il n'y a pas de charge. Cependant, il est possible de diminuer jusqu'à près de 5 % en utilisant le contrôle PID pour un moteur à courant continu non chargé une fois qu'il a démarré.

Modifier (25 février 2018) : Si vous souhaitez utiliser des transistors NPN au lieu de PNP, vous devez également considérer le flux de courant inverse sur les deux types. Lorsqu'il est déclenché (allumé), le courant passe de l'émetteur au collecteur sur les transistors PNP, mais c'est l'inverse pour les types NPN (du collecteur à l'émetteur). Par conséquent, les transistors PNP sont polarisés comme E(+) C(-) et pour NPN, il devrait être C(+) E(-).

Code

  • Code avec retouches graphiques
  • Code sans retouches graphiques
Code avec retouches graphiquesArduino
/* ############################################# ## Constantes de couleur pour Makeblock Me TFT LCD########################################## ###### */#define _BLACK 0#define _RED 1#define _GREEN 2#define _BLUE 3#define _YELLOW 4#define _CYAN 5#define _PINK 6#define _WHITE 7/* ######## ######################################## Affectations d'E/S######## ########################################### */int _chSpeedSet =A0, // Vitesse consigne _chKp =A1, // Lecture du coefficient proportionnel pour le contrôleur PID _chKi =A2, // Lecture du coefficient intégral pour le contrôleur PID _chKd =A3, // Lecture du coefficient dérivé pour le contrôleur PID _chMotorCmdCCW =3, // Sortie PWM vers le moteur pour le compteur- rotation horaire _chMotorCmdCW =2, // Sortie PWM vers le moteur pour rotation horaire _chSpeedRead =24, // Lecture de la vitesse _chDirection =25 ; // Lecture sélecteur de sens/* ########################################## #### Autres constantes ############################################ ### */#define _minRPM 0 // RPM minimum pour initier le changement de direction#define _maxRPM 6000 // Limite de RPM maximum#define _Tmax 90 // Limite de temps maximum pour la représentation graphique#define _DiscSlots 20 // Quantité de slots sur le disque d'index/ * ############################################### Variables globales ############################################### */Chaîne de caractères Cartesian_SetupDetails;direction booléenne, prevDirection;// Paramètres d'alarmefloat RALL=500.0, RAL=1000.0, RAH=4000.0, RAHH=4500.0;float Seconds=0.0, prevSeconds=0.0, prevRPM=0.0, prevRPMset=0.0, RPM=0.0, RPMset=0.0, OutputRPM=0.0, Kp=0.0, Ki=0.0, Kd=0.0, Kpmax=2.0, Kimax=1.0, Kdmax=1.0, E=0.0, Eprev=0.0, dT=1.0;/* ###### ########################################## CommandToTFT (TFTCmd) Fonction de commande pour Makeblock Me Paramètres d'entrée LCD TFT :(chaîne) TFTCmd :chaîne de commande###################################### ######### */void CommandToTFT(String TFTCmd){ /* Connexion série utilisée pour l'affichage */ Serial1.println(TFTCmd); delay(5);}/* ########### Fin de CommandToTFT() ########### *//* ########### #################################### *//* ############ #################################### Configuration_Cartésienne(Xmin, Xmax, Ymin, Ymax, Window_X1, Window_Y1, Window_X2 , Window_Y2, MinDashQty, ColorF, ColorX, ColorY) Fonction de dessin de l'axe XY cartésien pour Makeblock Me TFT LCD Paramètres d'entrée :(float) Xmin, Xmax, Ymin, Ymax :valeurs de la plage d'axe (int) Window_X1, Window_Y1___ :coin supérieur gauche de fenêtre graphique (int) Window_X2, Window_Y2___ :Coin inférieur droit de la fenêtre graphique (int) MinDashQty_____________ :Qté de tirets sur l'axe le plus court (int) ColorB, ColorX, ColorY :Couleurs de dessin pour Frame, X-axis et Y-axis Uses fonction externe CommandToTFT().############################################ ### */String Cartesian_Setup( float Xmin, float Xmax, float Ymin, float Ymax, int Window_X1, int Window_Y1, int Window_X2, int Window_Y2, int MinDashQty, int ColorF, int ColorX, int ColorY ){ /* Screen Limitations * / const int Affichage RésolutionX =319, Résolution d'affichageY =239 ; /* Limiter les chaînes de titre */ String XminTxt; if (abs(Xmin)>=1000000000) XminTxt ="X=" + String (Xmin/1000000000) + "G" ; else if (abs(Xmin)>=1000000) XminTxt ="X=" + String (Xmin/1000000) + "M" ; else if (abs(Xmin)>=1000) XminTxt ="X=" + String (Xmin/1000) + "K" ; else XminTxt ="X=" + Chaîne (Xmin); Chaîne XmaxTxt; if (abs(Xmax)>=1000000000) XmaxTxt ="X=" + String (Xmax/1000000000) + "G" ; else if (abs(Xmax)>=1000000) XmaxTxt ="X=" + String (Xmax/1000000) + "M" ; else if (abs(Xmax)>=1000) XmaxTxt ="X=" + String (Xmax/1000) + "K" ; else XmaxTxt ="X=" + Chaîne (Xmax); Chaîne YminTxt; if (abs(Ymin)>=1000000000) YminTxt ="Y=" + String (Ymin/1000000000) + "G" ; else if (abs(Ymin)>=1000000) YminTxt ="Y=" + String (Ymin/1000000) + "M" ; else if (abs(Ymin)>=1000) YminTxt ="Y=" + String (Ymin/1000) + "K" ; else YminTxt ="Y=" + Chaîne (Ymin); Chaîne YmaxTxt; if (abs(Ymax)>=1000000000) YmaxTxt ="Y=" + String (Ymax/1000000000) + "G" ; else if (abs(Ymax)>=1000000) YmaxTxt ="Y=" + String (Ymax/1000000) + "M" ; else if (abs(Ymax)>=1000) YmaxTxt ="Y=" + String (Ymax/1000) + "K" ; sinon YmaxTxt ="Y=" + chaîne (Ymax); /* Limites */ int XminPx =Window_X1+1; int XmaxPx =Window_X2-1; int YmaxPx =Window_Y1+1 ; int YminPx =Window_Y2-1; /* Origine */ int OrigineX =XminPx + (int)( (XmaxPx - XminPx) * abs(Xmin) / (abs(Xmax)+abs(Xmin)) ); int OrigineY =YmaxPx + (int)( (YminPx - YmaxPx) * abs(Ymax) / (abs(Ymax)+abs(Ymin)) ); /* Frame */ CommandToTFT ( "BOX(" + String(Window_X1) + "," + String(Window_Y1)+ "," + String(Window_X2) + "," + String(Window_Y2)+ "," + String( CouleurF) + ");" ); /* Axe X */ CommandToTFT ( "PL(" + String(Window_X1+1) + "," + String(OriginY) + "," + String(Window_X2-1) + "," + String(OriginY) + " ," + Chaîne(CouleurX) + ");" ); /* Axe Y */ CommandToTFT ( "PL(" + String(OriginX) + "," + String(Window_Y1+1) + "," + String(OriginX) + "," + String(Window_Y2-1) + " ," + Chaîne(CouleurY) + ");" ); /* Tirets :le nombre minimum de tirets est donné par "MinDashQty" et sera en pointillés sur le côté de l'axe le plus court par rapport à l'origine. Sur les autres sections, les tirets à marquer doivent être déterminés en tenant compte du rapport au plus petit côté de l'axe. */ /* Tiret */ int XlengthLeft =abs(XminPx-OriginX); int XlengthRight =abs(XmaxPx-OriginX); int YlengthLower =abs(YminPx-OriginY); int YlengthUpper =abs(YmaxPx-OriginY); int XlengthLeft_Mod, XlengthRight_Mod, YlengthLower_Mod, YlengthUpper_Mod; if (XlengthLeft<=1) XlengthLeft_Mod=32767; else XlengthLeft_Mod=XlengthLeft ; if (XlengthRight<=1) XlengthRight_Mod=32767 ; else XlengthRight_Mod=XlengthRight; if (YlengthLower<=1) YlengthLower_Mod=32767; sinon YlengthLower_Mod=YlengthLower; if (YlengthUpper<=1) YlengthUpper_Mod=32767; else YlengthUpper_Mod=YlengthUpper; int MinAxisLength =min ( min (XlengthLeft_Mod,XlengthRight_Mod), min (YlengthLower_Mod,YlengthUpper_Mod) ); int XdashesLeft =MinDashQty * XlengthLeft / MinAxisLength; int XdashesRight =MinDashQty * XlengthRight / MinAxisLength; int YdashesLower =MinDashQty * YlengthLower / MinAxisLength; int YdashesUpper =MinDashQty * YlengthUpper / MinAxisLength; int DashingInterval=2; // Min.interval btw.dashes /* X-Dash L */ DashingInterval =(int) (XlengthLeft / XdashesLeft); if (!(DashingInterval<2)) for (int i=OriginX; i>=XminPx; i-=DashingInterval) CommandToTFT ( "PL(" + String(i) + "," + String(OriginY-2) + " ," + Chaîne(i) + "," + Chaîne(OrigineY+2) + "," + Chaîne(CouleurX) + ");" ); /* X-Dash R */ DashingInterval =(int) (XlengthRight / XdashesRight); if (!(DashingInterval<2)) for (int i=OriginX; i<=XmaxPx; i+=DashingInterval) CommandToTFT ( "PL(" + String(i) + "," + String(OriginY-2) + ", " + Chaîne(i) + "," + Chaîne(OrigineY+2) + "," + Chaîne(CouleurX) + ");" ); /* Y-Dash-L */ DashingInterval =(int) (YlengthLower / YdashesLower); if (!(DashingInterval<2)) for (int i=OriginY; i<=YminPx; i+=DashingInterval) CommandToTFT ( "PL(" + String(OriginX-2) + "," + String(i) + ", " + Chaîne(OrigineX+2) + "," + Chaîne(i) + "," + Chaîne(CouleurY) + ");" ); /* Y-Dash-U */ DashingInterval =(int) (YlengthUpper / YdashesUpper); if (!(DashingInterval<2)) for (int i=OriginY; i>=YmaxPx; i-=DashingInterval) CommandToTFT ( "PL(" + String(OriginX-2) + "," + String(i) + " ," + Chaîne(OrigineX+2) + "," + Chaîne(i) + "," + Chaîne(CouleurY) + ");" ); /* Calcul des coordonnées pour afficher les valeurs des extrémités de l'axe */ int XminTxtX =Window_X1 - (int)(XminTxt.length()*6) - 1, XminTxtY =OriginY, XmaxTxtX =Window_X2 + 1, XmaxTxtY =OriginY, YminTxtX =OriginX, YminTxtY =Window_Y2 + 1, YmaxTxtX =OriginX, YmaxTxtY =Window_Y1 - 12 - 1 ; /* Contrôles :si une coordonnée est -1, elle doit tomber au-delà des limites d'affichage et la valeur respective ne doit pas être affichée */ if (XminTxtX<0) XminTxtX =-1 ; si ( (XminTxtY-12) <0 ) XminTxtY =-1 ; if ( (XmaxTxtX+6*XmaxTxt.length())> DisplayResolutionX ) XmaxTxtX =-1; si ( (XmaxTxtY+12)> DisplayResolutionY ) XmaxTxtY =-1; if ( (YminTxtX+6*YminTxt.length())> DisplayResolutionX ) YminTxtX =-1; si ( (YminTxtY+12)> DisplayResolutionY ) YminTxtY =-1; if ( (YmaxTxtX+6*YmaxTxt.length())> DisplayResolutionX ) YmaxTxtX =-1; si (YmaxTxtY<0) YmaxTxtY =-1 ; /* Titres de limite de plage */ if ( ( XminTxtX !=-1 ) &&( XminTxtY !=-1 ) ) CommandToTFT( "DS12(" + String(XminTxtX) + "," + String(XminTxtY) + ",'" + Chaîne(XminTxt) + "'," + Chaîne(CouleurX) + ");" ); if ( ( XmaxTxtX !=-1 ) &&( XmaxTxtY !=-1 ) ) CommandToTFT( "DS12(" + String(XmaxTxtX) + "," + String(XmaxTxtY) + ",'" + String(XmaxTxt) + " '," + Chaîne(CouleurX) + ");" ); if ( ( YminTxtX !=-1 ) &&( YminTxtY !=-1 ) ) CommandToTFT( "DS12(" + String(YminTxtX) + "," + String(YminTxtY) + ",'" + String(YminTxt) + " '," + Chaîne(CouleurY) + ");" ); if ( ( YmaxTxtX !=-1 ) &&( YmaxTxtY !=-1 ) ) CommandToTFT( "DS12(" + String(YmaxTxtX) + "," + String(YmaxTxtY) + ",'" + String(YmaxTxt) + " '," + Chaîne(CouleurY) + ");" ); /* Valeur de retour Chaîne Cartesian_Setup() renverra une chaîne de configuration graphique d'emballage au format suivant :"" La chaîne commence par '<' et se termine par '>' . Chaque valeur est délimitée par ',' */ /* Initialize */ String Cartesian_SetupDetails ="<"; Cartesian_SetupDetails +=( String(Xmin) + "," ); Cartesian_SetupDetails +=( String(Xmax) + "," ); Cartesian_SetupDetails +=( String(Ymin) + "," ); Cartesian_SetupDetails +=( String(Ymax) + "," ); Cartesian_SetupDetails +=( String(Window_X1) + "," ); Cartesian_SetupDetails +=( String(Window_Y1) + "," ); Cartesian_SetupDetails +=( String(Window_X2) + "," ); Cartesian_SetupDetails +=( String(Window_Y2) + "," ); /* Clôture */ Cartesian_SetupDetails +=">";return Cartesian_SetupDetails;}/* ########### Fin de Cartesian_Setup() ########### *// * ################################################### * //* ################################################ Cartesian_ClearPlotAreas(Descriptor, Color) Fonction de réinitialisation/effacement de la zone de tracé pour Makeblock Me TFT Paramètres d'entrée LCD :(Chaîne) Descripteur :Descripteur de configuration - renvoyé par Cartesian_Setup() (int) Couleur______ :Couleur à utiliser pour remplir la zone de tracé Utilise la fonction externe CommandToTFT ().################################################## */void Cartesian_ClearPlotAreas(String Descriptor, int Color){ int X1,Y1,X2,Y2; /* Coordonnées des limites des zones de tracé */ /* Extraction des valeurs du descripteur */ /* L[0] L[1] L[2] L[3] W[0] W[1] W[2] W[3 ] */ /* Xmin Xmax Ymin Ymax Window_X1 Window_Y1 Window_X2 Window_Y2 */ float L[4]; entier W[4] ; /* Valeurs stockées dans le descripteur */ int j=0; /* Compteur */ Chaîne D_Str =""; for (int i=1; i<=(Descriptor.length()-1); i++) if ( Descriptor[i] ==',' ) { if (j<4) L[j]=D_Str.toFloat( ); else W[j-4]=D_Str.toInt(); D_Str="" ; j++; } else D_Str +=Descripteur[i]; /* Origine */ int OrigineX =(W[0]+1) + (int)( ( (W[2]-1) - (W[0]+1) ) * abs(L[0]) / ( abs(L[1])+abs(L[0])) ); int OrigineY =(W[1]+1) + (int)( ( (W[3]-1) - (W[1]+1) ) * abs(L[3]) / (abs(L[3 ])+abs(L[2])) ); /* Nettoyer les zones de tracé */ //Area.1 :X+ Y+ X1 =OriginX + 2; Y1 =W[1] + 1; X2 =W[2] - 1; Y2 =OrigineY - 2; CommandToTFT ( "BOXF(" + Chaîne(X1) + "," + Chaîne(Y1) + "," + Chaîne(X2) + "," + Chaîne(Y2) + "," + Chaîne(Couleur + ");" ); //Zone.2 :X- Y+ X1 =W[0] + 1; Y1 =W[1] + 1; X2 =OrigineX - 2; Y2 =OrigineY - 2; CommandToTFT ( "BOXF(" + Chaîne(X1) + "," + Chaîne(Y1) + "," + Chaîne(X2) + "," + Chaîne(Y2) + "," + Chaîne(Couleur + ");" ); //Zone.3 :X- Y- X1 =W[0] + 1; Y1 =OrigineY + 2; X2 =OrigineX - 2; Y2 =W[3] - 1; CommandToTFT ( "BOXF(" + Chaîne(X1) + "," + Chaîne(Y1) + "," + Chaîne(X2) + "," + Chaîne(Y2) + "," + Chaîne(Couleur + ");" ); //Zone.4 :X+ Y- X1 =OrigineX + 2; Y1 =OrigineY + 2; X2 =W[2] - 1; Y2 =W[3] - 1; CommandToTFT ( "BOXF(" + Chaîne(X1) + "," + Chaîne(Y1) + "," + Chaîne(X2) + "," + Chaîne(Y2) + "," + Chaîne(Couleur + ");" );} /* ########### Fin de Cartesian_ClearPlotAreas() ########### *//* ############ ############################################## *//* # ################################################Cartésian_Line(Xp, Yp, X, Y, Descriptor, Color) Fonction de ligne cartésienne pour Makeblock Me TFT LCD Paramètres d'entrée :(int) Xp, Yp_____ :Coordonnées du tracé précédent - valeur y vs x (int) X, Y_______ :Coordonnées du tracé actuel - valeur y vs x (String) Descriptor :Setup Descriptor - renvoyé par Cartesian_Setup() (int) Color______ :Couleur de marquage à utiliser sur (x,y) Utilise la fonction externe CommandToTFT().############## ##################################### */void Cartésian_Line(float Xp, float Yp, float X, float Y , String Descriptor, int Color){ /* Extraction des valeurs du descripteur */ /* L[0] L[1] L[2] L[3] W[0] W[1] W[2] W[3] */ /* Xmin Xmax Ymin Ymax Window_X1 Window_Y1 Window_X2 Window_Y2 */ float L[4 ] ; entier W[4] ; /* Valeurs stockées dans le descripteur */ int j=0; /* Compteur */ Chaîne D_Str =""; for (int i=1; i<=(Descriptor.length()-1); i++) if ( Descriptor[i] ==',' ) { if (j<4) L[j]=D_Str.toFloat( ); else W[j-4]=D_Str.toInt(); D_Str="" ; j++; } else D_Str +=Descripteur[i]; /* Origine */ int OrigineX =(W[0]+1) + (int)( ( (W[2]-1) - (W[0]+1) ) * abs(L[0]) / ( abs(L[1])+abs(L[0])) ); int OrigineY =(W[1]+1) + (int)( ( (W[3]-1) - (W[1]+1) ) * abs(L[3]) / (abs(L[3 ])+abs(L[2])) ); entier XminPx =W[0] + 1 ; entier XmaxPx =W[2] - 1 ; entier YmaxPx =W[1] + 1 ; entier YminPx =W[3] - 1 ; si (Y>L[3]) Y=L[3] ; si (Y =(OrigineX-2) ) &&( DispXp <=(OrigineX+2) ) ) || ( ( DispYp> =(OrigineY-2) ) &&( DispYp <=(OrigineY+2) ) ) || ( ( DispX>=(OrigineX-2) ) &&( DispX <=(OrigineX+2) ) ) || ( ( DispY>=(OrigineY-2) ) &&( DispY <=(OrigineY+2) ) ) )) CommandToTFT( "PL(" + String(DispXp) + "," + String(DispYp) + "," + String(DispX) + "," + String(DispY) + "," + String(Couleur ) + ");" );}/* ########### Fin de la ligne_Cartésienne() ########### *//* ######## ######################################## *//* ####### ######################################## readFrequency(_DI_FrequencyCounter_Pin, _ReadingSpeed) Entrée de la fonction de lecture de fréquence Paramètres :(int) _DI_FrequencyCounter_Pin :Broche numérique à lire (float) _ReadingSpeed____________ :Vitesse de lecture personnalisée entre 0...10 (Note.1) Note.1 :_ReadingSpeed ​​est une valeur pour spécifier combien de temps les changements doivent être comptés. Il ne peut pas s'agir de 0 (zéro), de valeurs négatives ou d'une valeur supérieure à 10. Lorsque _ReadingSpeed ​​a changé, 1 seconde doit être divisée par cette valeur pour calculer la durée de comptage requise. Par example; - _ReadingSpeed ​​=0,1 -> l'entrée doit être comptée pendant 10 secondes (=1/0,1) - _ReadingSpeed ​​=0,5 -> l'entrée doit être comptée pendant 2 secondes (=1/0,5) - _ReadingSpeed ​​=2,0 -> l'entrée doit être comptée pendant 0,5 secondes (=1/2) - _ReadingSpeed ​​=4,0 -> l'entrée doit être comptée pendant 0,25 seconde (=1/4) Il est important de noter que l'augmentation de _ReadingSpeed ​​est un inconvénient en particulier sur les fréquences plus basses (généralement inférieures à 100 Hz) car l'erreur de comptage augmente jusqu'à 20%~40% en diminuant la fréquence.######################################## ######## */int readFrequency(int _DI_FrequencyCounter_Pin, float _ReadingSpeed){ pinMode(_DI_FrequencyCounter_Pin, INPUT); octet _DigitalRead, _DigitalRead_Previous =0 ; _Time long non signé =0, _Time_Init ; float _Frequency =0; if ( (_ReadingSpeed<=0) || (_ReadingSpeed>10) ) return (-1); else { _Time_Init =micros(); do { _DigitalRead =digitalRead(_DI_FrequencyCounter_Pin); if ( (_DigitalRead_Previous==1) &&(_DigitalRead==0) ) _Frequency++; _DigitalRead_Previous =_DigitalRead; _Time =micros(); } while ( _Time <(_Time_Init + (1000000/_ReadingSpeed)) ); } return (_ReadingSpeed * _Frequency);}/* ########### End of readFrequency() ########### *//* ############################################## *//* ############################################### controllerPID(RangeMin, RangeMax, _E, _Eprev, _dT, _Kp, _Ki, _Kd) PID Controller Function Input Parameters:(float) RangeMin:Minimum limit for output (float) RangeMax:Maximum limit for output (float) _E_____:Current error signal (float) _Eprev :Previous error signal (float) _dT____:Time difference as seconds (float) _Kp____:Proportional coefficient (float) _Ki____:Integral coefficient (float) _Kp____:Derivative coefficient Adjustment procedure:1. Set Kp=0, Ki=0, Kd=0. 2. Start to increase Kp until the system oscillates at fixed period (Pc) and note critical gain Kc =Kp. 3. Adjust final coefficients as follows. for P-control only :Kp =0.50*Kc for PI-control only :Kp =0.45*Kc, Ki =1.2/Pc for PID-control :Kp =0.60*Kc, Ki =2.0/Pc, Kd=Pc/8 4. Fine tuning could be done by slightly changing each coefficient.############################################### */ float controllerPID(float _E, float _Eprev, float _dT, float _Kp, float _Ki, float _Kd){ float P, I, D; /* Base Formula:U =_Kp * ( _E + 0.5*(1/_Ki)*(_E+_Eprev)*_dT + _Kd*(_E-_Eprev)/_dT ); */ P =_Kp * _E; /* Proportional Component */ I =_Kp * 0.5 * _Ki * (_E+_Eprev) * _dT; /* Integral Component */ D =_Kp * _Kd * (_E-_Eprev) / _dT; /* Derivative Component */ return (P+I+D);}/* ########### End of controllerPID() ########### *//* ############################################## *//* ############################################### Setup############################################### */void setup(){ Serial1.begin(9600); Serial1.println("CLS(0);");delay(20); analogReadResolution(12); pinMode(_chDirection,INPUT); // Direction selector reading pinMode(_chMotorCmdCCW,OUTPUT); // PWM output to motor for counter-clockwise turn pinMode(_chMotorCmdCW,OUTPUT); // PWM output to motor for clockwise turn // Initial killing the PWM outputs to motor analogWrite(_chMotorCmdCCW,0); analogWrite(_chMotorCmdCW,0); // Initial reading for direction selection Direction=digitalRead(_chDirection); // HIGH=CCW, LOW=CW prevDirection=Direction; // The section below prepares TFT LCD // Cartesian_Setup(Xmin, Xmax, Ymin, Ymax, Window_X1, Window_Y1, Window_X2, Window_Y2, MinDashQty, ColorF, ColorX, ColorY) Cartesian_SetupDetails =Cartesian_Setup(0, _Tmax, _minRPM, _maxRPM, 20, 20, 220, 120, 10, 0, 7, 7); CommandToTFT("DS12(250,10,'Dir:CW '," + String(_WHITE) + ");"); CommandToTFT("DS12(250,25,'____ Set'," + String(_YELLOW) + ");"); CommandToTFT("DS12(250,40,'____ RPM'," + String(_GREEN) + ");"); /* Alarm Values */ CommandToTFT("DS12(250,55,'AHH:" + String(RAHH) + "'," + String(_WHITE) + ");"); CommandToTFT("DS12(250,70,'AH :" + String(RAH) + "'," + String(_WHITE) + ");"); CommandToTFT("DS12(250,85,'AL :" + String(RAL) + "'," + String(_WHITE) + ");"); CommandToTFT("DS12(250,100,'ALL:"+ String(RALL) + "'," + String(_WHITE) + ");"); /* Alarm Window */ CommandToTFT("BOX(240,55,319,115," + String(_WHITE) + ");"); /* Alarm Lamps */ CommandToTFT("BOX(240,55,248,70," + String(_WHITE) + ");"); CommandToTFT("BOX(240,70,248,85," + String(_WHITE) + ");"); CommandToTFT("BOX(240,85,248,100," + String(_WHITE) + ");"); CommandToTFT("BOX(240,100,248,115," + String(_WHITE) + ");");}/* ############################################### Loop############################################### */void loop(){ // Initialization Time:Necessary for PID controller. int InitTime =micros(); // X-Axis Auto-Reset for Graphing if ( Seconds> 90.0 ) { Seconds =0.0; Cartesian_ClearPlotAreas(Cartesian_SetupDetails,0); } // Reading Inputs /* Controller Coefficients */ Kp =Kpmax * (float)analogRead(_chKp) / 4095; Ki =Kimax * (float)analogRead(_chKi) / 4095; Kd =Kdmax * (float)analogRead(_chKd) / 4095; /* Direction Selector */ Direction =digitalRead(_chDirection); /* HIGH=CCW, LOW=CW */ /* Actual RPM and RPM Setpoint Note that maximum selectable RPM is 5000. */ RPM =60 * (float)readFrequency(_chSpeedRead,4) / _DiscSlots; RPMset =5000 * (float)analogRead(_chSpeedSet) / 4095; // Calculations and Actions /* Error Signal, PID Controller Output and Final Output (PWM) to Motor */ E =RPMset - RPM; float cPID =controllerPID(E, Eprev, dT, Kp, Ki, Kd); if ( RPMset ==0 ) OutputRPM =0; else OutputRPM =OutputRPM + cPID; if ( OutputRPM <_minRPM ) OutputRPM =_minRPM; if ( OutputRPM> _maxRPM ) OutputRPM =_maxRPM; /* Changing Direction when inverted Note that no any graphical indication is performed on this function. */ if ( Direction !=prevDirection ) { /* Killing both of the PWM outputs to motor */ analogWrite(_chMotorCmdCCW,0); analogWrite(_chMotorCmdCW,0); /* Wait until motor speed decreases */ do { RPM =60 * (float)readFrequency(_chSpeedRead,4) / _DiscSlots; } while ( RPM> _minRPM ); } // Writing Outputs if (Direction==HIGH) analogWrite(_chMotorCmdCCW,(int)(255*OutputRPM/_maxRPM)); else analogWrite(_chMotorCmdCW, (int)(255*OutputRPM/_maxRPM)); // Graphing /* Indicating Direction */ if (Direction==HIGH) CommandToTFT("DS12(280,10,'CCW '," + String(_WHITE) + ");"); else CommandToTFT("DS12(280,10,'CW '," + String(_WHITE) + ");"); /* Plotting Curve */ Cartesian_Line(prevSeconds, prevRPMset, Seconds, RPMset, Cartesian_SetupDetails, _YELLOW); Cartesian_Line(prevSeconds, prevRPM, Seconds, RPM, Cartesian_SetupDetails, _GREEN); /* Indicating values of RPM Setpoint, PID Controller Coefficients, Error Signal, PID Controller Output and Final RPM Output (PWM) */ CommandToTFT( "DS12(20,150,'Set:" + String(RPMset) + " rpm " + "RPM:" + String(RPM) + " rpm '," + String(_WHITE) + ");"); CommandToTFT( "DS12(20,170,'Kp=" + String(Kp) + " " + "Ki=" + String(Ki) + " " + "Kd=" + String(Kd) + " " + "dT=" + String(dT*1000) + " ms '," + String(_WHITE) + ");"); CommandToTFT( "DS12(20,190,'e=" + String(E) + " " + "cPID=" + String(cPID) + " " + "RPMout=" + String(OutputRPM) + " '," + String(_WHITE) + ");"); /* Resetting Alarm Lamps */ CommandToTFT("BOXF(241,56,247,69," + String(_BLACK) + ");"); CommandToTFT("BOXF(241,71,247,84," + String(_BLACK) + ");"); CommandToTFT("BOXF(241,86,247,99," + String(_BLACK) + ");"); CommandToTFT("BOXF(241,101,247,114," + String(_BLACK) + ");"); /* Activating Necessary Alarm Lamps */ if (RPM>=RAHH) CommandToTFT("BOXF(241,56,247,69," + String(_RED) + ");"); if ((RPM>=RAH)&&(RPMRALL)&&(RPM<=RAL)) CommandToTFT("BOXF(241,86,247,99," + String(_RED) + ");"); if (RPM<=RALL) CommandToTFT("BOXF(241,101,247,114," + String(_RED) + ");"); // Storing Values generated on previous cycle Eprev =E; prevRPMset =RPMset; prevRPM =RPM; prevSeconds =Seconds; prevDirection =Direction; // Calculating control application cycle time and passed Seconds dT =float ( micros() - InitTime ) / 1000000.0; Seconds+=dT; }
Code without Graphical Touch-UpsArduino
/* ############################################### I/O Assignments############################################### */int _chSpeedSet =A0, // Speed setpoint _chKp =A1, // Proportional coefficient reading for PID controller _chKi =A2, // Integral coefficient reading for PID controller _chKd =A3, // Derivative coefficient reading for PID controller _chMotorCmdCCW =3, // PWM output to motor for counter-clockwise turn _chMotorCmdCW =2, // PWM output to motor for clockwise turn _chSpeedRead =24, // Speed reading _chDirection =25; // Direction selector reading/* ############################################### Other Constants ############################################### */#define _minRPM 0 // Minimum RPM to initiate direction changing#define _maxRPM 6000 // Maximum RPM limit#define _DiscSlots 20 // Qty of slots on Index Disc/* ############################################### Global Variables############################################### */boolean Direction, prevDirection;float RPM=0.0, RPMset=0.0, OutputRPM=0.0, Kp=0.0, Ki=0.0, Kd=0.0, Kpmax=2.0, Kimax=1.0, Kdmax=1.0, E=0.0, Eprev=0.0, dT=1.0;/* ############################################### readFrequency(_DI_FrequencyCounter_Pin, _ReadingSpeed) Frequency Reading Function Input Parameters:(int) _DI_FrequencyCounter_Pin :Digital pin to be read (float) _ReadingSpeed____________:Custom reading speed between 0...10 (Note.1) Note.1:_ReadingSpeed is a value to specify how long shall the changes be counted. It cannot be 0(zero), negative values or a value greater than 10. When _ReadingSpeed changed, 1 second shall be divided by this value to calculate required counting duration. For example; - _ReadingSpeed =0.1 -> input shall be counted during 10 seconds (=1/0.1) - _ReadingSpeed =0.5 -> input shall be counted during 2 seconds (=1/0.5) - _ReadingSpeed =2.0 -> input shall be counted during 0.5 seconds (=1/2) - _ReadingSpeed =4.0 -> input shall be counted during 0.25 seconds (=1/4) Importantly note that, increasing of _ReadingSpeed is a disadvantage especially on lower frequencies (generally below 100 Hz) since counting error increases up to 20%~40% by decreasing frequency.############################################### */int readFrequency(int _DI_FrequencyCounter_Pin, float _ReadingSpeed){ pinMode(_DI_FrequencyCounter_Pin,INPUT); byte _DigitalRead, _DigitalRead_Previous =0; unsigned long _Time =0, _Time_Init; float _Frequency =0; if ( (_ReadingSpeed<=0) || (_ReadingSpeed>10) ) return (-1); else { _Time_Init =micros(); do { _DigitalRead =digitalRead(_DI_FrequencyCounter_Pin); if ( (_DigitalRead_Previous==1) &&(_DigitalRead==0) ) _Frequency++; _DigitalRead_Previous =_DigitalRead; _Time =micros(); } while ( _Time <(_Time_Init + (1000000/_ReadingSpeed)) ); } return (_ReadingSpeed * _Frequency);}/* ########### End of readFrequency() ########### *//* ############################################## *//* ############################################### controllerPID(RangeMin, RangeMax, _E, _Eprev, _dT, _Kp, _Ki, _Kd) PID Controller Function Input Parameters:(float) RangeMin:Minimum limit for output (float) RangeMax:Maximum limit for output (float) _E_____:Current error signal (float) _Eprev :Previous error signal (float) _dT____:Time difference as seconds (float) _Kp____:Proportional coefficient (float) _Ki____:Integral coefficient (float) _Kp____:Derivative coefficient Adjustment procedure:1. Set Kp=0, Ki=0, Kd=0. 2. Start to increase Kp until the system oscillates at fixed period (Pc) and note critical gain Kc =Kp. 3. Adjust final coefficients as follows. for P-control only :Kp =0.50*Kc for PI-control only :Kp =0.45*Kc, Ki =1.2/Pc for PID-control :Kp =0.60*Kc, Ki =2.0/Pc, Kd=Pc/8 4. Fine tuning could be done by slightly changing each coefficient.############################################### */ float controllerPID(float _E, float _Eprev, float _dT, float _Kp, float _Ki, float _Kd){ float P, I, D; /* Base Formula:U =_Kp * ( _E + 0.5*(1/_Ki)*(_E+_Eprev)*_dT + _Kd*(_E-_Eprev)/_dT ); */ P =_Kp * _E; /* Proportional Component */ I =_Kp * 0.5 * _Ki * (_E+_Eprev) * _dT; /* Integral Component */ D =_Kp * _Kd * (_E-_Eprev) / _dT; /* Derivative Component */ return (P+I+D);}/* ########### End of controllerPID() ########### *//* ############################################## *//* ############################################### Setup############################################### */void setup(){ analogReadResolution(12); pinMode(_chDirection,INPUT); // Direction selector reading pinMode(_chMotorCmdCCW,OUTPUT); // PWM output to motor for counter-clockwise turn pinMode(_chMotorCmdCW,OUTPUT); // PWM output to motor for clockwise turn // Initial killing the PWM outputs to motor analogWrite(_chMotorCmdCCW,0); analogWrite(_chMotorCmdCW,0); // Initial reading for direction selection Direction=digitalRead(_chDirection); // HIGH=CCW, LOW=CW prevDirection=Direction;}/* ############################################### Loop############################################### */void loop(){ // Initialization Time:Necessary for PID controller. int InitTime =micros(); // Reading Inputs /* Controller Coefficients */ Kp =Kpmax * (float)analogRead(_chKp) / 4095; Ki =Kimax * (float)analogRead(_chKi) / 4095; Kd =Kdmax * (float)analogRead(_chKd) / 4095; /* Direction Selector */ Direction =digitalRead(_chDirection); /* HIGH=CCW, LOW=CW */ /* Actual RPM and RPM Setpoint Note that maximum selectable RPM is 5000. */ RPM =60 * (float)readFrequency(_chSpeedRead,4) / _DiscSlots; RPMset =5000 * (float)analogRead(_chSpeedSet) / 4095; // Calculations and Actions /* Error Signal, PID Controller Output and Final Output (PWM) to Motor */ E =RPMset - RPM; float cPID =controllerPID(E, Eprev, dT, Kp, Ki, Kd); if ( RPMset ==0 ) OutputRPM =0; else OutputRPM =OutputRPM + cPID; if ( OutputRPM <_minRPM ) OutputRPM =_minRPM; if ( OutputRPM> _maxRPM ) OutputRPM =_maxRPM; /* Changing Direction when inverted */ if ( Direction !=prevDirection ) { /* Killing both of the PWM outputs to motor */ analogWrite(_chMotorCmdCCW,0); analogWrite(_chMotorCmdCW,0); /* Wait until motor speed decreases */ do { RPM =60 * (float)readFrequency(_chSpeedRead,4) / _DiscSlots; } while ( RPM> _minRPM ); } // Writing Outputs if (Direction==HIGH) analogWrite(_chMotorCmdCCW,(int)(255*OutputRPM/_maxRPM)); else analogWrite(_chMotorCmdCW, (int)(255*OutputRPM/_maxRPM)); // Storing Values generated on previous cycle Eprev =E; prevDirection =Direction; // Calculating control application cycle time and passed Seconds dT =float ( micros() - InitTime ) / 1000000.0;}

Schémas

It's a prototype to explain DC motor speed control by using PID controller, and what should be considered for reversing.

Processus de fabrication

  1. Alliages de cuivre tungstène pour moteurs
  2. Contrôler un effet avec de vrais capteurs
  3. Arduino Nano :contrôler 2 moteurs pas à pas avec joystick
  4. Contrôler une matrice LED avec Arduino Uno
  5. Surveillance SMART de la température pour les écoles
  6. Bibliothèque de ports E/S 8 bits pour Arduino
  7. Matrice de clavier de prototypage à 64 touches pour Arduino
  8. Une entrée analogique isolée pour Arduino
  9. Robot pour une navigation intérieure super cool