{"id":1292,"date":"2020-11-26T05:48:00","date_gmt":"2020-11-26T05:48:00","guid":{"rendered":"http:\/\/visiorob.com.br\/?p=1292"},"modified":"2022-09-04T14:08:58","modified_gmt":"2022-09-04T14:08:58","slug":"implementacao-de-um-controle-pid-utilizando-arduino-e-freertos","status":"publish","type":"post","link":"https:\/\/visiorob.com.br\/index.php\/2020\/11\/26\/implementacao-de-um-controle-pid-utilizando-arduino-e-freertos\/","title":{"rendered":"Implementa\u00e7\u00e3o de um controle PID utilizando Arduino e FreeRTOS"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Controle PID<\/h2>\n\n\n\n<p>O PID (Proporcional-Integral-Derivativo) \u00e9 atualmente, segundo a National Instruments, o algoritmo de controle mais utilizado na ind\u00fastria. Isso se deve tanto ao seu desempenho robusto como tamb\u00e9m \u00e0 sua simplicidade funcional. Esse algoritmo tem por objetivo realizar o controle preciso de uma vari\u00e1vel em um sistema em malha fechada, isto \u00e9, sistemas em que a a\u00e7\u00e3o de controle depende da sa\u00edda (estado atual) da vari\u00e1vel a ser controlada. De forma mais pr\u00e1tica, o controle se d\u00e1 analizando o sinal de erro, que consiste na diferen\u00e7a entre o valor desejado (setpoint) e o valor atual (lido por um sensor). A todo momento o sinal de erro \u00e9 recalculado pelo algoritmo e utilizado para determinar as a\u00e7\u00f5es de controle sobre um determinado atuador, a fim de minimizar o sinal de erro e, idealmente, torn\u00e1-lo zero. Vamos agora dar uma breve analisada em quais s\u00e3o as fun\u00e7\u00f5es desempenhadas pelos agentes P, I e D no controle de processos.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">A\u00e7\u00e3o Proporcional &#8211; P<\/h3>\n\n\n\n<p>Age proporcionalmente \u00e0 amplitude do erro do sistema, multiplicando uma constante de proporcionalidade Kp pelo erro do sistemal, Ent\u00e3o:<\/p>\n\n\n\n<p>P = Kp * Erro<\/p>\n\n\n\n<p>A a\u00e7\u00e3o proporcional elimina as oscila\u00e7\u00f5es da vari\u00e1vel, tornando o sistema est\u00e1vel, mas n\u00e3o garante que a mesma esteja no valor desejado (setpoint), esse desvio \u00e9 denominado off-set. Um ganho proporcional muito alto gera um alto sinal de sa\u00edda, o que pode desestabilizar o sistema. Por\u00e9m, se o ganho proporcional \u00e9 muito baixo, o sistema falha em aplicar a a\u00e7\u00e3o necess\u00e1ria para corrigir os dist\u00farbios.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">A\u00e7\u00e3o Integral &#8211; I<\/h3>\n\n\n\n<p>Tem por objetivo eliminar o erro de estado estacion\u00e1rio (off-set), fazendo com que a vari\u00e1vel controlada fique muito pr\u00f3xima ao valor de setpoint mesmo que ocorram perturba\u00e7\u00f5es no sistema. A a\u00e7\u00e3o integral produz um sinal de sa\u00edda que \u00e9 proporcional \u00e0 magnitude e \u00e0 dura\u00e7\u00e3o do erro, ou seja, realiza a integra\u00e7\u00e3o do erro no tempo, portanto quanto maior for o tempo de perman\u00eancia do erro no sistema, maior ser\u00e1 a amplitude da a\u00e7\u00e3o integral. Computacionalmente falando a intrega\u00e7\u00e3o pode ser vista como uma somat\u00f3ria, com isso, a componente integral se equivale a um acumulador do erro do sistema, multiplicado por uma constante de integra\u00e7\u00e3o Ki, veja a f\u00f3rmula:<\/p>\n\n\n\n<p>I = I + (Ki * Erro)<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">A\u00e7\u00e3o Derivativa &#8211; D<\/h3>\n\n\n\n<p>Fornece uma corre\u00e7\u00e3o antecipada do erro, diminuindo o tempo de resposta e melhorando a estabilidade do sistema. Este parametro produz um sinal de sa\u00edda que \u00e9 proporcional \u00e0 taxa de varia\u00e7\u00e3o da vari\u00e1vel controlada, sendo inversamente proporcional \u00e0 velocidade da mesma. Por se tratar da varia\u00e7\u00e3o de um sinal, a componente derivativa pode ser calculada pela diferen\u00e7a dos dois \u00faltimos valores da vari\u00e1vel de interesse, multiplicados por uma constante Kd, vejamos:<\/p>\n\n\n\n<p>D = Kd * (ValorAtual &#8211; ValorAnterior)<\/p>\n\n\n\n<p>Aumentar o par\u00e2metro do tempo derivativo (Kd) far\u00e1 com que o sistema de controle reaja mais fortemente \u00e0 mudan\u00e7as do erro. Na pr\u00e1tica, a maioria dos sistemas de controle utilizam um Kd muito pequeno, pois a derivada tem a resposta muito sens\u00edvel ao ru\u00eddo no sinal da vari\u00e1vel de processo.<\/p>\n\n\n\n<p>Apresentados todos os agentes do algoritmo PID, chegamos a sua f\u00f3rmula final, sendo:<\/p>\n\n\n\n<p>PID = P + I + D<\/p>\n\n\n\n<p>Ent\u00e3o:<\/p>\n\n\n\n<p>PID = Kp * Erro + (I + (Ki * Erro)) + Kd * (ValorAtual &#8211; ValorAnterior)<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">O projeto<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Servomotor<\/h3>\n\n\n\n<p>Neste artigo iremos realizar o controle da posi\u00e7\u00e3o de um servomotor, portanto essa (a posi\u00e7\u00e3o) ser\u00e1 a nossa vari\u00e1vel de interesse. Para facilitar a compreens\u00e3o, veja abaixo a estrutura interna de um servo:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"543\" height=\"259\" src=\"https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/servo.jpg\" alt=\"\" class=\"wp-image-1294\" srcset=\"https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/servo.jpg 543w, https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/servo-300x143.jpg 300w\" sizes=\"auto, (max-width: 543px) 100vw, 543px\" \/><figcaption>Estrutura de um servomotor<br>Fonte: Site Mundo da El\u00e9trica<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Como podemos ver na imagem um servomotor possui tr\u00eas elementos principais:  O motor CC que emprega o movimento de rota\u00e7\u00e3o;  A caixa de engrenagens que reduz a velocidade e aumenta o torque; E o pot\u00eanciometro que est\u00e1 conectado junto ao eixo do servomotor. Portanto, quando o eixo gira, gira tamb\u00e9m o cursor do pot\u00eanciometro, o que nos permite saber, atrav\u00e9s da leitura da tensao no terminal central do potenci\u00f4metro, a posi\u00e7\u00e3o atual em que o motor se encontra. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Ponte H<\/h3>\n\n\n\n<p>Para realizar o acionamento do servomotor ser\u00e1 utilizado um circuito ponte H, cujo diagrama esquem\u00e1tico \u00e9 o seguinte:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/ponte.png\" alt=\"\" class=\"wp-image-1304\" width=\"347\" height=\"213\" srcset=\"https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/ponte.png 1024w, https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/ponte-300x184.png 300w, https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/ponte-768x472.png 768w\" sizes=\"auto, (max-width: 347px) 100vw, 347px\" \/><figcaption>Estrutura ponte H<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Com a ponte H podemos alterar o sentido em que a corrente passar\u00e1 pelo motor, alterando assim seu sentido de giro, apenas controlando o chaveamento do switches. Isso \u00e9 ilustrado na figura abaixo:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"315\" src=\"https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/funcionamento-1024x315.png\" alt=\"\" class=\"wp-image-1307\" srcset=\"https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/funcionamento-1024x315.png 1024w, https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/funcionamento-300x92.png 300w, https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/funcionamento-768x236.png 768w, https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/funcionamento-1536x472.png 1536w, https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/funcionamento.png 1920w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Modos de funcionamento de uma ponte H<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Para implementa\u00e7\u00e3o da ponte H ser\u00e1 utilizado o circuito integrado L298, esse CI possui 2 circuitos ponte H em seu encaplusamento, veja no diagrama esquem\u00e1tico:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"528\" src=\"https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/l298-1024x528.png\" alt=\"\" class=\"wp-image-1312\" srcset=\"https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/l298-1024x528.png 1024w, https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/l298-300x155.png 300w, https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/l298-768x396.png 768w, https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/l298.png 1033w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Esquem\u00e1tico do CI L298<\/figcaption><\/figure>\n<\/div>\n\n\n<p>O L298 permite o controle do servomotor utilizando apenas duas portas do microcontrolador, isso \u00e9 poss\u00edvel da seguinte forma: Com a porta EnA (enable) em n\u00edvel alto (+5V) uma das pontes est\u00e1 habilitada para o uso. Para definir o sentido de rota\u00e7\u00e3o do motor basta alterar as entradas In1 e In2, de modo que:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"400\" height=\"95\" src=\"https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/tabela-top.jpg\" alt=\"\" class=\"wp-image-1375\" srcset=\"https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/tabela-top.jpg 400w, https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/tabela-top-300x71.jpg 300w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><figcaption>Modos de trabalho do CI L298<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Al\u00e9m disso, o L298 permite que motores de at\u00e9 50V sejam comandados por apenas por 5V, j\u00e1 que ele possui entradas espec\u00edficas para acionamento e para alimenta\u00e7\u00e3o.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">PWM<\/h3>\n\n\n\n<p>Para realizar o controle da velocidade do servomotor ser\u00e1 utilizada a t\u00e9cnica de modula\u00e7\u00e3o por largura de pulso (PWM), Com o ela \u00e9 poss\u00edvel controlar a tens\u00e3o e corrente que ser\u00e3o entregues ao motor, isso \u00e9 feito ao ligar e desligar (chavear) o fornecimento de energia entre a fonte e a carga em uma taxa muito r\u00e1pida. Quanto mais tempo a alimenta\u00e7\u00e3o permanece ligada, em compara\u00e7\u00e3o com o tempo desligada, maior a quantidade total de pot\u00eancia fornecida \u00e0 carga. A raz\u00e3o de tempo em que o sinal pernace em n\u00edvel alto \u00e9 denomida Duty Cicle, o funcionamento pr\u00e1tico do PWM \u00e9 ilustrado na figura abaixo:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"864\" height=\"600\" src=\"https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/pwm_exemplo.png\" alt=\"\" class=\"wp-image-1378\" srcset=\"https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/pwm_exemplo.png 864w, https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/pwm_exemplo-300x208.png 300w, https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/pwm_exemplo-768x533.png 768w\" sizes=\"auto, (max-width: 864px) 100vw, 864px\" \/><figcaption>Funcionamento do PWM<\/figcaption><\/figure>\n\n\n\n<p>Note que a frequ\u00eancia dos tr\u00eas sinais \u00e9 sempre a mesma, isso \u00e9 fundamental para o funcionamento correto desta t\u00e9cnica, portanto, a \u00fanica condi\u00e7\u00e3o que se altera \u00e9 o duty cicle, sendo que, quanto maior o duty cicle, maior ser\u00e1 a tens\u00e3o m\u00e9dia entregue \u00e0 carga.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Montagem do circuito<\/h3>\n\n\n\n<p>Definido e descrito o hardware e as t\u00e9cnicas utilizadas, podemos finalmente realizar a montagem do nosso circuito. Al\u00e9m dos componentes j\u00e1 mecionados ser\u00e1 utilizado tamb\u00e9m um potenci\u00f4metro, para que possamos alterar o setpoint manualmente e assim, controlar de fato o motor. Acompanhe abaixo a liga\u00e7\u00e3o dos componentes:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"512\" height=\"263\" src=\"https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/Funky-Tumelo-Crift-1.png\" alt=\"\" class=\"wp-image-1353\" srcset=\"https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/Funky-Tumelo-Crift-1.png 512w, https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/Funky-Tumelo-Crift-1-300x154.png 300w\" sizes=\"auto, (max-width: 512px) 100vw, 512px\" \/><figcaption>Montagem do projeto<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Os dois potenci\u00f4metros, o do servomotor e o de controle do setpoint, est\u00e3o conectados \u00e0s entradas anal\u00f3gicas do Arduino. A ponte H \u00e9 alimentada com as tens\u00f5es de controle (5V) e de alimenta\u00e7\u00e3o do servomotor (representado pela bateria), o Enable A \u00e9 conectado diretamente aos 5V, mantendo uma das pontes sempre habilitada. Os pinos de controle In1 e In2 est\u00e3o ligados nas portas digitais PWM do Arduino, j\u00e1 que ser\u00e3o controladas atrav\u00e9s deste m\u00e9todo. Finalmente, as sa\u00eddas da ponte H OUT1 e OUT2 s\u00e3o ligadas aos terminais de alimenta\u00e7\u00e3o do servomotor.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Software<\/h3>\n\n\n\n<p>O software a ser embarcado no microcontrolador ser\u00e1 desenvolvido em linguagem C no ambiente Atmel Studio, n\u00e3o utilizaremos a IDE do Arduino tampouco as bibliotecas nativas dela, isso ser\u00e1 feito visando um melhor desempenho do microcontrolador, j\u00e1 que as bibliotecas do Arduino possuem um n\u00edvel muito alto de abstra\u00e7\u00e3o, prejudicando a velocidade de execu\u00e7\u00e3o das tarefas. Abaixo veja um paralelo entre os terminais do Arduino e do microcontrolador ATmega328P, embarcado no Arduino:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"696\" height=\"309\" src=\"https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/pinout.jpg\" alt=\"\" class=\"wp-image-1339\" srcset=\"https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/pinout.jpg 696w, https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/pinout-300x133.jpg 300w\" sizes=\"auto, (max-width: 696px) 100vw, 696px\" \/><figcaption>Pinout do ATmega328P no Arduino Uno<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Neste projeto tamb\u00e9m ser\u00e1 utilizado um sistema operacional de tempo real (RTOS), com ele conseguimos criar um ambiente multitarefas, j\u00e1 que ele alterna rapidamente entre as tarefas a serem executadas, diferentes de programas em single loop, que executam tudo de forma sequencial. O sistema escolhido para esse projeto foi o FreeRTOS, um dos mais utilizados do mundo, e que possui c\u00f3digo aberto.<\/p>\n\n\n\n<p>De forma resumida, a programa\u00e7\u00e3o com o FreeRTOS baseia-se em tr\u00eas conceitos principais: A divis\u00e3o do programa em tarefas (tasks), onde cada tarefa representa uma funcionalidade do sistema e \u00e9 tratada como uma rotina isolada; O uso de filas (queues) como meio de comunica\u00e7\u00e3o entre as tarefas, caso seja necess\u00e1ria a intera\u00e7\u00e3o entre elas; E, finalmente, a cria\u00e7\u00e3o de sem\u00e1foros (semaphores), para que diferentes tarefas possam compartilhar recursos de hardware de forma segura.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Programa\u00e7\u00e3o<\/h3>\n\n\n\n<p>Partimos agora para a programa\u00e7\u00e3o de nosso software. Inicialmente \u00e9 definida a frequ\u00eancia de trabalho e s\u00e3o declaradas as bibliotecas padr\u00e3o dos microcontroladores AVR e do FreeRTOS:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">#define F_CPU 16000000UL\n\n#include &lt;avr\/io.h>\n#include \"FreeRTOS.h\"\n#include \"task.h\"\n#include \"semphr.h\"\n#include \"queue.h\"<\/pre>\n\n\n\n<p>Logo ap\u00f3s, \u00e9 feito o mapeamento de hardware e s\u00e3o declaradas as tarefas, as filas e o sem\u00e1foro que ser\u00e3o utilizados:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/------Mapeamento de Hardware------\n\n#define PWM      PD3  \/\/Sinal que controla a velocidade do motor\t\t\n#define sentido  PD5  \/\/Sinal que controla o sentido de giro do motor\n#define controle PC0  \/\/Sinal de leitura do potenciometro de controle\n#define motor    PC1  \/\/Sinal de leitura do potenciometro do motor\n#define led      PC5  \/\/Led para o blink\n\n#define dutyCicle OCR2B \/\/Registrador que controla o duty cicle do PWM\t\t\n\n\/\/------Declara\u00e7\u00e3o das tarefas------\n\nvoid TaskSetpoint(void *pvParameters);\nvoid TaskPosicaoAtual(void *pvParameters);\nvoid TaskPID(void *pvParameters);\nvoid TaskBlink(void *pvParameters);\n\n\/\/------Declara\u00e7\u00e3o das filas------\n\nQueueHandle_t QueueSetpoint;\nQueueHandle_t QueuePosicaoAtual;\n\n\/\/------Declara\u00e7\u00e3o do sem\u00e1foro------\n\nSemaphoreHandle_t SemaforoADC; <\/pre>\n\n\n\n<p>Ao todo teremos 4 tarefas:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>TaskSetpoint &#8211; Nessa tarefa ser\u00e1 feita a leitura do potenci\u00f4metro que controla o setpoint.<\/li><li>TaskPosicaoAtual &#8211; Tarefa respons\u00e1vel por ler o potenci\u00f4metro do servomotor.<\/li><li>TaskPID &#8211; Tarefa na qual ser\u00e1 realizado o controle PID efetivamente, com o c\u00e1lculo dos par\u00e2metro e da sa\u00edda.<\/li><li>TaskBlink &#8211; Nosso projeto ter\u00e1 um led piscando \u00e0 uma frequ\u00eancia constante, a fim de comprovar que o c\u00f3digo est\u00e1 em execu\u00e7\u00e3o.<\/li><\/ul>\n\n\n\n<p>2 filas:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>QueueSetpoint &#8211; Meio de comunica\u00e7\u00e3o pelo qual a tarefa TaskSetpoint enviar\u00e1 o valor do setpoint para a tarefa TaskPID.<\/li><li>QueuePosicaoAtual &#8211; Faz a comunica\u00e7\u00e3o entre TaskPosicaoAtual eTaskPID.<\/li><\/ul>\n\n\n\n<p>1 sem\u00e1foro:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Sem\u00e1foroADC &#8211; Tem a fun\u00e7\u00e3o de limitar o acesso ao conversor AD do microcontrolador, evitando que duas tarefas tentem fazer uso dele ao mesmo tempo, o que iria corromper os dados e comprometer todo o funcionamento do sistema.<\/li><\/ul>\n\n\n\n<p>Em sequ\u00eancia, \u00e9 declarada a fun\u00e7\u00e3o principal do programa e s\u00e3o feitas as configura\u00e7\u00f5es iniciais<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">int main(void)\n{\n    DDRD |= (1&lt;&lt;PWM) | (1&lt;&lt;sentido); \/\/Bits de controle do \n                                     \/\/motor como sa\u00eddas\n\t\n    DDRC |= (1&lt;&lt;led);    \/\/Led como sa\u00edda\n\t\n    ADMUX = 0b01000000;  \/\/Habilita tens\u00e3o de refer\u00eancia interna \n                         \/\/e seleciona o ADC0 (setpoint) \n    ADCSRA = 0b10000111; \/\/Habilita o conversor AD e \n                         \/\/configura o prescaler para 128<\/pre>\n\n\n\n<p>Conforme o datasheet do microcontrolador ATmega328P (cuja leitura \u00e9 indispens\u00e1vel), os registradores DDR definem se determinada porta ser\u00e1 de entrada ou sa\u00edda (1 = sa\u00edda), os registradores PORT s\u00e3o respons\u00e1veis por escrever HIGH ou LOW nas sa\u00eddas. J\u00e1 os registradores ADMUX e ADCSRA dizem respeito \u00e0 configura\u00e7\u00e3o do conversor Anal\u00f3gico-Digital, necess\u00e1rio para a leitura dos potenci\u00f4metros presentes no sistema.<\/p>\n\n\n\n<p>Ainda na fun\u00e7\u00e3o principal s\u00e3o criados o sem\u00e1foro, as filas e as tarefas:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\t\/\/------Cria\u00e7\u00e3o do sem\u00e1foro------\n\t\n\tSemaforoADC = xSemaphoreCreateMutex();\n\t\n\t\/\/------Cria\u00e7\u00e3o das filas------\n\t\n\tQueueSetpoint = xQueueCreate(1, sizeof(double));\n\tQueuePosicaoAtual = xQueueCreate(1, sizeof(double));\n\t\n\t\/\/------Cria\u00e7\u00e3o das tarefas------\n\t\n\txTaskCreate(\n\t\tTaskSetpoint            \/\/Tarefa a ser configurada\n\t\t, \"Leitura do Setpoint\" \/\/Nome (para debug)\n\t\t, 128                   \/\/Stack reservada para a tarefa\n\t\t, NULL                  \/\/Par\u00e2metros passados para a tarefa\n\t\t, 0                     \/\/Prioridade da tarefa\n\t\t, NULL                  \/\/Handle da tarefa\n\t);\n\t\t\n\txTaskCreate(\n\t\tTaskPosicaoAtual\n\t\t, \"Leitura da posi\u00e7\u00e3o atual do motor\"\n\t\t, 128\n\t\t, NULL\n\t\t, 0\n\t\t, NULL\n\t);\n\t\t\n\txTaskCreate(\n\t\tTaskBlink\n\t\t, \"Blink led\"\n\t\t, 128\n\t\t, NULL\n\t\t, 1\n\t\t, NULL\n\t);\n\t\n\txTaskCreate(\n\t\tTaskPID\n\t\t, \"PID\"\n\t\t, 200\n\t\t, NULL\n\t\t, 0\n\t\t, NULL\n\t);\n\t\t\n\tvTaskStartScheduler(); \/\/ Inicia o RTOS\n\t\n    while (1) \n    {\n\t\t  \/\/Vazio, tudo \u00e9 executado nas tarefas\n    }\n}<\/pre>\n\n\n\n<p>O sem\u00e1foro ser\u00e1 do tipo MUTEX (exclus\u00e3o m\u00fatua) e as filas de apenas um elemento do tamanho de uma vari\u00e1vel do tipo double. \u00c9 importante ressaltar que, quando se utiliza da programa\u00e7\u00e3o com RTOS, o loop infinito deve permanecer vazio, j\u00e1 que todos os comandos e instru\u00e7\u00f5es s\u00e3o realizados dentro das tarefas.<\/p>\n\n\n\n<p>Terminada a fun\u00e7\u00e3o main, podemos finalmente focar na programa\u00e7\u00e3o das tarefas em si, vejamos:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void TaskBlink(void *pvParameters)\n{\n   (void)pvParameters;\n\t\n   while(1)\n   {\n       PORTC ^= (1&lt;&lt;led);                  \/\/Inverte o estado do led\n       vTaskDelay(500\/portTICK_PERIOD_MS); \/\/Aguarda 500 ms\n   }\n}<\/pre>\n\n\n\n<p>Come\u00e7ando pela tarefa mais simples, o blink do led, essa tarefa simplesmente inverte o estado do led (se est\u00e1 aceso, apaga, e se est\u00e1 apagado, acende) a cada 500 milissegundos.<\/p>\n\n\n\n<p>Na sequ\u00eancia, temos a tarefa respons\u00e1vel pelo controle do setpoint. Ela \u00e9 respons\u00e1vel por fazer a leitura do potenci\u00f4metro de controle, atrav\u00e9s do conversor AD, veja:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void TaskSetpoint(void *pvParameters)\n{\n   (void)pvParameters;\n\t\n   double setpointADC;\n\t\n   while(1)\n   {\n       \/\/Solicita o sem\u00e1foro (aguarda at\u00e9 conseguir)\n\t    xSemaphoreTake(SemaforoADC, portMAX_DELAY);\n\n       \/\/Seleciona o ADC0 (potenci\u00f4metro de controle)\n\t    ADMUX &amp;= ~(1&lt;&lt;MUX3) &amp; ~(1&lt;&lt;MUX1) &amp; ~(1&lt;&lt;MUX2) &amp; ~(1&lt;&lt;MUX0);\n\n       \/\/Inicia a convers\u00e3o\n\t    ADCSRA |= (1&lt;&lt;ADSC);\n\n       \/\/Espera a convers\u00e3o terminar\n\t    while(ADCSRA &amp; (1&lt;&lt;ADSC));\n\n       \/\/Atribui o valor lido \u00e0 vari\u00e1vel\n\t    setpointADC = ADC;\n\n       \/\/Escreve o valor do setpoint na fila\n\t    xQueueOverwrite(QueueSetpoint, &amp;setpointADC);\n\n       \/\/Libera o sem\u00e1foro\n\t    xSemaphoreGive(SemaforoADC);\n   }\n}<\/pre>\n\n\n\n<p>Observe que o primeira instru\u00e7\u00e3o dentro da tarefa \u00e9 aguardar at\u00e9 que o sem\u00e1foro esteja dispon\u00edvel, s\u00f3 depois \u00e9 feita a convers\u00e3o AD de fato. O valor da leitura \u00e9 escrito na fila e ent\u00e3o o sem\u00e1foro \u00e9 liberado. <\/p>\n\n\n\n<p>Em seguida, est\u00e1 a tarefa que diz respeito a leitura da posi\u00e7\u00e3o atual do motor que \u00e9 muito semelhante a tarefa de controle do setpoint, as \u00fanicas altera\u00e7\u00f5es s\u00e3o na sele\u00e7\u00e3o do canal anal\u00f3gico a ser lido e na fila em que ser\u00e3o escritos os dados, observe:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void TaskPosicaoAtual(void *pvParameters)\n{\n   (void)pvParameters;\n\t\n   double posicaoAtualADC;\n\t\n   while(1)\n   {\n       \/\/Solicita o sem\u00e1foro at\u00e9 conseguir\n\t    xSemaphoreTake(SemaforoADC, portMAX_DELAY);\n\t\n       \/\/Seleciona o ADC1 (posi\u00e7\u00e3o atual do motor)\t\n\t    ADMUX |= (1&lt;&lt;MUX0);\t\t\t\t\t\t\t\t\t\t\n\t    ADMUX &amp;= ~(1&lt;&lt;MUX3) &amp; ~(1&lt;&lt;MUX1) &amp; ~(1&lt;&lt;MUX2);\n\n       \/\/Inicia a convers\u00e3o\n\t    ADCSRA |= (1&lt;&lt;ADSC);\n\n       \/\/Espera a convers\u00e3o terminar\t\t\t\t\t\t\t\t\n\t    while (ADCSRA &amp; (1&lt;&lt;ADSC));\n\t\n       \/\/Atribui o valor lido \u00e0 vari\u00e1vel\t\t\t\t\t\t\t\n\t    posicaoAtualADC = ADC;\n\n       \/\/Escreve o valor da posi\u00e7\u00e3o atual na fila\n\t    xQueueOverwrite(QueuePosicaoAtual, &amp;posicaoAtualADC);\n\n       \/\/Libera o sem\u00e1foro\n       xSemaphoreGive(SemaforoADC);\t\t\t\t\t\t\t\n   }\n}<\/pre>\n\n\n\n<p>Finalmente, chegamos na tarefa do PID. Acompanhe o c\u00f3digo e em seguida sua explica\u00e7\u00e3o:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void TaskPID(void *pvParameters)\n{\n   (void)pvParameters;\n\t\n   \/\/Configura\u00e7\u00e3o do PWM\n   TCCR2A = 0b00100011; \/\/Fast PWM, n\u00e3o inversor, sa\u00edda OC2B (PD3)\n   TCCR2B = 5;\t\t    \/\/Aproximadamente 500Hz\n\t\n   \/\/Declara\u00e7\u00e3o e inicializa\u00e7\u00e3o das vari\u00e1veis necess\u00e1rias\n   double   setpoint,\n            posicaoAtual,\n\t         posicaoAnterior = 512,\n\t         erro = 0,\n\t         kp = 1.0,\n\t         ki = 0.001,\n\t         kd = 0.1,\n\t         proporcional = 0,\n\t         integral = 0,\n\t         derivativo = 0,\n\t         PID = 0;\n\t\n   while(1)\n   {\n       \/\/L\u00ea os dados da fila\n       xQueueReceive(QueueSetpoint, &amp;setpoint, portMAX_DELAY);\n       xQueueReceive(QueuePosicaoAtual, &amp;posicaoAtual, portMAX_DELAY);\n\n       \/\/C\u00e1lculo do erro\n       erro = setpoint - posicaoAtual;\n\n       \/\/C\u00e1lculo dos par\u00e2metros P, I e D\n       proporcional = kp*erro;\n       integral += ki*erro;\n       derivativo = kd*(posicaoAtual - posicaoAnterior);\n       PID = proporcional + integral + derivativo;\n \n       \/\/Teste para saber para qual lado o motor deve girar\n       if(erro > 0)\n       {\t\t\t\t\n            PORTD &amp;= ~(1&lt;&lt;sentido);\t\/\/Ajusta o sentido de rota\u00e7\u00e3o\n            TCCR2A &amp;= ~(1&lt;&lt;COM2B0);\t\/\/Desabilita o modo inversor\t\t\t\n       }\n       else\n       {\n            PORTD |= (1&lt;&lt;sentido);\t\/\/Ajusta o sentido de rota\u00e7\u00e3o\n            TCCR2A |= (1&lt;&lt;COM2B0);\t\/\/Habilita o modo inversor\n       }\n\n       posicaoAnterior = posicaoAtual; \/\/Atualiza a posi\u00e7\u00e3o anterior\n       dutyCicle = PID\/4; \/\/Atualiza o ciclo ativo do PWM\n   }\n}<\/pre>\n\n\n\n<p>Inicialmente \u00e9 feita a configura\u00e7\u00e3o do PWM, selecionando a porta que ser\u00e1 utilizada e, a princ\u00edpio, utilizando o modo n\u00e3o inversor. Em seguida s\u00e3o declaradas e inicializadas as vari\u00e1veis que ser\u00e3o utilizadas para implementa\u00e7\u00e3o do PID. Em um primeiro momento \u00e9 recomendado realizar a implementa\u00e7\u00e3o apenas da parcela proporcional do controlador, ou seja, com Ki e Kd iguais a zero e, \u00e0 medida que testes s\u00e3o realizados, ir variando esses par\u00e2metros em busca da melhor resposta do sistema na pr\u00e1tica.<\/p>\n\n\n\n<p>No loop infinito, o programa l\u00ea os dados das filas e ent\u00e3o s\u00e3o realizados os calculos do erro e dos par\u00e2metros P, I e D, conforme as f\u00f3rmulas apresentadas anteriormente nesse artigo. Feito isso, \u00e9 necess\u00e1rio realizar um teste para saber para qual lado o motor deve rotacionar e ent\u00e3o tomar algumas decis\u00f5es, vejamos:<\/p>\n\n\n\n<p>O conversor AD do ATmega328P possui 10 bits de resolu\u00e7\u00e3o, isso implica que os valores de setpoint e da posi\u00e7\u00e3o atual (lidos pelos potenci\u00f4metros) podem assumir valores de 0 a 1023. Portanto, sendo a posi\u00e7\u00e3o 0 o fim de curso no sentido anti-hor\u00e1rio e 1023 a posi\u00e7\u00e3o de fim de curso no sentido hor\u00e1rio, sempre que o valor do setpoint for maior que o valor da posi\u00e7\u00e3o atual (erro &gt; 0) o motor dever\u00e1 rotacionar no sentido hor\u00e1rio, caso contr\u00e1rio (erro &lt; 0) dever\u00e1 girar no sentido anti-hor\u00e1rio. Para implementar isso em nosso c\u00f3digo, utilizaremos uma das sa\u00eddas da ponte H (definida no programa como &#8220;sentido&#8221;) para controlar o sentido de rota\u00e7\u00e3o, e a outra sa\u00edda (definida como &#8220;PWM&#8221;) para controlar a velocidade com que o motor gira, dependendo da dist\u00e2ncia em que a posi\u00e7\u00e3o atual est\u00e1 do setpoint. No fim das contas, o que se tem \u00e9 o seguinte:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"201\" src=\"https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/funcionamento-top-1024x201.png\" alt=\"\" class=\"wp-image-1373\" srcset=\"https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/funcionamento-top-1024x201.png 1024w, https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/funcionamento-top-300x59.png 300w, https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/funcionamento-top-768x150.png 768w, https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/funcionamento-top-1536x301.png 1536w, https:\/\/visiorob.com.br\/wp-content\/uploads\/2020\/11\/funcionamento-top.png 1868w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Modos de funcionamento do servomotor<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Quando o erro \u00e9 menor que zero, o PWM \u00e9 configurado para trabalhar no modo inversor, ou seja, o valor atribu\u00eddo para o duty cicle diz respeito ao tempo que o sinal permanecer\u00e1 em n\u00edvel baixo. J\u00e1 quando o erro \u00e9 maior que zero, o PWM trabalha no modo n\u00e3o inversor, com o duty cicle sendo proporcional ao per\u00edodo em n\u00edvel alto do sinal PWM. Em ambos os casos, quando o duty cicle (P+I+D) for igual a zero o motor ficar\u00e1 parado, ja que ter\u00e1 a mesma tens\u00e3o em seus dois terminais.<\/p>\n\n\n\n<p>A \u00faltima a\u00e7\u00e3o a se fazer nessa tarefa \u00e9 a atualiza\u00e7\u00e3o da vari\u00e1vel posi\u00e7\u00e3o anterior, que passa a ser o valor da \u00faltima leitura, e a atribui\u00e7\u00e3o do duty cicle sendo a vari\u00e1vel PID dividida por 4, para efeitos de normaliza\u00e7\u00e3o, j\u00e1 que ela possui 10 bits e o duty cicle possui apenas 8 bits.<\/p>\n\n\n\n<p>Com isso, se d\u00e1 por finalizado o nosso projeto de controle de servomotor utilizando o algoritmo PID, arduino e o sistema operacional de tempo real FreeRTOS. <a href=\"https:\/\/drive.google.com\/file\/d\/1clrfm4ECEHxTFvyRD5-rzYKUBgN5ysF5\/view?usp=sharing\" target=\"_blank\" rel=\"noreferrer noopener\">Clicando aqui<\/a>, voc\u00ea pode fazer o download dos arquivos desenvolvidos ao longo desse artigo, incluindo um arquivo de simula\u00e7\u00e3o.<\/p>\n\n\n\n<p>Obrigado pela aten\u00e7\u00e3o!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Refer\u00eancias<\/h2>\n\n\n\n<p><strong>Explicando a Teoria PID<\/strong>. Dispon\u00edvel em: &lt;https:\/\/www.ni.com\/pt-br\/innovations\/white-papers\/06\/pid-theory-explained.html&gt;. Acesso em: 18 nov. 2020.<\/p>\n\n\n\n<p><strong>O que \u00e9 Servo motor e como funciona?<\/strong>&nbsp;Dispon\u00edvel em: &lt;https:\/\/www.mundodaeletrica.com.br\/o-que-e-servo-motor-e-como-funciona\/&gt;. Acesso em: 19 nov. 2020.<\/p>\n\n\n\n<p><strong>Controlador PID digital: Uma modelagem pr\u00e1tica para microcontroladores<\/strong>. Dispon\u00edvel em: &lt;https:\/\/www.embarcados.com.br\/controlador-pid-digital-parte-1\/&gt;. Acesso em: 19 nov. 2020.<\/p>\n\n\n\n<p><strong>ATmega328P 8-bit AVR Microcontroller with 32K Bytes In-System Programmable Flash DATASHEET<\/strong>. Dispon\u00edvel em: &lt;https:\/\/ww1.microchip.com\/downloads\/en\/DeviceDoc\/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&gt;.<\/p>\n\n\n\n<p><strong>Arduino &#8211; PinMapping<\/strong>. Dispon\u00edvel em: &lt;https:\/\/www.arduino.cc\/en\/Hacking\/PinMapping&gt;. Acesso em: 23 nov. 2020.<\/p>\n\n\n\n<p><strong>Curso de Eletr\u00f4nica &#8211; O que \u00e9 PWM &#8211; Pulse Width Modulation<\/strong>. Dispon\u00edvel em: &lt;http:\/\/www.bosontreinamentos.com.br\/eletronica\/curso-de-eletronica\/curso-de-eletronica-o-que-e-pwm-pulse-width-modulation\/&gt;. Acesso em: 25 nov. 2020.<\/p>\n\n\n\n<p>\u200c<\/p>\n\n\n\n<p>\u200c<\/p>\n\n\n\n<p>\u200c<\/p>\n\n\n\n<p>\u200c<\/p>\n\n\n\n<p>\u200c<\/p>\n\n\n\n<p>\u200c<\/p>\n","protected":false},"excerpt":{"rendered":"<div class=\"mh-excerpt\"><p>Controle PID O PID (Proporcional-Integral-Derivativo) \u00e9 atualmente, segundo a National Instruments, o algoritmo de controle mais utilizado na ind\u00fastria. Isso se deve tanto ao seu <a class=\"mh-excerpt-more\" href=\"https:\/\/visiorob.com.br\/index.php\/2020\/11\/26\/implementacao-de-um-controle-pid-utilizando-arduino-e-freertos\/\" title=\"Implementa\u00e7\u00e3o de um controle PID utilizando Arduino e FreeRTOS\">[&#8230;]<\/a><\/p>\n<\/div>","protected":false},"author":22,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[42],"tags":[34,45,46,49,47,48,50],"class_list":["post-1292","post","type-post","status-publish","format-standard","hentry","category-programacao","tag-arduino","tag-freertos","tag-pid","tag-ponte-h","tag-programacao-em-tempo-real","tag-pwm","tag-servomotor"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/visiorob.com.br\/index.php\/wp-json\/wp\/v2\/posts\/1292","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/visiorob.com.br\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/visiorob.com.br\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/visiorob.com.br\/index.php\/wp-json\/wp\/v2\/users\/22"}],"replies":[{"embeddable":true,"href":"https:\/\/visiorob.com.br\/index.php\/wp-json\/wp\/v2\/comments?post=1292"}],"version-history":[{"count":79,"href":"https:\/\/visiorob.com.br\/index.php\/wp-json\/wp\/v2\/posts\/1292\/revisions"}],"predecessor-version":[{"id":1459,"href":"https:\/\/visiorob.com.br\/index.php\/wp-json\/wp\/v2\/posts\/1292\/revisions\/1459"}],"wp:attachment":[{"href":"https:\/\/visiorob.com.br\/index.php\/wp-json\/wp\/v2\/media?parent=1292"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/visiorob.com.br\/index.php\/wp-json\/wp\/v2\/categories?post=1292"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/visiorob.com.br\/index.php\/wp-json\/wp\/v2\/tags?post=1292"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}