Ir para o conteúdo principal

Navegação por Encoder


Na seção anterior, você aprendeu sobre como usar o Elapsed Time para permitir que seu robô navegue autonomamente pelo ambiente ao seu redor. Ao começar, muitas das ações do robô podem ser realizadas ligando um motor por um tempo específico. Eventualmente, essas ações baseadas em tempo podem não ser precisas ou repetíveis o suficiente. Fatores ambientais, como o estado da carga da bateria durante a operação e o desgaste dos mecanismos pelo uso, podem afetar todas as ações baseadas em tempo. Felizmente, há uma maneira de fornecer feedback ao robô sobre como ele está operando, usando sensores; dispositivos usados para coletar informações sobre o robô e o ambiente ao seu redor.

Com o Elapsed Time, para mover o robô para uma distância específica, você teve que estimar a quantidade de tempo e a porcentagem de ciclo de trabalho necessária para ir do ponto a ao ponto b. No entanto, os motores REV vêm com encoders embutidos, que fornecem feedback na forma de ticks (ou contagens) por revolução do motor. As informações fornecidas pelos encoders podem ser usadas para mover o motor para uma posição-alvo ou uma distância-alvo.

Mover os motores para uma posição específica, usando os encoders, elimina possíveis imprecisões ou inconsistências do uso do Elapsed Time. O foco desta seção é mover o robô para uma posição-alvo usando encoders.

Existem dois artigos que abordam os conceitos básicos dos encoders. "Using Encoders" explora os fundamentos dos diferentes tipos de modos de motor, bem como alguns exemplos de aplicação desses modos no código. Nesta seção, concentraremos no uso de RUN_TO_POSITION.

O outro artigo, "Encoders", concentra-se na funcionalidade geral de um encoder.

Recomenda-se que você revise ambos os artigos antes de prosseguir com este guia. Links abaixo.

Utilizando encoders
Encoders

Fundamentos da programação com Encoders

Comece criando um Op Mode simples chamado HelloRobot_EncoderAuton

Ao criar um Op Mode, uma decisão precisa ser tomada quanto a defini-lo ou não como modo autônomo. Para aplicações com duração inferior a 30 segundos, geralmente exigidas para a jogabilidade competitiva, recomenda-se alterar o tipo de Op Mode para autônomo. Para aplicações com duração superior a 30 segundos, definir o código como o tipo de Op Mode autônomo limitará seu código autônomo a 30 segundos de tempo de execução. Se você planeja exceder os 30 segundos incorporados ao SDK, é recomendável manter o código como um tipo de Op Mode teleoperado. Para obter informações sobre como os Op Modes funcionam, visite a seção de Introdução à Programação. Para obter mais informações sobre como alterar o tipo de Op Mode, confira a seção Banco de Testes - OnBot Java.

A estrutura do Op Mode abaixo é simplificada e inclui apenas os componentes necessários para criar o código baseado em encoders.

package org.firstinspires.ftc.teamcode;

import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
import com.qualcomm.robotcore.eventloop.opmode.Disabled;
import com.qualcomm.robotcore.hardware.DcMotor;
import com.qualcomm.robotcore.hardware.DcMotorSimple;

@Autonomous //sets the op mode as an autonomous op mode 

public class HelloWorld_EncoderAuton extends LinearOpMode {
    private DcMotor leftmotor;
    private DcMotor rightmotor;
    
    @Override
    public void runOpMode() {
        leftmotor = hardwareMap.get(DcMotor.class, "leftmotor");
        rightmotor = hardwareMap.get(DcMotor.class, "rightmotor");
        
        // Wait for the game to start (driver presses PLAY)
        waitForStart();

        // run until the end of the match (driver presses STOP)
        while (opModeIsActive()){
        
        }
    }
}

Assim como em toda navegação relacionada ao drivetrain, a direção de um dos motores precisa ser invertida para que ambos os motores se movam na mesma direção. Como a Classe Bot V2 ainda está sendo usada, adicione a linha rightmotor.setDirection(DcMotor.Direction.REVERSE); ao código abaixo da linha de código rightmotor = hardwareMap.get(DcMotor.class, "rightmotor");.

public void runOpMode() {
        leftmotor = hardwareMap.get(DcMotor.class, "leftmotor");
        rightmotor = hardwareMap.get(DcMotor.class, "rightmotor");
        
        rightmotor.setDirection(DcMotor.Direction.REVERSE);
        
        waitForStart();

Para obter mais informações sobre a direcionalidade dos motores, confira a página inicial desse capítulo.

Lembrando do uso de encoders que usar o modo RUN_TO_POSITION requer um processo de três etapas. O primeiro passo é definir a posição alvo. Para definir a posição alvo, adicione as linhas leftmotor.setTargetPosition(1000); e rightmotor.setTargetPosition(1000); ao Op Mode após o comando waitForStart();. Para obter uma posição alvo que corresponda a uma distância alvo, são necessários alguns cálculos, que serão abordados posteriormente. Por enquanto, defina a posição alvo como 1000 ticks.

waitForStart();

leftmotor.setTargetPosition(1000);
rightmotor.setTargetPosition(1000);

while (opModeIsActive()){
        
        }

O próximo passo é definir ambos os motores para o modo RUN_TO_POSITION. Adicione as linhas leftmotor.setMode(DcMotor.RunMode.RUN_TO_POSITION); e rightmotor.setMode(DcMotor.RunMode.RUN_TO_POSITION); ao seu código, abaixo das linhas de código setTargetPosition.

waitForStart();

leftmotor.setTargetPosition(1000);
rightmotor.setTargetPosition(1000);

leftmotor.setMode(DcMotor.RunMode.RUN_TO_POSITION);
rightmotor.setMode(DcMotor.RunMode.RUN_TO_POSITION);

while (opModeIsActive()){
        
        }

O foco principal do processo de três etapas é definir uma meta, dizer ao robô para se mover para essa meta e a que velocidade (ou velocidade) o robô deve atingir essa meta. Normalmente, o próximo passo recomendado é calcular a velocidade e definir uma velocidade alvo com base nos ticks. No entanto, isso requer bastante matemática para encontrar a velocidade apropriada. Para fins de teste, é mais importante garantir que a parte principal do código esteja funcionando antes de se aprofundar demais na criação do código. Uma vez que a função setPower foi abordada em seções anteriores e comunicará ao sistema qual velocidade relativa (ou, neste caso, ciclo de trabalho) é necessária para atingir a meta, ela pode ser usada no lugar de setVelocity por enquanto.

Adicione as linhas para definir a potência de ambos os motores para 80% do ciclo de trabalho.

waitForStart();

leftmotor.setTargetPosition(1000);
rightmotor.setTargetPosition(1000);

leftmotor.setMode(DcMotor.RunMode.RUN_TO_POSITION);
rightmotor.setMode(DcMotor.RunMode.RUN_TO_POSITION);

leftmotor.setPower(0.8);
rightmotor.setPower(0.8);

while (opModeIsActive()){
        
        }

Agora que todas as três etapas do RUN_TO_POSITION foram adicionadas ao código, o código pode ser testado. No entanto, se você deseja aguardar o motor atingir sua posição alvo antes de continuar no programa, pode usar um loop while que verifica se o motor está ocupado (ainda não atingiu seu destino). Para este programa, vamos editar o while (opModeIsActive()) {}.

Lembre-se de que, dentro de um Op Mode linear, um loop while deve sempre ter o booleano opModeIsActive() como condição. Essa condição garante que o loop while será encerrado quando o botão de parada for pressionado.

Edite o loop while para incluir as funções leftmotor.isBusy() e rightmotor.isBusy(). Isso verificará se o motor esquerdo e o motor direito estão ocupados se movendo para uma posição alvo. O loop while será interrompido quando qualquer um dos motores atingir a posição alvo.

while (opModeIsActive() && (leftmotor.isBusy() && rightmotor.isBusy())) {

}

Atualmente, o loop while está esperando que qualquer um dos motores atinja a posição alvo. Pode haver ocasiões em que você deseja aguardar que ambos os motores atinjam sua posição alvo. Nesse caso, o seguinte loop pode ser usado:

while (opModeIsActive() && (leftmotor.isBusy() || rightmotor.isBusy()))

Grave e execute o modo de operação duas vezes seguidas. O robô se move conforme esperado na segunda vez? Tente desligar e depois ligar o Control Hub. Como o robô se move?

Na seção Conceitos Básicos de Encoder, é esclarecido que todas as portas de encoder começam em 0 ticks quando o Control Hub é ligado. Como você não desligou o Control Hub entre as execuções, na segunda vez que você executou o modo de operação, os motores já estavam, ou próximos, à posição alvo. Ao executar um código, é desejável garantir que certas variáveis comecem em um estado conhecido. Para os ticks do encoder, isso pode ser alcançado configurando o modo como STOP_AND_RESET_ENCODER. Adicione este bloco ao modo de operação na seção de inicialização. Cada vez que o modo de operação é inicializado, os ticks do encoder serão resetados para zero.

public void runOpMode() {
        leftmotor = hardwareMap.get(DcMotor.class, "leftmotor");
        rightmotor = hardwareMap.get(DcMotor.class, "rightmotor");
        
        rightmotor.setDirection(DcMotor.Direction.REVERSE);
        
        leftmotor.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
        rightmotor.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
        
        waitForStart();

Para obter mais informações sobre o modo de motor STOP_AND_RESET_ENCODERS, consulte a seção STOP_AND_RESET_ENCODERS no guia de Uso de Encoders.

Converter Ticks do encoder para uma distância

Na seção anterior, a estrutura básica necessária para usar RUN_TO_POSITION foi criada. A inserção de leftmotor.setTargetPosition(1000); e rightmotor.setTargetPosition(1000); no código define a posição alvo como 1000 ticks. Qual é a distância do ponto inicial do robô até o ponto para o qual o robô se move após a execução deste código?

Em vez de tentar medir ou estimar a distância que o robô percorre, os ticks do encoder podem ser convertidos de quantidade de ticks por revolução do encoder para quantos ticks do encoder são necessários para mover o robô por uma unidade de distância, como milímetros ou polegadas. Saber a quantidade de ticks por unidade de medida permite definir uma distância específica. Por exemplo, se você passar pelo processo de conversão e descobrir que um conjunto de rodas leva 700 ticks para mover uma polegada, isso pode ser usado para calcular o número total de ticks necessários para mover o robô por 24 polegadas.

Lembre-se de que o guia tem como base a Class Bot V2. O REV DUO Build System utiliza o sistema métrico. Como parte do processo de conversão faz referência ao diâmetro das rodas, esta seção fará a conversão para ticks por milímetro.

Para o processo de conversão as seguintes informações são necessárias:

  • Ticks que ocorrem em uma revolução completa do encoder.

  • Redução total de engrenagens no motor

    • Incluindo todas as reduções proporcionadas por caixas de engrenagens e componentes de transmissão de movimento, como engrenagens, correntes e coroas, ou correias e polias.
  • Circunferência das rodas

Ticks por revolução

A quantidade de ticks por revolução do eixo do encoder depende do motor e do encoder. Os fabricantes de motores com encoders embutidos fornecerão informações sobre a quantidade de ticks por revolução. Para os Motores HD Hex, o encoder conta com 28 ticks por revolução do eixo do motor.

Visite o site do fabricante do seu motor ou encoder para obter mais informações sobre a contagem de ticks do encoder. Para os Motores HD Hex ou Motores Core Hex, consulte a documentação do motor em nosso site.

Redução total

Como os ticks por revolução do eixo do encoder ocorrem antes de qualquer redução de engrenagem, é necessário calcular a redução total da engrenagem. Isso inclui a caixa de engrenagens e qualquer redução adicional proveniente dos componentes de transmissão de movimento. Para encontrar a redução total da engrenagem, use a fórmula de Engrenagem Composta.

Para o Class Bot V2, existem duas Carcaças UltraPlanetary, 4:1 e 5:1, e uma redução adicional de engrenagem da Saída UltraPlanetary para as rodas, na proporção de 72 dentes para 45 dentes.

As UltraPlanetary usam a relação de engrenagem nominal como descritor. As relações de engrenagem reais podem ser encontradas no Manual do Usuário do UltraPlanetary.

Utilizando a fórmula de engrenagens compostas para o robô V2 a redução total é:

3.61/1 * 5.23/1 * 72/45 = 30.21

Ao contrário das engrenagens helicoidais usadas para transferir movimento para as rodas, as Carcaças de Caixa de Engrenagens UltraPlanetary são sistemas de engrenagens planetárias. Para facilitar os cálculos, as relações de engrenagem para as Carcaças já estão reduzidas.

Circunferência da roda

O Class Bot V2 utiliza as Rodas de Tração de 90mm. 90mm é o diâmetro da roda. Para obter a circunferência apropriada, utilize a seguinte fórmula:

cirncunferência = diãmetro * π

Você pode calcular isso com papel e caneta, mas para os propósitos desse guia, isso pode ser calculado pelo código.

Devido ao desgaste e tolerâncias de fabricação, o diâmetro de algumas rodas pode ser nominalmente diferente. Para obter resultados mais precisos, considere medir sua roda para confirmar que o diâmetro é preciso.

Para recapitular, para o Classe Robô V2 as seguintes informações são verdadeiras:

Descrição Valor
Ticks por revolução 28 ticks
Redução total 30.21
Circunferência da roda 90mm * π

Cada uma dessas informações será usada para encontrar o número de ticks do encoder (ou contagens) por milímetro que a roda se move. Em vez de se preocupar em calcular essas informações manualmente, esses valores podem ser adicionados ao código como variáveis constantes. Para fazer isso, crie três variáveis:

  • COUNTS_PER_MOTOR_REV
  • DRIVE_GEAR_REDUCTION
  • WHEEL_CIRCUNFERENCE_MM

A convenção de nomenclatura comum para variáveis constantes é conhecida como CONSTANT_CASE, onde o nome da variável está todo em maiúsculas e as palavras são separadas por um sublinhado.

Adicione as variáveis à classe OpMode, onde as variáveis de hardware são definidas. Definir as variáveis dentro dos limites da classe, mas fora do modo operacional (op mode), permite que elas sejam referenciadas em outros métodos ou funções dentro da classe. Para garantir que as variáveis sejam referenciáveis, elas são definidas como variáveis static final double. O modificador static permite referências às variáveis em qualquer lugar dentro da classe, e o modificador final indica que essas variáveis são constantes e não são alteradas em outros lugares no código. Como essas variáveis não são do tipo inteiro, são classificadas como variáveis double.

public class HelloWorld_EncoderAuton extends LinearOpMode {
    private DcMotor leftmotor;
    private DcMotor rightmotor;
    
    static final double     COUNTS_PER_MOTOR_REV    = 28.0; 
    static final double     DRIVE_GEAR_REDUCTION    = 30.21;   
    static final double     WHEEL_CIRCUMFERENCE_MM  = 90.0 * Math.PI;

Agora que essas três variáveis foram definidas, elas podem ser usadas para calcular outras duas variáveis: a quantidade de contagens do encoder por rotação da roda e o número de contagens por milímetro que a roda se move.

Para calcular as contagens por revolução da roda, multiplique COUNTS_PER_MOTOR_REV por DRIVE_GEAR_REDUCTION. Use a seguinte fórmula:

y = a * b

Onde:

  • a = COUNTS_PER_MOTOR_REV
  • b = DRIVE_GEAR_REDUCTION
  • y = COUNTS_PER_WHEEL_REV

Crie a variável COUNTS_PER_MOTOR_REV pelo código. Ela também pode ser static final double.

public class HelloWorld_EncoderAuton extends LinearOpMode {
    private DcMotor leftmotor;
    private DcMotor rightmotor;
    
    static final double     COUNTS_PER_MOTOR_REV    = 28.0; 
    static final double     DRIVE_GEAR_REDUCTION    = 30.24;   
    static final double     WHEEL_CIRCUMFERENCE_MM  = 90.0 * 3.14;
    
    static final double     COUNTS_PER_WHEEL_REV    = COUNTS_PER_MOTOR_REV * DRIVE_GEAR_REDUCTION;

Uma vez que COUNTS_PER_WHEEL_REV é calculado, use-o para calcular os pulsos por milímetro que a roda se move. Para fazer isso, divida COUNTS_PER_WHEEL_REV pela CIRCUNFERÊNCIA_DA_RODA_MM. Utilize a seguinte fórmula.

x = (a * b)/c = y/c

Onde: