Problemas para medir o rpm de um motor com encoder.

Oi, pessoal, tudo bem?

Faz um bom tempo que eu estou tentando contar pulsos com um encoder aqui em casa por meio de um motor 12V e um arduino UNO... O Encoder contabiliza e mostra perfeitamente no monitor serial quando o motor é girado manualmente de forma devagar, porém, há um problema: Ocorre quando o motor está girando, pois o mesmo possui 64 furos, mas o Encoder leva cerca de 7 SEGUNDOS para detectar  64 furos,  totalizando assim, uma volta... Obviamente o motor já deveria ter dados muito mais voltas neste tempo, mas o encoder aparentemente não é capaz de informar isso de forma rápida e instantânea para o arduino.
Com isso, vem a dúvida, onde está o problema?


* Na programação?;

*No Encoder?;

*No arduino?;

*Na rotação do motor (já alterei muitas vezes com um transistor e as variações são mínimas);

*No circuito ligado ao ENCODER?

Eu já experimentei muitas coisas diferentes, testei vários valores de resistores para o Led Infravermelho (entre 180R e 220R, pois a partir de 330R ele não detecta mais);

Testei diversos valores de resistor para o coletor do fototransistor;

Alterei a velocidade do motor diversas vezes, mas o encoder continua com dificuldade para ler em tempo real.

Em anexo eu estou deixando o circuito e a programação do encoder. Obs.: Eu também utilizei um potenciômetro de 10K para regular o sinal PWM na base do transistor BC548, no caso, o A0 é referente a entrada analógica do arduino, o port 3 é o sinal PWM direcionado a base do transistor. Também utilizei um resistor e um Led ligados em série no port 13 para representar o estado do encoder (se ele está detectando um furo ou não)

Programação:



const int encoderIn = 8;                // Entrada para o arduino interpretar quando um furo for detectado
const int statusLED = 13;            // Saída para o LED de teste
int motorDC = 3;                         // Saída PWM no port 3 para o controle do motor DC 12V 
unsigned int analogPin = A0;       // Declara o analogPin como variável do tipo int com valores positivos

float valpotenciometro = 0;           //Variável para armazenar o valor lido do potenciometro
int detectState=0;                       //Variável para ler o Status do Encoder
int ContFuros=0;                        //Variável para contar o número de Furos detectados pelo Encoder
int NumeroVoltas=0;                   //Variável para informar quantas voltas foram dadas
float PosicaoGraus=0;                //Variável para informar a posição em graus do motor 


void setup()
{
     Serial.begin(9600);
     pinMode(analogPin, INPUT); //Definindo o sinal enviado pelo potenciômetro (pino A0) como entrada
     pinMode(motorDC, OUTPUT); //Definindo o sinal PWM (pino 3) como saída
     pinMode(encoderIn, INPUT); //Definindo o sinal do encoder (pino 8) como entrada
     pinMode(statusLED, OUTPUT); //Definindo o LED de status do encoder (pino 13) como saída
}
void loop()
{
     valpotenciometro = analogRead(analogPin); //relacionando o valor do potenciômetro com do PWM
     int pwm = map(valpotenciometro, 0, 1023, 0, 255);
     analogWrite(motorDC, pwm);
     Serial.println(pwm);

     detectState=digitalRead(encoderIn);
     if (detectState == HIGH) //Se o encoder detectar o furo, o mesmo irá...
     {
          digitalWrite(statusLED, HIGH); //Led de status acende
          ContFuros=ContFuros + 1; //O número de Furos contados será acrescentado em uma unidade
         PosicaoGraus=PosicaoGraus + 5.538; //A nova posição do disco será acrescida de 5.538 graus
         while(digitalRead(encoderIn)==1);
         {
               if(PosicaoGraus>=360)
               {
                     PosicaoGraus = 0;
               }
      }
}
      else
      {
               digitalWrite(statusLED, LOW); //Deixa o Led de status em nível baixo.
      }
Serial.print("\tNúmero de voltas:");
Serial.print(NumeroVoltas); //Mostra o número de voltas que infelizmente cresce a cada 7 segundos
Serial.print("\tpulsos: ");
Serial.print(ContFuros); //Mostra o número de furos contados que estranhamente demoram para ser contabilizados.
Serial.print("\tPosição em graus: ");
Serial.println(PosicaoGraus); //"Posição em graus do motor"


if(ContFuros>64)                               //Se a quantidade de furos for maior do que 64......
{
NumeroVoltas = NumeroVoltas +1;        
ContFuros = 0;                                 //Então o motor deu uma volta completa    
}


}

Exibições: 2692

Anexos

Responder esta

Respostas a este tópico

Uma observação: O problema não ocorre se eu girar o motor manualmente e de forma devagar.

Bom dia Lucas; Já experimento colocar entre o Arduíno e o sensor um LM358 para amplificar o sinal do sensor.Atte;JSMB

Fui ao centro e decidi comprar para testar, vlw pela dica!
Eu comprei o LM358P. Depois de chegar em casa eu vi que existe o LM358N também, você saberia dizer se tem muita diferença?

Assim que você quer dizer? 

Eu tentei esta ligação, acho que houve um pouco de diferença sim, mas ainda está longe do necessário...

Eu experimentei mudar o valor do Serial.begin e parece que tive uma mudança consideravelmente grande, vou fazer mais alguns testes para saber se está certo mesmo ou não.

Olá Lucas.

 

      Estou publicando uma implementação do seu Sistema, usando um Arduino UNO, a qual funcionou, inclusive em simulação no Proteus.

      Mas antes, gostaria de esclarecer o motivo pelo qual vc não conseguiu fazê-lo funcionar como esperava.

 

      Ocorre Lucas, que há vários problemas conceituais no seu Sistema, tanto para detecção dos Pulsos vindos do Sensor Ótico, como em relação ao tratamento dos dados.

 

      Sobre a detecção dos Pulsos, vc precisa detectar um “edge”, ou seja, uma passagem do sinal de um nível lógico para o outro nível lógico. No seu caso, como os Pulsos estão vindo do Sensor, quando o feixe do LED emissor está interrompido, o sinal no coletor do Transistor do Sensor está em nível1” (ou “HIGH”, se preferir). Quando o furo do Disco (acoplado ao eixo do Motor) entra na frente do sensor, o feixe atinge a base do Transistor e o sinal no coletor do mesmo vai a nível0” (ou “LOW”, se preferir). Logo que o furo “sai” da frente do Sensor, então a saída volta a nível1”. Então vc precisa detectar um “edge”. Pode ser por exemplo a passagem do sinal do Sensor de “1” pra “0”. Mas poderia ser também a passagem de “0” para “1”. Não faria diferença, o importante é detectar o “edge”.

 

      Mas embora a detecção do “edge” não esteja sendo feita de forma adequada, ela não é o real problema no seu Sistema. O real problema, é o tempo de processamento dos dados. Este tempo, entenda que inclui quaisquer análises que vc faça nos dados, ou cálculos com estes dados, ou exibição destes dados. Vou explicar o problema:  esse tempo de processamento dos dados, é significativo no seu Sistema, ou seja, não é um tempo a ser desprezado. Porém vc quer fazer o processamento destes dados, a cada Pulso do Sensor detectado. Mas o intervalo de tempo entre um Pulso e outro irá variar. Quando vc gira o Motor “na mão”, claro este intervalo é bem mais longo. Mas quando vc liga o Motor pra valer, o intervalo será bem menor (quanto maior a velocidade do Motor, menor o intervalo entre os pulsos). Se o intervalo for menor ou muito próximo do tempo que vc gasta fazendo o processamento dos dados, obviamente que a coisa toda vai “emperrar”, pois ou vc irá “perder” Pulsos, ou não vai conseguir fazer o processamento dos dados para todos os Pulsos contados. No seu caso, vai acontecer a primeira opção: vc vai “perder” Pulsos, pois enquanto vc está fazendo o processamento dos dados, outros pulsos chegam e seu Sistema não consegue detectá-los na hora que eles ocorrem.

 

      Para resolver isso, existem duas coisas que vc precisa fazer:

 

      1) deve detectar os Pulsos de forma mais eficiente. Para isto deve usar Interrupções, que asseguram que nenhum pulso deixará de ser detectado.

 

      2) deve implementar um mecanismo de taxa de exibição dos dados. Veja, o que mais compromete seu Sistema, é a exibição de dados a cada Pulso, pois esta exibição normalmente é um processo “lento” se comparado a outras tarefas no código sendo executado pelo Arduino. Vc pode tentar exibir os dados a cada Pulso detectado, mas isto terá um limite máximo para a velocidade do Motor: vai chegar uma velocidade que a coisa irá enroscar. Se isso ocorrer (se “enroscar”), nem todos os pulsos serão exibidos e poderá após algum tempo (pode ser vários segundos ou até minutos) ocorrer “overflow” no Buffer da Serial do Arduino, pois este Buffer estará entupido de dados dos “Serial.print” (que chegam numa taxa muito maior que o Arduino consegue enviá-los pela Serial). Neste caso (se ocorrer o “overflow”), vc começará a ver prints “quebrados” no texto do Terminal do Arduino.

 

 

      Assim para que seu Sistema funcione pelo menos conseguindo contabilizar todos os Pulsos do Sensor, use Interrupção para detectar os Pulsos. No Arduino UNO, vc pode usar ou o pino 2 ou o 3. Como o “3” vc já está usando para o PWM do Motor, então use o pino2” para ligar o sinal do Sensor Ótico. Veja no circuito da simulação no Proteus, onde fiz isso, ligando no pino2”, o sinal do Sensor Ótico (figura a seguir):

 

(clique na figura para "zoom")

 

 

      Já para melhorar o tempo de processamento, aumente a velocidade da Serial do Arduino. Isto não reduz o tempo gasto na preparação dos dados “printados”, mas reduz significativamente o tempo de transmissão desses dados, o que é importante, para diminuir a possibilidade do “overflow” do Buffer da Serial. O ideal é usar a taxa de 115200, que foi a que eu usei na implementação. Não se esqueça de mudar a taxa na janela do Terminal do Arduino também, para evitar que os dados recebidos no computador fiquem “doidos”.

 

      Veja o resultado da simulação no Proteus:

 

(clique na figura para "zoom")

 

 

      Observe que também estou medindo o “tempo de preparação” da impressão na Serial (no Terminal é o valor “tPrint”, em mili-segundos). Inicialmente, este tempo é menor (pois o Buffer ainda tem menos dados), mas logo aumenta e estabiliza em torno de 6 a 7 mili-segundos. (nota: o tempo de transmissão não está incluído no “tPrint”, e é calculado de outra forma, mas ainda assim simples de calcular, e se vc não souber como, me pergunte que eu explico)

 

      Note também que estou usando uma taxa de pulsos do Sensor, que equivale a uma rotação relativamente baixa de aproximadamente 187 RPM. No circuito está explicado como ajustar a taxa de pulsos para ser equivalente à velocidade desejada, em RPM.

 

      Aumentando a taxa de Pulsos do Sensor, irá piorar a possibilidade de vc perder algum pulso em termos de processamento dos dados. Mas devido ao uso de Interrupção, os Pulsos serão sempre todos detectados. Vc poderá observar isso no Terminal do Arduino, pois mesmo que não consiga processar os dados a tempo, ainda assim a contagem de pulsos e a posição em graus estarão sempre corretos.

 

      Se fizer a simulação no Proteus, irá perceber que ela será um tanto “pesada”, pois até mesmo para o Simulador, há restrições de tempo de processamento na exibição dos dados, o que pode fazer eventualmente que ocorra o “overflow” do Buffer da Serial (travando o Terminal ou provocando “quebra” dos dados exibidos). Até mesmo falha na simulação pode ocorrer, se o Simulador para compensar tentar diminuir demais o “dt” (diferencial de tempo da Simulação), este “dt” chegar a um valor tão pequeno que o Simulador “não aguenta”.

  

      Minha sugestão:  use a técnica que utilizei na implementação que estou publicando aqui. Mas também mude a taxa de exibição de dados, evitando exibi-los para todo Pulso detectado. Claro isso vai exigir que vc analise como os dados são úteis pra vc, e como eles devem ser realmente exibidos para que tenham algum significado.

 

      Segue a implementação, incluindo arquivos de simulação:  Encoder_Lucas_02.zip

  

      Nota:  na exibição dos dados no Terminal do Arduino, retirei os caracteres de “TAB” e também a acentuação, pois eles estavam aparecendo “esquisitos” na tela do Terminal. E não se esqueça da velocidade da Serial. Veja também que usei um "toggle" para o LED sinalizador, de forma que se vc medir a frequência dos pulsos no LED, ela será exatamente a metade dos Pulsos enviados pelo Sensor Ótico.

      Caso tenha alguma dúvida, não deixe de perguntar.

  

      Abrçs,

 

      Elcids

Boa tarde, Elcids.

Poxa, eu nem sei o que dizer, muito obrigado por toda esta dedicação com a sua resposta, imagino que isso tenha tirado um bom tempo seu...
Acredito ter entendido os pontos falhos do meu sistema. Um termo ou outro eu desconheço, mas irei estudá-los um pouco mais, da mesma forma para alguns comandos do seu código que me impressionaram muito.
Irei estudar calmamente e, em seguida, fazer os testes. Se eu acabar enroscando com alguma dúvida cruel eu pergunto então rsrsrs.
Muito obrigado mesmo Elcids!

Olá Lucas.

 

      Encontrei um pequeno deslize no código que publiquei no post anterior. Se vc observar na figura que mostra o Monitor Serial na simulação, vai perceber que a Contagem de Voltas já inicia em 1. Isto ocorre porque enquanto o “setup()” do Arduino ainda está executando, os pulsos do Encoder já estão chegando, o que provoca a execução da ISR correspondente (a “ISR_Encoder”), e há tempo suficiente para uma volta completa, uma vez que coloquei um “delay(2000)” no final do setup.

      Assim, quando o “loop()” inicia, já foi contabilizado uma volta. Vc poderia perguntar: mas porque a Contagem de Pulsos começa no zero?  Ocorre que eu zerei esta contagem logo antes de terminar o setup, mas não fiz isso com a contagem de voltas. Então vc poderia perguntar também: não bastaria apenas zerar as duas contagens no final do setup? Sim, isto poderia ser feito, porém para isto vc precisa obrigatoriamente desabilitar as IRQs (Interrupções), zerar as duas contagens, e então reabilitar as IRQs (claro, senão elas serão ignoradas pelo Processador do Arduino). Para não precisar fazer isso, veja que criei uma “Flag” que habilita ou desabilita o Monitoramento. E dentro da ISR ista Flag é verificada, para ver se deve ou não contabilizar os Pulsos do Encoder. E logo no início do loop(), a Flag é setada (com o valor “true”), habilitando assim o monitoramento e garantindo que ele virtualmente “comece do zero”.

      Veja este trecho no loop, na figura a seguir:

 (clique na figura para "zoom")

 

 

      Vc poderia também perguntar: mas isto não consome tempo no loop? Sim consome, porém este tempo é de menos de 1us (1 micro segundo), o que é desprezível frente a todo o restante do processamento.

       Veja o resultado da simulação no Proteus, e observe no Monitor Serial que agora a contagem de voltas inicia no zero:

 (clique na figura para "zoom")

 

 

      Aproveito para mencionar uma coisa que esqueci no primeiro post. Observe que no Proteus não encontrei um Motor que pudesse ser associado a um Disco com Furos, o qual acionaria o Encoder Ótico. Por isso usei um Gerador de Pulsos e liguei a saída dos pulsos para acionar o LED do Foto-Acoplador, gerando assim os Pulsos para o Arduino. A única ressalva sobre isso, é que os Pulsos não são cadenciados pela rotação do Motor, mas o foco aqui é a detecção e monitoramento dos Pulsos, e isso foi alcançado. Mas o código não depende disso, e vc pode usá-lo no seu Sistema, bastando mudar o pino onde o sinal do Encoder é ligado (no caso tem que ser o pino "2", pois o "3" vc já está usando), e mudando a velocidade do Monitor Serial no Computador.

 

      Também, veja que no código, há diversas técnicas usadas para evitar que o processamento dos dados “atrapalhem” a contabilização dos Pulsos. Conforme vc vai analisando o código, vai perceber cada uma dessas técnicas.

 

      Ah sim antes que eu me esqueça, muito importanteNÃO faça processamento pesado dentro da ISR (nem desta, e nem em nenhuma outra que vc venha a escrever). A ISR deve executar o mais rápido possível, e portanto processamento pesado não deve ser feito nela. Os motivos para isto posso publicar posteriormente, pois é bastante técnico e envolve vários conceitos e implicações.

  

      Segue a implementação usando a Flag que habilita o Monitoramento, incluindo arquivos de simulação:  Encoder_Lucas_03.zip

  

      Caso tenha alguma dúvida, não deixe de perguntar.

  

      Abrçs,

 

      Elcids

Olá Elcids.

Peço desculpas pelo tempo que levei para responder. Isso  ocorreu, pois confesso que acabei estranhando alguns comandos e, por isto, decidi estudar um pouco mais para entender para que serviam algumas coisas como, por exemplo: millis(); o que é uma "unsigned long"; Como uma nova rotina funciona; Como funciona uma interrupção etc.
No fim eu acabei pesquisando e vendo uma série de vídeos para poder entender e analisar o código. Analisei e acredito ter entendido praticamente tudo, digo "praticamente tudo", pois uma coisa ou outra eu não consegui compreender...Por isso eu queria saber se vc poderia me dizer para que essas coisas que eu não compreendi servem e também queria perguntar por uma outra informação que deixei entre as perguntas, caso possa, é claro, pois você já me ajudou e muito com toda as suas explicações, além do código que passou como exemplo.


*Bem, a minha primeira dúvida é com relação a esta linha :                #define pino_Encoder    2
Eu entendi que você utilizou o pino 2, pois apenas o pino 2 e o pino 3 podem ser utilizados para interrupção, mas vi um youtuber comentando que o pino 2 está associado a interrupção 0, e o pino 3 está associado a interrupção 1, conforme a Figura 1. Por isso, imaginei que o certo seria : "#define pino Encoder 0". Isso me deixou um pouco confuso.

*Com relação a esta linha:                                                    #define passo_Graus (float)360/total_Furos

Ela apenas diz que o valor inserido em "passo_Graus" será do tiplo float? Só fiquei  em dúvida nela, pois nunca tinha visto um float entre parênteses antes...

*Com relação a esta linha:                                                       volatile bool estado_LED = !LED_ON;

Na realidade, a minha dúvida é só sobre o "volatile" mesmo. Vi que ele é um modificador responsável por evitar que o compilador realize otimizações no código, mas isto soou meio vago para mim ainda... Vi que ele foi usado em duas FLAGs, com exceção da flag trigger_Dados.


*Com relação a esta linha:                                                       int valor_PWM;

Aqui eu só estranhei o fato de você não ter inicializado a variável com zero, pois o mesmo não foi feito com as outras.

*Com relação a todas as linhas que aparece isso:                    static unsigned long ref_Tempo = 0;

Acredito que esta seja uma das minhas maiores dúvidas com relação ao código, vi que você declarou a variável "ref_Tempo"  em todas as FLAGs, mas não utilizou a utilizou na FLAG "verifica_Encoder()". No caso, qual seria a função dela nesta FLAG?

*Com relação a esta  linha:                                                     linhaSerial.println(posicao_Graus,2);

Só não entendi para que serve o número 2...

*Eu entendi que não é para fazer processamento pesado dentro de uma rotina de interrupção, mas eu posso ter mais de uma interrupção no mesmo programa? Para o caso de, por exemplo, eu querer adicionar um botão para resetar tudo.

*A minha última dúvida é com relação a última mensagem que você enviou. No caso, "enquanto o “setup()” do Arduino ainda estiver executando, os pulsos do Encoder já estão chegando, o que provoca a execução da ISR correspondente". Mas como isso ocorre se o motor inicia-se desligado conforme esta linha: digitalWrite(pino_MOTOR, !MOTOR_ON);?   Pelo que eu entendi, o código iniciaria da seguinte forma: Carregaria  o “setup()”; Iniciaria a ISR caso houvesse uma borda de descida (já que a interrupção ocorre por FALLING), porém, não ocorreria a interrupção já que o motor encontraria-se parado; Após um tempo de delay, o programa partiria para o "loop()"; Verificaria o verifica_Encoder() e seguiria em frente, pois o mesmo seria falso já que o motor estaria parado e só começaria a se mover a partir do momento em que o millis() alcançasse pelo menos 250 milissegundos dentro do "void controla_Motor()".

Acredito que seja isso... Desculpe se eu perguntei muitas coisas. Seria muito grato (já sou na vdd) se pudesse respondê-las, mas ficaria contente também apenas com a resposta da última pergunta que eu fiz.
Mais uma vez muito obrigado pela ajuda, sinto que fiquei mais experiente depois das informações que você compartilhou comigo!

Abraços,

 

      Lucas

 

Olá Lucas.

      Espero que esteja se saindo bem com seu Sistema.

      Vamos então responder todas as suas duvidas, na ordem que vc as colocou (vc não numerou, mas eu fiz uma enumeração pra facilitar identificar posteriormente).

      1) de fato, no Arduino UNO vc pode usar os pinos 2 e 3 como pinos de IRQ (Interrupt Request). Para esclarecer sua dúvida, iniciemos pelo trecho de código onde estou configurando o Arduino para usar o pino 2 como "fonte" de uma IRQ. Veja na figura:

(clique na figura para "zoom")

      Observe que marquei a linha 315. Mas na linha anterior, o pino 2 (chamado de "pino_Encoder" devido ao #define no início do código), é configurado como pino de entrada, com um "Resistor de Pullup". Isto é necessário, pois esta é a configuração do Hardware do pino, o qual tem que ser uma entrada para poder receber as IRQs (que virão do sinal do Encoder). O "Resistor de Pullup", é apenas uma questão de garantir que o pino não fique "flutuando" (em alta impedância), caso durante seus testes com o Sistema ligado, vc desconecte o Sensor em algum momento (isso tem diversas consequências, e algumas das mais importantes até a veiarada desconhece).

      Mas na linha marcada (linha 315), é feita a configuração da IRQ, através da chamada à função "attachInterrupt", cujo link no site do Arduino, é este:  "attachInterrupt"

      Observe que o "attachInterrupt" tem três parâmetros, e o primeiro muitas vezes causa confusão nas pessoas. Veja que este parâmetro parece estar relacionado justamente com o número do pino do Arduino. Mas na verdade, este parâmetro deve ser o número da Interrupção, que conforme vc mesmo disse, no Arduino UNO pode-se usar a "Interrupt 0" ou a "Interrupt 1".

      O Problema aqui, é a diversidade de Arduinos existentes:  UNO, Mega, Due, ESP32, etc. Imagine que vc use então o pino 2 do UNO no seu Projeto: vc vai e liga o sinal do Encoder no pino 2 do UNO e aí coloca no primeiro parâmetro da função "attachInterrupt" o valor "0", já que a "Interrupt 0" é a que está associada ao pino 2 do UNO. Tudo bem. Mas e se amanhã vc quiser usar outro Arduino, o que vai acontecer?  Ocorre que o pino 2 poderá estar associado a outra IRQ neste outro Arduino, e aí vai dar zica porque quando o Encoder gerar o sinal de IRQ, vai disparar sim uma interrupção, mas não será a "Interrupt 0" que está sendo especificada no seu código. Bem e o que acontece neste caso? O "firmware" do Arduino chamará uma outra ISR (Interrupt Service Routine) que não será a que vc especificou. Conclusão: ele não vai executar o código da ISR que vc esperava que ele executasse.

      Para evitar esse problema e "universalizar" o código, de forma que o mesmo código funcione em qualquer Arduino (desde que o pino que vc escolheu suporte IRQs, é claro), ao invés de se especificar "na bucha" o número da IRQ, nós podemos chamar uma função que descubra para nós qual é a IRQ a partir do número do pino que estamos usando. Esta função é a "digitalPinToInterrupt(pino)", onde "pino" é o número do pino que estaremos usando para gerar as IRQs. Como isso funciona? Da seguinte forma:  essa função sabe qual é a placa pra qual vc está compilando o código, e a partir disso, ela busca em uma Tabela, qual é o Interrupt (a IRQ) associado com aquele pino, para aquela placa. Então não tem erro. Em resumo: deixe que a "digitalPinToInterrupt" faça esse mapeamento pra vc, descobrindo automaticamente qual é a Interrupt que precisa ser usada para a placa que vc está compilando. Fazendo dessa forma, quando a IRQ ocorrer, certamente seu código irá executar a ISR que vc especificou no "attachInterrupt", seja pra qual placa vc estiver compilando.

      2) sobre a definição do "step_Graus", conforme mostrado na figura a seguir:

(clique na figura para "zoom")

      Observe que isto é a definição de um valor constante, pois 360 é uma constante, e "total_Furos" é igual a 64, também constante. Inclusive podemos fazer a conta de quanto vai dar:  step_Graus = 360/64 = 5.625 graus. Um valor fixo, com 3 dígitos após o ponto decimal.

      No entanto, dependendo de onde vc usar este "define", o resultado poderá não ser o que vc espera. Aqui, o risco é que o Compilador do Arduino (Linguagem C++), assuma por conta dele (na verdade por "convenção" da Linguagem), que deve usar apenas a parte inteira do valor, que neste caso seria apenas o "5", descartando a fração ".625", o que provocaria no seu código, um erro no cálculo da posição angular.

      Para evitar isso, independente de onde vc usar a constante "step_Graus" no seu código, usamos um "qualificador" do tipo que queremos como resultado pra aquela constante. Tecnicamente isto é chamado de "casting", e é muito usado por quem domina "seriamente" a Linguagem C/C++. Pra fazer este "casting", colocamos na frente da constante, o tipo que queremos como resultado, neste caso o "float", porém deve estar entre parênteses (pois é a sintaxe do casting na Linguagem C/C++) conforme vc notou, ou seja: (float). Isto vai garantir que independente do local onde vc use o "step_Graus" no seu programa, ele sempre resulte em um valor fracionário (o "float").

      Bem, vc pode estar perguntando: mas em que situação poderia resultar em um valor inteiro com o descarte da parte fracionária?  Isto é mais comum do que vc provavelmente imagina (e causa muitos problemas no código do povo mundo afora). Mas vou aguardar que vc estude mais sobre o "casting" em C/C++, pois certamente vc além de descobrir a resposta, irá aprender muito mais sobre os tipos na Linguagem, que é uma das coisas mais importantes a saber.

      3) sobre a questão do "volatile", conforme a figura a seguir:

(clique na figura para "zoom")

      Conforme vc mesmo disse, o "volatile" está relacionado a um tipo de otimização que o compilador possa vir a fazer. A questão aqui é:  por que "possa"? Ele não faz sempre? E quando ele faz, qual o problema com essa otimização? Mas afinal que raio de otimização ele faz?

      É uma otimização, mas pode ser vista também como uma "esperteza" do Compilador, na tentativa de obter duas coisas:  diminuir o tamanho do código resultante, e aumentar a velocidade de execução deste código.

      Você deve ter ouvido falar, que o Processador (no Arduino UNO seria um AVR de 8 bits) tem Registradores internos. Esses Registradores são usados para armazenar de forma temporária, valores durante a execução do código. Mas por que se usa estes Registradores? Simplesmente porque o acesso a eles costuma ser muito mais rápido que o acesso à Memória RAM do Processador (em processadores "comuns", pode chegar a ser quatro vezes mais rápido, o que é uma grande diferença de velocidade). Então, é comum que o Compilador use os Registradores para armazenar de forma temporária os valores que seu código está trabalhando. Mas porque de forma temporária? Porque seu código pode ter muitas variáveis, as quais estão armazenadas na Memória RAM, e a quantidade de Registradores é limitada, de forma que não dá pra ter todas os valores presentes nos Registradores. Ou seja, quando vc vai utilizar uma determinada variável, o código busca ela na Memória, copiando-a para um Registrador, e como este é bem mais rápido, o código que vai fazer todo o processamento com o valor daquela variável, irá executar mais rápido.

      Mas há um grande problema nisso tudo:  e se sua variável estiver sendo também alterada dentro de uma ISR, o que vai acontecer?  Aí vc estará em maus lençóis, porque se um código trouxe para um Registrador o valor de uma variável, vc supõe que aquela variável não vai mudar, a não ser que vc faça isso explicitamente neste trecho de código. Mas quando uma IRQ ocorre e é aceita, o Processador "suspende" temporariamente a execução do código, e vai executar a ISR daquela IRQ. Tranquilo, quando a ISR terminar, o Processador retoma a execução que ele tinha "suspendido". Mas e se na ISR, vc alterou o valor da Variável que o código "suspenso" estava usando (e que ele achava que não iria mudar) ? Pois é: quando a ISR terminar, e a execução do código suspenso for retomada, a Variável estará com um valor diferente do que estava quando ela foi carregada no Registrador, e aí este trecho de código vai estar trabalhando com uma cópia "desatualizada" da Variável. Um grande problema.

      Como resolver?  Declare a variável com o qualificador "volatile". E o que ele faz?  Faz o seguinte:  toda vez que vc declara uma variável como "volatile", isso obriga o compilador a gerar um código que sempre lê da memória o valor daquela variável, toda vez que ela estiver sendo usada num trecho de código. Isto "garante" que mesmo que uma variável seja alterada dentro de uma ISR, o "resto" do código sempre esteja com o valor atualizado daquela variável, já que em qualquer lugar onde aquela variável estiver sendo usada, será lido sempre o valor da memória (ao invés de usar a esperteza, considerando que um Registrador já tem a cópia da variável). Claro, como a variável será sempre lida da memória toda vez que a acessarmos (ao invés de usar uma presumida cópia presente em um registrador), isso sacrificará um percentual de velocidade, mas em geral é muito pouco, já que são poucas as variáveis que acessamos dentro das ISRs (e por isso deve-se evitar acessar um caminhão de variáveis globais dentro de uma ISR).

      Mas há um ponto a mencionar:  o "volatile" não irá funcionar corretamente em todos as situações. Estes casos onde o "volatile" poderá não ser a solução completa, estão relacionados com o tamanho da variável (1 byte, 2 bytes, 4 bytes)  e se  o Processador é de 8 bits, 16 bits, ou 32 bits (inclusive se estende para mais bits, como 64 ou mais). O problema ocorre quando o tamanho da variável é maior que o "tamanho" do Processador. Exemplo:  vc está usando uma variável do tipo "int" que tem o tamanho de 16 bits no Arduino UNO, e o Processador do UNO todos sabemos que é um Processador de 8 bits. Num caso assim, o "volatile" poderá não ser suficiente pra resolver o problema exposto. Além do "volatile" será necessário usar uma técnica chamada "atomicidade", que apesar de ser simples, não irei abordar neste momento pra não me estender mais.

      Ah sim:  após a explicação acima, nem precisa dizer nada sobre a Flag "trigger_Dados" que vc perguntou. Mas mesmo assim dizendo:  esta Flag não está sendo alterada dentro da ISR (dê uma olha e confira), e por isso não precisou do "volatile".

      4) sobre a variável "valor_PWM", que vc mencionou o fato dela não ter sido inicializada. Veja:  sempre que vc estiver usando uma variável em algum lugar no seu código, vc espera que esta variável tenha um valor que faça sentido. Pra garantir isso, é comum se inicializar as variáveis no momento que as declaramos. Mas se vc tiver certeza que antes de usar o valor de uma variável,  o seu código garanta que aquela variável irá receber um valor válido e que faça sentido, não será necessário inicializar a variável (e inclusive seria uma "perda de tempo"). Analise os momentos que o código usa a variável "valor_PWM", e vc verá que é esse justamente o caso.

      Claro, se vc não tiver certeza, sempre inicialize com um valor que faça sentido quando sua variável for usada pela primeira vez no seu código.

      Mas aqui há uma curiosidade que vale a pena mencionar. Por padrão, a Linguagem C/C++, sempre inicializa todas as variáveis "globais", mesmo que vc não faça isso. Quase sempre (em 99,99% dos casos), este valor é "0" (zero). Ou seja: quando vc não inicializar, saiba que assim que o código começar a executar, a variável será será sim inicializada e com gigantesca probabilidade será com o valor zero. Se vc não acreditar, faça o teste.

      5) sobre a questão do "static unsigned long ref_Tempo"  na rotina "verifica_Encoder" (esta é uma rotina, função, e de jeito nenhum é uma "Flag" como vc disse). Isto foi uma distração, pois fiz algum "Control C" e "Control V", e acabei esquecendo de deletar aquela linha na "verifica_Encoder". Por favor, desconsidere, pois está sobrando ali.

      6) sobre a questão do "Serial.println( Posicao_Graus, 2 )",  peço que veja este link, do site do Arduino:    "Serial - Arduino"

      Mas na figura abaixo há um print traduzido do site, onde vc pode ver a resposta na parte que marquei em verde e azul:

(clique na figura para "zoom")

      7) sobre o que vc perguntou em ter mais de uma Interrupção, sim, pode sim.

      Veja, vc pode não saber, mas enquanto seu código está executando no Arduino, além da "sua" Interrupção no pino "2",  várias poderão estar ocorrendo. Vou citar as mais evidentes:  Interrupção para cadenciar o "millis" (como vc acha que o "millis" vai sendo incrementado a cada mili-segundo?), Interrupção da Serial do Arduino (sim, ela acontece uma vez pra cada caracter que vc envia ou recebe através da Serial), Interrupção do I2C (sim, esta também ocorre toda vez que vc usa o I2C, porém esta é uma das mais problemáticas e mal resolvidas, tipo adolescente mesmo, e pra cada byte que vc envia ou recebe através do I2C, pelo menos 2 Interrupções ocorrem). E há outras ainda.

      Então, a sua Interrupção é apenas mais numa na jogada. Logo, se vc tiver duas ou mais, não será nenhum problema, desde que vc saiba usar adequadamente o Mecanismo de Interrupções, escrevendo ISRs corretamente e respeitando as regras para isso (algumas são chatinhas).

      Interrupções são sensacionais, mas é preciso usá-las adequadamente. Eu já fiz diversos projetos comerciais onde existem dezenas de Interrupções concorrentes,  desde as mais simples até as mais sofisticadas. E tudo funciona perfeitamente.

      Sobre o tal Botão pra "resetar tudo", provavelmente não vale a pena gastar um pino de IRQ pra fazer isso (acredito que seria melhor usar uma outra técnica convencional). Mas se vc achar que deve fazer usando IRQ...

      8) finalmente sobre sua questão de que o Motor está desligado no início, no setup(). Sim, vc está correto nas suas afirmações, pois como o Motor está inicialmente desligado, não existirão os Pulsos no sinal do EncoderNo entanto , veja que isso não se aplica à simulação que fiz no Proteus, pois como expliquei no post anterior, o Encoder que implementei está desvinculado do Motor, e funciona cadenciado por um Gerador de Pulsos. Então este é o motivo de eu ter usado a Flag "habilita_Monitor", pois o Gerador de Pulsos para o Encoder, já está funcionando quando o setup é executado. Ter a Flag não faz mal algum, mesmo se na prática o Encoder é cadenciado pelo Motor. Além disso, usar esta Flag foi uma ótima oportunidade para demonstrar a técnica de como fazer esse "controle".

      Espero ter esclarecido. Mas se tiver algo mais, não deixe de perguntar.

      Abrçs,

      Elcids

Boa noite,

Sobre o encoder, existe no mercado um modulo encoder que já tem o Opamp LM358. 

Sensor Velocidade Módulo Encoder Acoplador Óptico Arduino

Como é o disco do encoder? Qual é o RPM máximo do motor que esta testando?

RSS

© 2024   Criado por Marcelo Rodrigues.   Ativado por

Badges  |  Relatar um incidente  |  Termos de serviço