Eu estava fazendo um pequeno projeto com um Arduino Uno. Envolveu interrupções, pois estou usando codificadores para medir o quanto o sistema de roda diferencial avança. Meu robô se move apenas para frente. Portanto, uso apenas um único canal de cada codificador. Aqui estão minhas duas rotinas de interrupção:
ISR (INT0_vect){ encoderRPos = encoderRPos + 1; } ISR (INT1_vect){ encoderLPos = encoderLPos + 1; }
As variáveis encoderRPos
e encoderLPos
são do tipo volatile int
. Eu entendo que as variáveis que sofrem alteração em qualquer rotina de interrupção precisam ser do tipo volátil. Isso é para avisar outras partes do código que usam essas variáveis que podem mudar a qualquer momento.
Mas o que aconteceu no meu código foi um pouco estranho e não consegui explicar. Aqui está como eu calculo a distância movida pela roda esquerda:
#define distancePerCount 0.056196868 float SR = distancePerCount * (encoderRPos - encoderRPosPrev); float SL = distancePerCount * (encoderLPos - encoderLPosPrev); encoderRPosPrev = encoderRPos; encoderLPosPrev = encoderLPos;
Mas quando imprimo o seguinte no meu monitor serial, noto uma anomalia:
Se você olhar para o terceiro coluna, (SL) seu valor é muito alto por algum tempo. Isso está atrapalhando todos os meus cálculos.
A única pista que posso obter, se eu pegar o valor de SL que obtive ( 3682), que é sempre uma constante, e calcular de volta (encodeLPos - encoderLPosPrev)
, obterei 65519,66, que está próximo do valor máximo de unsigned int
. O que significa que (encoderLPos - encoderLPosPrev)
está causando um estouro enquanto ambos os valores cuja diferença é tomada estão em torno de apenas 5.000!
E consegui resolver. Foi por sorte. Foi assim que modifiquei o código:
static int encoderRPosPrev = 0; static int encoderLPosPrev = 0; int diffL = (encoderLPos - encoderLPosPrev); int diffR = (encoderRPos - encoderRPosPrev); float SR = distancePerCount * diffR; float SL = distancePerCount * diffL; encoderRPosPrev = encoderRPos; encoderLPosPrev = encoderLPos;
Não consigo compreender o que aconteceu. Há algo sobre variáveis voláteis que eu deveria saber?
Atualização: Aqui está o código completo, se você quiser dar uma olhada. E está funcionando muito bem depois de mudar para o que foi sugerido na resposta aceita.
Comentários
Resposta
Você precisa aprender sobre seções críticas .
O que O que provavelmente está acontecendo é que as variáveis estão sendo alteradas pelas rotinas de interrupção no meio dos cálculos. Sua “correção” reduz o tempo gasto fazendo o cálculo com as variáveis voláteis, tornando menos provável que haja uma colisão.
O que você deve fazer é copiar as variáveis voláteis para as variáveis locais com as interrupções desativadas para isso breve período.
cli(); int l = encoderLpos; int r = encoderRpos; sei();
Como o Arduino é uma CPU de 8 bits, são necessárias várias instruções de montagem para realizar operações matemáticas em valores de 16 bits. O ponto flutuante é ainda pior usando muitas instruções para uma adição simples. Divisão e multiplicação usam consideravelmente mais. Uma interrupção tem muitas oportunidades para disparar durante essa lista de instruções. Fazendo uma atribuição como essa e usando as novas variáveis locais em seus cálculos, as instruções necessárias para lidar com as variáveis voláteis são mantidas em um mínimo absoluto. Ao desligar as interrupções durante a atribuição, você garante que as variáveis nunca poderão ser alteradas enquanto você as estiver usando. Este trecho de código é chamado de seção crítica .
Comentários
- Esse pode ser o caso. Apenas pensando, você poderia explicar por que isso não está acontecendo aleatoriamente, mas em um momento específico sempre que executo o código? Além disso, por que dá o valor particular?
- Aqui está uma ótima referência ao cli / sei. nongnu.org/avr-libc/user-manual/… . Com a barreira de memória, a declaração de voláteis não é realmente necessária no código acima. Aqui está uma leitura divertida sobre este assunto. kernel .org / doc / Documentation / volatile-considerando-prejudicial.txt
- @MikaelPatel Bom, mas não tão relevante para MCUs.O volátil é necessário nesta situação para evitar que o compilador otimize as instâncias onde ele pensa que ‘ não está sendo usado (o valor nunca muda). O cli / sei existe para tornar a operação atômica WRT a única outra thread (interrupções) que executa.
- Você tentou compilar o código com e sem volátil? Mas com a seção crítica (cli / sei). O que estou tentando discutir é o conceito de barreira de memória e como isso fornece acesso volátil (e ordenação correta) do compilador com a necessidade de declarar variáveis como voláteis. A maioria dos programadores são ensinados que qualquer variável acessada em um ISR deve ser declarada volátil, mas há muito mais nesta história.
- Eu não ‘ não acho que o compilador tem muito conceito do que cli () e sei () fazem e como isso afetaria coisas como a otimização de variáveis que não deveriam ‘ ser otimizadas. Tudo o que sei () e cli () fazem é manipular o sinalizador habilitado para interrupção global em seu registro. Eles não fazem nada para o fluxo do código.
could you explain why it is not happening randomly but at a specific time every time I run the code? Also why does it give the particular value?
– Provavelmente, poderia fazer isso se visse o código inteiro. Enquanto isso, leia: gammon.com.au/interrupts3683 / .056196868 = 65537
então parece que aumentou no momento errado, sim? Você está acessando uma variável que pode ser alterada em uma interrupção várias vezes nesse código, portanto, obter uma cópia local, enquanto as interrupções estão desativadas, seria muito mais seguro.