Manufactura industrial
Internet industrial de las cosas | Materiales industriales | Mantenimiento y reparación de equipos | Programación industrial |
home  MfgRobots >> Manufactura industrial >  >> Manufacturing Technology >> Proceso de manufactura

Trucos para controlar motores de CC

Componentes y suministros

Arduino debido
De hecho, puedes usar cualquier placa Arduino.
× 1
Makeblock Me TFT LCD
Este es un componente opcional. Puede utilizar otro tipo de pantalla o prefiere no utilizar ninguna.
× 1
Resistencia de 4,75 k ohmios
Las resistencias pueden variar según su implementación.
× 4
Potenciómetro giratorio (genérico)
3 de ellos son opcionales (solo para ajustar los coeficientes del controlador).
× 4
Transistor de uso general PNP
Los transistores que se utilizarán pueden variar según su implementación.
× 4
Motor de CC (genérico)
× 1
Interruptor deslizante
Esto se usa para la selección de dirección.
× 1
Sensor de velocidad fotoeléctrico HC-020K
× 1
Breadboard (genérico)
× 1
Cables de puente (genéricos)
× 1

Aplicaciones y servicios en línea

Arduino IDE

Acerca de este proyecto

Control de velocidad y dirección de motores de CC mediante controlador PID y salidas PWM

Introducción

Casi en todos los proyectos disponibles, a los creadores les gustaría controlar las velocidades y la dirección del motor juntas, pero prefieren principalmente enviar PWM directamente a motores de CC, incluso a través de un circuito de control de motor. Pero este método siempre falla si necesita igualar la velocidad exactamente como lo desea debido a los hermanos gemelos llamados "fricción" e "inercia".

(Por favor, nunca culpe a los gemelos. Sea lo que sea y cuando quiera tomar algunas medidas con o sin algo, los gemelos vienen inmediatamente y toman medidas para ayudarlo a mantener todo bajo control. Mientras que la inercia permite que las cosas "piensen" antes de actuar, La fricción limita su aceleración y velocidad. Y la "potencia" no es "nada" si no está bajo control total.

Por lo tanto, si intenta controlar la velocidad de un motor directamente enviando su entrada como señal PWM a la salida, la velocidad real nunca se encontrará con su punto de ajuste y habrá una diferencia significativa (error), como se puede ver en la imagen de arriba. Aquí necesitamos otra forma, y ​​se llama "control PID".

Controlador PID

¿Qué es el control PID? Imagínese cómo conducir su automóvil:para comenzar a moverse desde la parada completa, debe presionar el pedal del acelerador más que durante el crucero normal. Durante el movimiento a una velocidad (casi) constante, no es necesario que pise demasiado el pedal del acelerador, sino que solo recupera la pérdida de velocidad cuando es necesario. Además, lo sueltas ligeramente si la aceleración es superior a tus necesidades. Esta es también la forma de "conducción eficiente".

Entonces, el controlador PID hace exactamente lo mismo:el controlador lee la diferencia “Señal de error (e)” entre el punto de ajuste y la salida real. Tiene 3 componentes diferentes denominados “Proporcional”, “Integral” y “Derivado”; por lo que el nombre del controlador aparece después de la primera letra de cada uno. El componente proporcional simplemente define la pendiente (aceleración) de la salida del controlador con respecto a la señal de error real. La pieza integral suma las señales de error a tiempo para minimizar el error final. Y el componente derivado vigila la aceleración de la señal de error y realiza un "ajuste". No daré aquí más y más detalles, busque más en Internet si está interesado.

En mi programa Arduino, el controlador PID está escrito como una función que se muestra a continuación:

  float controllerPID (float _E, float _Eprev, float _dT, float _Kp, float _Ki, float _Kd) {float P, I, D; / * Fórmula base:U =_Kp * (_E + 0.5 * (1 / _Ki) * (_ E + _Eprev) * _ dT + _Kd * (_ E-_Eprev) / _ dT); * / P =_Kp * _E; / * Componente proporcional * / I =_Kp * 0.5 * _Ki * (_E + _Eprev) * _dT; / * Componente integral * / D =_Kp * _Kd * (_E-_Eprev) / _dT; / * Componente derivado * / return (P + I + D);}  

Luego, el valor de salida final se determina simplemente agregando el valor de salida actual y la salida del controlador PID. Es la siguiente sección del programa principal junto con el cálculo de la señal de error y la salida del controlador PID:

  / * Señal de error, salida del controlador PID y salida final (PWM) al motor * / E =RPMset - RPM; float cPID =controllerPID (E, Eprev, dT, Kp, Ki, Kd); if ( RPMset ==0) OutputRPM =0; de lo contrario, OutputRPM =OutputRPM + cPID; si (OutputRPM <_minRPM) OutputRPM =_minRPM;  

Circuito de alimentación del motor de CC

Por supuesto, nunca se recomienda conducir un motor de CC directamente desde la salida de Arduino o una placa de control similar. Los motores de CC necesitan una cantidad significativa de corriente en comparación con los que no pueden ser suministrados por las salidas de las tarjetas controladoras. Entonces necesitas conducir bobinas de relé. Pero aquí surge otro problema:los relés tienen partes mecánicas y pueden fallar a mediano o largo plazo. Necesitamos otro componente aquí, transistores.

En realidad, los motores de CC funcionan con corriente, no con voltaje. Entonces, al usar este principio, decidí usar transistores. Pero debe elegir el transistor correcto capaz de soportar la corriente del motor. Al principio, haga funcionar el motor directamente conectándolo a la fuente de alimentación y mida la corriente en las condiciones máximas de funcionamiento, o consulte las especificaciones del fabricante.

Después de hacer esto, decidí usar cuatro transistores de unión bipolar BC307A PNP en un "puente" para determinar la dirección de la corriente a través de las bobinas del motor (en realidad, un conjunto NPN BC337 funcionaría mejor debido a la capacidad de soportar corrientes de colector significativamente más altas, pero no lo hice) no los tengo en ese momento).

Dado que las corrientes del motor deben pasar a través de la ruta del transistor Emisor-Colector, es necesario utilizar transistores con aproximadamente los mismos coeficientes de ganancia de corriente CC (hfe). Para verificarlo, puede usar el siguiente circuito y recopilar transistores que le brindan aproximadamente la misma lectura de corriente en el amperímetro. Para diseñar esos circuitos preliminares, debe considerar lo siguiente:

  • Busque " Base-Emitter On-Voltage ”( VBEon ) de transistor. Es el voltaje mínimo que se aplicará a la base para encender el transistor.
  • Encuentre la " ganancia de corriente CC típica ”( hfe ) del transistor alrededor de la corriente del colector cerca de la corriente del motor. Por lo general, es la relación entre la corriente del colector ( IC ) y Corriente base ( IB ), hfe =IC / IB .
  • Busque " Corriente continua máxima del colector ”De transistores ( ICmax ). La corriente CC del motor nunca debe exceder este valor en términos de valores absolutos. Puedo usar BC307 ya que el motor que uso necesita 70 mA, mientras que el transistor tiene ICmax (abs) =100 mA.

Ahora puede determinar el valor de la resistencia que se conectará a la base:al principio, debe considerar las limitaciones de la salida de su tarjeta controladora y tratar de mantener la corriente de la base lo más mínima posible (por lo que se recomienda seleccionar la ganancia de corriente CC de los transistores como máxima como sea posible. Tome la clasificación de voltaje de salida en la placa del controlador como " Voltaje de disparo ”( VT ) y busque la corriente base requerida ( IBreq ) dividiendo la corriente del motor ( IM ) a la ganancia de corriente CC ( hfe ) del transistor: IBreq =IM / hfe .

Luego, determine el voltaje que se dejará caer a través de la resistencia ( VR ), restando Voltaje del emisor base ( VBEon ) de voltaje de disparo : VR =VT - VBEon .

Finalmente, divida el voltaje que se dejará caer a través de Resistencia ( RV ) a Corriente base requerida ( IBreq ) para encontrar el Valor de resistencia ( R ): R =VR / IBreq .

[Formulación combinada: R =(VT - VBEon) * hfe / IM ]

En mi caso:

  • Corriente del motor:IM =70 mA
  • Parámetros BC307A:ICmax =100 mA, hfe =140 (medí aproximadamente), VBEon =0,62 V
  • Voltaje de disparo:VT =3.3 V (salida PWM de Arduino Due)
  • R =5360 ohmios (así que decidí usar 4900 ohmios fabricados por un 2K2 y 2K7, para asegurar una cobertura completa del rango de RPM y el circuito solo absorbe ~ 0.6 mA de la salida PWM, un diseño adecuado).

Cambio de dirección y notas importantes

Para revertir la dirección de un motor de CC, es suficiente revertir el flujo de corriente. Para hacer eso, simplemente podemos hacer un circuito puente con cuatro conjuntos de transistores. Sobre el esquema; La Salida PWM # 2 activa T1A y T1B mientras que la Salida PWM # 3 activa T2A y T2B, por lo que se cambia la corriente que pasa a través del motor.

Pero aquí tenemos que considerar otro problema:cuando recién arrancan, los motores eléctricos absorben una corriente de arranque transitoria significativamente más alta que la corriente nominal que lee durante el funcionamiento normal / continuo (los fabricantes solo dan corrientes nominales). La corriente de arranque puede ser aproximadamente el 130% de la nominal para motores de pequeña potencia y aumenta según la potencia del motor. Por lo tanto, si alimenta el motor directamente desde una fuente de voltaje e inmediatamente invierte la polaridad durante el funcionamiento, el motor absorbe niveles extremos de corriente ya que no se detiene por completo. Finalmente, esto puede resultar en que se apague la fuente de energía o se quemen las bobinas del motor. Es posible que esto no sea tan importante y se sienta para los motores de potencia muy pequeña, pero se vuelve importante si aumentan los niveles de potencia en los que está trabajando. Pero si enciende el motor a través de un transistor o un conjunto de transistores (como Darlington Couple), no tiene ese problema, ya que los transistores ya limitan la corriente.

De todos modos, consideré una pequeña rutina en el programa:cuando se cambia la selección de dirección durante la ejecución, el programa lleva ambas salidas de comando a cero al principio y espera el motor hasta que se detenga por completo. Luego termina su tarea y devuelve todo el control a la rutina principal.

  if (Direction! =prevDirection) {/ * Eliminando ambas salidas PWM al motor * / analogWrite (_chMotorCmdCCW, 0); analogWrite (_chMotorCmdCW, 0); / * Espere hasta que la velocidad del motor disminuya * / do {RPM =60 * (float) readFrequency (_chSpeedRead, 4) / _ DiscSlots; } while (RPM> _minRPM); }  

Lectura rápida

En mi aplicación utilicé un sensor de velocidad HC-020K barato. Envía pulsos al nivel de su voltaje de suministro, y la hoja de datos dice que el voltaje de suministro es de 5V. Sin embargo, mi placa es Arduino Due y no puede aceptarla. Así que lo encendí directamente desde la salida de 3,3 V de Due, y sí, funcionó. Y la siguiente función está escrita para leer la frecuencia y la salida del HC-020K.

  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 (); hacer {_DigitalRead =digitalRead (_DI_FrequencyCounter_Pin); if ((_DigitalRead_Previous ==1) &&(_DigitalRead ==0)) _Frequency ++; _DigitalRead_Previous =_DigitalRead; _Tiempo =micros (); } while (_Time <(_Time_Init + (1000000 / _ReadingSpeed))); } return (_ReadingSpeed ​​* _Frequency); }  

Tenga en cuenta que la rueda del HC-020K tiene 20 ranuras, simplemente lea la frecuencia se debe dividir por 20 para obtener las revoluciones por segundo como frecuencia. Entonces el resultado debe multiplicarse por 60 para obtener RPM.

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

Retoques gráficos

Para mostrar las entradas y los resultados, utilicé una pantalla LCD TFT Makeblock Me y escribí la función CommandToTFT () para enviarle comandos. En realidad, la razón de esta función es simplemente cambiar el punto de conexión en serie en una sola fila en el programa, cuando sea necesario.

Las funciones Cartesian_Setup (), Cartesian_ClearPlotAreas () y Cartesian_Line () están escritas para preparar el área de trazado gráfico, limpiar el área de trazado cuando se llega al final del eje horizontal (aquí es "tiempo") y trazar gráficos, respectivamente. Consulte el manual de Makeblock Me TFT LCD para obtener más detalles si le interesan las funciones gráficas aquí, porque no las explicaré aquí ya que en realidad están fuera del alcance de este blog.

Finalización

Aquí copio el programa con y sin funciones gráficas por separado, para que pueda revisar la Implementación del control de velocidad y dirección de forma independiente o con gráficos. Además, en los Códigos puede encontrar más explicaciones sobre las funciones programadas.

Por último, normalmente no se puede reducir la velocidad de un motor de CC por debajo del 10-20% de su velocidad nominal, incluso sin carga. Sin embargo, es posible disminuir hasta casi un 5% mediante el uso de control PID para un motor de CC descargado una vez que se enciende.

Editar (25 de febrero de 2018): Si desea utilizar transistores NPN en lugar de PNP, debe considerar también el flujo de corriente inversa en ambos tipos. Cuando se activa (se enciende), la corriente fluye del emisor al colector en los transistores PNP, pero es inverso para los tipos NPN (del colector al emisor). Por lo tanto, los transistores PNP están polarizados como E (+) C (-) y para NPN debería ser C (+) E (-).

Código

  • Código con retoques gráficos
  • Código sin retoques gráficos
Código con retoques gráficos Arduino
 / * ########################################### ## Constantes de color para 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 / * ######## ##################################### Asignaciones de E / S ####### ###################################### * / int _chSpeedSet =A0, // Velocidad setpoint _chKp =A1, // Lectura de coeficiente proporcional para controlador PID _chKi =A2, // Lectura de coeficiente integral para controlador PID _chKd =A3, // Lectura de coeficiente derivado para controlador PID _chMotorCmdCCW =3, // Salida PWM al motor para contador- giro en el sentido de las agujas del reloj _chMotorCmdCW =2, // Salida PWM al motor para giro en el sentido de las agujas del reloj _chSpeedRead =24, // Lectura de velocidad _chDirection =25; // Lectura del selector de dirección / * ######################################### #### Otras constantes ########################################## ### * / # define _minRPM 0 // RPM mínimas para iniciar el cambio de dirección # define _maxRPM 6000 // Límite máximo de RPM # define _Tmax 90 // Límite de tiempo máximo para graficar # define _DiscSlots 20 // Cantidad de ranuras en el disco índice / * ############################################### Variables globales ############################################### */Cuerda Cartesian_SetupDetails; boolean Direction, prevDirection; // Configuración de alarmafloat 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, RPM de salida =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) Función de comando para Makeblock Me Parámetros de entrada de LCD TFT:(Cadena) TFTCmd:Cadena de comando #################################### ######### * / void CommandToTFT (String TFTCmd) {/ * Conexión en serie utilizada para la visualización * / Serial1.println (TFTCmd); delay (5);} / * ########### Fin de CommandToTFT () ########### * // * ########### ################################ * // * ############ ################################# Configuración_cartesiana (Xmin, Xmax, Ymin, Ymax, Window_X1, Window_Y1, Window_X2 , Window_Y2, MinDashQty, ColorF, ColorX, ColorY) Función de dibujo del eje XY cartesiano para Makeblock Me TFT LCD Parámetros de entrada:(flotante) Xmin, Xmax, Ymin, Ymax:Valores del rango del eje (int) Window_X1, Window_Y1___:Esquina superior izquierda de ventana de gráfico (int) Window_X2, Window_Y2___:Esquina inferior derecha de la ventana de gráfico (int) MinDashQty_____________:Cant. de guiones en el eje más corto (int) ColorB, ColorX, ColorY:Colores de dibujo para Usos de marco, eje X y eje Y función externa 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) {/ * Limitaciones de pantalla * / const int Display ResoluciónX =319, DisplayResolutionY =239; / * Limitar cadenas de título * / String XminTxt; if (abs (Xmin)> =1000000000) XminTxt ="X =" + String (Xmin / 1000000000) + "G"; si no (abs (Xmin)> =1000000) XminTxt ="X =" + String (Xmin / 1000000) + "M"; si no (abs (Xmin)> =1000) XminTxt ="X =" + Cadena (Xmin / 1000) + "K"; más XminTxt ="X =" + Cadena (Xmin); String XmaxTxt; if (abs (Xmax)> =1000000000) XmaxTxt ="X =" + String (Xmax / 1000000000) + "G"; si no (abs (Xmax)> =1000000) XmaxTxt ="X =" + String (Xmax / 1000000) + "M"; si no (abs (Xmax)> =1000) XmaxTxt ="X =" + String (Xmax / 1000) + "K"; más XmaxTxt ="X =" + Cadena (Xmax); String YminTxt; if (abs (Ymin)> =1000000000) YminTxt ="Y =" + String (Ymin / 1000000000) + "G"; de lo contrario, si (abs (Ymin)> =1000000) YminTxt ="Y =" + Cadena (Ymin / 1000000) + "M"; de lo contrario, si (abs (Ymin)> =1000) YminTxt ="Y =" + Cadena (Ymin / 1000) + "K"; más YminTxt ="Y =" + Cadena (Ymin); String YmaxTxt; if (abs (Ymax)> =1000000000) YmaxTxt ="Y =" + String (Ymax / 1000000000) + "G"; si no (abs (Ymax)> =1000000) YmaxTxt ="Y =" + String (Ymax / 1000000) + "M"; de lo contrario, si (abs (Ymax)> =1000) YmaxTxt ="Y =" + String (Ymax / 1000) + "K"; más YmaxTxt ="Y =" + Cadena (Ymax); / * Límites * / int XminPx =Window_X1 + 1; int XmaxPx =Window_X2-1; int YmaxPx =Window_Y1 + 1; int YminPx =Window_Y2-1; / * Origen * / int OrigenX =XminPx + (int) ((XmaxPx - XminPx) * abs (Xmin) / (abs (Xmax) + abs (Xmin))); int OriginY =YmaxPx + (int) ((YminPx - YmaxPx) * abs (Ymax) / (abs (Ymax) + abs (Ymin))); / * Marco * / CommandToTFT ("BOX (" + Cadena (Ventana_X1) + "," + Cadena (Ventana_Y1) + "," + Cadena (Ventana_X2) + "," + Cadena (Ventana_Y2) + "," + Cadena ( ColorF) + ");"); / * Eje X * / CommandToTFT ("PL (" + Cadena (Ventana_X1 + 1) + "," + Cadena (OrigenY) + "," + Cadena (Ventana_X2-1) + "," + Cadena (OrigenY) + " , "+ Cadena (ColorX) +"); "); / * Eje Y * / CommandToTFT ("PL (" + Cadena (OrigenX) + "," + Cadena (Ventana_Y1 + 1) + "," + Cadena (OrigenX) + "," + Cadena (Ventana_Y2-1) + " , "+ Cadena (ColorY) +"); "); / * Guiones:La cantidad mínima de guiones viene dada por "MinDashQty" y estará guiada en el lado del eje más corto con respecto al origen. En las otras secciones, los guiones que se marcarán se determinarán considerando la relación con el lado del eje más corto. * / / * Dashing * / 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; más XlengthRight_Mod =XlengthRight; if (YlengthLower <=1) YlengthLower_Mod =32767; else 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) + " , "+ Cadena (i) +", "+ Cadena (OrigenY + 2) +", "+ Cadena (ColorX) +"); "); / * 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) + ", "+ Cadena (i) +", "+ Cadena (OrigenY + 2) +", "+ Cadena (ColorX) +"); "); / * 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) + ", "+ Cadena (OrigenX + 2) +", "+ Cadena (i) +", "+ Cadena (ColorY) +"); "); / * 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) + " , "+ Cadena (OrigenX + 2) +", "+ Cadena (i) +", "+ Cadena (ColorY) +"); "); / * Calculando coordenadas para mostrar los valores del punto final del eje * / int XminTxtX =Window_X1 - (int) (XminTxt.length () * 6) - 1, XminTxtY =OriginY, XmaxTxtX =Window_X2 + 1, XmaxTxtY =OriginY, YminTxtX =OriginX, YminTxtY =Ventana_Y2 + 1, YmaxTxtX =OrigenX, YmaxTxtY =Ventana_Y1 - 12 - 1; / * Controles:Si alguna coordenada es -1, caerá más allá de los límites de visualización y el valor respectivo no se mostrará * / if (XminTxtX <0) XminTxtX =-1; si ((XminTxtY-12) <0) XminTxtY =-1; if ((XmaxTxtX + 6 * XmaxTxt.length ())> DisplayResolutionX) XmaxTxtX =-1; if ((XmaxTxtY + 12)> DisplayResolutionY) XmaxTxtY =-1; if ((YminTxtX + 6 * YminTxt.length ())> DisplayResolutionX) YminTxtX =-1; if ((YminTxtY + 12)> DisplayResolutionY) YminTxtY =-1; if ((YmaxTxtX + 6 * YmaxTxt.length ())> DisplayResolutionX) YmaxTxtX =-1; si (YmaxTxtY <0) YmaxTxtY =-1; / * Límite de rango de títulos * / if ((XminTxtX! =-1) &&(XminTxtY! =-1)) CommandToTFT ("DS12 (" + String (XminTxtX) + "," + String (XminTxtY) + ", '" + Cadena (XminTxt) + "'," + Cadena (ColorX) + ");"); if ((XmaxTxtX! =-1) &&(XmaxTxtY! =-1)) CommandToTFT ("DS12 (" + Cadena (XmaxTxtX) + "," + Cadena (XmaxTxtY) + ", '" + Cadena (XmaxTxt) + " ', "+ Cadena (ColorX) +"); "); if ((YminTxtX! =-1) &&(YminTxtY! =-1)) CommandToTFT ("DS12 (" + Cadena (YminTxtX) + "," + Cadena (YminTxtY) + ", '" + Cadena (YminTxt) + " ', "+ Cadena (ColorY) +"); "); if ((YmaxTxtX! =-1) &&(YmaxTxtY! =-1)) CommandToTFT ("DS12 (" + Cadena (YmaxTxtX) + "," + Cadena (YmaxTxtY) + ", '" + Cadena (YmaxTxt) + " ', "+ Cadena (ColorY) +"); "); / * Valor devuelto String Cartesian_Setup () devolverá una configuración gráfica de empaquetado de cadenas en el siguiente formato:"" La cadena comienza con '<' y termina con '>' . Cada valor está delimitado por ',' * / / * 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) + ","); / * Cierre * / Cartesian_SetupDetails + =">"; return Cartesian_SetupDetails;} / * ########### Fin de Cartesian_Setup () ########### * // * ############################################## * // * ############################################ Cartesian_ClearPlotAreas (Descriptor, Color) Función Restablecer / Borrar área de trazado para Makeblock Me TFT LCD Parámetros de entrada:(Cadena) Descriptor:Descriptor de configuración - devuelto por Cartesian_Setup () (int) Color______:Color que se utilizará para rellenar el área de trazado Utiliza la función externa CommandToTFT (). ############################################# * / void Cartesian_ClearPlotAreas (Descriptor de cadena, int Color) {int X1, Y1, X2, Y2; / * Coordenadas de límites para áreas de parcela * / / * Extracción de valores del descriptor * / / * 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]; int W [4]; / * Valores almacenados en Descriptor * / int j =0; / * Contador * / String D_Str =""; for (int i =1; i <=(Descriptor.length () - 1); i ++) if (Descriptor [i] ==',') {if (j <4) L [j] =D_Str.toFloat ( ); más W [j-4] =D_Str.toInt (); D_Str =""; j ++; } más D_Str + =Descriptor [i]; / * Origen * / int OrigenX =(W [0] +1) + (int) (((W [2] -1) - (W [0] +1)) * abs (L [0]) / ( abs (L [1]) + abs (L [0]))); int OrigenY =(W [1] +1) + (int) (((W [3] -1) - (W [1] +1)) * abs (L [3]) / (abs (L [3 ]) + abs (L [2]))); / * Borrando áreas de parcela * / //Area.1:X + Y + X1 =OriginX + 2; Y1 =W [1] + 1; X2 =W [2] - 1; Y2 =OrigenY - 2; CommandToTFT ("BOXF (" + Cadena (X1) + "," + Cadena (Y1) + "," + Cadena (X2) + "," + Cadena (Y2) + "," + Cadena (Color) + "); "); //Área.2:X- Y + X1 =W [0] + 1; Y1 =W [1] + 1; X2 =OrigenX - 2; Y2 =OrigenY - 2; CommandToTFT ("BOXF (" + Cadena (X1) + "," + Cadena (Y1) + "," + Cadena (X2) + "," + Cadena (Y2) + "," + Cadena (Color) + "); "); //Área.3:X- Y- X1 =W [0] + 1; Y1 =OrigenY + 2; X2 =OrigenX - 2; Y2 =W [3] - 1; CommandToTFT ("BOXF (" + Cadena (X1) + "," + Cadena (Y1) + "," + Cadena (X2) + "," + Cadena (Y2) + "," + Cadena (Color) + "); "); //Área.4:X + Y- X1 =OrigenX + 2; Y1 =OrigenY + 2; X2 =W [2] - 1; Y2 =W [3] - 1; CommandToTFT ("BOXF (" + Cadena (X1) + "," + Cadena (Y1) + "," + Cadena (X2) + "," + Cadena (Y2) + "," + Cadena (Color) + "); ");} / * ########### Fin de Cartesian_ClearPlotAreas () ########### * // * ############ ########################################## * // * # ########################################### Línea_cartesiana (Xp, Yp, X, Y, Descriptor, Color) Función de línea cartesiana para Makeblock Me TFT LCD Parámetros de entrada:(int) Xp, Yp_____:Coordenadas del gráfico anterior - valor y vs x (int) X, Y_______:Coordenadas del gráfico actual - valor y vs x (String) Descriptor:Setup Descriptor - devuelto por Cartesian_Setup () (int) Color______:Color de marcado que se usará en (x, y) Utiliza la función externa CommandToTFT (). ############################################################################################################################################################################################################################################### ################################ * / void Cartesian_Line (float Xp, float Yp, float X, float Y , String Descriptor, int Color) {/ * Extrayendo valores del Descriptor * / / * 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 ]; int W [4]; / * Valores almacenados en Descriptor * / int j =0; / * Contador * / String D_Str =""; for (int i =1; i <=(Descriptor.length () - 1); i ++) if (Descriptor [i] ==',') {if (j <4) L [j] =D_Str.toFloat ( ); más W [j-4] =D_Str.toInt (); D_Str =""; j ++; } más D_Str + =Descriptor [i]; / * Origen * / int OrigenX =(W [0] +1) + (int) (((W [2] -1) - (W [0] +1)) * abs (L [0]) / ( abs (L [1]) + abs (L [0]))); int OrigenY =(W [1] +1) + (int) (((W [3] -1) - (W [1] +1)) * abs (L [3]) / (abs (L [3 ]) + abs (L [2]))); int XminPx =W [0] + 1; int XmaxPx =W [2] - 1; int YmaxPx =W [1] + 1; int YminPx =W [3] - 1; si (Y> L [3]) Y =L [3]; si (Y 
 =(OrigenX-2)) &&(DispXp <=(OrigenX + 2))) || ((DispYp>
 =(OrigenY-2)) &&(DispYp <=(OrigenY + 2) )) || ((DispX> =(OrigenX-2)) &&(DispX <=(OrigenX + 2))) || ((DispY> =(OrigenY-2)) &&(DispY <=(OrigenY + 2) )))) CommandToTFT ("PL (" + Cadena (DispXp) + "," + Cadena (DispYp) + "," + Cadena (DispX) + "," + Cadena (DispY) + "," + Cadena (Color ) + ");");} / * ########### Fin de la línea_cartesiana () ########### * // * ######## ##################################### * // * ####### ##################################### readFrequency (_DI_FrequencyCounter_Pin, _ReadingSpeed) Entrada de función de lectura de frecuencia Parámetros:(int) _DI_FrequencyCounter_Pin:Pin digital a leer (flotante) _ReadingSpeed____________:Velocidad de lectura personalizada entre 0 ... 10 (Note.1) Note.1:_ReadingSpeed ​​es un valor para especificar cuánto tiempo se contarán los cambios. 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. Por ejemplo; - _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(){ 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. Por ejemplo; - _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;}

Esquemas

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

Proceso de manufactura

  1. Aleaciones de tungsteno y cobre para motores
  2. Control de un efecto con sensores reales
  3. Arduino Nano:Controla 2 motores paso a paso con joystick
  4. Controlar una matriz de LED con Arduino Uno
  5. Monitoreo SMART de temperatura para escuelas
  6. Biblioteca de puertos IO de 8 bits para Arduino
  7. Matriz de teclado de prototipos de 64 teclas para Arduino
  8. TFT Shield para Arduino Nano - Iniciar
  9. Una entrada analógica aislada para Arduino
  10. Un detector de rayos para Arduino
  11. Robot para navegación interior supercogedora