{"id":2951,"date":"2026-09-18T23:39:38","date_gmt":"2026-09-18T23:39:38","guid":{"rendered":"https:\/\/garikoitz.info\/blog\/?p=2951"},"modified":"2026-09-18T23:39:39","modified_gmt":"2026-09-18T23:39:39","slug":"pidcontrol-libreria-de-control-para-arduino","status":"publish","type":"post","link":"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/","title":{"rendered":"PIDControl &#8211; Librer\u00eda de control para Arduino"},"content":{"rendered":"\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_88 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\/pidcontrol-libreria-de-control-para-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\/pidcontrol-libreria-de-control-para-arduino\/#Instalacion\" >Instalaci\u00f3n<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Instalacion_manual_desde_el_archivo_ZIP\" >Instalaci\u00f3n manual desde el archivo ZIP<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Instalacion_desde_el_gestor_de_librerias_del_IDE\" >Instalaci\u00f3n desde el gestor de librer\u00edas del IDE<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Tratamiento_de_la_senal\" >Tratamiento de la se\u00f1al<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Filtro_de_media_movil\" >Filtro de media m\u00f3vil<\/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\/pidcontrol-libreria-de-control-para-arduino\/#Filtro_de_mediana\" >Filtro de mediana<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Filtro_paso_bajo\" >Filtro paso bajo<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Combinar_mediana_y_paso_bajo\" >Combinar mediana y paso bajo<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Filtro_complementario\" >Filtro complementario<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Que_filtro_elegir\" >Qu\u00e9 filtro elegir<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Identificar_el_proceso\" >Identificar el proceso<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Identificacion_mediante_StepTest\" >Identificaci\u00f3n mediante StepTest<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#%C2%BFPor_que_uso_varios_escalones\" >\u00bfPor qu\u00e9 uso varios escalones?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Identificacion_mediante_RelayTest\" >Identificaci\u00f3n mediante RelayTest<\/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\/pidcontrol-libreria-de-control-para-arduino\/#StepTest_o_RelayTest\" >StepTest o RelayTest<\/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\/pidcontrol-libreria-de-control-para-arduino\/#Recuperar_una_identificacion_anterior\" >Recuperar una identificaci\u00f3n anterior<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-18\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Sintonizar_el_controlador\" >Sintonizar el controlador<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-19\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Sintonizacion_despues_de_StepTest\" >Sintonizaci\u00f3n despu\u00e9s de StepTest<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-20\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Sintonizacion_despues_de_RelayTest\" >Sintonizaci\u00f3n despu\u00e9s de RelayTest<\/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\/pidcontrol-libreria-de-control-para-arduino\/#Elegir_entre_PI_y_PID\" >Elegir entre PI y PID<\/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\/pidcontrol-libreria-de-control-para-arduino\/#Aplicar_la_sintonizacion\" >Aplicar la sintonizaci\u00f3n<\/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\/pidcontrol-libreria-de-control-para-arduino\/#Configurar_el_controlador\" >Configurar el controlador<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-24\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Accion_de_control\" >Acci\u00f3n de control<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-25\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Estructura_del_controlador\" >Estructura del controlador<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-26\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#PV_Tracking\" >PV Tracking<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-27\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Limites_de_la_salida\" >L\u00edmites de la salida<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-28\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Tiempo_de_muestreo\" >Tiempo de muestreo<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-29\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Puesta_en_marcha\" >Puesta en marcha<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-30\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Codigo_de_ejemplo\" >C\u00f3digo de ejemplo<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-31\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/#Enlaces\" >Enlaces<\/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\">PIDControl nace de una mezcla bastante habitual entre necesidad, curiosidad y esa mala costumbre de mirar algo que ya funciona y pensar que seguramente se puede complicar un poco m\u00e1s.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La idea inicial era relativamente sencilla. Quer\u00eda disponer de una librer\u00eda PID para Arduino con la que pudiera trabajar de una forma parecida a como estoy acostumbrado en control de procesos. Manual y autom\u00e1tico, seguimiento de PV, transferencia bumpless, anti-windup, diferentes estructuras de PID, acci\u00f3n directa o inversa y poco m\u00e1s. El problema es que, una vez empiezas a tirar del hilo, descubres que el PID es casi la parte f\u00e1cil. Antes hay que medir una se\u00f1al que normalmente viene con ruido, picos y alguna lectura que parece enviada directamente desde otra dimensi\u00f3n. Despu\u00e9s hay que entender c\u00f3mo responde el proceso, identificarlo, obtener una sinton\u00eda razonable y, solo entonces, dejar que el controlador haga su trabajo sin intentar destruir aquello que pretend\u00edamos controlar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Mientras desarrollaba los art\u00edculos anteriores sobre identificaci\u00f3n y sintonizaci\u00f3n me di cuenta de otra cosa que termin\u00f3 siendo una de las principales motivaciones del proyecto. Encontraba librer\u00edas PID, encontraba filtros, encontraba ejemplos de autotuning y encontraba implementaciones aisladas de m\u00e9todos de identificaci\u00f3n, pero no encontraba una soluci\u00f3n que cubriera todas las etapas del control de un proceso dentro de una misma librer\u00eda y con una filosof\u00eda coherente.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As\u00ed que ese termin\u00f3 siendo el objetivo de PIDControl. No crear simplemente otra implementaci\u00f3n de la ecuaci\u00f3n PID, porque de esas ya tenemos suficientes para sobrevivir varias vidas, sino intentar recorrer el camino completo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La intenci\u00f3n tampoco es convertir un Arduino en una plataforma de control industrial. La idea es disponer de una herramienta con la que podamos recorrer todo el proceso de forma ordenada, entender qu\u00e9 estamos haciendo en cada etapa y, sobre todo, evitar el m\u00e9todo tradicional de introducir tres constantes encontradas en Internet, pulsar AUTO y esperar que la termodin\u00e1mica tenga un buen d\u00eda.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En esta entrada no voy a documentar cada clase, cada m\u00e9todo y cada par\u00e1metro de la librer\u00eda. Para eso est\u00e1 la documentaci\u00f3n completa del proyecto. Lo que quiero es recorrer el flujo principal con ejemplos pr\u00e1cticos. Veremos c\u00f3mo tratar la se\u00f1al, c\u00f3mo identificar el proceso, c\u00f3mo obtener una sinton\u00eda y c\u00f3mo terminar control\u00e1ndolo. Porque al final el objetivo no es tener un PID. El objetivo es controlar algo sin convertir el experimento en una investigaci\u00f3n posterior de la compa\u00f1\u00eda de seguros.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Instalacion\"><\/span>Instalaci\u00f3n<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La librer\u00eda puede instalarse manualmente desde el archivo ZIP del proyecto o directamente desde el gestor incluido en Arduino IDE.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Instalacion_manual_desde_el_archivo_ZIP\"><\/span>Instalaci\u00f3n manual desde el archivo ZIP<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">La forma m\u00e1s sencilla durante el desarrollo o cuando queremos instalar una versi\u00f3n concreta consiste en descargar el archivo desde <a href=\"https:\/\/garikoitz.info\/blog\/descargas\/PIDControl\/\" target=\"_blank\" rel=\"noopener\">aqu\u00ed <\/a>o desde <a href=\"https:\/\/github.com\/deurus\/PIDControl\" target=\"_blank\" rel=\"noopener\">Github<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En el men\u00fa seleccionamos<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Sketch\n  -> Include Library\n     -> Add .ZIP Library<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Seleccionamos el ZIP descargado y Arduino copiar\u00e1 autom\u00e1ticamente la librer\u00eda en su carpeta de bibliotecas. Despu\u00e9s de la instalaci\u00f3n podremos utilizarla simplemente incluyendo los m\u00f3dulos que necesitemos.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">#include &lt;PIDControl.h>\n#include &lt;PIDControlFilters.h><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Si adem\u00e1s queremos utilizar identificaci\u00f3n o sintonizaci\u00f3n podemos a\u00f1adir<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">#include &lt;StepTest.h>\n#include &lt;RelayTest.h>\n#include &lt;PIDTuning.h><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">No es necesario incluir todos los m\u00f3dulos en todos los proyectos. Si \u00fanicamente queremos utilizar el controlador PID podemos incluir solo <code>PIDControl.h<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Instalacion_desde_el_gestor_de_librerias_del_IDE\"><\/span>Instalaci\u00f3n desde el gestor de librer\u00edas del IDE<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Cuando <code>PIDControl<\/code> est\u00e9 disponible en el repositorio de librer\u00edas de Arduino, la instalaci\u00f3n ser\u00e1 todav\u00eda m\u00e1s sencilla. Abrimos el gestor desde<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Sketch\n  -> Include Library\n     -> Manage Libraries<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Buscamos PIDControl, seleccionamos la versi\u00f3n que queramos instalar y pulsamos <strong>Instalar<\/strong>. Arduino se encargar\u00e1 de descargar la librer\u00eda y dejarla disponible para todos nuestros proyectos. Esta ser\u00e1 la opci\u00f3n m\u00e1s c\u00f3moda para la mayor\u00eda de usuarios porque tambi\u00e9n permite recibir las nuevas versiones desde el propio IDE sin tener que descargar manualmente cada ZIP.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Tratamiento_de_la_senal\"><\/span>Tratamiento de la se\u00f1al<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Para los ejemplos de esta entrada voy a utilizar una placa <strong>TCLab montada sobre un Arduino Uno R4 WiFi<\/strong>. No hay nada especialmente m\u00e1gico en esta combinaci\u00f3n. Un Arduino Uno R3, un Leonardo o cualquier otra placa compatible con el formato habitual de pines de Arduino puede servir perfectamente siempre que adaptemos, si fuera necesario, la lectura anal\u00f3gica y la generaci\u00f3n de la salida.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Utilizo TCLab simplemente porque la tengo disponible y porque resulta especialmente c\u00f3moda para este tipo de pruebas. Tenemos una variable de proceso f\u00e1cil de entender, la temperatura, una fuente de calor cuya potencia podemos modificar y una din\u00e1mica suficientemente lenta como para poder ver con claridad qu\u00e9 est\u00e1 haciendo el controlador sin necesitar un laboratorio entero alrededor.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Tampoco es necesario disponer de una TCLab para seguir los ejemplos. Un montaje casero con una sonda de temperatura y alg\u00fan elemento capaz de aportar calor puede cumplir exactamente la misma funci\u00f3n. Podemos utilizar una resistencia calefactora, un transistor de potencia, una peque\u00f1a l\u00e1mpara o cualquier otro sistema cuya potencia podamos controlar desde Arduino mediante PWM o una etapa de potencia adecuada. Lo importante no es el hardware concreto, sino disponer de una variable que podamos medir y de una salida que produzca una respuesta observable sobre ella.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En nuestro caso trabajaremos principalmente con la temperatura T1 como PV y con la potencia aplicada al primer calefactor Q1 como OP. El segundo sensor de temperatura T2 nos servir\u00e1 tambi\u00e9n para observar c\u00f3mo evoluciona t\u00e9rmicamente el conjunto, aunque no intervenga directamente en el lazo de control principal.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Antes de pensar en identificar el proceso o en calcular una sinton\u00eda conviene resolver algo bastante menos glamuroso, pero mucho m\u00e1s importante de lo que parece. <strong>La se\u00f1al que entregamos al controlador tiene que ser m\u00ednimamente fiable<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Una lectura anal\u00f3gica nunca es completamente perfecta. Podemos encontrarnos ruido, peque\u00f1as variaciones de ADC, interferencias, alg\u00fan valor aislado incorrecto o simplemente una se\u00f1al que cambia unas pocas cent\u00e9simas continuamente aunque f\u00edsicamente el proceso apenas se haya movido. Si alimentamos directamente al controlador con todo eso, el PID har\u00e1 exactamente lo que le hemos pedido. Reaccionar\u00e1 tambi\u00e9n a cosas que probablemente no nos interesan.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">PIDControl incluye varios filtros para tratar estos casos antes de que la se\u00f1al llegue al controlador.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Filtro_de_media_movil\"><\/span>Filtro de media m\u00f3vil<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">La media m\u00f3vil toma las \u00faltimas muestras y calcula su valor medio.<\/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=\"\">MovingAverageFilter&lt;10> filtro;\n\ndouble PV = filtro.Update(PVRaw);<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Es probablemente el filtro m\u00e1s sencillo de entender y funciona bien cuando tenemos ruido aleatorio aproximadamente sim\u00e9trico alrededor del valor real.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El precio que pagamos es retardo. Cuantas m\u00e1s muestras incluyamos en la ventana, m\u00e1s suave ser\u00e1 la se\u00f1al, pero tambi\u00e9n m\u00e1s tardar\u00e1 el filtro en reflejar un cambio real del proceso.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Por eso aumentar el n\u00famero de muestras hasta que la gr\u00e1fica quede perfectamente recta no es necesariamente una buena idea. Una se\u00f1al preciosa que llega varios segundos tarde sigue siendo una mala se\u00f1al para controlar.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Filtro_de_mediana\"><\/span>Filtro de mediana<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">El filtro de mediana resulta especialmente \u00fatil cuando el problema no es tanto el ruido continuo sino los <strong>picos aislados<\/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=\"\">40,02\n40,04\n57,81\n40,03\n40,05<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La temperatura dif\u00edcilmente ha saltado diecisiete grados durante una d\u00e9cima de segundo para volver inmediatamente despu\u00e9s a su valor anterior. Lo m\u00e1s probable es que esa muestra sea simplemente incorrecta.<\/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=\"\">MedianFilter&lt;5> filtro;\n\ndouble PV = filtro.Update(PVRaw);<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Con un filtro de mediana ordenamos las muestras y nos quedamos con el valor central. En el ejemplo anterior, el 57,81 pr\u00e1cticamente desaparece del problema.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Para se\u00f1ales procedentes de sensores anal\u00f3gicos es uno de mis filtros favoritos como primera barrera. No intenta suavizar todo indiscriminadamente, sino eliminar aquellas muestras que claramente no encajan con las dem\u00e1s.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Filtro_paso_bajo\"><\/span>Filtro paso bajo<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Despu\u00e9s de eliminar posibles picos todav\u00eda podemos tener peque\u00f1as oscilaciones continuas. Para este caso resulta \u00fatil un filtro paso bajo de primer orden.<\/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=\"\">LowPassFilter filtro(1.0);\n\ndouble PV = filtro.Update(PVRaw, dt);<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">El par\u00e1metro 1.0 representa su constante de tiempo en segundos. Cuanto mayor sea esta constante de tiempo, m\u00e1s suavizada estar\u00e1 la se\u00f1al y m\u00e1s lenta ser\u00e1 tambi\u00e9n su respuesta.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">PIDControl utiliza la expresi\u00f3n:<\/p>\n\n\n\n<div class=\"wp-block-math\"><math display=\"block\"><semantics><mrow><mi>\u03b1<\/mi><mo>=<\/mo><mn>1<\/mn><mo>\u2212<\/mo><msup><mi>e<\/mi><mrow><mo lspace=\"0em\" rspace=\"0em\">\u2212<\/mo><mrow><mi mathvariant=\"normal\">\u0394<\/mi><\/mrow><mi>t<\/mi><mi>\/<\/mi><mi>\u03c4<\/mi><\/mrow><\/msup><\/mrow><annotation encoding=\"application\/x-tex\">\u03b1=1\u2212e^{\u2212\u0394t\/\u03c4}<\/annotation><\/semantics><\/math><\/div>\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\">y actualiza la salida mediante:<\/p>\n\n\n\n<div class=\"wp-block-math\"><math display=\"block\"><semantics><mrow><mi>P<\/mi><msub><mi>V<\/mi><mi>f<\/mi><\/msub><mtext>\u200b<\/mtext><mo form=\"prefix\" stretchy=\"false\">(<\/mo><mi>k<\/mi><mo form=\"postfix\" stretchy=\"false\">)<\/mo><mo>=<\/mo><mi>P<\/mi><msub><mi>V<\/mi><mi>f<\/mi><\/msub><mo form=\"prefix\" stretchy=\"false\">(<\/mo><mi>k<\/mi><mo>\u2212<\/mo><mn>1<\/mn><mo form=\"postfix\" stretchy=\"false\">)<\/mo><mo>+<\/mo><mi>\u03b1<\/mi><mo form=\"prefix\" stretchy=\"false\">[<\/mo><mi>P<\/mi><mi>V<\/mi><mo form=\"prefix\" stretchy=\"false\">(<\/mo><mi>k<\/mi><mo form=\"postfix\" stretchy=\"false\">)<\/mo><mo>\u2212<\/mo><mi>P<\/mi><msub><mi>V<\/mi><mi>f<\/mi><\/msub><mo form=\"prefix\" stretchy=\"false\">(<\/mo><mi>k<\/mi><mo>\u2212<\/mo><mn>1<\/mn><mo form=\"postfix\" stretchy=\"false\">)<\/mo><mo form=\"postfix\" stretchy=\"false\">]<\/mo><\/mrow><annotation encoding=\"application\/x-tex\">PV_{f}\u200b(k)=PV_{f}(k\u22121)+\u03b1[PV(k)\u2212PV_{f}(k\u22121)]<\/annotation><\/semantics><\/math><\/div>\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\">Esto tiene una ventaja importante frente a utilizar simplemente un factor arbitrario entre cero y uno, ya que estamos definiendo el comportamiento del filtro mediante una magnitud f\u00edsica, su constante de tiempo.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Combinar_mediana_y_paso_bajo\"><\/span>Combinar mediana y paso bajo<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Puede ser interesante usar una combinaci\u00f3n de ambos filtros. Primero eliminamos los posibles picos mediante una mediana de cinco muestras y posteriormente suavizamos el ruido restante con un paso bajo.<\/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=\"\">MedianFilter&lt;5> pvMedian;\nLowPassFilter pvLowPass(1.0);\n\ndouble pvMedianValue = pvMedian.Update(PVRaw);\ndouble PV = pvLowPass.Update(pvMedianValue, dt);<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La mediana se ocupa principalmente de las muestras an\u00f3malas y el paso bajo del ruido continuo.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Filtro_complementario\"><\/span>Filtro complementario<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">PIDControl tambi\u00e9n incluye un filtro complementario, aunque este tiene un prop\u00f3sito diferente.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">No est\u00e1 pensado simplemente para suavizar una \u00fanica se\u00f1al. Su utilidad aparece cuando tenemos <strong>dos estimaciones diferentes de la misma variable<\/strong> y cada una tiene ventajas en una zona distinta.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Un ejemplo cl\u00e1sico ser\u00eda combinar un giroscopio, muy bueno detectando cambios r\u00e1pidos pero que acumula deriva, con un aceler\u00f3metro, que proporciona una referencia absoluta m\u00e1s lenta pero estable. Un maqueta t\u00edpica en la que se usa es en un <a href=\"https:\/\/garikoitz.info\/blog\/2020\/06\/sintonizar-pid-con-arduino-ii-balancin-simple\/\" target=\"_blank\" rel=\"noopener\">balanc\u00edn<\/a> como el que implement\u00e9 hace unos a\u00f1os, aunque cualquier cosa que queramos controlar que lleve un giroscopio necesita al menos un filtro complementario.<\/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=\"\">ComplementaryFilter filtro(0.98);\n\ndouble valor = filtro.Update(\n    estimacionRapida,\n    referenciaLenta\n);<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">En nuestro ejemplo t\u00e9rmico no tenemos dos medidas independientes de T1 con caracter\u00edsticas complementarias, por lo que no tendr\u00eda demasiado sentido utilizarlo. Tener una herramienta disponible no obliga a utilizarla. Algo que tambi\u00e9n conviene recordar cuando empezamos a a\u00f1adir filtros al c\u00f3digo como si estuvi\u00e9ramos coleccionando Pok\u00e9mon.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Que_filtro_elegir\"><\/span>Qu\u00e9 filtro elegir<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Como regla pr\u00e1ctica, podemos pensar en algo parecido a esto.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Problema observado<\/th><th>Filtro que probar\u00eda primero<\/th><\/tr><\/thead><tbody><tr><td>Peque\u00f1o ruido aleatorio<\/td><td>Media m\u00f3vil<\/td><\/tr><tr><td>Picos aislados<\/td><td>Mediana<\/td><\/tr><tr><td>Ruido continuo en se\u00f1al anal\u00f3gica<\/td><td>Paso bajo<\/td><\/tr><tr><td>Picos m\u00e1s ruido continuo<\/td><td>Mediana + paso bajo<\/td><\/tr><tr><td>Dos estimaciones complementarias<\/td><td>Complementario<\/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\">No existe una configuraci\u00f3n universal. Depender\u00e1 del sensor, del tiempo de muestreo y, sobre todo, de la velocidad del proceso que queremos controlar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En procesos lentos como una temperatura podemos permitirnos cierto filtrado sin afectar demasiado al comportamiento global. En un sistema muy r\u00e1pido, utilizar un filtro excesivamente lento puede convertirse en otro problema m\u00e1s.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Esto es especialmente importante cuando queremos identificar el proceso. El algoritmo de identificaci\u00f3n no sabe que hemos colocado un filtro delante de la PV. Para \u00e9l, ese retardo adicional forma parte del proceso observado. <strong>Si filtramos demasiado, podemos terminar identificando un tiempo muerto mayor del que realmente tiene la planta<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Por tanto, el objetivo no consiste en conseguir la curva m\u00e1s bonita posible. Consiste en eliminar suficiente ruido como para que la se\u00f1al sea \u00fatil sin ocultar la din\u00e1mica que queremos estudiar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Una vez que tenemos una PV razonablemente limpia podemos pasar al siguiente problema. <strong>Averiguar c\u00f3mo responde realmente nuestro proceso cuando modificamos la salida<\/strong>. Ah\u00ed es donde entran los m\u00e9todos de identificaci\u00f3n de PIDControl.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Identificar_el_proceso\"><\/span>Identificar el proceso<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Una vez que tenemos una se\u00f1al razonablemente limpia llega el siguiente problema. Necesitamos averiguar c\u00f3mo responde el proceso cuando modificamos la salida.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Podr\u00edamos empezar asignando valores a <math xmlns=\"http:\/\/www.w3.org\/1998\/Math\/MathML\"><semantics><mrow><msub><mi>K<\/mi><mi>c<\/mi><\/msub><\/mrow><annotation encoding=\"application\/x-tex\">K_c<\/annotation><\/semantics><\/math>, <math xmlns=\"http:\/\/www.w3.org\/1998\/Math\/MathML\"><semantics><mrow><msub><mi>K<\/mi><mi>i<\/mi><\/msub><\/mrow><annotation encoding=\"application\/x-tex\">K_i<\/annotation><\/semantics><\/math> y <math xmlns=\"http:\/\/www.w3.org\/1998\/Math\/MathML\"><semantics><mrow><msub><mi>K<\/mi><mi>d<\/mi><\/msub><\/mrow><annotation encoding=\"application\/x-tex\">K_d<\/annotation><\/semantics><\/math>, poner el controlador en autom\u00e1tico y comprobar qu\u00e9 sucede. Es un m\u00e9todo perfectamente v\u00e1lido si nuestro objetivo es convertir la puesta en marcha en una actividad de riesgo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La alternativa consiste en <strong>identificar primero el proceso<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">PIDControl incorpora dos m\u00e9todos distintos para hacerlo:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><code>StepTest<\/code> <\/strong>identifica el proceso a partir de uno o varios escalones de salida<\/li>\n\n\n\n<li><strong><code>RelayTest<\/code> <\/strong>provoca una oscilaci\u00f3n controlada alrededor de un punto de trabajo<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Los dos persiguen informaci\u00f3n diferente y, por tanto, no son m\u00e9todos intercambiables.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Identificacion_mediante_StepTest\"><\/span>Identificaci\u00f3n mediante StepTest<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">El ensayo de escal\u00f3n es probablemente el m\u00e9todo m\u00e1s intuitivo. Partimos de un proceso estabilizado y modificamos la salida una cantidad conocida. Despu\u00e9s observamos cu\u00e1nto cambia la PV y cu\u00e1nto tarda en hacerlo. En nuestro caso podemos comenzar, por ejemplo, con la TCLab estabilizada al 20 % de potencia, es decir, ponemos la OP al 20% y esperamos a que estabilice.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">MAN 20<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A continuaci\u00f3n lanzamos el ensayo:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">STEP(10,2,UP,POS,0.3,60,0.10,0.4,30,0,60,15,45,0,80,0,100)<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A primera vista parece la contrase\u00f1a del WiFi del vecino, pero realmente solo estamos indicando las condiciones del ensayo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En este ejemplo estamos pidiendo:<\/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=\"\">Escalon de OP          10 %\nNumero de escalones    2\nPrimer escalon         UP\nGanancia esperada      positiva\nBanda estable          +\/-0.3 C\nTiempo estable         60 s\nPendiente maxima       0.10 C\/min\nRespuesta minima       0.4 C\nTiempo maximo          30 min\nLimites OP             0..60 %\nLimites PV             15..45 C\nRango completo PV      0..80 C\nRango completo OP      0..100 %<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La librer\u00eda espera primero a que el proceso est\u00e9 estable. Despu\u00e9s aplica el escal\u00f3n, observa la respuesta, espera de nuevo el estacionario y repite el proceso si hemos solicitado m\u00e1s de un salto.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Todo se realiza de forma no bloqueante, por lo que el <code>loop()<\/code> de Arduino contin\u00faa ejecut\u00e1ndose normalmente. El objetivo es obtener un modelo de primer orden con tiempo muerto o FOPDT.<\/p>\n\n\n\n<div class=\"wp-block-math\"><math display=\"block\"><semantics><mrow><mi>G<\/mi><mo form=\"prefix\" stretchy=\"false\">(<\/mo><mi>s<\/mi><mo form=\"postfix\" stretchy=\"false\">)<\/mo><mo>=<\/mo><mfrac><mrow><msub><mi>K<\/mi><mi>p<\/mi><\/msub><mtext>\u200b<\/mtext><msup><mi>e<\/mi><mrow><mo lspace=\"0em\" rspace=\"0em\">\u2212<\/mo><msub><mi>T<\/mi><mn>0<\/mn><\/msub><mtext>\u200b<\/mtext><mi>s<\/mi><\/mrow><\/msup><mtext>\u200b<\/mtext><\/mrow><mrow><msub><mi>T<\/mi><mrow><mi>p<\/mi><mtext>\u200b<\/mtext><\/mrow><\/msub><mi>s<\/mi><mo>+<\/mo><mn>1<\/mn><\/mrow><\/mfrac><\/mrow><annotation encoding=\"application\/x-tex\">G(s)=\\frac{K_{p}\u200be^{\u2212T_{0}\u200bs}\u200b}{T_{p\u200b}s+1}<\/annotation><\/semantics><\/math><\/div>\n\n\n\n<div style=\"height:20px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Los tres par\u00e1metros principales son <strong>Kp<\/strong> que representa la ganancia del proceso, <strong>T<sub>0<\/sub><\/strong> que representa el tiempo muerto y <strong>Tp<\/strong> que representa la constante de tiempo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Imaginad que en una de las pruebas realizadas con TCLab hemos obtenido aproximadamente:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Kp = 0.431\nT0 = 27.5 s\nTp = 147.2 s<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Esto ya nos dice bastante sobre el proceso. Sabemos cu\u00e1nto cambia aproximadamente la temperatura por cada incremento de salida, cu\u00e1nto tarda en comenzar a apreciarse la respuesta y con qu\u00e9 velocidad evoluciona despu\u00e9s.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La librer\u00eda tambi\u00e9n puede calcular la ganancia normalizada cuando conocemos los rangos completos de PV y OP.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Esto resulta \u00fatil para comparar procesos expresados en unidades distintas, aunque para calcular posteriormente la sinton\u00eda de nuestro proceso utilizaremos normalmente la ganancia en unidades de ingenier\u00eda.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"%C2%BFPor_que_uso_varios_escalones\"><\/span>\u00bfPor qu\u00e9 uso varios escalones?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Podr\u00edamos identificar el proceso utilizando un \u00fanico cambio de salida, pero realizar varios nos permite comprobar si el comportamiento es razonablemente repetible.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Step1: 20 % -> 30 %\nStep2: 30 % -> 20 %<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">En un sistema perfectamente lineal deber\u00edan ser muy parecidos, pero en el mundo real probablemente no lo sean.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En TCLab, por ejemplo, la transferencia de calor cambia con la temperatura y con las p\u00e9rdidas hacia el ambiente. El proceso que vemos alrededor del 30 % de potencia no tiene por qu\u00e9 comportarse exactamente igual que alrededor del 60 %. Esto no significa que la identificaci\u00f3n haya fallado. Significa que acabamos de descubrir que la f\u00edsica sigue neg\u00e1ndose a leer nuestros modelos matem\u00e1ticos.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Identificacion_mediante_RelayTest\"><\/span>Identificaci\u00f3n mediante RelayTest<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">El segundo m\u00e9todo incluido en PIDControl utiliza un ensayo de rel\u00e9. En lugar de aplicar un escal\u00f3n y esperar a que el proceso alcance un nuevo estacionario, hacemos oscilar la salida alrededor de un punto de trabajo. Supongamos que tenemos el proceso estabilizado aproximadamente con 40%.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">MAN 40<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Podemos lanzar directamente:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">RELAY(10,0.5,3,CURRENT,15,20,60,15,48)<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">donde:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Amplitud del rele      10 %\nHisteresis             0.5 C\nCiclos validos         3\nReferencia             PV actual\nTiempo maximo          15 min\nLimites OP             20..60 %\nLimites PV             15..48 C<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Como el bias inicial es del 40 % y la amplitud es del 10 %, la salida ir\u00e1 alternando entre 50% y 30%.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Cuando la PV supera la referencia m\u00e1s la hist\u00e9resis, la salida cambia de estado. Cuando cae por debajo de la referencia menos la hist\u00e9resis, vuelve al estado contrario. El resultado es una oscilaci\u00f3n controlada alrededor del punto de funcionamiento.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">De esa oscilaci\u00f3n obtenemos principalmente dos par\u00e1metros, <strong>Ku<\/strong> que es la ganancia \u00faltima y <strong>Tu<\/strong> que es el periodo \u00faltimo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Con estos par\u00e1metros podemos calcular un n\u00famero m\u00e1s limitado de sinton\u00edas PI y PID que con el m\u00e9todo anterior en lazo abierto, ya que la identificaci\u00f3n realizada tambi\u00e9n es m\u00e1s limitada.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"StepTest_o_RelayTest\"><\/span>StepTest o RelayTest<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Los dos m\u00e9todos sirven para identificar el proceso, pero la informaci\u00f3n que proporcionan es distinta.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>M\u00e9todo<\/th><th>Resultado<\/th><th>Caracter\u00edstica<\/th><\/tr><\/thead><tbody><tr><td>StepTest<\/td><td><math xmlns=\"http:\/\/www.w3.org\/1998\/Math\/MathML\"><semantics><mrow><msub><mi>K<\/mi><mi>p<\/mi><\/msub><\/mrow><annotation encoding=\"application\/x-tex\">K_p<\/annotation><\/semantics><\/math>, <math xmlns=\"http:\/\/www.w3.org\/1998\/Math\/MathML\"><semantics><mrow><msub><mi>T<\/mi><mn>0<\/mn><\/msub><\/mrow><annotation encoding=\"application\/x-tex\">T_0<\/annotation><\/semantics><\/math>, <math xmlns=\"http:\/\/www.w3.org\/1998\/Math\/MathML\"><semantics><mrow><msub><mi>T<\/mi><mi>p<\/mi><\/msub><\/mrow><annotation encoding=\"application\/x-tex\">T_p<\/annotation><\/semantics><\/math><\/td><td>Obtiene un modelo din\u00e1mico FOPDT<\/td><\/tr><tr><td>RelayTest<\/td><td><math xmlns=\"http:\/\/www.w3.org\/1998\/Math\/MathML\"><semantics><mrow><msub><mi>K<\/mi><mi>u<\/mi><\/msub><\/mrow><annotation encoding=\"application\/x-tex\">K_u<\/annotation><\/semantics><\/math>, <math xmlns=\"http:\/\/www.w3.org\/1998\/Math\/MathML\"><semantics><mrow><msub><mi>T<\/mi><mi>u<\/mi><\/msub><\/mrow><annotation encoding=\"application\/x-tex\">T_u<\/annotation><\/semantics><\/math><\/td><td>Obtiene las condiciones de oscilaci\u00f3n \u00faltima<\/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\"><code>StepTest<\/code> resulta especialmente interesante cuando queremos disponer de un modelo del proceso y utilizar m\u00e9todos de sintonizaci\u00f3n basados en \u00e9l.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>RelayTest<\/code> evita tener que esperar varios estados estacionarios y nos permite obtener directamente los par\u00e1metros necesarios para m\u00e9todos basados en <math xmlns=\"http:\/\/www.w3.org\/1998\/Math\/MathML\"><semantics><mrow><msub><mi>K<\/mi><mi>u<\/mi><\/msub><\/mrow><annotation encoding=\"application\/x-tex\">K_u<\/annotation><\/semantics><\/math> y <math xmlns=\"http:\/\/www.w3.org\/1998\/Math\/MathML\"><semantics><mrow><msub><mi>T<\/mi><mi>u<\/mi><\/msub><\/mrow><annotation encoding=\"application\/x-tex\">T_u<\/annotation><\/semantics><\/math>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La elecci\u00f3n depender\u00e1 del proceso y de cu\u00e1nto podamos perturbarlo. En ambos casos conviene realizar los ensayos alrededor de una zona de operaci\u00f3n representativa. Identificar un proceso a 25 \u00b0C para despu\u00e9s trabajar permanentemente a 75 \u00b0C puede producir resultados bastante menos emocionantes de lo que esperamos.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dicho esto, <strong>siempre que el proceso nos lo permita<\/strong> conviene realizar un <code><strong>StepTest<\/strong><\/code>, ya que esto nos brinda una gama <strong>m\u00e1s <\/strong>amplia de <strong>sinton\u00edas <\/strong>y lo m\u00e1s importante, <strong>simularlas en lazo cerrado<\/strong>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Recuperar_una_identificacion_anterior\"><\/span>Recuperar una identificaci\u00f3n anterior<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Hay una situaci\u00f3n especialmente irritante durante una prueba larga. Esperas varios minutos a que termine la identificaci\u00f3n, obtienes unos resultados estupendos y entonces consigues borrar la ventana del puerto serie. Por eso PIDControl conserva en memoria el \u00faltimo resultado v\u00e1lido de cada ensayo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Podemos recuperarlos con<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">IDENT\t\t\/\/ Recupera los dos m\u00e9todos     \nIDENT STEP\t\/\/ Recupera solo Kp, T0 y Tp\nIDENT RELAY\t\/\/ Recupera Ku y Tu<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Los resultados permanecen en memoria aunque limpiemos el monitor serie. Naturalmente desaparecen si reiniciamos o apagamos Arduino.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Sintonizar_el_controlador\"><\/span>Sintonizar el controlador<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Una vez identificado el proceso ya tenemos la informaci\u00f3n necesaria para calcular una primera sinton\u00eda razonable. La idea de PIDControl es que el usuario no tenga que terminar un ensayo, copiar resultados, buscar una tabla en Internet y empezar a hacer cuentas por separado.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La librer\u00eda utiliza el resultado de la identificaci\u00f3n y ofrece directamente una propuesta PI y otra PID. No las aplica autom\u00e1ticamente. Primero las calcula, las muestra y deja que decidamos cu\u00e1l queremos probar. Me parece una forma bastante m\u00e1s sensata de trabajar que terminar un ensayo y entregar inmediatamente el control a la primera combinaci\u00f3n de par\u00e1metros que aparezca. El m\u00e9todo de sintonizaci\u00f3n utilizado depende de c\u00f3mo hayamos identificado el proceso.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Sintonizacion_despues_de_StepTest\"><\/span>Sintonizaci\u00f3n despu\u00e9s de StepTest<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Cuando utilizamos <strong><code>StepTest<\/code> <\/strong>obtenemos un modelo del proceso basado en su ganancia, su tiempo muerto y su velocidad de respuesta. A partir de ese modelo PIDControl calcula dos alternativas.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La primera es una sintonizaci\u00f3n <strong>IMC \/ Lambda PI<\/strong>. Es una buena opci\u00f3n cuando queremos un controlador sencillo y robusto, especialmente en procesos lentos como temperatura, nivel o muchas aplicaciones habituales de control de procesos. La segunda es una <strong>Lambda PID<\/strong>. Utiliza el mismo modelo identificado, pero a\u00f1ade acci\u00f3n derivativa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En ambos casos podemos decidir cu\u00e1nto queremos exigir al controlador mediante tres niveles de respuesta, <code>AGGRESSIVE<\/code> , <code>NORMAL<\/code> y <code>ROBUST<\/code>. <code>AGGRESSIVE<\/code> busca una respuesta m\u00e1s r\u00e1pida, <code>ROBUST<\/code> sacrifica velocidad a cambio de una respuesta m\u00e1s conservadora y <code>NORMAL<\/code> queda entre ambas. Tambi\u00e9n podemos introducir manualmente el valor Lambda si queremos controlar directamente ese compromiso.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Mi primera elecci\u00f3n en un proceso nuevo ser\u00eda normalmente comenzar con la PI y utilizar la PID \u00fanicamente si realmente necesitamos mejorar la respuesta y disponemos de una se\u00f1al suficientemente limpia. A\u00f1adir derivada porque existe la opci\u00f3n suele ser una forma bastante eficaz de crear un problema que antes no ten\u00edamos.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Sintonizacion_despues_de_RelayTest\"><\/span>Sintonizaci\u00f3n despu\u00e9s de RelayTest<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>RelayTest<\/code> proporciona otro tipo de informaci\u00f3n. En este caso obtenemos <code>Ku<\/code> y <code>Tu<\/code>, que describen el comportamiento del proceso alrededor de una oscilaci\u00f3n mantenida. A partir de estos valores PIDControl calcula dos propuestas mediante <strong>Tyreus-Luyben<\/strong>, una es una sintonizaci\u00f3n PI y la otra una PID.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">He elegido Tyreus-Luyben como m\u00e9todo integrado porque proporciona normalmente una respuesta menos agresiva que los ajustes cl\u00e1sicos de Ziegler-Nichols y me parece un punto de partida m\u00e1s razonable para una librer\u00eda que pretende utilizarse sobre procesos reales y no \u00fanicamente para conseguir una gr\u00e1fica espectacular durante treinta segundos.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Elegir_entre_PI_y_PID\"><\/span>Elegir entre PI y PID<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Que la librer\u00eda pueda calcular ambas alternativas no significa que debamos utilizar siempre la PID. En muchos procesos un PI es m\u00e1s que suficiente. Tiene menos par\u00e1metros, suele ser m\u00e1s f\u00e1cil de ajustar y es menos sensible al ruido. A\u00f1adir derivada empieza a tener m\u00e1s inter\u00e9s cuando la din\u00e1mica est\u00e1 bien identificada, la medida es razonablemente limpia y necesitamos mejorar la respuesta utilizando tambi\u00e9n la velocidad de cambio de la PV.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Como criterio inicial podemos pensar as\u00ed:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Situaci\u00f3n<\/th><th>Primera opci\u00f3n<\/th><\/tr><\/thead><tbody><tr><td>Proceso t\u00e9rmico o lento<\/td><td>PI<\/td><\/tr><tr><td>Se\u00f1al con bastante ruido<\/td><td>PI<\/td><\/tr><tr><td>Proceso sencillo y estable<\/td><td>PI<\/td><\/tr><tr><td>Se\u00f1al limpia y din\u00e1mica bien conocida<\/td><td>PI o PID<\/td><\/tr><tr><td>Necesitamos una respuesta m\u00e1s anticipativa<\/td><td>PID<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Aplicar_la_sintonizacion\"><\/span>Aplicar la sintonizaci\u00f3n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">PIDControl trabaja con <code>Kc<\/code>, <code>Ki<\/code> y <code>Kd<\/code>, por lo que las sintonizaciones calculadas pueden aplicarse directamente al controlador. Desde la consola utilizamos:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">TUNE Kc Ki Kd       \/\/ por ejemplo TUNE 4.448 0.0112 0<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">El comando anterior aplicar\u00eda una sintonizaci\u00f3n PI porque el t\u00e9rmino derivativo es cero. Si posteriormente queremos probar la propuesta PID simplemente introducimos los valores correspondientes que haya calculado la librer\u00eda.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En resumen:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">IDENT\nMuestra todas las identificaciones almacenadas\n\nIDENT STEP\nMuestra Kp, T0 y Tp junto con las sinton\u00edas\nIMC \/ Lambda PI y Lambda PID\n\nIDENT RELAY\nMuestra Ku y Tu junto con las sinton\u00edas\nTyreus-Luyben PI y PID\n\nTUNE Kc Ki Kd\nAplica una sintonizaci\u00f3n al controlador\n\nSTATUS\nMuestra la configuraci\u00f3n y estado actual<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Con esto ya hemos recorrido una buena parte de la cadena. Primero hemos tratado la se\u00f1al, despu\u00e9s hemos identificado el proceso y finalmente hemos obtenido una sintonizaci\u00f3n PI o PID.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El siguiente paso es comprobar si todo ese trabajo sirve para algo \u00fatil. Ya podemos pasar al control en MAN y AUTO y ver c\u00f3mo responde el proceso cuando dejamos que PIDControl tome las decisiones.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Configurar_el_controlador\"><\/span>Configurar el controlador<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Una vez tratada la se\u00f1al, identificado el proceso y obtenida una sintonizaci\u00f3n, ya podemos configurar el controlador. Buena parte de esta configuraci\u00f3n se realiza en el propio momento en que declaramos el objeto <code>PIDControl<\/code>, aunque la librer\u00eda incluye unos valores por defecto pensados para cubrir una configuraci\u00f3n habitual y reducir al m\u00ednimo el c\u00f3digo necesario.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La declaraci\u00f3n m\u00e1s corta necesita \u00fanicamente las variables de proceso y los tres par\u00e1metros de sintonizaci\u00f3n.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">PIDControl pid(&amp;PV, &amp;OP, &amp;SP, Kc, Ki, Kd);<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">PIDControl pid(&amp;PV, &amp;OP, &amp;SP, 4.45, 0.15, 0.0);<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Con esta declaraci\u00f3n la librer\u00eda utiliza autom\u00e1ticamente sus valores por defecto. El controlador se crea en modo <code>MAN<\/code>, con estructura <code>PI_D<\/code>, acci\u00f3n <code>REVERSE<\/code>, PV Tracking activado, l\u00edmites de salida entre 0 y 100 % y un tiempo de muestreo de 100 ms. La salida manual inicial toma el valor que tenga <code>OP<\/code> en el momento de crear el controlador. <code>Kc<\/code>, <code>Ki<\/code> y <code>Kd<\/code> no tienen valores por defecto, ya que debemos proporcionarlos nosotros al construir el objeto.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Par\u00e1metro<\/th><th>Valor por defecto<\/th><\/tr><\/thead><tbody><tr><td>Modo<\/td><td><code>MAN<\/code><\/td><\/tr><tr><td>Estructura<\/td><td><code>PI_D<\/code><\/td><\/tr><tr><td>Acci\u00f3n<\/td><td><code>REVERSE<\/code><\/td><\/tr><tr><td>PV Tracking<\/td><td><code>ON<\/code><\/td><\/tr><tr><td>OP m\u00ednima<\/td><td><code>0<\/code><\/td><\/tr><tr><td>OP m\u00e1xima<\/td><td><code>100<\/code><\/td><\/tr><tr><td>Tiempo de muestreo<\/td><td><code>100 ms<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Para muchos procesos esta configuraci\u00f3n puede utilizarse directamente. En nuestro ejemplo con TCLab coincide adem\u00e1s con la configuraci\u00f3n que queremos utilizar, por lo que la declaraci\u00f3n corta es suficiente.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La librer\u00eda permite tambi\u00e9n utilizar la declaraci\u00f3n completa. Puede resultar \u00fatil cuando queremos dejar expl\u00edcita la configuraci\u00f3n en el c\u00f3digo o cuando necesitamos utilizar una estructura, una acci\u00f3n o un comportamiento de seguimiento diferentes de los valores por defecto.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">PIDControl pid(&amp;PV, &amp;OP, &amp;SP, Kc, Ki, Kd, PIDType::PI_D, PIDAction::REVERSE, true);<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">PIDControl pid(&amp;PV, &amp;OP, &amp;SP, 4.45, 0.15, 0.0, PIDType::PI_D, PIDAction::REVERSE, true);<\/pre>\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 un controlador en autom\u00e1tico debemos comprobar siempre que la acci\u00f3n de control es correcta. La forma m\u00e1s sencilla de elegirla consiste en preguntarnos qu\u00e9 debe hacer la OP cuando aumenta la PV. En nuestro proceso t\u00e9rmico, si aumenta la temperatura queremos reducir la potencia del calefactor, por lo que necesitamos acci\u00f3n <code>REVERSE<\/code>. Este es precisamente el valor por defecto de PIDControl, por lo que en nuestro ejemplo no necesitamos configurar nada adicional. Si trabaj\u00e1ramos con un proceso en el que al aumentar la PV tambi\u00e9n debiera aumentar la OP utilizar\u00edamos <code>DIRECT<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La acci\u00f3n puede modificarse en cualquier momento desde c\u00f3digo o desde la consola.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">ACTION REVERSE<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">ACTION DIRECT<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Aunque <code>REVERSE<\/code> sea el valor por defecto, conviene entender bien este par\u00e1metro antes de pasar a AUTO. Elegir una acci\u00f3n incorrecta convierte la realimentaci\u00f3n negativa que pretend\u00edamos conseguir en realimentaci\u00f3n positiva, algo que suele conseguir que el experimento deje de ser aburrido bastante r\u00e1pido.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Estructura_del_controlador\"><\/span>Estructura del controlador<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">PIDControl permite trabajar con las estructuras <code>PID<\/code>, <code>PI_D<\/code> e <code>I_PD<\/code>. La estructura utilizada por defecto es <code>PI_D<\/code>, que tambi\u00e9n es la que vamos a emplear en TCLab.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En <code>PI_D<\/code> las acciones proporcional e integral trabajan sobre el error mientras que la derivativa trabaja sobre la PV. Esto evita el salto de la acci\u00f3n derivativa cuando modificamos el SP.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Podemos cambiar la estructura posteriormente si lo necesitamos.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">STRUCT PID<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">STRUCT PI_D<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">STRUCT I_PD<\/pre>\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\">El PV Tracking tambi\u00e9n est\u00e1 activado por defecto. Mientras el controlador permanece en manual, el SP interno sigue a la PV mientras se conserva por separado la consigna solicitada por el usuario. Esto facilita posteriormente el paso de MAN a AUTO porque el controlador comienza desde una situaci\u00f3n coherente con el estado actual del proceso y puede realizar una transferencia sin un salto brusco de salida. Por tanto, con la declaraci\u00f3n corta tampoco necesitamos activar expresamente esta funci\u00f3n. Si queremos modificar su comportamiento podemos hacerlo mediante:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">TRACK ON<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">TRACK OFF<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Limites_de_la_salida\"><\/span>L\u00edmites de la salida<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Los l\u00edmites de salida son por defecto de 0 a 100 %, que ya encajan con nuestro ejemplo. Solo ser\u00eda necesario modificarlos si el actuador o el proceso requieren otro rango.<\/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=\"\">OUTLIMITS 0 100<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">PIDControl incorpora adem\u00e1s anti-windup. Cuando la OP alcanza alguno de estos l\u00edmites, la integral no contin\u00faa creciendo en una direcci\u00f3n que intentar\u00eda llevar todav\u00eda m\u00e1s lejos una salida que ya est\u00e1 saturada.<\/p>\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 tiempo de muestreo por defecto es de 100 ms, que tambi\u00e9n utilizaremos en nuestras pruebas con TCLab. De nuevo, este comando no es necesario mientras queramos conservar el valor por defecto. Podemos modificarlo cuando la din\u00e1mica del proceso o la adquisici\u00f3n de datos requieran otro periodo de ejecuci\u00f3n.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">TS 100<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Puesta_en_marcha\"><\/span>Puesta en marcha<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Llegados a este punto ya tenemos el controlador configurado y podemos ponerlo a trabajar. Tambi\u00e9n conviene aclarar que <strong>PIDControl puede utilizarse directamente desde aqu\u00ed<\/strong>, sin necesidad de pasar previamente por filtros, <code>StepTest<\/code>, <code>RelayTest<\/code> ni m\u00e9todos de sintonizaci\u00f3n. Si ya conocemos unos valores razonables de <code>Kc<\/code>, <code>Ki<\/code> y <code>Kd<\/code>, podemos cargarlos y comenzar a controlar el proceso. Todo lo anterior no es obligatorio para utilizar la librer\u00eda, aunque merece la pena comprenderlo porque nos permite trabajar con una se\u00f1al mejor tratada, conocer c\u00f3mo responde realmente el proceso y obtener una sintonizaci\u00f3n con algo m\u00e1s de fundamento que el tradicional m\u00e9todo de prueba, error y esperanza.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La puesta en marcha deber\u00eda comenzar siempre en modo manual. De esta forma podemos llevar el proceso hasta una zona de operaci\u00f3n razonable y comprobar que la relaci\u00f3n entre PV y OP es la esperada antes de entregar el control al PID. En nuestro ejemplo con TCLab podemos comenzar fijando la potencia al 40 %<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">MAN 40<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Esperamos a que la temperatura se encuentre cerca de una zona estable y cargamos la sintonizaci\u00f3n que queramos utilizar. Si los valores ya se proporcionaron al declarar el objeto no ser\u00eda necesario hacerlo de nuevo, pero durante las pruebas resulta muy c\u00f3modo utilizar <code>TUNE<\/code> para cambiar de sintonizaci\u00f3n sin recompilar el programa.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">TUNE 4.45 0.15 0<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Con <code>Kd<\/code> igual a cero estaremos trabajando como un controlador PI. Una vez comprobado que la acci\u00f3n de control, la estructura y los l\u00edmites son correctos podemos realizar el paso a autom\u00e1tico.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">AUTO<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Como el PV Tracking est\u00e1 activado por defecto, el SP interno ha estado siguiendo a la PV mientras permanec\u00edamos en manual. PIDControl utiliza esta situaci\u00f3n para realizar una transferencia bumpless e intentar mantener la OP existente en el momento del cambio, evitando que el simple paso de MAN a AUTO provoque un salto innecesario en la salida.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A partir de ese momento podemos modificar la consigna.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">SP 50<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Tambi\u00e9n podemos realizar el paso a autom\u00e1tico y establecer directamente el nuevo SP mediante una \u00fanica orden.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">AUTO 50<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">El controlador comenzar\u00e1 entonces a modificar la OP para llevar la PV hacia la consigna. En nuestro proceso t\u00e9rmico aumentar\u00e1 la potencia mientras la temperatura est\u00e9 por debajo del SP y la ir\u00e1 reduciendo a medida que se aproxime al objetivo. Si posteriormente cambiamos la consigna, el controlador volver\u00e1 a ajustar la salida de acuerdo con la nueva situaci\u00f3n.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Este es el m\u00ednimo necesario para comenzar a utilizar PIDControl. Todo lo anterior nos ayuda a llegar hasta aqu\u00ed con una se\u00f1al mejor tratada, una identificaci\u00f3n del proceso y una sintonizaci\u00f3n calculada, pero la librer\u00eda no obliga a seguir ese recorrido completo. Se puede empezar directamente por el control y profundizar despu\u00e9s, aunque normalmente entender lo que ocurre antes de pulsar <code>AUTO<\/code> suele salir bastante m\u00e1s barato que intentar entenderlo despu\u00e9s.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Codigo_de_ejemplo\"><\/span>C\u00f3digo de ejemplo<span class=\"ez-toc-section-end\"><\/span><\/h2>\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=\"\">#include &lt;PIDControl.h>\n#include &lt;PIDControlFilters.h>\n\nusing PIDControlFilters::MedianFilter;\nusing PIDControlFilters::LowPassFilter;\n\n\/\/ ------------------------------------------------------------\n\/\/ Hardware TCLab\n\/\/ ------------------------------------------------------------\n\nconst uint8_t PIN_T1 = A0;\nconst uint8_t PIN_Q1 = 3;\n\n\/\/ Arduino UNO R4 + TCLab\nconst uint8_t ADC_BITS = 14;\nconst double ADC_REFERENCE_V = 3.300;\n\n\/\/ TCLab limita Q1 a PWM = 200 cuando OP = 100 %\nconst uint8_t Q1_PWM_MAX = 200;\n\n\/\/ ------------------------------------------------------------\n\/\/ PID\n\/\/ ------------------------------------------------------------\n\ndouble SP = 40.0;\ndouble PV = 0.0;\ndouble OP = 0.0;\n\ndouble Kc = 4.45;\ndouble Ki = 0.15;\ndouble Kd = 0.0;\n\n\/\/ PI-D, REVERSE y PV Tracking activado\nPIDControl pid(\n    &amp;PV, &amp;OP, &amp;SP,\n    Kc, Ki, Kd,\n    PIDType::PI_D,\n    PIDAction::REVERSE,\n    true\n);\n\n\/\/ ------------------------------------------------------------\n\/\/ Filtrado\n\/\/ ------------------------------------------------------------\n\nMedianFilter&lt;5> medianFilter;\nLowPassFilter lowPassFilter(1.0);\n\nconst unsigned long Ts_ms = 100;\nunsigned long previousTime = 0;\n\n\/\/ ------------------------------------------------------------\n\/\/ Lectura de temperatura\n\/\/ ------------------------------------------------------------\n\ndouble ReadTemperatureC()\n{\n    \/\/ Se descarta la primera conversion\n    analogRead(PIN_T1);\n\n    const int adc = analogRead(PIN_T1);\n\n    const double adcMax =\n        (double)((1UL &lt;&lt; ADC_BITS) - 1UL);\n\n    const double voltage =\n        adc * ADC_REFERENCE_V \/ adcMax;\n\n    \/\/ Sensor TMP36 de TCLab\n    return (voltage - 0.5) * 100.0;\n}\n\n\/\/ ------------------------------------------------------------\n\/\/ Salida calefactor\n\/\/ ------------------------------------------------------------\n\nvoid WriteHeater(double percent)\n{\n    if (percent &lt; 0.0)\n        percent = 0.0;\n\n    if (percent > 100.0)\n        percent = 100.0;\n\n    const int pwm =\n        (int)(percent * Q1_PWM_MAX \/ 100.0);\n\n    analogWrite(PIN_Q1, pwm);\n}\n\n\/\/ ------------------------------------------------------------\n\/\/ Setup\n\/\/ ------------------------------------------------------------\n\nvoid setup()\n{\n    Serial.begin(115200);\n\n    pinMode(PIN_Q1, OUTPUT);\n    analogWrite(PIN_Q1, 0);\n\n    analogReference(AR_EXTERNAL);\n    analogReadResolution(ADC_BITS);\n\n    pid.SetOutputLimits(0.0, 100.0);\n    pid.SetSampleTime(Ts_ms);\n\n    \/\/ Empezamos en manual\n    pid.SetManualOutput(30.0);\n    pid.SetMode(PIDMode::MAN);\n\n    previousTime = millis();\n}\n\n\/\/ ------------------------------------------------------------\n\/\/ Loop\n\/\/ ------------------------------------------------------------\n\nvoid loop()\n{\n    const unsigned long now = millis();\n\n    if ((unsigned long)(now - previousTime) &lt; Ts_ms)\n        return;\n\n    const double dt =\n        (now - previousTime) \/ 1000.0;\n\n    previousTime = now;\n\n    \/\/ 1. Leer temperatura\n    const double PVRaw =\n        ReadTemperatureC();\n\n    \/\/ 2. Eliminar picos\n    const double PVMedian =\n        medianFilter.Update(PVRaw);\n\n    \/\/ 3. Suavizar ruido\n    PV =\n        lowPassFilter.Update(PVMedian, dt);\n\n    \/\/ 4. Calcular PID\n    pid.Compute();\n\n    \/\/ 5. Aplicar salida\n    WriteHeater(OP);\n\n    \/\/ 6. Monitorizar\n    Serial.print(\"SP=\");\n    Serial.print(SP, 2);\n\n    Serial.print(\" PVRaw=\");\n    Serial.print(PVRaw, 2);\n\n    Serial.print(\" PV=\");\n    Serial.print(PV, 2);\n\n    Serial.print(\" OP=\");\n    Serial.println(OP, 2);\n}<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Enlaces\"><\/span>Enlaces<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/github.com\/deurus\/PIDControl\" target=\"_blank\" rel=\"noopener\">Github<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/garikoitz.info\/blog\/descargas\/PIDControl\/\" target=\"_blank\" rel=\"noopener\">Carpeta de descargas<\/a><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introducci&oacute;n PIDControl nace de una mezcla bastante habitual entre necesidad, curiosidad y esa mala costumbre de mirar algo que ya funciona y pensar que seguramente se puede complicar un poco m&aacute;s. La idea inicial era relativamente sencilla. Quer&iacute;a disponer de una librer&iacute;a PID para Arduino con la que pudiera trabajar de una forma parecida a como estoy acostumbrado en control de procesos. Manual y autom&aacute;tico, seguimiento de PV, transferencia bumpless, anti-windup, diferentes estructuras de PID, acci&oacute;n directa o inversa y&#8230;<\/p>\n<p class=\"read-more\"><a class=\"btn btn-default\" href=\"https:\/\/garikoitz.info\/blog\/2026\/09\/pidcontrol-libreria-de-control-para-arduino\/\"> Leer m\u00e1s<span class=\"screen-reader-text\">  Leer m\u00e1s<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":2991,"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":[276,16,280,279,278,143,96,275,122,142,17,277,101,173],"class_list":["post-2951","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-arduino","category-pid","category-programacion","tag-api","tag-arduino","tag-filtro-complentario","tag-filtro-mediana","tag-filtro-paso-bajo","tag-i-pd","tag-identificacion-de-procesos","tag-libreria","tag-pi","tag-pi-d","tag-pid","tag-pidcontrol","tag-sintonia-pid","tag-tclab"],"_links":{"self":[{"href":"https:\/\/garikoitz.info\/blog\/wp-json\/wp\/v2\/posts\/2951","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=2951"}],"version-history":[{"count":39,"href":"https:\/\/garikoitz.info\/blog\/wp-json\/wp\/v2\/posts\/2951\/revisions"}],"predecessor-version":[{"id":2993,"href":"https:\/\/garikoitz.info\/blog\/wp-json\/wp\/v2\/posts\/2951\/revisions\/2993"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/garikoitz.info\/blog\/wp-json\/wp\/v2\/media\/2991"}],"wp:attachment":[{"href":"https:\/\/garikoitz.info\/blog\/wp-json\/wp\/v2\/media?parent=2951"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/garikoitz.info\/blog\/wp-json\/wp\/v2\/categories?post=2951"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/garikoitz.info\/blog\/wp-json\/wp\/v2\/tags?post=2951"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}