{"id":2797,"date":"2026-09-12T16:26:13","date_gmt":"2026-09-12T16:26:13","guid":{"rendered":"https:\/\/garikoitz.info\/blog\/?p=2797"},"modified":"2026-09-12T21:36:35","modified_gmt":"2026-09-12T21:36:35","slug":"introduccion-al-pid-y-su-implementacion-en-arduino","status":"publish","type":"post","link":"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/","title":{"rendered":"Introducci\u00f3n al PID y su implementaci\u00f3n en Arduino"},"content":{"rendered":"\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_87_1 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">\u00cdndice<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Alternar tabla de contenidos\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 eztoc-toggle-hide-by-default' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#Introduccion\" >Introducci\u00f3n<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#Un_ejemplo_sencillo_control_de_temperatura\" >Un ejemplo sencillo: control de temperatura<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#SP_PV_OP_y_Error\" >SP, PV, OP y Error<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#%C2%BFQue_hace_realmente_un_PID\" >\u00bfQu\u00e9 hace realmente un PID?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#Accion_proporcional\" >Acci\u00f3n proporcional<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#Accion_integral\" >Acci\u00f3n integral<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#Accion_derivativa\" >Acci\u00f3n derivativa<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#%C2%BFP_PI_PD_o_PID\" >\u00bfP, PI, PD o PID?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#Ki_y_Kd_frente_a_Ti_y_Td\" >Ki y Kd frente a Ti y Td<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#Ecuaciones\" >Ecuaciones<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#PID_clasico\" >PID cl\u00e1sico<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#PI-D\" >PI-D<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#I-PD\" >I-PD<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#El_PID_llevado_a_codigo\" >El PID llevado a c\u00f3digo<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#Tiempo_de_muestreo\" >Tiempo de muestreo<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#Saturacion_y_anti-windup\" >Saturaci\u00f3n y anti-windup<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-17\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#Derivative_kick_y_ruido\" >Derivative kick y ruido<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-18\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#Accion_de_control\" >Acci\u00f3n de control<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-19\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#Inicializacion_y_cambio_manualautomatico\" >Inicializaci\u00f3n y cambio manual\/autom\u00e1tico<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-20\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#Inicializacion_de_la_integral\" >Inicializaci\u00f3n de la integral<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-21\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#Paso_manualautomatico\" >Paso manual\/autom\u00e1tico<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-22\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#PV_Tracking\" >PV Tracking<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-23\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#Codigo_basico_en_Arduino\" >C\u00f3digo b\u00e1sico en Arduino<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-24\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#No_sintonices_a_ciegas\" >No sintonices a ciegas<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-25\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#Serie_Introduccion_al_PID_y_su_implementacion_en_Arduino\" >Serie Introducci\u00f3n al PID y su implementaci\u00f3n en Arduino<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-26\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/#Lectura_recomendada\" >Lectura recomendada<\/a><\/li><\/ul><\/nav><\/div>\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Introduccion\"><\/span>Introducci\u00f3n<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">El controlador PID es probablemente uno de los algoritmos de control m\u00e1s utilizados en la industria. Podemos encontrarlo regulando temperaturas, presiones, caudales, velocidades, posiciones y pr\u00e1cticamente cualquier variable que podamos medir y sobre la que tengamos alguna forma de actuar. A pesar de ello, su funcionamiento b\u00e1sico es relativamente sencillo y podemos implementarlo sin demasiadas dificultades en un Arduino.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En este art\u00edculo vamos a entender qu\u00e9 hace realmente un PID y c\u00f3mo llevarlo a c\u00f3digo, simplificando todo lo posible la parte matem\u00e1tica. Sin embargo, lo que no vamos a hacer es probar valores de las constantes de sinton\u00eda al azar hasta conseguir una gr\u00e1fica que parezca aceptable. Programar el controlador es solo una parte del problema. Para utilizarlo correctamente necesitaremos conocer c\u00f3mo responde nuestro proceso y, a partir de esa informaci\u00f3n, obtener unos par\u00e1metros de sinton\u00eda apropiados.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El recorrido completo de esta serie ser\u00e1 entender el PID, implementarlo, identificar el proceso, calcular una sinton\u00eda y finalmente comprobarla y refinarla. En esta primera parte nos centraremos en las dos primeras etapas. La identificaci\u00f3n y la sinton\u00eda calculada quedar\u00e1n para los siguientes art\u00edculos.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Un_ejemplo_sencillo_control_de_temperatura\"><\/span>Un ejemplo sencillo: control de temperatura<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Vamos a utilizar como ejemplo un proceso de control de temperatura. Imaginemos una peque\u00f1a c\u00e1mara t\u00e9rmica en la que queremos mantener 50 \u00b0C. Disponemos de una sonda para medir la temperatura y de una resistencia de calentamiento cuya potencia podemos modificar mediante una salida <abbr title=\"Pulse Width Modulation, modulaci\u00f3n del ancho del pulso\">PWM<\/abbr> de Arduino y la electr\u00f3nica de potencia correspondiente.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Podr\u00edamos resolver el problema con un control todo\/nada, si estamos por debajo de 50\u00b0C conectamos la resistencia y, si estamos por encima, la desconectamos. Para muchas aplicaciones puede ser suficiente, pero la temperatura tender\u00e1 a oscilar alrededor de la consigna. Un PID permite actuar de forma progresiva haciendo que la potencia aplicada dependa de cu\u00e1nto nos falta, cu\u00e1nto tiempo llevamos desviados y, cuando resulte \u00fatil, de la velocidad con la que cambia la temperatura.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"SP_PV_OP_y_Error\"><\/span>SP, PV, OP y Error<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Antes de hablar de P, I y D necesitamos cuatro conceptos. <abbr title=\"Set Point: valor de consigna del controlador\">SP<\/abbr>, de Set Point, es la consigna o valor que queremos alcanzar. <abbr title=\"Process Variable: variable de proceso que estamos midiendo\">PV<\/abbr>, de Process Variable, es la variable que estamos midiendo. <abbr title=\"Output: salida calculada por el controlador\">OP<\/abbr>, de Output, es la salida del controlador, aquello que podemos modificar para actuar sobre el proceso. Finalmente, el error es la diferencia entre el valor deseado y el valor actual.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Variable<\/strong><\/td><td><strong>Significado<\/strong><\/td><td><strong>Ejemplo<\/strong><\/td><\/tr><tr><td>SP<\/td><td>Consigna<\/td><td>50 \u00b0C<\/td><\/tr><tr><td>PV<\/td><td>Temperatura medida<\/td><td>42 \u00b0C<\/td><\/tr><tr><td>OP<\/td><td>Potencia aplicada<\/td><td>0-100 %<\/td><\/tr><tr><td>Error (e)<\/td><td>SP \u2212 PV<\/td><td>+8 \u00b0C<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<div style=\"height:25px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Si queremos 50\u00b0C y estamos midiendo 42\u00b0C, el error ser\u00e1 de +8\u00b0C. Si la temperatura sube hasta 52\u00b0C, el error pasa a ser \u22122\u00b0C. El signo nos indica a qu\u00e9 lado de la consigna nos encontramos.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"%C2%BFQue_hace_realmente_un_PID\"><\/span>\u00bfQu\u00e9 hace realmente un PID?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">PID son las iniciales de Proporcional, Integral y Derivativo. Podemos recordar intuitivamente la funci\u00f3n de cada t\u00e9rmino pensando que P se fija en cu\u00e1nto nos falta en este momento, I en cu\u00e1nto error llevamos acumulado y D en la velocidad con la que est\u00e1 cambiando la variable. No es una definici\u00f3n matem\u00e1tica rigurosa, pero resulta \u00fatil para entender qu\u00e9 aporta cada acci\u00f3n antes de entrar en las ecuaciones.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Accion_proporcional\"><\/span>Acci\u00f3n proporcional<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>P=K_{c}\\cdot e<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Kc es la ganancia proporcional del controlador. Cuanto mayor sea el error, mayor ser\u00e1 la actuaci\u00f3n. En nuestro proceso t\u00e9rmico si queremos 50\u00b0C y estamos a 25\u00b0C necesitaremos una actuaci\u00f3n importante, mientras que si estamos a 49,5\u00b0C probablemente no tenga sentido aplicar la misma potencia.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El valor de Kc determina cu\u00e1nto reaccionar\u00e1 el controlador. Una ganancia peque\u00f1a producir\u00e1 una respuesta suave pero posiblemente lenta, mientras que una ganancia demasiado grande puede producir sobreimpulso e incluso oscilaciones. El principal inconveniente de utilizar \u00fanicamente acci\u00f3n proporcional es que muchos procesos terminan estabiliz\u00e1ndose ligeramente alejados de la consigna. Esa diferencia permanente recibe el nombre de offset.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Accion_integral\"><\/span>Acci\u00f3n integral<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>I(k) = I(k\u22121) + Ki \u00b7 e(k) \u00b7 Ts<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Imaginemos que el proceso se estabiliza en 48,5\u00b0C cuando queremos mantener 50\u00b0C. El error es peque\u00f1o, pero permanece durante mucho tiempo. La acci\u00f3n integral corrige precisamente esa situaci\u00f3n acumulando el error. Mientras sigamos por debajo de la consigna, la integral ir\u00e1 aumentando poco a poco su contribuci\u00f3n y elevando la salida hasta eliminar el offset.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El problema aparece cuando utilizamos una acci\u00f3n integral demasiado agresiva, porque podemos acumular m\u00e1s correcci\u00f3n de la necesaria y provocar sobreimpulso u oscilaciones. M\u00e1s adelante veremos adem\u00e1s qu\u00e9 ocurre cuando la salida llega a sus l\u00edmites f\u00edsicos y la integral sigue acumul\u00e1ndose.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Accion_derivativa\"><\/span>Acci\u00f3n derivativa<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>D = \u2212Kd \u00b7 (PV(k) \u2212 PV(k\u22121)) \/ Ts<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Supongamos que estamos a 45\u00b0C. No es lo mismo estar a esa temperatura aumentando lentamente que hacerlo mientras la temperatura sube varios grados por segundo. En ambos casos el error es exactamente el mismo, pero en el segundo sabemos que nos aproximamos r\u00e1pidamente a la consigna y probablemente convenga empezar a reducir la actuaci\u00f3n.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La acci\u00f3n derivativa utiliza la velocidad de cambio de la se\u00f1al. Podemos imaginarla como una especie de amortiguador, si la temperatura aumenta con demasiada rapidez, D reduce la actuaci\u00f3n antes de alcanzar la consigna. Su principal inconveniente es que tambi\u00e9n responde a cambios r\u00e1pidos provocados por ruido, por lo que no siempre aporta una mejora.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"%C2%BFP_PI_PD_o_PID\"><\/span>\u00bfP, PI, PD o PID?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Una confusi\u00f3n habitual consiste en asumir que un PID completo ser\u00e1 siempre mejor que un PI, un PD o incluso un P. No es as\u00ed. Cada t\u00e9rmino debe existir porque resuelve un problema concreto, y a\u00f1adir acciones innecesarias puede empeorar el comportamiento del controlador.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Controlador<\/strong><\/td><td><strong>Cu\u00e1ndo puede resultar apropiado<\/strong><\/td><td><strong>Ejemplos t\u00edpicos<\/strong><\/td><\/tr><tr><td>P<\/td><td>Podemos tolerar cierto offset o el propio proceso presenta comportamiento integral.<\/td><td>Nivel sencillo, algunos procesos integradores.<\/td><\/tr><tr><td>PI<\/td><td>Necesitamos eliminar offset y la derivada aporta poco o tenemos una medida ruidosa.<\/td><td>Temperatura, caudal, presi\u00f3n, velocidad.<\/td><\/tr><tr><td>PD<\/td><td>Necesitamos amortiguamiento o anticipaci\u00f3n, pero no integral adicional.<\/td><td>Posici\u00f3n, servomecanismos, sistemas mec\u00e1nicos.<\/td><\/tr><tr><td>PID<\/td><td>Necesitamos eliminar offset y D mejora claramente la din\u00e1mica.<\/td><td>Procesos con inercia, algunos controles t\u00e9rmicos y de movimiento.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<div style=\"height:25px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Un buen ejemplo de PD es un control de posici\u00f3n. La acci\u00f3n proporcional empuja hacia el punto deseado y la derivativa act\u00faa como amortiguamiento de modo que cuanto m\u00e1s r\u00e1pidamente nos aproximamos al objetivo, m\u00e1s empieza a frenar. En este tipo de sistemas la derivada puede tener mucho sentido aunque no necesitemos acci\u00f3n integral.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En un control de temperatura \u00abcasero\u00bb es muy habitual que un PI sea suficiente. La integral elimina el offset y, debido a que la din\u00e1mica t\u00e9rmica suele ser relativamente lenta, la derivada puede aportar poco o resultar sensible al ruido de la medida. La idea importante es que m\u00e1s t\u00e9rminos no significa mejor control. Si un PI controla correctamente el proceso, a\u00f1adir D no tiene por qu\u00e9 aportar ninguna ventaja.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Ki_y_Kd_frente_a_Ti_y_Td\"><\/span>Ki y Kd frente a Ti y Td<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">No todos los libros, controladores y librer\u00edas expresan los par\u00e1metros del PID de la misma forma. Una formulaci\u00f3n muy habitual en control industrial es la forma ideal o no interactiva:<\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>OP = Kc \\left[ e(t) + (\\frac{1}{T_{i}}) \u222be(t)dt + Td \u00b7 \\frac{de(t)}{dt} \\right]<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">En esta expresi\u00f3n Kc es la ganancia proporcional del controlador, Ti es el tiempo integral y Td el tiempo derivativo. Tanto Ti como Td tienen unidades de tiempo. Un Ti menor implica una acci\u00f3n integral m\u00e1s intensa, mientras que un Td mayor aumenta el peso de la derivada.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En una implementaci\u00f3n digital tambi\u00e9n es habitual trabajar con ganancias independientes. Si ambas expresiones representan la misma estructura PID, las relaciones entre ambas formas son:<\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>\\begin{align}\n&amp; K_{i}=\\frac{K_{c}}{T_{i}} \\hspace{0.2cm};\\hspace{0.2cm} K_{d}={K_{c}}\\times {T_{d}}\n\\end{align}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Esto ser\u00e1 importante cuando lleguemos a la sinton\u00eda, porque muchos m\u00e9todos proporcionan directamente Kc, Ti y Td, mientras que nuestro c\u00f3digo o una determinada librer\u00eda puede esperar Kc, Ki y Kd. No debemos introducir Ti directamente en una variable llamada Ki ni Td en una variable llamada Kd.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Por ejemplo, si una regla de sinton\u00eda proporciona Kc = 2, Ti = 10s y Td = 2s, para una implementaci\u00f3n con ganancias independientes tendremos Ki = 0,2 y Kd = 4. Adem\u00e1s, antes de convertir par\u00e1metros debemos comprobar siempre qu\u00e9 formulaci\u00f3n PID utiliza el controlador o la librer\u00eda, porque no todas las implementaciones son equivalentes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A lo largo de estos art\u00edculos utilizaremos Kc para la ganancia proporcional del controlador y reservaremos Kp para la ganancia del proceso durante la identificaci\u00f3n. De esta forma evitamos mezclar dos conceptos completamente distintos.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Ecuaciones\"><\/span>Ecuaciones<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Hasta ahora hemos hablado de las acciones proporcional, integral y derivativa, pero todav\u00eda falta una cuesti\u00f3n importante y es que no todos los controladores que llamamos PID aplican P, I y D sobre la misma se\u00f1al. Dos controladores con los mismos valores de Kc, Ti y Td pueden responder de forma diferente si utilizan estructuras distintas.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"PID_clasico\"><\/span>PID cl\u00e1sico<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">En la estructura PID cl\u00e1sica las tres acciones trabajan sobre el error.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/04\/PID_bloques.avif\" target=\"_blank\" rel=\" noopener\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"301\" src=\"https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/04\/PID_bloques-1024x301.png\" alt=\"\" class=\"wp-image-1592\" srcset=\"https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/04\/PID_bloques-1024x301.png 1024w, https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/04\/PID_bloques-300x88.png 300w, https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/04\/PID_bloques-768x225.png 768w, https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/04\/PID_bloques-604x177.png 604w, https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/04\/PID_bloques.avif 1114w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Un cambio brusco de SP modifica instant\u00e1neamente el error. Por tanto, P reacciona de inmediato y D tambi\u00e9n puede hacerlo con un pico importante. Este \u00faltimo efecto es el famoso <strong>derivative kick<\/strong> que veremos al llevar el algoritmo a c\u00f3digo.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"PI-D\"><\/span>PI-D<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">En una estructura PI-D, P e I contin\u00faan trabajando sobre el error, pero D se calcula sobre PV.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/05\/PI-D_bloques.avif\" target=\"_blank\" rel=\" noopener\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"301\" src=\"https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/05\/PI-D_bloques-1024x301.png\" alt=\"\" class=\"wp-image-1613\" srcset=\"https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/05\/PI-D_bloques-1024x301.png 1024w, https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/05\/PI-D_bloques-300x88.png 300w, https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/05\/PI-D_bloques-768x225.png 768w, https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/05\/PI-D_bloques-604x177.png 604w, https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/05\/PI-D_bloques.avif 1114w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Al cambiar SP, P mantiene su respuesta inmediata, pero D no reacciona directamente al salto de consigna, solo lo har\u00e1 cuando PV empiece realmente a moverse.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"I-PD\"><\/span>I-PD<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">En I-PD \u00fanicamente la acci\u00f3n integral trabaja sobre el error. P y D se aplican sobre PV.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/05\/I-PD_bloques.avif\" target=\"_blank\" rel=\" noopener\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"301\" src=\"https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/05\/I-PD_bloques-1024x301.png\" alt=\"\" class=\"wp-image-1614\" srcset=\"https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/05\/I-PD_bloques-1024x301.png 1024w, https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/05\/I-PD_bloques-300x88.png 300w, https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/05\/I-PD_bloques-768x225.png 768w, https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/05\/I-PD_bloques-604x177.png 604w, https:\/\/garikoitz.info\/blog\/wp-content\/uploads\/2022\/05\/I-PD_bloques.avif 1114w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Con I-PD, un cambio de SP no llega directamente ni a P ni a D, por lo que la respuesta a cambios de consigna es m\u00e1s progresiva. Esto puede ser deseable cuando queremos evitar movimientos bruscos de OP, aunque tambi\u00e9n elimina la respuesta proporcional inmediata al cambio de SP.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"El_PID_llevado_a_codigo\"><\/span>El PID llevado a c\u00f3digo<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">error = SP - PV;\n\nP = Kc * error;\nI += Ki * error * Ts;\nD = -Kd * (PV - PVant) \/ Ts;\n\nOP = P + I + D;\nPVant = PV;\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Tiempo_de_muestreo\"><\/span>Tiempo de muestreo<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">El PID no deber\u00eda calcularse simplemente cada vez que Arduino termina loop(), porque el tiempo de ejecuci\u00f3n del programa puede variar. Tanto la integral como la derivada dependen del tiempo, por lo que debemos ejecutar el c\u00e1lculo con un periodo conocido Ts.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">const unsigned long Ts_ms = 100;\nconst double Ts = Ts_ms \/ 1000.0;\n\nif (millis() - tiempoAnterior >= Ts_ms)\n{\n    tiempoAnterior += Ts_ms;\n\n    \/\/ Leer PV\n    \/\/ Calcular PID\n    \/\/ Actualizar OP\n}\n<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">De esta forma evitamos bloquear el programa con delay() y mantenemos aproximadamente constante el intervalo entre c\u00e1lculos.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Saturacion_y_anti-windup\"><\/span>Saturaci\u00f3n y anti-windup<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Todo actuador tiene l\u00edmites f\u00edsicos. En nuestro proceso t\u00e9rmico podemos normalizar la potencia entre 0 y 100 %, pero el controlador podr\u00eda calcular te\u00f3ricamente valores superiores. La resistencia de calentamiento no puede entregar un 130 % de potencia por mucho que el PID lo solicite.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">PID calcula: 105 % \u2192 125 % \u2192 150 % \u2192 180 %\nSalida f\u00edsica: 100 %\n<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">El problema es que la integral puede continuar acumulando error aunque la salida real ya est\u00e9 saturada. Cuando finalmente nos acerquemos a la consigna, toda esa integral acumulada tendr\u00e1 que descargarse antes de recuperar el control, provocando normalmente un sobreimpulso importante. Este fen\u00f3meno recibe el nombre de integral windup.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Una primera protecci\u00f3n consiste en limitar el valor de la integral. Una alternativa algo mejor es utilizar <strong>integraci\u00f3n condicional<\/strong>, es decir, si la salida ya est\u00e1 saturada y el error intenta empujarla todav\u00eda m\u00e1s hacia esa saturaci\u00f3n, dejamos temporalmente de integrar. Cuando el error cambie de sentido y ayude a abandonar el l\u00edmite, volvemos a hacerlo.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Derivative_kick_y_ruido\"><\/span>Derivative kick y ruido<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Si calculamos la derivada sobre el error y modificamos bruscamente SP, el error tambi\u00e9n cambia instant\u00e1neamente. D interpreta ese salto como una velocidad enorme y puede producir un pico brusco en la salida, fen\u00f3meno conocido como derivative kick. Una soluci\u00f3n habitual consiste en calcular la derivada sobre la PV, ya que la variable real no puede cambiar instant\u00e1neamente simplemente porque hayamos modificado la consigna.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La derivada responde adem\u00e1s a cualquier cambio r\u00e1pido, incluido el ruido. Un ADC, una sonda digital o una se\u00f1al cuantificada pueden presentar peque\u00f1as variaciones aunque la variable f\u00edsica apenas haya cambiado. Podemos filtrar la medida, pero un filtrado excesivo introduce retardo tambi\u00e9n en P e I; cuando utilizamos D suele ser preferible filtrar espec\u00edficamente su acci\u00f3n. Si D no aporta una mejora clara, la opci\u00f3n m\u00e1s sencilla sigue siendo no utilizarla.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Accion_de_control\"><\/span>Acci\u00f3n de control<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Antes de poner en marcha el PID debemos configurar correctamente su <strong>acci\u00f3n de control<\/strong>, que puede ser <strong>directa<\/strong> o <strong>inversa<\/strong>. Una forma sencilla de determinarla es olvidarnos moment\u00e1neamente de las ecuaciones y hacernos la siguiente pregunta:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ante un incremento de la PV, \u00bfqu\u00e9 debe hacer la OP para corregirlo?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Si al aumentar PV necesitamos <strong>aumentar OP<\/strong>, la acci\u00f3n del controlador es <strong>directa<\/strong>. Si al aumentar PV necesitamos <strong>disminuir OP<\/strong>, la acci\u00f3n es <strong>inversa<\/strong>.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">PV \u2191 \u2192 OP \u2191 ACCI\u00d3N DIRECTA \nPV \u2191 \u2192 OP \u2193 ACCI\u00d3N INVERSA<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Es importante no confundir la acci\u00f3n del controlador con la respuesta del proceso. Para decidir si el PID debe configurarse como directo o inverso debemos pensar siempre en <strong>qu\u00e9 tiene que hacer OP cuando cambia PV para llevarla de nuevo hacia SP<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Volviendo a nuestro ejemplo, ante un incremento de la la temperatura (PV+) la resistencia tiene que entregar menos potencia (OP-), por lo que la <strong>acci\u00f3n <\/strong>que necesitamos configurar es <strong>inversa<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Inicializacion_y_cambio_manualautomatico\"><\/span>Inicializaci\u00f3n y cambio manual\/autom\u00e1tico<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Cuando arrancamos un PID no deber\u00edamos asumir que todas sus variables internas pueden comenzar simplemente a cero. Si el proceso ya est\u00e1 funcionando y necesita una determinada salida para mantenerse cerca de su punto de trabajo, inicializar la integral a cero puede provocar un salto importante de OP en el momento en que activemos el controlador.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Imaginemos que el proceso est\u00e1 estable alrededor de 50\u00b0C con una potencia manual del 35%. Si pasamos a autom\u00e1tico con la integral inicializada a cero, el controlador calcular\u00e1 inicialmente la salida \u00fanicamente a partir de P y D. El resultado puede ser muy diferente de ese 35% que manten\u00eda el proceso estabilizado y provocar una perturbaci\u00f3n precisamente durante el cambio de modo.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Inicializacion_de_la_integral\"><\/span>Inicializaci\u00f3n de la integral<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Una estrategia sencilla consiste en inicializar la contribuci\u00f3n integral de forma que la salida calculada por el PID sea aproximadamente igual a la salida que ya estaba aplicando el proceso. Si conocemos OPmanual en el instante de la transferencia podemos utilizar:<\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>I_{0} = OPmanual \u2212 P_{0} \u2212 D_{0}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">De esta manera, en el primer c\u00e1lculo OP = P + I + D seguir\u00e1 siendo aproximadamente igual a la salida anterior. Despu\u00e9s la integral evolucionar\u00e1 normalmente seg\u00fan el error. Si calculamos D sobre PV tambi\u00e9n conviene inicializar PVant = PV, evitando que una medida antigua o no inicializada produzca una derivada artificial.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">double error = SP - PV;\ndouble P = Kc * error;\n\nPVant = PV;\ndouble D = 0.0;\n\nintegral = OPmanual - P - D;\nintegral = constrain(integral, minOP, maxOP);\n<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La idea general es que el controlador debe empezar desde un estado coherente con el estado real del proceso. Inicializar variables internas sin tener en cuenta d\u00f3nde estamos puede introducir una perturbaci\u00f3n que no pertenece al proceso, sino a nuestra propia implementaci\u00f3n.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Paso_manualautomatico\"><\/span>Paso manual\/autom\u00e1tico<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">La inicializaci\u00f3n anterior est\u00e1 directamente relacionada con la transferencia entre modo manual y autom\u00e1tico. En manual, el usuario fija directamente OP y el PID no gobierna el proceso. Cuando pasamos a autom\u00e1tico, queremos que el controlador tome el control sin provocar un salto brusco de salida. A este comportamiento se le suele denominar bumpless transfer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Para conseguirlo necesitamos que las variables internas del PID se ajusten durante la transferencia. La integral es normalmente el t\u00e9rmino m\u00e1s importante, porque act\u00faa como memoria del controlador y puede absorber la diferencia entre la salida actual y las contribuciones P y D.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"PV_Tracking\"><\/span>PV Tracking<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Una estrategia muy utilizada para facilitar la transferencia entre manual y autom\u00e1tico consiste en hacer que, mientras el controlador est\u00e1 en manual, la consigna siga a la variable de proceso. Es decir, SP = PV mientras permanecemos en manual. Esta funci\u00f3n se conoce habitualmente como PV Tracking.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Supongamos que dejamos el proceso estabilizado manualmente en 46\u00b0C mientras la consigna autom\u00e1tica almacenada permanece en 60\u00b0C. Si activamos directamente el PID aparecer\u00e1 un error de 14\u00b0C y el controlador reaccionar\u00e1 con fuerza. Con PV Tracking, mientras estamos en manual SP sigue a PV, de modo que al pasar a autom\u00e1tico el error inicial es aproximadamente cero.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Inicializaci\u00f3n, PV Tracking y bumpless transfer son conceptos distintos pero complementarios. La inicializaci\u00f3n hace coherentes las variables internas del PID con el estado real del proceso, PV Tracking reduce el error inicial durante manual y la transferencia sin salto persigue que OP no cambie bruscamente al pasar a autom\u00e1tico.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Codigo_basico_en_Arduino\"><\/span>C\u00f3digo b\u00e1sico en Arduino<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">El siguiente ejemplo re\u00fane los conceptos principales y a\u00f1ade modo manual, PV Tracking, inicializaci\u00f3n al pasar a autom\u00e1tico y selecci\u00f3n del tipo de PID. Se mantiene deliberadamente sencillo para que sea f\u00e1cil relacionar cada bloque con la explicaci\u00f3n anterior.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"enlighter\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">const int PIN_PV = A0;\nconst int PIN_OP = 6;\nconst unsigned long Ts_ms = 100;\n\nenum TipoPID   { PID_CLASICO, PI_D, I_PD };\nenum AccionPID { DIRECT, REVERSE };\nenum ModoPID   { MANUAL, AUTOMATICO };\n\nclass PID\n{\npublic:\n    PID(double *input, double *output, double *setpoint,\n        double kc, double ki, double kd,\n        TipoPID estructura, AccionPID accion, bool pvTracking)\n        : Input(input), Output(output), Setpoint(setpoint),\n          Kc(kc), Ki(ki), Kd(kd),\n          estructuraPID(estructura), accionPID(accion),\n          pvTrackingActivo(pvTracking)\n    {\n        modoPID = MANUAL;\n        minOutput = 0.0;\n        maxOutput = 100.0;\n        sampleTimeMs = 100;\n        integral = 0.0;\n        PVant = *Input;\n        errorAnt = *Setpoint - *Input;\n        salidaManual = *Output;\n        SPoperador = *Setpoint;\n        tiempoAnterior = 0;\n    }\n\n    bool Compute()\n    {\n        if (modoPID == MANUAL)\n        {\n            *Output = Limitar(salidaManual);\n            if (pvTrackingActivo) *Setpoint = *Input;\n            return false;\n        }\n\n        unsigned long ahora = millis();\n        if (ahora - tiempoAnterior &lt; sampleTimeMs) return false;\n\n        double Ts = (ahora - tiempoAnterior) \/ 1000.0;\n        tiempoAnterior = ahora;\n\n        double error = *Setpoint - *Input;\n        double signo = SignoAccion();\n\n        double P = (estructuraPID == I_PD)\n            ? -signo * Kc * (*Input)\n            :  signo * Kc * error;\n\n        double D = (estructuraPID == PID_CLASICO)\n            ?  signo * Kd * (error - errorAnt) \/ Ts\n            : -signo * Kd * ((*Input) - PVant) \/ Ts;\n\n        double incrementoIntegral = signo * Ki * error * Ts;\n        double nuevaIntegral = integral + incrementoIntegral;\n        double opSinLimitar = P + nuevaIntegral + D;\n\n        if (!((opSinLimitar > maxOutput &amp;&amp; incrementoIntegral > 0.0) ||\n              (opSinLimitar &lt; minOutput &amp;&amp; incrementoIntegral &lt; 0.0)))\n            integral = nuevaIntegral;\n\n        *Output = Limitar(P + integral + D);\n\n        PVant = *Input;\n        errorAnt = error;\n\n        return true;\n    }\n\n    void SetMode(ModoPID nuevoModo)\n    {\n        if (nuevoModo == modoPID) return;\n\n        if (nuevoModo == MANUAL)\n        {\n            salidaManual = Limitar(*Output);\n            SPoperador = *Setpoint;\n            modoPID = MANUAL;\n\n            if (pvTrackingActivo) *Setpoint = *Input;\n            return;\n        }\n\n        *Setpoint = pvTrackingActivo ? *Input : SPoperador;\n        InicializarBumpless();\n        modoPID = AUTOMATICO;\n        tiempoAnterior = millis();\n    }\n\n    void SetManualOutput(double valor)\n    {\n        salidaManual = Limitar(valor);\n        if (modoPID == MANUAL) *Output = salidaManual;\n    }\n\n    double GetManualOutput() { return salidaManual; }\n\n    void SetTunings(double kc, double ki, double kd)\n    {\n        if (kc &lt; 0 || ki &lt; 0 || kd &lt; 0) return;\n\n        Kc = kc;\n        Ki = ki;\n        Kd = kd;\n\n        if (modoPID == AUTOMATICO) InicializarBumpless();\n    }\n\n    void SetStructure(TipoPID estructura)\n    {\n        if (estructura == estructuraPID) return;\n\n        estructuraPID = estructura;\n        if (modoPID == AUTOMATICO) InicializarBumpless();\n    }\n\n    void SetDirection(AccionPID accion)\n    {\n        if (accion == accionPID) return;\n\n        accionPID = accion;\n        if (modoPID == AUTOMATICO) InicializarBumpless();\n    }\n\n    void SetPVTracking(bool activar)\n    {\n        if (activar == pvTrackingActivo) return;\n\n        if (activar)\n        {\n            SPoperador = *Setpoint;\n            pvTrackingActivo = true;\n            if (modoPID == MANUAL) *Setpoint = *Input;\n        }\n        else\n        {\n            pvTrackingActivo = false;\n            if (modoPID == MANUAL) *Setpoint = SPoperador;\n        }\n    }\n\n    void SetSetpoint(double valor)\n    {\n        SPoperador = valor;\n\n        if (modoPID == AUTOMATICO || !pvTrackingActivo)\n            *Setpoint = valor;\n    }\n\n    void SetOutputLimits(double minimo, double maximo)\n    {\n        if (minimo >= maximo) return;\n\n        minOutput = minimo;\n        maxOutput = maximo;\n\n        salidaManual = Limitar(salidaManual);\n        *Output = Limitar(*Output);\n\n        if (modoPID == AUTOMATICO) InicializarBumpless();\n    }\n\n    void SetSampleTime(unsigned long tiempoMs)\n    {\n        if (tiempoMs > 0) sampleTimeMs = tiempoMs;\n    }\n\nprivate:\n    double *Input, *Output, *Setpoint;\n    double Kc, Ki, Kd;\n    double integral, PVant, errorAnt;\n    double salidaManual, SPoperador;\n    double minOutput, maxOutput;\n\n    TipoPID estructuraPID;\n    AccionPID accionPID;\n    ModoPID modoPID;\n\n    bool pvTrackingActivo;\n    unsigned long sampleTimeMs, tiempoAnterior;\n\n    double SignoAccion()\n    {\n        return (accionPID == REVERSE) ? 1.0 : -1.0;\n    }\n\n    double Limitar(double valor)\n    {\n        if (valor > maxOutput) return maxOutput;\n        if (valor &lt; minOutput) return minOutput;\n        return valor;\n    }\n\n    void InicializarBumpless()\n    {\n        double error = *Setpoint - *Input;\n        double signo = SignoAccion();\n\n        double P = (estructuraPID == I_PD)\n            ? -signo * Kc * (*Input)\n            :  signo * Kc * error;\n\n        PVant = *Input;\n        errorAnt = error;\n        integral = *Output - P;\n    }\n};\n\n\n\/\/ ==========================================================\n\/\/ PROCESO\n\/\/ ==========================================================\n\ndouble SP = 50.0;\ndouble PV = 0.0;\ndouble OP = 0.0;\n\ndouble Kc = 1.0;\ndouble Ki = 0.4;\ndouble Kd = 0.0;\n\nPID pid(&amp;PV, &amp;OP, &amp;SP, Kc, Ki, Kd, PI_D, REVERSE, true);\n\n\n\/\/ ==========================================================\n\/\/ SETUP\n\/\/ ==========================================================\n\nvoid setup()\n{\n    pinMode(PIN_OP, OUTPUT);\n\n    pid.SetSampleTime(Ts_ms);\n    pid.SetOutputLimits(0.0, 100.0);\n    pid.SetManualOutput(35.0);\n    pid.SetMode(MANUAL);\n}\n\n\n\/\/ ==========================================================\n\/\/ LOOP\n\/\/ ==========================================================\n\nvoid loop()\n{\n    PV = analogRead(PIN_PV) * 100.0 \/ 1023.0;\n\n    pid.Compute();\n\n    analogWrite(PIN_OP, (int)(OP * 255.0 \/ 100.0));\n}<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Como veis, implementar un PID digital con unas protecciones y caracter\u00edsticas m\u00ednimas hace que el c\u00f3digo alcance f\u00e1cilmente unos cientos de l\u00edneas. En este caso est\u00e1 todo contenido en un \u00fanico archivo, aunque la l\u00f3gica del PID se ha separado en una clase independiente para mantener la funci\u00f3n <code>void loop()<\/code> m\u00e1s limpia y facilitar su reutilizaci\u00f3n en otras implementaciones.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Lo ideal ser\u00eda trasladar esta clase a una librer\u00eda independiente, dejando las funciones <code>void setup()<\/code> y <code>void loop()<\/code> centradas \u00fanicamente en la l\u00f3gica principal de nuestra aplicaci\u00f3n. Sin embargo, el objetivo de este apartado no es construir una librer\u00eda completa, sino que el lector comprenda c\u00f3mo se realiza internamente la implementaci\u00f3n de un controlador PID digital y qu\u00e9 elementos debemos tener en cuenta.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En el art\u00edculo 4 veremos con m\u00e1s detalle las librer\u00edas disponibles para Arduino, sus caracter\u00edsticas y hasta qu\u00e9 punto pueden encargarse de todo este trabajo por nosotros.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Aqu\u00ed ten\u00e9is las funcionalidades disponibles de nuestra implementaci\u00f3n:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/ Trabajar en manual\npid.SetMode(MANUAL);\npid.SetManualOutput(40.0);\n\n\/\/ Cambiar la sintonia\npid.SetTunings(1.5, 0.15, 0.0);\n\n\/\/ Seleccionar PI-D\npid.SetStructure(PI_D);\n\n\/\/ Accion inversa\npid.SetDirection(REVERSE);\n\n\/\/ Activar PV Tracking\npid.SetPVTracking(true);\n\n\/\/ Pasar a automatico sin salto de OP\npid.SetMode(AUTOMATICO);\n\n\/\/ Introducir la nueva consigna\npid.SetSetpoint(50.0);<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">He dejado <code>Kd = 0<\/code>, por lo que inicialmente tenemos un controlador PI. Los valores utilizados para <code>Kc<\/code> y <code>Ki<\/code> son solo un ejemplo de implementaci\u00f3n y no representan una sinton\u00eda v\u00e1lida para nuestro proceso. <strong>La pregunta realmente interesante es de d\u00f3nde deber\u00edan salir esos valores.<\/strong><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"No_sintonices_a_ciegas\"><\/span>No sintonices a ciegas<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Podr\u00edamos empezar a modificar Kc, Ki y Kd hasta conseguir una respuesta aparentemente aceptable, pero podemos hacerlo de una forma bastante m\u00e1s sistem\u00e1tica. El propio Arduino puede ayudarnos a provocar una respuesta conocida en el proceso y registrar qu\u00e9 ocurre. Por ejemplo, podemos colocar el controlador en manual, aumentar la salida del 30 al 40% y observar c\u00f3mo evoluciona la temperatura.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">De esa respuesta podemos obtener <strong>cu\u00e1nto responde el proceso<\/strong>, <strong>cu\u00e1nto tarda en empezar a hacerlo<\/strong> y <strong>con qu\u00e9 rapidez evoluciona<\/strong>. En nuestro ejemplo de control de temperatura podemos testear en lazo abierto (ya llegaremos aqu\u00ed), en otras situaciones no podremos trabajar en lazo abierto y tendremos que obtener esa informaci\u00f3n manteniendo el proceso en lazo cerrado. El objetivo es llegar a par\u00e1metros medidos del proceso y utilizarlos para calcular la sinton\u00eda.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En el siguiente art\u00edculo veremos c\u00f3mo identificar el proceso en lazo abierto y en lazo cerrado.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Serie_Introduccion_al_PID_y_su_implementacion_en_Arduino\"><\/span>Serie Introducci\u00f3n al PID y su implementaci\u00f3n en Arduino<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Art\u00edculo 1: Entender e implementar antes de sintonizar<\/li>\n\n\n\n<li><a href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/identificacion-de-procesos-para-la-sintonia-de-un-pid\/\" target=\"_blank\" rel=\"noopener\">Art\u00edculo 2: Identificaci\u00f3n de procesos para la sinton\u00eda de un PID<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/metodos-de-sintonia-pid-a-partir-de-la-identificacion-del-proceso\/\" target=\"_blank\" rel=\"noopener\">Art\u00edculo 3: M\u00e9todos de sinton\u00eda PID a partir de la identificaci\u00f3n del proceso<\/a><\/li>\n\n\n\n<li>Art\u00edculo 4: Librer\u00edas PID y herramientas de identificaci\u00f3n para Arduino<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Lectura_recomendada\"><\/span>Lectura recomendada<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">[1] Tore H\u00e4gglund. <em>Process Control in Practice<\/em>. De Gruyter, 2023. eBook ISBN: 978-3-11-110495-9.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[2] Karl J. \u00c5str\u00f6m, Tore H\u00e4gglund. <em>Control PID avanzado<\/em>. Pearson Educaci\u00f3n, 2009. ISBN: 978-84-8322-511-0.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[3] Daniel Chuk. <em>Los sistemas de primer orden y los controladores PID<\/em>. 2012. [<a href=\"https:\/\/garikoitz.info\/blog\/descargas\/ControladoresPID.pdf\">Link<\/a>] [<a href=\"http:\/\/dea.unsj.edu.ar\/control2\/ControladoresPID.pdf\">Link2<\/a>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[4] Fernando Morilla Garc\u00eda. <em>\u00bfQu\u00e9 quieres controlar? \u00bfHas probado con controladores PID?<\/em> Dpto. de Inform\u00e1tica y Autom\u00e1tica, UNED, 2015. [<a href=\"https:\/\/www.dia.uned.es\/~fmorilla\/MaterialDidactico\/P2015_Conferencia_PID_FMorilla_19Mayo2015.pdf\">Link<\/a>]<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introducci&oacute;n El controlador PID es probablemente uno de los algoritmos de control m&aacute;s utilizados en la industria. Podemos encontrarlo regulando temperaturas, presiones, caudales, velocidades, posiciones y pr&aacute;cticamente cualquier variable que podamos medir y sobre la que tengamos alguna forma de actuar. A pesar de ello, su funcionamiento b&aacute;sico es relativamente sencillo y podemos implementarlo sin demasiadas dificultades en un Arduino. En este art&iacute;culo vamos a entender qu&eacute; hace realmente un PID y c&oacute;mo llevarlo a c&oacute;digo, simplificando todo lo posible&#8230;<\/p>\n<p class=\"read-more\"><a class=\"btn btn-default\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/introduccion-al-pid-y-su-implementacion-en-arduino\/\"> Leer m\u00e1s<span class=\"screen-reader-text\">  Leer m\u00e1s<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":2823,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"wpupg_custom_link":[],"wpupg_custom_link_behaviour":[],"wpupg_custom_link_nofollow":[],"wpupg_custom_image":[],"wpupg_custom_image_id":[],"footnotes":""},"categories":[15,213,210],"tags":[94,16,149,148,143,96,147,144,145,150,122,142,17,146,101],"class_list":["post-2797","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-arduino","category-pid","category-programacion","tag-acp","tag-arduino","tag-curva-de-reaccion","tag-derivativo","tag-i-pd","tag-identificacion-de-procesos","tag-integral","tag-lazo-abierto","tag-lazo-cerrado","tag-oscilacion-mantenida","tag-pi","tag-pi-d","tag-pid","tag-proporcional","tag-sintonia-pid"],"_links":{"self":[{"href":"https:\/\/garikoitz.info\/blog\/wp-json\/wp\/v2\/posts\/2797","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/garikoitz.info\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/garikoitz.info\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/garikoitz.info\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/garikoitz.info\/blog\/wp-json\/wp\/v2\/comments?post=2797"}],"version-history":[{"count":48,"href":"https:\/\/garikoitz.info\/blog\/wp-json\/wp\/v2\/posts\/2797\/revisions"}],"predecessor-version":[{"id":2914,"href":"https:\/\/garikoitz.info\/blog\/wp-json\/wp\/v2\/posts\/2797\/revisions\/2914"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/garikoitz.info\/blog\/wp-json\/wp\/v2\/media\/2823"}],"wp:attachment":[{"href":"https:\/\/garikoitz.info\/blog\/wp-json\/wp\/v2\/media?parent=2797"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/garikoitz.info\/blog\/wp-json\/wp\/v2\/categories?post=2797"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/garikoitz.info\/blog\/wp-json\/wp\/v2\/tags?post=2797"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}