Olá robô - Programação

Olá robô - Introdução a programação


Olá robô - escolhendo seu caminho

Em quase toda a aula de programação, a primeira lição ensinada é alguma variação do código Olá Mundo. Olá Mundo, frequentemente um segmento de código de uma ou duas linhas, exibe a linha Olá Mundo quando o código é construído e executado. Embora este código possa parecer uma introdução muito simples à programação, ele apresenta vários conceitos cruciais na programação. Olá Mundo é a primeira lição que muitos estudantes têm na lógica de programação, bem como na sintaxe específica da linguagem. Mas, o mais importante, a simplicidade do Olá Mundo permite que ele seja um ponto de teste para o sistema usado para executar o código.

Embora seja possível exibir Olá Mundo ou Olá Robô em um dispositivo Android no Sistema de Controle REV, isso não serve exatamente para o mesmo propósito. Para considerar adequadamente a sintaxe, lógica e teste no Sistema de Controle REV, é necessário prestar atenção a uma multiplicidade de elementos do sistema como atuadores e sensores. Por essa razão, a lição Olá Mundo foi adaptada para Olá Robô.

Ao final deste guia, os usuários devem entender como configurar seu robô e testar seus mecanismos de robô. O esboço a seguir percorre o fluxo e os objetivos desta seção. Escolha o caminho que melhor se adequa às suas necessidades.

Se você é novo na programação ou no Sistema de Controle REV, recomendamos que você siga todo o guia para aprender como utilizar adequadamente o sistema.

Introdução

Ferramentas de programação

Existem três ferramentas de programação para o Sistema de Controle REV. Conheça os benefícios de cada opção e escolha aquela que melhor atenda às suas necessidades. A seção também inclui instruções sobre como acessar a opção que você escolher.

Op Modes

O que são Op Modes? Saiba mais sobre os diferentes tipos de Op Modes no Sistema de Controle REV.

Configuração

Importância da configuração

O que é Configuração e por que você deve configurar antes de começar a programar?

Configurando hardware comum

Aprenda como configurar hardware comumente utilizado, como motores, servos e sensores.

Erros comuns no mapeamento de hardware

Entender e resolver os erros comuns que ocorrem ao configurar e mapear hardware

banco de testes: introdução

Banco de testes

Por que criar um banco de testes com atuadores e sensores pode ajudar na programação. Este banco de testes, ou algo equivalente, será usado nas seções seguintes.

Noções básicas de testes

Entenda por que o aprendizado é um dos aspectos mais importantes do desenvolvimento de software e como ele difere da solução de problemas (troubleshooting).

Banco de testes: blocos

Criando um Op Mode

Concentra-se em como navegar na interface de blocos e criar um modo operacional (op mode).

Fundamentos da programação

Quebra a estrutura e os elementos-chave necessários para um modo operacional (op mode), bem como alguns dos componentes essenciais dos Blocos e da lógica de programação.

Programando Atuadores

Como codificar servos e motores. Esta seção orienta a lógica básica de codificação de atuadores, controle de atuadores com um gamepad e uso de telemetria.

Banco de dados: OnBot Java

Criando um OpMode

Concentre-se em como navegar na interface OnBot Java e criar um op mode.

Fundamentos da programação

Explique a estrutura e os elementos-chave necessários para um "op mode", bem como alguns dos componentes essenciais do Java.

Programando Atuadores

Como codificar servos e motores. Esta seção orienta sobre a lógica básica de programação de atuadores, controlar atuadores com um gamepad e usar telemetria.

Programando Sensores

Como programar um dispositivo digital. Esta seção foca na lógica básica de codificação de um dispositivo digital, como um Sensor de Toque REV.

Controle de robô

Crie um robô simples

Apresenta um possível robô para trabalhar, assim como o arquivo de configuração usado nas seções seguintes.

Noções básicas de transmissão

Diferenças entre drivetrains diferenciais e omnidirecionais e seu impacto nos tipos de controle teleoperados.

Movimentação do robô: blocos

Noções básicas de programação de transmissão

O que considerar ao programar os motores do drivetrain e como aplicar isso a um controle teleoperado no estilo arcade.

Tempo decorrido

Aprenda a usar o conceito de tempo decorrido para criar programas autônomos controlados por tempo.

Movimentação por encoder

Noções básicas de programação de transmissão

O que considerar ao programar os motores do drivetrain e como aplicar isso a um controle teleoperado no estilo arcade.

Tempo decorrido

Aprenda a usar o conceito de tempo decorrido para criar programas autônomos controlados por tempo.

Movimentação por encoder

Aprenda a usar encoders para criar trajetórias autônomas mais consistentes.

Controle de braço: blocos

Controle de braço

Noções básicas de progrmação de braço

Introdução à codificação de um braço para controle teleoperado e trabalho com um interruptor de limite.

Programando um braço para uma posição

Utilizando encoders de motores para mover um braço para uma posição específica, como de 45 graus para 90 graus.

Utilizando limites para controlar a amplitude de movimento

Trabalhando com os conceitos básicos de controle de braço, encoder de motor e interruptores de limite para controlar a amplitude de movimento de um braço.

Controle de braço: OnBot

Noções básicas de progrmação de braço

Introdução à codificação de um braço para controle teleoperado e trabalho com um interruptor de limite.

Programando um braço para uma posição

Utilizando encoders de motores para mover um braço para uma posição específica, como de 45 graus para 90 graus.

Utilizando limites para controlar a amplitude de movimento

Trabalhando com os conceitos básicos de controle de braço, encoder de motor e interruptores de limite para controlar a amplitude de movimento de um braço.

Ferramentas de programação


Escolher a ferramenta ou linguagem de programação apropriada é uma das decisões mais cruciais que um usuário pode tomar. No sistema de controle REV, existem três ferramentas de programação para escolher: Blocks, OnBot Java e Android Studio. Cada ferramenta apresenta diferentes benefícios e níveis de dificuldade.

Básica Intermediária Avançada
Blocos OnBot Android Studio

As ferramentas de programação para o Software Development Kit (SDK) foram escolhidas como um meio de proporcionar aos usuários a capacidade de escolher entre alternativas, mas também como um meio de permitir que os usuários avancem naturalmente da programação básica para a avançada. Um usuário pode começar com Blocks e progredir para o Android Studio de maneira razoável. De fato, essa é frequentemente nossa sugestão para usuários iniciantes. O Android Studio é uma ferramenta muito poderosa, mas, enquanto você está aprendendo os fundamentos da programação, pode ser mais um obstáculo do que uma ajuda.

Revise as seções a seguir para aprender sobre cada ferramenta de programação e os benefícios associados a ela. Depois de concluir a revisão, faça uma escolha informada sobre qual ferramenta funcionará melhor para você!

Blocos

A Ferramenta de Programação Blocks é uma ferramenta visual que permite aos programadores usar um navegador da web para criar, editar e salvar seus modos de operação (op modes). Blocks, assim como outras ferramentas de programação baseadas em blocos, é uma coleção de trechos de código predefinidos que os usuários podem arrastar e soltar nas linhas de código apropriadas.

module

O Blocks foi criado para atender a usuários com pouca ou nenhuma experiência em programação. Ao contrário do OnBot Java ou do Android Studio, o Blocks trabalha para isolar e proteger os usuários das complexidades do SDK. A interface do Blocks realiza isso ocultando ou abstraindo parte da complexidade mais avançada que o sistema requer, como chamadas a bibliotecas ou classes específicas. Os trechos de código fazem essas conexões e assumem essas complexidades pelo usuário.

Um dos principais benefícios do Blocks são as funcionalidades integradas que permitem aos usuários fazer uma transição natural de pouco ou nenhum conhecimento em programação para uma compreensão básica de Java. O Blocks ensina aos usuários a lógica da programação, protegendo-os de erros de sintaxe. À medida que os usuários ganham mais confiança e habilidade, podem usar a opção "Mostrar Java". "Mostrar Java" permite que os usuários vejam a sintaxe Java correspondente a cada bloco adicionado ao código.

Resumo dos Benefícios

Oculta complexidades do usuário, permitindo que ele se concentre em aprender a lógica

Tem uma opção para Mostrar Java que permite aos usuários ver qual seria a sintaxe correspondente em Java

Interface baseada na Web – acessível na maioria dos dispositivos

Salva diretamente no robô

Tão poderoso quanto OnBot Java

Acessando o Blocos

Esta seção pressupõe que você já tenha seguido as etapas de configuração do seu sistema de controle REV. Para obter mais informações sobre como configurar seu sistema de controle, consulte o guia Introdução ao Control Hub.

Esta seção também pressupõe que você tenha um navegador da web com JavaScript habilitado.

  1. Vá para as Configurações de Wi-Fi em um computador com Windows 10, clicando no ícone de Wi-Fi.
  2. Uma vez exibida a lista de redes Wi-Fi disponíveis na vizinhança, selecione a rede que corresponde ao nome do seu ponto de acesso Wi-Fi.
  3. Insira a senha que você definiu ao configurar o sistema de controle.
  4. Após conectar-se, abra um navegador com suporte a JavaScript (o FIRST recomenda o Google Chrome).
  5. Acesse o endereço IP http://192.168.43.1:8080.
  6. Na parte superior da página do Console do Controlador do Robô, deve haver 3 opções de menu: Blocks, OnBot Java e Manage. Escolha Blocks.

Senhas são sensíveis a maiúsculas e minúsculas. Se você não se lembrar da sua senha, use o Hardware Client para verificar a seção Program e Manage do Console do Controlador do Robô.

OnBot Java

A ferramenta de programação baseada em texto que permite aos programadores usar um navegador da web para criar, editar e salvar seus modos de operação (op modes) em Java.

OnBot Java é ideal para programadores com habilidades básicas a avançadas em Java que desejam escrever modos de operação (op modes) baseados em texto. OnBot Java compartilha algumas das propriedades de isolamento do Blocks, mas dá aos usuários acesso a elementos mais complexos das bibliotecas do SDK. Por exemplo, OnBot Java exige que os usuários realizem chamadas a classes como o hardwareMap, que estão ocultas nos trechos de código do Blocks.

OnBot Java compartilha uma interface baseada na web com a ferramenta de programação Blocks. O modelo baseado module na web é de fácil acesso na maioria dos dispositivos para realizar alterações no código, reduzindo a necessidade de ter um dispositivo dedicado para alterações no código.

Resumo dos Benefícios

Oculta complexidades do usuário, permitindo que ele se concentre em aprender a lógica

Tem uma opção para Mostrar Java que permite aos usuários ver qual seria a sintaxe correspondente em Java

Interface baseada na Web – acessível na maioria dos dispositivos

Salva diretamente no robô

Tão poderoso quanto OnBot Java

Acessando o OnBot Java

Esta seção pressupõe que você já tenha seguido as etapas de configuração do seu sistema de controle REV. Para obter mais informações sobre como configurar seu sistema de controle, consulte as seções "Getting Started" ou "Managing the Control System" no Guia do Sistema de Controle. Esta seção também pressupõe que você tenha um navegador da web com JavaScript habilitado.

  1. Vá para as Configurações de Wi-Fi em um computador com Windows 10, clicando no ícone de Wi-Fi.
  2. Uma vez exibida a lista de redes Wi-Fi disponíveis na vizinhança, selecione a rede que corresponde ao nome do seu ponto de acesso Wi-Fi.
  3. Insira a senha que você definiu ao configurar o sistema de controle.
  4. Após conectar-se, abra um navegador com suporte a JavaScript (o FIRST recomenda o Google Chrome).
  5. Acesse o endereço IP http://192.168.43.1:8080.
  6. Na parte superior da página do Console do Controlador do Robô, deve haver 3 opções de menu: Blocks, OnBot Java e Manage. Escolha OnBot Java.

As senhas são sensíveis a maiúsculas e minúsculas. Se você não se lembrar da sua senha, use o Hardware Client para verificar a seção Program e Manage do Console do Controlador do Robô.

Android Studio

Um ambiente de desenvolvimento integrado avançado para criar aplicativos Android. Essa ferramenta é a mesma que os desenvolvedores profissionais de aplicativos Android utilizam. O Android Studio é recomendado apenas para usuários avançados que possuam ampla experiência em programação Java.

module

O Android Studio oferece aos programadores com um entendimento avançado de Java um ambiente de desenvolvimento mais poderoso para trabalhar. Ele oferece recursos avançados de edição e depuração que não estão disponíveis no OnBot Java ou Blocks. Também permite aos programadores trabalhar com bibliotecas de terceiros que não estão incluídas no SDK. No entanto, o Android Studio não é um software baseado na web e exigirá um laptop dedicado para ser executado.

Resumo dos Benefícios

Acesso a classes de biblioteca mais complicadas para uma programação mais avançada

Recursos aprimorados de edição e depuração Permite acesso a bibliotecas de terceiros

Acessando o Android Studio

Para aprender como baixar e trabalhar corretamente com o Android Studio, por favor, visite a FTC Wiki.

A FIRST Global não oferece suporte para o Android Studio.

Op Modes

Os modos de operação (ou op modes) são programas de computador utilizados para personalizar ou especificar o comportamento de um robô. O Controlador do Robô, seja o Control Hub (REV-31-1595) ou um dispositivo Android emparelhado com um Expansion Hub (REV-31-1153), armazena e executa os op modes. A Estação do Motorista permite que os usuários selecionem qualquer um dos op modes armazenados no Controlador do Robô e inicializem, iniciem ou interrompam os op modes.

No SDK, existem dois tipos de modos de operação (op modes): autônomo e teleoperação. Ambos os tipos de op modes possuem funcionalidades de inicialização, início e parada no telefone da Estação do Motorista. Cada recurso corresponde a diferentes tipos de segmentos de código que serão discutidos em detalhes nas seções específicas do Test Bed para cada ferramenta de programação deste documento.

A principal diferença entre os modos de operação autônomo e de teleoperação está em como eles aparecem na aplicação da Estação do Motorista. Os modos de operação autônomo aparecem em um menu suspenso no lado esquerdo da aplicação da Estação do Motorista. A Estação do Motorista atribui um temporizador de 30 segundos aos modos de operação autônomos. Se o modo de operação autônomo não for interrompido manualmente antes do final dos 30 segundos, a Estação do Motorista interromperá automaticamente o código. Os modos de operação de teleoperação aparecerão em um menu suspenso no lado direito da aplicação da Estação do Motorista. Esses modos de operação serão executados até serem interrompidos manualmente.

Também vale destacar que no SDK, os modos de operação podem ser modos de operação lineares ou modos de operação iterativos. Este guia foca nos modos de operação lineares, que executam linhas de código em uma ordem sequencial. Para chamar repetidamente ações dentro de um modo de operação linear, é necessário usar uma função de loop. Este tópico será discutido em detalhes conforme você acompanha este guia.

Olá robô - Configuração

Olá robô - Configuração

Pré-configuração


A configuração é uma das etapas mais comumente mal compreendidas ou esquecidas necessárias para programar um robô. Esta seção tem o objetivo de explicar a importância da configuração e dissipar equívocos comuns sobre ela, respondendo às seguintes perguntas:

  1. O que é configuração?
  2. Como você configura elementos de hardware?
  3. Quais são os problemas comuns causados por um problema com o arquivo de configuração?

A importância da configuração

Embora cada REV Control Hub seja o mesmo, os robôs controlados pelo Control Hub não são. Cada Control Hub tem o mesmo número de portas de motor, portas de servo, portas digitais, e assim por diante, mas como cada usuário utiliza essas portas varia de sistema para sistema. Por exemplo, um Sensor de Cor V3 pode ser conectado à I2C Bus 1 no Hub de um usuário, mas outro usuário pode usar o mesmo barramento para conectar um Sensor de Distância de 2m.

O Control Hub sabe que há um dispositivo I2C conectado à porta, mas não possui naturalmente as informações necessárias para traduzir essas informações para um Op Mode ou informar ao Op Mode quais drivers precisam ser acessados para usar esse sensor. O usuário precisa fornecer informações adicionais, para que o software interno no Hub possa pegar informações do Op Mode e aplicá-las a uma porta de hardware externa correspondente e vice-versa. Esse processo é conhecido como mapeamento de hardware. O mapeamento de hardware é um processo de duas etapas que inclui: a criação de um arquivo legível conhecido como arquivo de configuração e chamadas ao mapeamento de hardware dentro de um Op Mode.

Arquivo de configuração

O arquivo de configuração é um arquivo legível criado pelo usuário por meio do aplicativo Driver Station. Ao criar um arquivo de configuração, os usuários são obrigados a atribuir cada dispositivo a uma porta, selecionar o tipo de dispositivo a partir das opções fornecidas pelo SDK e dar a ele um nome exclusivo.

Na programação, é importante distinguir entre variáveis, dando a cada variável um nome diferente.

  1. Assim que um arquivo de configuração é salvo ou ativado, o robô será reiniciado. Esse reinício é feito para que o SDK possa ler o arquivo, determinar quais dispositivos estão presentes e adicionar os dispositivos à classe hardwareMap.

Mapeamento de hardware

No lado do usuário, no modo de operação criado, está a classe hardwareMap. Esta classe é onde as informações criadas na configuração estão disponíveis para uso no código Blocks, OnBot Java ou Android Studio.

O nível de acesso ou interação que um usuário tem com a classe hardwareMap depende da ferramenta de programação que estão usando. Como o Blocks é uma coleção de trechos de código predefinidos, ele cria referências ao hardwareMap sempre que um trecho de código de variável, correspondente a um hardware externo, é referenciado pela primeira vez. No entanto, com OnBot Java e Android Studio, a referência ao hardwareMap requer a criação de uma variável atribuída a uma unidade de hardware externa dentro do hardwareMap.

As informações sobre como fazer referência à classe hardwareMap em Java serão explicadas com mais detalhes na seção Banco de dados - OnBot Java.

Configurando dispositivos de hardware comum

Acessando o utilitário de configuração

Selecione o menu no canto superior direito da Estação do Motorista. Em seguida, selecione "Configure Robot" (Configurar Robô).

module

Na página de configurações disponíveis, selecione "New" (Novo).

module

Na página de configuração de dispositivos USB, selecione "Control Hub Portal" (Portal do Control Hub).

Observação: Se você tiver um Expansion Hub, ele aparecerá como "Expansion Hub Portal" (Portal do Expansion Hub).

module

Dentro do Portal do Hub, selecione o dispositivo que deseja configurar. Neste caso, selecione o Control Hub.

Observação: se você tiver um Expansion Hub conectado a um Control Hub, o Expansion Hub também aparecerá como um dispositivo configurável no portal.

module

Isso o levará para a página mostrada na imagem. A partir daqui, você pode configurar motores, servos e sensores que está usando. Siga o restante do guia para descobrir como configurar dispositivos que serão usados na seção de Teste.

Observação: A forma como os dispositivos Digitais e Analógicos são configurados difere significativamente da forma como os dispositivos I2C são configurados. Isso ocorre porque cada porta física I2C é um barramento diferente que pode hospedar vários sensores diferentes. Para obter mais informações sobre os diferentes tipos de sensores, consulte a seção de sensores.

module

Configurando o hardware

A próxima seção mostrará como configurar componentes que serão usados no Test Bed. O tipo de hardware e os nomes foram escolhidos levando em consideração o plano de aula Hello World. Os usuários devem observar as notas dentro das etapas para considerar ao criar arquivos de configuração para outras instâncias.

Olá robô - Configuração

Motor


Motor

Selecione Motors

module

A página do Motor permitirá que você configure todas as quatro portas de motor no Hub. Na Porta 0, abra o menu suspenso e selecione "REV Robotics Core Hex Motor" (Motor Hexagonal Core da REV Robotics).

Observação: No seu arquivo de configuração, você deve configurar as portas de motor para o tipo de motor que está usando.

module

Dê o nome de "test_motor" ao motor. Selecione "done" (concluído).

Observação: Lembre-se de que, ao nomear hardware no arquivo de configuração, o REV Control System diferencia maiúsculas de minúsculas (case-sensitive).

module

Olá robô - Configuração

Servo


Selecione Servos

module

A página do Servo permitirá que você configure todas as seis portas de servo no Hub. Na Porta 0, abra o menu suspenso e selecione Servo.

Observação: Os Servos da REV podem ser configurados como um Servo comum ou como um Servo de Rotação Contínua. O tipo de dispositivo ao qual um servo é configurado deve corresponder ao modo no qual o sensor está. Para obter mais informações sobre os modos de sensor, consulte a seção de Sensores da REV Robotics.

module

Nomeie o servo como test_servo. Selecione "Done" (Concluído).

Observação: lembre-se de que ao nomear hardware no arquivo de configuração, o REV Control System diferencia maiúsculas de minúsculas.

module

Olá robô - Configuração

Dispositivos Digitais


Selecione Digital Devices

module

A página de Dispositivos Digitais permitirá que você configure todas as oito portas digitais no Hub. Na Porta 1, abra o menu suspenso e selecione Dispositivo Digital.

Observação: Sensores de Toque (Touch Sensors) devem sempre ser configurados em portas de números ímpares. Consulte a seção de Sensores Digitais para obter mais informações.

Observação: Sensores de Toque podem ser configurados como um REV Touch Sensor ou como um Dispositivo Digital. No FTC SDK, o tipo de dispositivo configurado altera as classes e métodos que podem ser usados.

module

Nomeie o dispositivo digital como test_touch. Selecione "Done" (Concluído).

Observação: lembre-se de que ao nomear hardware no arquivo de configuração, o REV Control System diferencia maiúsculas de minúsculas.

module

Olá robô - Configuração

Dispositivos I2C


Selecione I2C Bus 0

module

Selecione "Add" (Adicionar).

Observação: Cada barramento I2C pode hospedar mais de um sensor I2C, desde que os endereços I2C não entrem em conflito. O Barramento 0 sempre hospedará o IMU interno. Para obter mais informações sobre sensores I2C, consulte a seção de I2C.

module

Na Porta 1, que foi criada na etapa anterior, abra o menu suspenso e selecione REV Color Sensor V3.

Observação: Se estiver usando sensores de cor V1 ou V2, selecione REV Color/Range Sensor. Para obter mais informações sobre a configuração com os sensores de cor REV, consulte as Datasheets do Sensor de Cor.

module

Nomeie o sensor de cor como test_color. Selecione "Done" (Concluído).

Observação: lembre-se de que ao nomear hardware no arquivo de configuração, o REV Control System diferencia maiúsculas de minúsculas.

module

Olá robô - Configuração

Salvando a configuração


Pressione "Done" duas vezes até chegar à página de Dispositivos USB na configuração. Na página de Dispositivos USB na configuração, pressione "Save" (Salvar).

module

Dê o nome de "helloRobotTest" à configuração e selecione "Ok" (OK).

Observação: O FTC SDK não obriga que você siga uma convenção de nomenclatura específica, mas é comum nomear configurações em lowerCamelCase.

module

Pressione "back" para ativar a configuração salva. Seu Controlador do Robô reiniciará assim que você ativar uma nova configuração.

module

Olá robô - Configuração

Erros comuns


Dentro do mundo da programação e do software, os erros podem se apresentar em muitas formas e tipos diferentes. Ao mapear hardware, há dois erros principais que você pode encontrar. Ambos os erros se enquadram em categorias comuns de erros de software:

Erros de Interface - são erros entre como uma interface deveria funcionar e como ela realmente se comporta.

Erros em Tempo de Execução - são erros que ocorrem quando um programa está sendo executado.

Erro de interface

Os erros de interface ocorrem no SDK quando os parâmetros da interface do SDK não são atendidos. No processo de mapeamento de hardware, o erro de interface mais comum ocorre com a ferramenta de programação Blocks. Como mencionado na seção de Introdução à Programação, o Blocks oculta as complexidades da biblioteca do SDK dos usuários. Uma maneira de fazer isso é criando automaticamente referências ao hardwareMap quando trechos de código para uma unidade de hardware externo são usados.

Para automatizar as chamadas ao hardwareMap, a interface Blocks lê o arquivo de configuração e cria variáveis de hardware com base nas informações encontradas. Por esse motivo, é importante criar um arquivo de configuração antes de tentar codificar.

A imagem abaixo mostra duas versões diferentes da interface Blocks. Na versão sem arquivo de configuração, não há menus suspensos para acessar trechos de código específicos para atuadores ou sensores. Na versão da interface com um arquivo de configuração, os menus suspensos estão presentes e os trechos de código específicos para motores estão acessíveis.

module

Erros de tempo de execução

Dentro do SDK, erros em tempo de execução ocorrem durante a inicialização ou execução. Um dos erros em tempo de execução mais comuns dentro do REV Control System é exemplificado na imagem abaixo.

module

Esse erro indica uma inconsistência entre como um dispositivo de hardware é chamado no código e como isso se compara ao nome usado no arquivo de configuração. Existem duas maneiras diferentes de ocorrer esse erro.

A primeira ocorrência desse erro é quando não há um arquivo de configuração encontrado. Isso pode significar que um arquivo de configuração não foi criado, um arquivo foi criado, mas não está ativo, ou o arquivo errado está sendo usado. Quando qualquer uma dessas situações acontece, o código está solicitando um nome e tipo de dispositivo que o hardware map não consegue localizar no arquivo de configuração. O programa para no primeiro nome de dispositivo que não consegue localizar.

Uma referência incorreta ao hardwareMap também pode causar esse erro. Ao contrário do Blocks, OnBot Java e Android Studio exigem que um programador codifique manualmente a chamada ao hardwareMap. Se o nome de referência na chamada não corresponder ao nome do dispositivo no arquivo de configuração (é sensível a maiúsculas e minúsculas), o código será compilado sem falhas, mas o erro em tempo de execução ocorrerá. Vamos usar o arquivo de configuração da seção anterior como exemplo, onde há um sensor de toque chamado "test_touch" e um motor chamado "test_motor".

As aspas indicam que "test_touch" e "test_motor" representam uma variável de string que corresponde aos nomes dos dispositivos na configuração.

public class HR_test extends LinearOpMode{
    private DigitalChannel test_touch;
    private DcMotor test_motor;
    
    @Override
    public void runOpMode(){
        //get the touch sensor and motor from hardwareMap 
        test_touch = hardwareMap.get(DigitalChannel.class, "test_touch");
        test_motor = hardwareMap.get(DcMotor.class, "test_moto");
        
    }
}

Observe no exemplo de linhas de código que o hardwareMap.get() para "test_motor" está escrito como "test_moto" em vez de "test_motor". Quando o código está sendo compilado, não há uma verificação imediata de que o nome solicitado está no hardwareMap. Essa verificação é feita quando o código é executado no robô. Quando a comunicação com o hardwareMap é iniciada, ele procura por "test_moto" e, quando não consegue encontrar, cria o erro em tempo de execução mencionado acima.

Olá robô - Banco de testes

Olá robô - Banco de testes

Banco de testes


Um dos passos mais importantes no processo de design de engenharia e no ciclo de vida do desenvolvimento de software é o teste. Ao trabalhar com código, garantir que ele funcione sem erros e atenda ao padrão decidido na fase de planejamento do processo é crucial. Para garantir que o código está funcionando conforme o previsto, os testes precisam ser realizados. Antes de entrar nas seções de introdução à programação, Test Bed - Blocks ou Test Bed - OnBot Java; é importante entender o teste, os benefícios de criar um banco de testes, os componentes necessários para as próximas seções e como usar os gamepads. Siga adiante nesta seção para aprender mais sobre o teste!

Seção Objetivos da seção
Noções básicas de testes Saiba por que é um dos aspectos mais importantes do desenvolvimento de software e como ele difere da solução de problemas.
Banco de testes Por que a criação de uma plataforma de teste de atuadores e sensores pode ajudar na programação. Este banco de testes, ou algo equivalente, será usado nas seções seguintes.
Utilizando controles Compreender as convenções de nomenclatura para programar um gamepad.

Tenha em mente que esta é a introdução ao guia básico de programação. Test Bed - Blocks e Test Bed - OnBot Java irão guiá-lo pelos fundamentos da programação com o Sistema de Controle REV.

Noções básicas de testes

O objetivo dos testes é identificar, isolar e corrigir possíveis problemas em um projeto antes que o design seja colocado em uso. Os testes assumem diferentes formas ou fornecem diferentes métricas para diversos propósitos no design. Um mecanismo, como um atirador, por exemplo, pode ser testado para confirmar que está funcionando de forma confiável. Durante a fase de planejamento do processo de design, você deve criar várias métricas de desempenho, qualidade e confiabilidade. Quando o design é construído, ou o programa é escrito, essas métricas ajudarão a identificar se o mecanismo atende aos padrões esperados. Se os padrões de operação não forem atendidos, o problema precisa ser isolado.

Para corrigir um problema no processo de design, é necessário isolar a origem do problema. Para entender como isso funciona, considere o seguinte exemplo:

A equipe adquiriu recentemente um Control Hub e um Motor Core Hex. Eles conectam o Motor Core Hex ao Control Hub usando o cabeamento correto, mas quando tentam executar o código, o motor não se move. Qual é a razão mais provável para essa falha: O problema está no programa; o problema está no motor; o problema está no fio que conecta o motor ao Hub; o problema está no Hub.

Sem mais informações, não há uma maneira precisa de descobrir por que o motor não está funcionando. Para estreitar as possibilidades, os diferentes componentes precisam ser testados até que a causa raiz do problema seja encontrada. A prática comum é começar com um código que se sabe funcionar, como um dos códigos de exemplo no SDK. Se o motor ainda não funcionar, a próxima coisa que a equipe deve verificar é se os fios estão funcionando conforme o esperado. A equipe deve passar por cada componente, testando ou solucionando problemas, para identificar o que está funcionando e o que não está.

Uma vez que a origem do problema foi isolada, é necessário corrigi-lo. A duração da correção depende das fontes do problema e de sua profundidade. Por exemplo, se um modo operacional não estiver funcionando conforme o esperado, a correção pode ser uma mudança simples, como no arquivo de configuração ou no hardwareMap. Um problema maior que requer um redesenho, como um mecanismo que não atende às métricas de desempenho, aciona um reinício do processo de design de engenharia.

Testes x Solução de problemas

Anteriormente, o teste foi definido como o processo de identificar, isolar e corrigir possíveis problemas durante o processo de design. Isso difere da solução de problemas, que é o processo de identificar, isolar e corrigir problemas em um mecanismo que passou pelo processo de teste e funcionou conforme o esperado.

Na seção de solução de problemas, foram usados exemplos da luz indicadora do motor de um carro. Nesse exemplo, o indicador conhecido de uma falha foi a luz do motor do carro. A luz do motor informa ao motorista que há algo errado com o carro, mas para encontrar a causa do problema, etapas de solução de problemas e diagnóstico devem ser realizadas. Para manter essa comparação, o teste é o que os engenheiros do carro usam para estabelecer as métricas do desempenho esperado do motor. Se esses padrões não forem atendidos, a luz do motor acende para alertar o motorista sobre o problema.

Banco de testes

Um dos inconvenientes ao testar código em um sistema de componentes, como o REV Control System, é que não há garantia de que todos os componentes estão funcionando como deveriam. Por exemplo, se um motor no robô não estiver funcionando, existem várias razões potenciais para a falha. O motor, a porta do motor no Control Hub, o fio que conecta o motor à porta e o código são todas causas potenciais de falha do motor.

Se uma falha ocorrer após a montagem do robô, pode ser difícil retroceder e fazer alterações ou solucionar problemas sem ter que desmontar o robô. Uma das maneiras de se preparar para essa circunstância é criar um banco de testes antes de criar um robô.

Ao testar código, não assuma que uma falha ocorre devido ao mecanismo em vez do código. Testar e solucionar problemas, embora sejam conceitos semelhantes, são fundamentalmente diferentes. Verificar o código ou usar um código conhecido que funcione sempre deve ocorrer antes de solucionar problemas com componentes como atuadores e sensores.

Um banco de testes é um ambiente de teste para componentes de hardware e software, comumente usado no mundo da engenharia. As aplicações de bancos de testes incluem uma ampla variedade de equipamentos e testes de medição. Em alguns casos, um banco de testes é um equipamento para testar um produto específico; em outros casos, é um sistema de componentes que cria um ambiente de teste. Independentemente disso, o objetivo final de um banco de testes é garantir que um componente esteja funcionando antes de ser usado para seu propósito pretendido.

Criar um banco de testes facilita o processo de solução de problemas se houver uma falha durante o teste de código. O objetivo desta seção é criar um banco de testes para testar código básico nas seções de Test Bed - Blocks e Test Bed - OnBot Java.

Criando um banco de testes

O design de um banco de testes depende do caso de uso e dos recursos disponíveis. Por exemplo, um dos requisitos de design para o banco de testes apresentado abaixo foi a acessibilidade. Observe que a disposição dos componentes de hardware na extrusão permite que os atuadores, sensores e Control Hub sejam removidos ou trocados com facilidade.

IMG_6726 2.png

Outra consideração importante de design para este banco de testes foi incluir os componentes comuns necessários para ensinar aos usuários o básico da programação com o REV Control System. Neste caso, os componentes foram escolhidos a partir do REV FTC Starter Kit.

  1. Control Hub
  2. REV Core Hex Motor
  3. Smart Robot Servo
  4. Touch Sensor
  5. Color Sensor V3
  6. Bateria

Qualquer um desses componentes do banco de testes pode ser trocado por um componente equivalente. Por exemplo, se você tiver um Expansion Hub em vez de um Control Hub. No entanto, com um Expansion Hub, pode ser necessário considerar o local para o telefone do controlador do robô.

Existem outras considerações de design menores, mas importantes, a serem feitas para um banco de testes. Por exemplo, ao adicionar um atuador a um banco de testes, considere as seguintes perguntas:

Qual nível de restrição o atuador precisa?

Como você poderá observar o comportamento do atuador?

Utilizando controles

As seções do Banco de Testes destacam os componentes de robô necessários para aprender os conceitos básicos de programação utilizados nas seções Test Bed - Blocks e Test Bed - OnBot Java. No entanto, há dois componentes adicionais necessários para ter sucesso nos testes de seu código: um Driver Hub (ou dispositivo Android equivalente para Estação do Condutor) e um gamepad.

Para obter informações sobre a configuração de um Driver Hub e gamepad, por favor, visite o guia Começando com o Driver Hub.

gamepad and driver hub.png

Todos os botões em um gamepad podem ser programados para uma tarefa ou comportamento específico. Ao longo do Guia Olá Robô, você encontrará várias situações onde o gamepad é utilizado. Conhecer a convenção de nomenclatura geral para os gamepads ajudará você a programá-los corretamente. O guia pressupõe que você está usando um gamepad da Logitech ou um gamepad PS4, como o Etpark Wired Controller for PS4 (REV-39-1865). Para entender como programar um gamepad, especialmente com diferenças na forma como certos botões são nomeados, consulte o gráfico e a tabela a seguir, que mostram a correspondência entre as linhas de código e cada botão.

spaces_UOOiQ4S2QcMWmVoSmeQ8_uploads_7AeoWPIv7Tp4XqxnNF68_Gamepad-Labels.webp

Tipos de Dados

Booleano

Dados booleanos têm dois valores possíveis: Verdadeiro e Falso. Esses dois valores também podem ser representados por Ligado e Desligado, ou 1 e 0. Botões, bumpers e gatilhos no gamepad fornecem dados booleanos ao seu robô. Por exemplo, um botão que não está pressionado retornará o valor Falso e um botão que está pressionado retornará o valor Verdadeiro.

Float

Dados de ponto flutuante são números que podem incluir casas decimais e valores positivos ou negativos. No gamepad, os dados de ponto flutuante retornados estarão entre 1 e -1 para a posição do joystick em cada eixo. Alguns exemplos de valores possíveis são 0.44, 0, -0.29 ou -1.

Olá robô - Banco de testes

Blocos


A Ferramenta de Programação por Blocos é uma ferramenta visual de programação que permite aos programadores usar um navegador da web para criar, editar e salvar seus modos operacionais (op modes). Os blocos, assim como outras ferramentas de programação baseadas em blocos, são uma coleção de trechos de código predefinidos que os usuários podem arrastar e soltar na linha de código apropriada. Nesta seção, os usuários podem aprender a criar um modo operacional, bem como os conceitos básicos de programação dos atuadores e sensores apresentados no banco de testes. Siga o guia para obter uma compreensão aprofundada do trabalho com Blocos ou navegue até a seção que atenda às suas necessidades:

Criando um Op Mode

Antes de mergulhar e criar o seu primeiro modo operacional (op mode), é importante considerar o conceito de convenções de nomenclatura. Ao escrever código, o objetivo é ser o mais claro possível sobre o que está acontecendo dentro do código. É aqui que entra o conceito de convenções de nomenclatura. Convenções de nomenclatura comuns foram estabelecidas pelo mundo da programação para denotar variáveis, classes, funções, etc. Modos operacionais compartilham algumas semelhanças com classes. Portanto, a convenção de nomenclatura para modos operacionais tende a seguir a convenção de nomenclatura para classes, onde a primeira letra de cada palavra é maiúscula.

Esta seção pressupõe que você já acessou a plataforma de Blocos durante a introdução à programação no Guia Olá Robô. Se você não souber como acessar os Blocos, por favor, reveja esta seção antes de continuar.

Para começar, acesse o Console do Controlador do Robô e vá para a página de Blocos. No canto superior direito, há um botão "Criar Novo Modo Operacional", clique nele.

Blocks - Start Screen.png

Clicar no botão "Criar Novo Modo Operacional" abrirá a janela "Criar Novo Modo Operacional". Esta janela permite que os usuários nomeiem seus modos operacionais e selecionem um código de exemplo para desenvolver. Para este guia, utilize o exemplo padrão "BasicOpMode" e nomeie o modo operacional como "HelloRobot_TeleOp", conforme mostrado na imagem abaixo.

image-1702900943740.png

Depois de nomear o modo operacional, clique em 'OK' para prosseguir. A criação de um modo operacional abrirá a página principal de programação por blocos. Antes de prosseguir com a programação, reserve um tempo para aprender e entender os seguintes componentes-chave dos Blocos, apresentados na imagem abaixo.

Blocks - Main Screen.png

Salvar Modo Operacional - Clique neste botão para salvar um modo operacional no robô. É importante salvar o modo operacional sempre que parar de trabalhar em um código para que o progresso não seja perdido.

TeleOperado/Autônomo - Esta seção de blocos permite aos usuários alternar entre os dois tipos de modos operacionais: teleoperado e autônomo.

Blocos Categorizados - Esta seção da tela é onde os blocos de programação são categorizados e acessíveis. Por exemplo, clicar em Lógica abrirá o acesso a blocos de programação como declarações if/else.

Espaço de Programação - Este espaço é onde os blocos são adicionados para construir programas.

Se uma configuração foi feita, os Atuadores, Sensores e Outros Dispositivos na seção de Blocos Categorizados devem aparecer como menus suspensos, onde blocos específicos para hardware podem ser acessados. Se isso não acontecer, um arquivo de configuração não foi criado. Para obter mais informações, visite a página de Configuração antes de prosseguir com a programação.

Fundamentos da programação

Durante o processo de criação de um modo operacional, a ferramenta de Blocos solicitou a seleção de um código de exemplo. Nos Blocos, esses exemplos funcionam como modelos, fornecendo os blocos e a estrutura lógica para diferentes casos de uso em robótica. Na seção anterior, o código de exemplo BasicOpMode foi selecionado. Este código de exemplo, visto na imagem abaixo, é a estrutura base necessária para ter um modo operacional funcional.

Blocks - Basic Op Mode.png

Um modo operacional pode frequentemente ser considerado um conjunto de instruções para um robô seguir a fim de entender o mundo ao seu redor. O BasicOpMode fornece o conjunto inicial de instruções necessárias para que um modo operacional funcione adequadamente. Embora este exemplo seja fornecido aos usuários para reduzir algumas das complexidades da programação à medida que aprendem, ele introduz alguns dos blocos de código mais importantes. Também é importante entender o que está acontecendo na estrutura do BasicOpMode, para que os blocos de código sejam colocados na área correta.

Principais blocos do modo operacional

Blocks - comments main.png

Comentários são blocos de código que beneficiam o usuário humano. Eles são usados pelos programadores para explicar a função de uma seção de código. Isso é especialmente útil em ambientes de programação colaborativa. Se o código é repassado de um programador para outro, os comentários comunicam a intenção do código ao outro programador. Blocos como // são comentários escritos pela equipe técnica da FIRST para informar ao usuário o que acontecerá quando blocos forem adicionados diretamente abaixo do comentário.

Blocks - intilization comment.png

Por exemplo, quaisquer blocos de programação que são colocados após o comentário (e antes do waitForStart bloco) serão executados quando o modo operacional for selecionado pela primeira vez por um usuário na Estação do Condutor. Normalmente, os blocos colocados nesta seção têm o propósito de criar e definir variáveis entre as fases de inicialização e início do modo operacional.

Uma variável é uma localização de armazenamento com um nome simbólico associado, que contém alguma quantidade conhecida ou desconhecida de informações referidas como um valor. Variáveis podem ser números, caracteres ou até mesmo motores e servos.

Blocks - Wait for Start.png

Quando o Controlador do Robô atinge o bloco waitForStart, ele irá parar e aguardar até receber um comando de Iniciar da Estação do Condutor. Um comando de Iniciar não será enviado até que o usuário pressione o botão Iniciar na Estação do Condutor. Qualquer código após o bloco waitForStart será executado após o botão Iniciar ser pressionado.

Após o bloco waitForStart, há um bloco condicional if que só será executado se o modo operacional ainda estiver ativo (ou seja, um comando de parada não foi recebido).

Blocks - ifdo.png

As instruções if-then (if-else) são semelhantes ao conceito de causa e efeito. Se a causa (ou condição) acontecer, então execute o efeito.

Quaisquer blocos que são colocados após o comentário // pur run blocks here e antes do bloco // OpMode is active serão executados sequencialmente pelo Controlador do Robô depois que o botão Iniciar for pressionado.

O bloco while é uma estrutura de controle iterativa ou de loop.

Blocks - while.png

Este controle realizará as etapas listadas na parte "do" do bloco enquanto a condição opModeIsActive() for verdadeira. Isso significa que as instruções incluídas na parte "do" do bloco serão repetidamente executadas enquanto o modo operacional HelloRobot_TeleOp estiver em execução.

Assim que o usuário pressiona o botão Parar, a condição opModeIsActive() não é mais verdadeira e o loop while para de se repetir.

Funções e métodos

A seção anterior não entrou em uma discussão detalhada dos blocos de função (ou método) roxos. Funções e métodos são procedimentos semelhantes em programação que são mais avançados do que o que será abordado neste guia.

Por enquanto, a coisa mais importante a saber é que ocasionalmente será necessário chamar métodos dentro das bibliotecas SDK para realizar uma determinada tarefa. Por exemplo, a linha while (opModeIsActive()) chama o método opModeIsActive, que é o procedimento no SDK que consegue dizer quando o robô foi iniciado ou parado.

Blocks - Methods.png

Quando suas habilidades de programação estiverem mais avançadas, reserve um tempo para explorar os conceitos de funções e métodos, e descubra como eles podem ajudar a aprimorar seu código.

Programando atuadores

Noções básicas do servo

O objetivo desta seção é cobrir alguns dos conceitos básicos de programação de um servo nos Blocos. No final desta seção, os usuários devem ser capazes de controlar um servo com um gamepad, além de compreender algumas das necessidades fundamentais de programação do servo.

Esta seção está considerando o Smart Robot Servo no seu modo padrão. Se o seu servo foi alterado para funcionar no modo contínuo ou com limites angulares, ele não se comportará da mesma forma utilizando os exemplos de código abaixo. Você pode aprender mais sobre o Smart Robot Servo ou alterar o modo do servo através do SRS Programmer clicando nos hiperlinks abaixo.

Servo robô inteligente
Programador SRS

Com um servo típico, você pode especificar uma posição alvo para o servo. O servo girará o eixo do motor para mover-se até a posição alvo e, em seguida, manterá essa posição, mesmo se for aplicada uma força moderada para tentar perturbar sua posição.

assets_-M4_pJHI8HTuZFQTNfcy_-MBtA_rAKndrhw3UEHOy_-MBtffLxjwr8kU4I5jjV_image.webp

Para ambos os Blocos e o OnBot Java, você pode especificar uma posição alvo que varia de 0 a 1 para um servo. Para um servo com um alcance de 270°, se a faixa de entrada for de 0 a 1, então um sinal de entrada de 0 faria com que o servo virasse para -135°. Para um sinal de entrada de 1, o servo viraria para +135°. Entradas entre o mínimo e o máximo têm ângulos correspondentes distribuídos uniformemente entre o ângulo mínimo e máximo do servo. Isso é importante ter em mente ao aprender a programar servos.

Como esta seção se concentrará em servos, é importante entender como acessar servos dentro dos Blocos. No topo da seção de Blocos Categorizados, há um menu suspenso para Atuadores. Quando o menu é selecionado, ele mostrará duas opções: DcMotor ou Servo. Selecionar Servo abrirá uma janela lateral preenchida com vários blocos relacionados a servos.

Blocks - selecting servo blocks.png

Programando um servo

No menu Servo selecione o bloco

Servo - set to 0.png

O bloco acima mudará de nome dependendo do nome do servo em um arquivo de configuração. Se houver vários servos em um arquivo de configuração, a seta ao lado de "test_servo" irá abrir um menu com todos os servos na configuração.

Adicione este bloco ao código do modo operacional dentro do

Loops - While (opModeIsActive).png

Clique no bloco numérico para mudar de

Servo - set to 0.png

Para:

Servo - set to 1.png

blocks-servo - first block set.png

Selecione "Salvar Modo Operacional" no canto superior direito no Console do Controlador do Robô.

Tente executar este modo operacional no banco de testes duas vezes e considere as seguintes perguntas:

A intenção do bloco setPosition é definir a posição do servo. Se o servo já estiver na posição definida quando o código for executado, ele não mudará de posição. Vamos tentar adicionar outro bloco setPosition e ver o que muda. Arraste mais um bloco setPosition para o código do modo operacional, abaixo do bloco de comentário Put initialization blocks here.

blocks-servo - secondblock set.png

Tente executar este modo operacional no banco de testes e considere a seguinte pergunta: O que é diferente da execução anterior?

O bloco setPosition(0) que foi adicionado na etapa acima altera a posição do servo para 0 durante a fase de inicialização, portanto, quando o modo operacional é executado, o servo sempre se moverá para a posição 1. Para algumas aplicações, iniciar o servo em um estado conhecido, como na posição zero, é benéfico para o funcionamento de um mecanismo. Definir o servo no estado conhecido na inicialização garante que ele esteja na posição correta quando o modo operacional é executado.

Programando um servo com um controle

O foco deste exemplo é atribuir determinadas posições do servo a botões no gamepad. Para este exemplo, o estado conhecido permanecerá na posição 0, de modo que após a inicialização o servo estará na posição de -135 graus do intervalo do servo. A lista a seguir mostra quais botões correspondem a quais posições do servo.

Se você estiver usando um controle PS4, como o Etpark Wired Controller para PS4 (REV-39-1865), consulte a seção Usando Controles na página anterior para determinar como o código do controle usado nesta seção se traduz para o controle do PS4.

Botões Posição em graus Posição no código
Y -135 0
X 0 0.5
B 0 0.5
A 135 1

A melhor maneira de alternar a posição do servo será usar uma instrução condicional if/else if. Uma instrução if avalia se uma declaração condicional é verdadeira ou falsa. Se a declaração condicional for verdadeira, uma ação definida (como o movimento do servo) é executada. Se a declaração condicional for falsa, a ação não é executada.

Uma instrução if/else if aceita várias declarações condicionais diferentes. Se a primeira declaração condicional for considerada falsa, então a segunda declaração condicional é analisada. Cada declaração no if/else if será analisada uma por uma até que uma declaração seja considerada verdadeira ou todas as declarações sejam consideradas falsas. Para este exemplo, haverá três condições que precisarão ser verificadas.

No menu Lógica em Blocos, selecione o bloco if e arraste-o para dentro do loop while do modo operacional.

Blocks - if do if do.png

Clique no ícone de Configurações azul e branco para o bloco if/else if. Isso exibirá um menu pop-up que permite modificar o bloco if/else if.

blocks - creating elifs.png

Arraste um bloco else if da parte esquerda do menu pop-up e encaixe-o sob o bloco if. Arraste um segundo bloco else if da parte esquerda e encaixe-o no lado direito sob o primeiro bloco else if.

Blocks - elif.png

Existem três caminhos diferentes neste bloco if/else if. Cada um corresponde a uma das três posições escolhidas do servo: 0, 0.5 e 1. No entanto, existem quatro botões diferentes que serão usados para este exemplo. Tanto o botão B quanto o botão X devem ser capazes de mover o servo para a posição 0.5. Para fazer isso, o operador lógico "or" precisa ser usado.

O operador lógico "or" considera dois operandos; se qualquer um (ou ambos) forem verdadeiros, a declaração "or" é verdadeira. Se ambos os operandos forem falsos, a declaração "or" é falsa.

Da categoria Lógica no Blocks, selecione o bloco and.

Blocks - and block.png

Adicione este bloco ao bloco if/else if, como mostrado na imagem abaixo. Use o menu suspenso no bloco para alterá-lo de um bloco and para um bloco or.

Blocks - adding or.png

Todos os blocos relacionados ao gamepad estão no Menu do Gamepad.

blocks - servo gamepad control (1).png

Adicione cada botão ao bloco if/else if conforme visto na imagem abaixo.

blocks - servo if else gamepad.png

Adicione blocos de setar a posição do servo para 1 em cada seção do bloco if/else if. Defina a posição do servo para corresponder a cada botão.

blocks - servo if else gamepad finished.png

Existem três caminhos diferentes neste bloco if/else if. Se a primeira declaração condicional for verdadeira (o botão Y está pressionado), o servo se move para a posição de código 0 e as outras declarações condicionais são ignoradas. Se a primeira condição for falsa (o botão Y não está pressionado), a segunda condição é analisada. Lembre-se de que esse comportamento se repete até que uma condição seja atendida ou todas as condições tenham sido testadas e consideradas falsas.

blocks - servo final code.png

Servoss e telemetria

Telemetria é o processo de coletar e transmitir dados. Na robótica, a telemetria é usada para enviar dados internos de atuadores e sensores para a Estação do Condutor. Esses dados podem então ser analisados pelos usuários para tomar decisões que possam melhorar o código.

A telemetria mais útil do servo é a posição do servo ao longo de sua faixa de 270 graus. Para obter essa informação, a seguinte linha precisa ser usada.

Para acessar os blocos de telemetria, selecione o menu suspenso de Utilitários. O menu de utilitários está em ordem alfabética, então a telemetria está localizada mais para o final das opções do menu suspenso. Selecione o seguinte bloco do menu de telemetria:

Telemetry - key and number.png

Blocks - telemetry select.png

Arraste o seguinte bloco e coloque-o abaixo do bloco if/else if:

Telemetry - key and number.png

blocks - servo telemetry.png

Do menu do Servo, puxe o bloco abaixo. Arraste e conecte-o ao parâmetro de número nos blocos de telemetria.

Blocks - .position.png

Mude o parâmtro key para "Servo Position"

blocks - servo telemetry 2.png

Quando o modo operacional é executado, o bloco de telemetria exibirá as informações de posição atual com a Chave de Posição do Servo. O número correspondente à posição atual mudará conforme a posição do eixo do servo se altera.

Noções básicas de motor

Modifique o seu modo operacional para adicionar o código relacionado ao motor. Isso pode ser feito limpando suas modificações de código atuais ou adicionando o código relacionado ao motor ao seu modo operacional atual.

O objetivo desta seção é abordar alguns conceitos básicos de programação de um motor dentro do ambiente de Blocos. Ao final desta seção, os usuários deverão ser capazes de controlar um motor usando um gamepad, além de compreender alguns princípios básicos de trabalho com encoders de motor.

Dado que esta seção se concentrará em motores, é importante compreender como acessar motores dentro do ambiente de Blocos. Na parte superior da seção de Blocos Categorizados, há um menu suspenso para Atuadores. Quando o menu é selecionado, serão exibidas duas opções: DcMotor ou Servo. Selecionar DC Motor abrirá uma janela lateral preenchida com vários blocos relacionados a motores.

Blocks - Finding motors.png

Controlando motores

No menu Dc Motor em Blocos, selecione o bloco:

Motor -  testmotor set power.png

O bloco acima terá nomes diferentes dependendo do nome do motor em um arquivo de configuração. Se houver vários motores em um arquivo de configuração, a seta ao lado de "test_motor" abrirá um menu com todos os motores presentes na configuração.

Adicione este bloco ao código do modo operacional (op mode) dentro do loop while.

Blocks - test motor full code.png

Selecione "Salve Op Mode" no canto superior direito na Console do Controlador do Robô.

Tente executar este modo operacional no banco de testes e considere as seguintes perguntas:

O nível de potência enviado ao motor depende do número numérico atribuído ao motor. A mudança de 1 para 0,3 reduziu a velocidade do motor de 100% do ciclo de trabalho para 30% do ciclo de trabalho. Enquanto isso, a mudança para -1 permitiu que o motor girasse a 100% do ciclo de trabalho na direção oposta. Assim, a potência pode ser variada para mover um motor para frente ou para trás.

No entanto, o bloco de motor setPower(1) executará o motor na direção atribuída até que algo no código pare o motor ou cause uma mudança na direção.]

Para compreender melhor os motores e o conceito de ciclo de trabalho, consulte a documentação sobre Motores e Escolha de Atuadores da REV Robotics

Controlando motores pelo controle

Na seção anterior, você aprendeu como configurar o motor para funcionar em um nível de potência específico em uma direção específica. No entanto, em algumas aplicações, pode ser necessário controlar o motor com um gamepad para alterar facilmente a direção ou o nível de potência de um mecanismo.

Do menu Gamepad em Blocos, selecione o bloco leftStickY.

Blocks - LeftStickY selection.png

Arraste o bloco leftStickY para que ele se encaixe no lado direito do bloco Power. Este conjunto de blocos irá repetidamente ler o valor do joystick esquerdo (posição y) do gamepad #1 e definir a potência do motor para o valor Y do joystick esquerdo.

Blocks - Gamepad1 motor power.png

Observe que, para os gamepads Logitech F310, o valor Y de um joystick varia de -1, quando o joystick está em sua posição mais alta, até +1, quando o joystick está em sua posição mais baixa. Se o motor não estiver se movendo na direção desejada, adicionar um símbolo negativo ou um operador de negação à linha de código alterará a direção do motor em relação ao gamepad.

Da seção Matemática no Blocks, selecione o bloco da imagem abaixo.

Blocks - negative symbolselection.png

Arraste o bloco do símbolo de negativo para que ele se encaixe entre os blocos de Power e leftStickY, como na imagem abaixo:

Blocks - negation operator for motor control.png

Motores e telemetria

Lembre-se de que telemetria é o processo de coletar e transmitir dados. Na robótica, a telemetria é usada para enviar dados internos de atuadores e sensores para a Estação do Motorista. Esses dados podem ser analisados pelos usuários para tomar decisões que melhorem o código.

Para obter dados de telemetria do motor, é necessário usar encoders de motor. Os motores DC REV, como o Core Hex Motor, são equipados com encoders internos que transmitem informações na forma de contagens.

Para acessar os blocos de telemetria, selecione o menu Utilitários. O menu Utilitários está em ordem alfabética, então a telemetria está localizada na parte inferior das opções do menu. Selecione o bloco abaixo e faça o seguinte:

Blocks - telemetry select (1).png

Arraste esse bloco para baixo do bloco anteriormente feito, como mostra a seguinte imagem:

Blocks - adding motor telelmetry.png

Do menu DC Motor, pegue o bloco "CurrentPosition". Arraste o bloco e conecte-o ao parâmetro numérico nos blocos de telemetria.

test_motor.currenrPosition.png

Mude o parâmetro key para "Counts per Revolution"

Blocks - counts per rev telemetry.png

Quando o modo operacional é executado, o bloco de telemetria exibirá as informações de posição atual com a chave "Counts Per Revolution". O número correspondente à posição atual mudará à medida que a posição do eixo do motor for alterada.

Para obter mais informações sobre a programação de encoders, consulte a página "Using Encoders" (Usando Encoders). Para mais informações sobre a métrica de contagens por revolução e como utilizá-la, confira a página "Encoders".

Programando um sensor

Noções básicas de sensor

Modifique o seu modo operacional para adicionar o código relacionado ao dispositivo digital. Isso pode ser feito limpando as modificações atuais do seu código ou adicionando o código do dispositivo digital ao seu modo operacional.

O objetivo desta seção é abordar alguns dos conceitos básicos de programação de um dispositivo digital, ou Sensor de Toque, dentro do Blocks. Como esta seção se concentrará em dispositivos digitais, é importante entender como acessar blocos específicos para esses dispositivos. No topo da seção "Categorize Blocks", há um menu suspenso para "Other Devices". Quando o menu é selecionado, ele exibirá uma opção para "Digital Devices". Selecionar "Digital Devices" abrirá uma janela lateral preenchida com vários blocos relacionados a dispositivos digitais. O bloco mais frequentemente usado é o de State

Blocks - grabbing digital device info.png

Antes de programar com um Sensor de Toque ou outro dispositivo digital, é importante entender o que é um dispositivo digital e quais são as aplicações comuns para esses dispositivos. Visite a página de Sensores Digitais para obter mais informações.

Programando um dispositivo digital

As informações provenientes de dispositivos digitais vêm em dois estados, também conhecidos como estados binários. A maneira mais comum de utilizar essas informações é por meio de uma instrução condicional, como uma declaração if/else.

Do Menu Lógico em Blocos, selecione o bloco if/else.

Blocks - ifdo (1).png

Arraste esse bloco e coloque-o abaixo do bloco de comentário Put run blocks here

blocks _ts adding if do.png

Selecione um bloco de State no menu de Dispositivos Digitais e adicione-o ao bloco if/do/else, conforme mostrado na imagem abaixo.

Blocks - ts - add statwe.png

O bloco de State armazena a informação binária FALSO/VERDADEIRO do sensor de toque e atua como a condição para o bloco if/else. Se o State for verdadeiro, qualquer código colocado na parte de "fazer" do bloco será ativado. Se o State for falso, qualquer coisa colocada na parte de "senão" do bloco será ativada.

O estado FALSO/VERDADEIRO de um Sensor de Toque REV corresponde a se o botão no Sensor de Toque está pressionado ou não. Quando o botão não está pressionado, o estado do Sensor de Toque é Verdadeiro. Quando o botão é pressionado, o estado do Sensor de Toque é Falso.

Para ajudar a lembrar como os estados físicos e digitais do sensor correspondem nas próximas seções, vamos usar alguns comentários.

Blocos de comentário podem ser encontrados no menu Diversos.

Blocks - comments 2.png

Após achar esse bloco, monte um sistema conforme a imagem abaixo:

Blocks_Ts_add comments.png

O próximo passo no processo é usar a telemetria para exibir o status do Sensor de Toque no dispositivo da Estação do Motorista. Para fazer isso, vamos criar uma variável de string chamada touchStatus.

Uma String é uma sequÇecia de caracteres

blocks - creating variables.png

  1. Clique no menu Variables. Isso abrirá uma janela lateral.
  2. Selecione o bloco Create variable...
  3. Um prompt do Controlador de Robôs da FIRST aparecerá pedindo um nome para a variável. Nomeie a variável como touchStatus. Clique em OK.

Esse processo criou uma variável chamada touchStatus. Atualmente, touchStatus está indefinido; para defini-lo, o bloco precisa ser usado. Este bloco pode ser encontrado no menu Variáveis, agora que a variável foi criada.

selecting variable specific code.png

Arraste esse bloco e coloque-o abaixo do comentário Touch is not pressed.

Adicione outro abaixo do Touch is pressed.

blocks - adding touchStatus.png

O bloco touchStatus permite que você altere o valor da variável. Dependendo de qual estado ele estiver, touchStatus será definido para outra String. Para isso selecione o bloco de String do menu Text. Como a imagem abaixo mostra:

string.png

Adicione um bloco de string ao touchStatus. Preencha o bloco com uma mensagem de status que relate o estado do sensor. Como "Not pressed" e "Pressed".

naming strings.png

Para exibir essas informações na Driver Station, é necessário utilizar a telemetria. Para acessar os blocos de telemetria, selecione o menu suspenso de Utilitários. O menu de utilitários está em ordem alfabética, então a telemetria está localizada mais para o final das opções do menu suspenso. Selecione o bloco abaixo:

Blocks - telemetry select2.png

Arraste esse bloco e coloque-o abaixo do bloco if/else.

blocks - ts - add telemetry.png

Arraste o bloco de touchStatus para a área de texto da função addData e mude o parâmetro key para "Button"

blocks - ts -telemetry finished.png

Enquanto esse programa estiver operando o estado do botão será mostrado na telemetria.

Dispositivos digitais como um interruptor de limite

Um dos usos mais comuns para um dispositivo digital, como um sensor de toque, é utilizá-lo como um interruptor de limite. A intenção de um interruptor de limite é interromper o funcionamento de um mecanismo, como um braço ou elevador, antes que ultrapasse suas limitações físicas. Nessa aplicação, a energia precisa ser cortada do motor quando o limite é atingido.

O conceito de um interruptor de limite envolve muitas etapas semelhantes à seção anterior sobre a programação de um dispositivo digital. Por essa razão, vamos retomar a partir do conjunto de blocos a seguir:

Blocks_Ts_add comments (1).png

O bloco if/else estabelece um ambiente condicional para o interruptor de limite. Se o sensor de toque não estiver pressionado, o motor pode funcionar; no entanto, se estiver pressionado, o motor não pode funcionar. Para adicionar isso ao código, o bloco Power precisa ser utilizado.

Motor -  testmotor set power.png

Para obter informações sobre onde encontrar blocos específicos para motores, por favor, reveja a seção sobre motores.

Adicione um bloco de Power abaixo do comentário "Touch is not Pressed". Altere o valor de Power para 0.3. Adicione outro bloco de Power abaixo do comentário "Touch is Pressed". Altere o valor de Power para 0.

blocks - ls - adding motor info.png

Este bloco if/else introduz os conceitos básicos de um interruptor de limite. Como na maioria dos sensores, é bom ter telemetria que atualiza a Estação do Motorista sobre o status do sensor. Considere o código da seção anterior ou o seguinte código como ideias potenciais para telemetria.

blocks-Limit switch.png

Olá robô - Banco de testes

OnBot Java


O OnBot Java é uma ferramenta de programação baseada em texto que permite aos programadores usar um navegador da web para criar, editar e salvar seus modos de operação Java (op modes). Nesta seção, os usuários podem aprender como criar um op mode, bem como os conceitos básicos de programação dos atuadores e sensores apresentados no banco de testes.

Siga o guia para obter uma compreensão aprofundada de como trabalhar com o OnBot Java ou navegue até a seção que atenda às suas necessidades:

Seção Objetivos da seção
Criando um Op Mode Concentre-se em como navegar na interface do OnBot Java e criar um op mode.
Fundamentos da programação Explora a estrutura e os elementos-chave necessários para um modo de operação (op mode), assim como alguns dos componentes essenciais do Java.
Programando acionadores Como codificar servos e motores. Esta seção guia através da lógica básica de codificar atuadores, controlar atuadores com um gamepad e utilizar telemetria.
Programando sensores Como codificar um dispositivo digital. Esta seção concentra-se na lógica básica de programar um dispositivo digital, como um Sensor de Toque REV.

Criando um Op Mode

Antes de mergulhar e criar o seu primeiro modo de operação (op mode), você deve considerar o conceito de convenções de nomenclatura. Ao escrever código, o objetivo é ser o mais claro possível sobre o que está acontecendo dentro do código. É aqui que entra o conceito de convenções de nomenclatura. Convenções de nomenclatura comuns foram estabelecidas pelo mundo da programação para denotar variáveis, classes, funções, etc. Os modos de operação compartilham algumas semelhanças com as classes. Assim, a convenção de nomenclatura para modos de operação tende a seguir a convenção de nomenclatura para classes; onde a primeira letra de cada palavra é maiúscula.

Esta seção pressupõe que você já acessou a plataforma OnBot Java durante a introdução ao "Olá Robô - Programação". Se você não souber como acessar o OnBot Java, por favor, reveja esta seção antes de continuar.

Para começar, acesse o Robot Controller Console e vá para a página do OnBot Java. Há algumas coisas importantes para observar na página principal do OnBot Java.

module

  1. Criar Novo Modo de Operação - O botão de sinal de adição abre uma janela para criar um novo modo de operação.

  2. Painel do Navegador de Projetos - Este painel mostra todos os arquivos de projeto Java no Controlador do Robô.

  3. Painel de Edição de Código-Fonte - Esta área é a principal para edição de código.

  4. Painel de Mensagens - Este painel fornece mensagens sobre o sucesso ou falha na compilação do código.

  5. Compilar Tudo - Compila todos os arquivos .java no Controlador do Robô.

Quando um modo de operação é criado ou editado, o editor OnBot Java salvará automaticamente o arquivo .java no sistema de arquivos do Controlador do Robô. No entanto, para executar o código no Controlador do Robô, o arquivo de texto .java precisa ser convertido para um binário que pode ser carregado dinamicamente no aplicativo FTC Robot Controller. Essa conversão é feita compilando os modos de operação.

Selecione o botão Create new Op Mode. Isso abrirá a janela New File. Esta janela permite que os usuários escolham configurações como: nomear seus modos op, selecionar um código de exemplo para desenvolver, ou escolher o tipo de modo op. Para este guia, selecione as seguintes seções:

onbot - create new op mode.png

Após as configurações adequadas terem sido escolhidas, selecione "OK" para criar o modo op. O novo arquivo será exibido no Painel de Navegação do Projeto.

Fundamentos da programação

Durante o processo de criação de um modo op, a ferramenta OnBot Java oferecia várias opções para escolher. Essas opções definem quais informações já estão incluídas no modo op, o que pode simplificar o que um programador precisa fazer do seu lado. Por exemplo, uma opção foi dada para selecionar um exemplo. No OnBot Java, esses exemplos atuam como modelos; fornecendo declarações, estrutura lógica e sintaxe para diferentes casos de uso em robótica.

Na seção anterior, as seguintes configurações foram selecionadas: a opção Setup Code for Configured Hardware, a opção TeleOp e um exemplo de código chamado BlankLinearOpMode. Essas opções combinadas configuram a estrutura básica de código necessária para ter um modo op funcional.

Um modo op é considerado um conjunto de instruções para um robô seguir a fim de entender o mundo ao seu redor. Embora o SDK forneça estruturas prontas para modos op, entender quais conceitos o modelo está utilizando e por que, ajuda a aumentar o conhecimento em programação. Siga nesta seção para aprender mais sobre o modelo de modo op e os conceitos de programação que compõem sua estrutura.

package org.firstinspires.ftc.teamcode;

import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
import com.qualcomm.robotcore.hardware.Blinker;
import com.qualcomm.robotcore.hardware.Gyroscope;
import com.qualcomm.robotcore.hardware.ColorSensor;
import com.qualcomm.robotcore.hardware.Servo;
import com.qualcomm.robotcore.hardware.DigitalChannel;
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;
import com.qualcomm.robotcore.util.ElapsedTime;

@TeleOp

public class HelloWorld_TeleOp extends LinearOpMode {
    private Gyroscope imu;
    private ColorSensor test_color;
    private DcMotor test_motor;
    private Servo test_servo;
    private DigitalChannel test_touch;


    @Override
    public void runOpMode() {
        imu = hardwareMap.get(Gyroscope.class, "imu");
        test_color = hardwareMap.get(ColorSensor.class, "test_color");
        test_motor = hardwareMap.get(DcMotor.class, "test_motor");
        test_servo = hardwareMap.get(Servo.class, "test_servo");
        test_touch = hardwareMap.get(DigitalChannel.class, "test_touch");

        telemetry.addData("Status", "Initialized");
        telemetry.update();
        // Wait for the game to start (driver presses PLAY)
        waitForStart();

        // run until the end of the match (driver presses STOP)
        while (opModeIsActive()) {
            telemetry.addData("Status", "Running");
            telemetry.update();

        }
    }

O bloco de código fornece a estrutura do modelo de modo op com base na Configuração Hello Robot e com alguns comentários ausentes. Se outra configuração estiver sendo usada, o código será ligeiramente diferente, mas muitos dos conceitos subjacentes são os mesmos.

Conceitos de programação

No início do modo op, há uma anotação que ocorre antes da definição da classe. Essa anotação afirma que este é um modo op teleoperado (ou seja, controlado pelo motorista):]

@TeleOp

Em Java, as anotações são metadados ou informações descritivas sobre o código. Neste caso, a anotação está sendo usada para informar ao sistema que este modo de operação é tele-operado. Alterar a anotação de @TeleOp para @Autonomous modificará o código para um modo de operação autônomo.

public class HelloWorld_TeleOp extends LinearOpMode {

Você também pode observar que o editor OnBot Java criou cinco variáveis de membro privado para este modo de operação. Essas variáveis irão conter referências aos cinco dispositivos configurados que o editor OnBot Java detectou na configuração ativa.

private Gyroscope imu;
private ColorSensor test_color;
private DcMotor test_motor;
private Servo test_servo;
private DigitalChannel test_touch;

Em seguida, há um método sobrescrito chamado runOpMode. Todo modo de operação do tipo LinearOpMode deve implementar este método. Este método é chamado quando um usuário seleciona e executa o modo de operação.

    @Override
    public void runOpMode() {

O mapeamento de hardware foi introduzido na seção de configuração, como um processo de duas partes. A primeira parte do processo consistiu em criar um arquivo de configuração. A segunda parte do processo é obter referências aos dispositivos de hardware a partir do objeto hardwareMap.

O objeto hardwareMap está disponível para uso no método runOpMode. Trata-se de um objeto do tipo classe hardwareMap.

No início do método runOpMode, o modo de operação utiliza o objeto hardwareMap para obter referências aos dispositivos de hardware listados no arquivo de configuração do Controlador do Robô:

        imu = hardwareMap.get(Gyroscope.class, "imu");
        test_color = hardwareMap.get(ColorSensor.class, "test_color");
        test_motor = hardwareMap.get(DcMotor.class, "test_motor");
        test_servo = hardwareMap.get(Servo.class, "test_servo");
        test_touch = hardwareMap.get(DigitalChannel.class, "test_touch");

A chamada do método hardwareMap.get() é utilizada para recuperar os dispositivos de hardware e atribuí-los a variáveis. A chamada do método aceita dois argumentos: uma referência à classe específica de dispositivos de hardware à qual o dispositivo pertence e o nome do dispositivo de hardware no arquivo de configuração. O nome fornecido no hardwareMap.get() precisa corresponder ao nome do dispositivo no arquivo de configuração. Se os nomes não coincidirem, o modo de operação gerará um erro em tempo de execução indicando que não é possível encontrar o dispositivo.

Para mais infomrações sobre erros de execução cheque nossa seção de Erros comuns

Nas próximas declarações do exemplo, o modo de operação solicita ao usuário que pressione o botão de início para continuar. Ele utiliza outro objeto disponível no método runOpMode. Esse objeto é chamado de "telemetry", e o modo de operação utiliza o método addData para adicionar uma mensagem a ser enviada para a Estação do Motorista. Em seguida, o modo de operação chama o método update para enviar a mensagem para a Estação do Motorista. Depois disso, ele chama o método waitForStart para aguardar até que o usuário pressione o botão de início na estação do motorista para iniciar a execução do modo de operação.

Telemetria é o processo de coleta e transmissão de dados. Na robótica, a telemetria é frequentemente utilizada para enviar dados internos provenientes de atuadores e sensores para a Estação do Motorista. Esses dados podem ser analisados pelos usuários para tomar decisões que possam aprimorar o código.

        telemetry.addData("Status", "Initialized");
        telemetry.update();
        // Wait for the game to start (driver presses PLAY)
        waitForStart();

Todas as operações lineares (linear op modes) devem conter uma instrução waitForStart para garantir que o robô não começará a executar o modo de operação até que o motorista pressione o botão de início.

Após receber um comando de início, o modo de operação entra em um loop while e continua iterando neste loop até que o modo de operação não esteja mais ativo (ou seja, até que o usuário pressione o botão de parada na Estação do Motorista):

        // run until the end of the match (driver presses STOP)
        while (opModeIsActive()) {
            telemetry.addData("Status", "Running");
            telemetry.update();

        }

Conforme o modo de operação itera no loop while, ele continuará a enviar mensagens de telemetria com o índice "Status" e a mensagem "Running" para serem exibidas na Estação do Motorista.

Sintaxe

Linguagens de programação, assim como qualquer idioma, possuem um conjunto de regras e princípios orientadores que permitem que as declarações sejam compreendidas universalmente. Elementos como pontuação, estrutura de palavras e formatação desempenham um papel na interpretação de uma linha de código. Em linguística e ciência da computação, as regras que governam a estrutura de uma sentença são conhecidas como sintaxe.

É importante compreender a sintaxe do Java, pois erros de sintaxe serão comuns e difíceis de rastrear sem um nível básico de entendimento.

Programação Orientada a objetos

Esta seção fez várias referências a métodos, objetos e classes. Todos esses são tópicos de programação intermediários a avançados, frequentemente centrados no conceito de programação orientada a objetos. O objetivo do guia Olá Robô é servir como um curso introdutório para programação de robôs, em vez de aprofundar-se em conceitos de programação.

No entanto, mantenha a programação orientada a objetos em mente à medida que suas habilidades se desenvolvem. Por enquanto, a coisa mais importante a saber é que, ocasionalmente, métodos dentro das bibliotecas do SDK precisarão ser chamados para realizar uma determinada tarefa. Por exemplo, a linha OláRobo_TeleOp.opModeIsActive() chama o método opModeIsActive, que é o procedimento no SDK capaz de indicar quando o modo de operação foi ativado pelo telefone da estação do motorista.

À medida que avançarmos, grande parte do código específico para motores, servos ou sensores lidará com chamadas a outros métodos ou classes.

For more information on classes and methods in the SDK check out the Java Doc.

Programando acionadores

Noções básicas do servo

O objetivo desta seção é abordar alguns conceitos básicos de programação de um servo no OnBot Java. Ao final desta seção, os usuários devem ser capazes de controlar um servo com um gamepad, bem como entender algumas das necessidades fundamentais de programação relacionadas ao servo.

Esta seção está considerando o Servo Robô Inteligente no seu modo padrão. Se o seu servo foi alterado para funcionar em modo contínuo ou com limites angulares, ele não se comportará da mesma forma usando os exemplos de código abaixo. Você pode aprender mais sobre o Servo Robô Inteligente ou alterar o modo do servo através do SRS Programmer clicando nos hiperlinks.

Com um servo típico, você pode especificar uma posição alvo para o servo. O servo girará seu eixo do motor para mover-se até a posição alvo e, em seguida, manterá essa posição, mesmo que forças moderadas sejam aplicadas para tentar perturbar sua posição.

module

Tanto para o Blocks quanto para o OnBot Java, você pode especificar uma posição alvo que varia de 0 a 1 para um servo. Para um servo com uma faixa de 270°, se a faixa de entrada fosse de 0 a 1, então um sinal de entrada de 0 faria com que o servo se movesse para -135°. Para um sinal de entrada de 1, o servo se moveria para +135°. Entradas entre o mínimo e o máximo têm ângulos correspondentes distribuídos uniformemente entre o ângulo mínimo e máximo do servo. Isso é importante ter em mente enquanto aprende a programar servos.

Programando um Servo

Adicione a seguinte linha ao loop while do Op Mode: test_servo.setPosition(1);

Como segue abaixo:

        while (opModeIsActive()) {
            test_servo.setPosition(1);
            telemetry.addData("Status", "Running");
            telemetry.update();

        }

Selecione Build everything para compilar o código

Execute este modo operacional (op mode) no banco de testes duas vezes e considere as seguintes perguntas:

A intenção do test_servo.setPosition(); é definir a posição do servo. Se o servo já estiver na posição definida quando o código é executado, ele não mudará de posição. Vamos tentar adicionar a linha test_servo.setPosition(0); ao código na seção de inicialização.

public void runOpMode() {
        imu = hardwareMap.get(Gyroscope.class, "imu");
        test_color = hardwareMap.get(ColorSensor.class, "test_color");
        test_motor = hardwareMap.get(DcMotor.class, "test_motor");
        test_servo = hardwareMap.get(Servo.class, "test_servo");
        test_touch = hardwareMap.get(DigitalChannel.class, "test_touch");
        
        test_servo.setPosition(0);

        telemetry.addData("Status", "Initialized");
        telemetry.update();
        // Wait for the game to start (driver presses PLAY)
        waitForStart();

        // run until the end of the match (driver presses STOP)
        while (opModeIsActive()) {
            test_servo.setPosition(1);
            telemetry.addData("Status", "Running");
            telemetry.update();

        }
    }
}

Tente executar este modo operacional no banco de testes. Dê algum tempo entre pressionar "init" e pressionar "play" e considere a seguinte pergunta: O que é diferente da execução anterior?

A linha test_servo.setPosition(0); que foi adicionada na etapa anterior altera a posição do servo para 0 durante a fase de inicialização, então, quando o modo operacional é executado, o servo sempre se moverá para a posição 1. Para algumas aplicações, iniciar o servo em um estado conhecido, como na posição zero, é benéfico para o funcionamento de um mecanismo. Definir o servo no estado conhecido durante a inicialização garante que ele esteja na posição correta quando o modo operacional é executado.

Programando um servo com um controle

O foco deste exemplo é atribuir determinadas posições de servo a botões no gamepad. Para este exemplo, o estado conhecido permanecerá na posição 0, de modo que, após a inicialização, o servo estará na posição de -135 graus no intervalo do servo. A lista a seguir mostra quais botões correspondem a quais posições do servo.

Botões Posição em graus Posiçaõ no código
Y -135 0
X 0 0.5
B 0 0.5
A 135 1

A melhor maneira de alternar a posição do servo será usar uma instrução condicional if/else if. Uma instrução if avalia se uma afirmação condicional é verdadeira ou falsa. Se a afirmação condicional for verdadeira, uma ação definida (como o movimento do servo) é realizada. Se a afirmação condicional for falsa, a ação não é realizada.

Uma instrução if/else if aceita várias afirmações condicionais diferentes. Se a primeira afirmação condicional for falsa, então a segunda afirmação condicional é analisada. Para entender melhor esse conceito, considere o seguinte código:

if (gamepad1.y){
    //move to -135 degrees
    test_servo.setPosition(0);
    
} else if (gamepad1.x || gamepad1.b) {
    //move to 0 degrees
    test_servo.setPosition(0.5);

} else if (gamepad1.a) {
    //move to 135 degrees 
    test_servo.setPosition(1);
    
}

Existem três caminhos diferentes nesta instrução if/else if. Se a primeira afirmação condicional for verdadeira (o botão Y está pressionado), o servo se move para a posição 0 e as outras afirmações condicionais são ignoradas. Se a primeira condição for falsa (o botão Y não está pressionado), a segunda condição é analisada. Esse comportamento se repete até que uma condição seja atendida ou todas as condições tenham sido testadas e consideradas falsas.

|| é um operador lógico em Java. Esse símbolo é o equivalente ao "ou". Usando isso como um parâmetro condicional, ele vai verificar se o botão x ou b estão pressionados para ser verdadeira.

public void runOpMode() {
        imu = hardwareMap.get(Gyroscope.class, "imu");
        test_color = hardwareMap.get(ColorSensor.class, "test_color");
        test_motor = hardwareMap.get(DcMotor.class, "test_motor");
        test_servo = hardwareMap.get(Servo.class, "test_servo");
        test_touch = hardwareMap.get(DigitalChannel.class, "test_touch");
        
        test_servo.setPosition(0);

        telemetry.addData("Status", "Initialized");
        telemetry.update();
        // Wait for the game to start (driver presses PLAY)
        waitForStart();

        // run until the end of the match (driver presses STOP)
        while (opModeIsActive()) {
            if (gamepad1.y){
                //move to -135 degrees
                test_servo.setPosition(0);
    
            } else if (gamepad1.x || gamepad1.b) {
                //move to 0 degrees
                test_servo.setPosition(0.5);

            } else if (gamepad1.a) {
                //move to 135 degrees 
                test_servo.setPosition(1);
                        }
                        
            telemetry.addData("Status", "Running");
            telemetry.update();

        }
    }
}

Servos e telemetria

Lembrando que telemetria é o processo de coletar e transmitir dados. Na robótica, a telemetria é usada para enviar dados internos de atuadores e sensores para a Estação do Motorista (Driver Station). Esses dados podem ser analisados pelos usuários para tomar decisões que podem aprimorar o código.

A telemetria mais útil do servo é a posição do servo ao longo de sua faixa de 270 graus. Para obter essa informação, a seguinte linha precisa ser usada.

test_servo.getPosition();

Na seção de fundamentos de programação, a linha telemetry.addData(); foi discutida brevemente. Essa chamada de método recebe um parâmetro de chave (key) e variável e envia as informações para a Estação do Motorista. A chave é uma string, ou uma linha de texto, que deve definir a variável. Neste caso, telemetry.addData(); está sendo usado para enviar para a Estação do Motorista a posição do servo conforme ela é alterada, então a chave pode ser "Posição do Servo". O parâmetro, no entanto, será a chamada do método test_servo.getPosition();.

double motorPower = 0; 
while (opModeIsActive()) {
           if (gamepad1.y){
                //move to -135 degrees
                test_servo.setPosition(0);
    
            } else if (gamepad1.x || gamepad1.b) {
                //move to 0 degrees
                test_servo.setPosition(0.5);

            } else if (gamepad1.a) {
                //move to 135 degrees 
                test_servo.setPosition(1);
            
            telemetry.addData("Servo Position", test_servo.getPosition());
            telemetry.addData("Status", "Running");
            telemetry.update();

        }

Noções básicas de motor

Modifique o seu modo de operação (op mode) para adicionar o código relacionado ao motor. Isso pode ser feito limpando as modificações atuais do seu código ou adicionando o código relacionado ao motor ao seu modo de operação atual.

O objetivo desta seção é abordar alguns conceitos básicos de programação de um motor no ambiente OnBot Java. Ao final desta seção, os usuários deverão ser capazes de controlar um motor usando um gamepad, bem como compreender alguns dos fundamentos ao lidar com codificadores de motor.

Controlando motores

Adicione a linha test_motor.setPower(1); ao loop while do modo de operação (op mode).

        while (opModeIsActive()) {
            test_motor.setPower(1);
            
            telemetry.addData("Status", "Running");
            telemetry.update();

        }

Selecione Build Everything para compilar o código

Tente executar este modo de operação no banco de testes e considere as seguintes perguntas:

O nível de potência enviado ao motor depende do número numérico atribuído ao motor. A mudança de 1 para 0,3 diminuiu a velocidade do motor de 100% do ciclo de trabalho para 30% do ciclo de trabalho. Enquanto isso, a mudança para -1 permitiu que o motor girasse a 100% do ciclo de trabalho na direção oposta. Portanto, a potência pode ser variada para mover um motor para frente ou para trás. No entanto, a linha test_motor.setPower(1); fará com que o motor funcione na direção atribuída até que algo no código pare o motor ou cause uma mudança na direção.

Controlando motores com um gamepad

Na seção anterior, você aprendeu como configurar o motor para funcionar em um nível de potência específico em uma direção específica. No entanto, em algumas aplicações, pode ser necessário controlar o motor com um gamepad para alterar facilmente a direção ou o nível de potência de um mecanismo.

Para esta seção, vamos criar uma variável dupla chamada motorPower. Essa variável será criada dentro do modo de operação (op mode), mas fora do loop while.

public void runOpMode() {
        imu = hardwareMap.get(Gyroscope.class, "imu");
        test_color = hardwareMap.get(ColorSensor.class, "test_color");
        test_motor = hardwareMap.get(DcMotor.class, "test_motor");
        test_servo = hardwareMap.get(Servo.class, "test_servo");
        test_touch = hardwareMap.get(DigitalChannel.class, "test_touch");
        
        double motorPower = 0; 

        telemetry.addData("Status", "Initialized");
        telemetry.update();
        // Wait for the game to start (driver presses PLAY)
        waitForStart();
        // run until the end of the match (driver presses STOP)
        while (opModeIsActive()) {
            
            telemetry.addData("Status", "Running");
            telemetry.update();

        }
    }
}

Um tipo de dado double é um tipo numérico que pode armazenar números com pontos decimais. Dado que a potência, ou ciclo de trabalho, do motor opera em uma escala entre 1 e -1, a variável motorPower precisará ser capaz de armazenar dados numéricos com pontos decimais.

Considere as seguintes linhas de código:

  motorPower = - this.gamepad1.left_stick_y;
  test_motor.setPower(motorPower);

A linha motorPower = -this.gamepad1.left_stick_y; recebe uma entrada numérica que corresponde à posição do joystick do gamepad conforme ele se move ao longo do eixo y, e a atribui à variável motorPower. A próxima linha, test_motor.setPower(motorPower);, define a potência do motor como sendo igual à variável motorPower.

Observe que, para os gamepads Logitech F310, o valor Y de um joystick varia de -1, quando o joystick está em sua posição mais alta, a +1, quando o joystick está em sua posição mais baixa. Para alterar a relação direcional entre o motor e o joystick, de modo que a posição mais alta do joystick esteja correlacionada com a direção para frente do motor, é necessário usar um símbolo negativo ou o operador de negação.

// run until the end of the match (driver presses STOP)
double motorPower = 0; 
while (opModeIsActive()) {
            motorPower = - this.gamepad1.left_stick_y;
            test_motor.setPower(motorPower);
            
            telemetry.addData("Status", "Running");
            telemetry.update();

        }

Motores e telemetria

Lembre-se de que telemetria é o processo de coletar e transmitir dados. Na robótica, a telemetria é usada para enviar dados internos de atuadores e sensores para a Estação do Condutor (Driver Station). Esses dados podem então ser analisados pelos usuários para tomar decisões que podem melhorar o código.

Uma das formas mais comuns de dados de telemetria de motores é a informação retirada do encoder do motor. Motores DC REV, como o Core Hex Motor, são equipados com encoders internos que transmitem informações de posição na forma de contagens. Para obter informações dos encoders, a seguinte linha precisa ser utilizada:

test_motor.getCurrentPosition();

Na seção de fundamentos de programação, a linha telemetry.addData(); foi brevemente discutida. Essa chamada de método recebe um parâmetro de chave e variável e envia as informações para a Estação do Condutor (Driver Station). A chave é uma string, ou uma linha de texto, que deve definir a variável. Neste caso, o telemetry.addData(); está sendo usado para exibir a posição do motor na forma de contagens do encoder, então a chave pode ser "Encoder Value." No entanto, o parâmetro será a chamada de método test_motor.getCurrentPosition();.

double motorPower = 0; 
while (opModeIsActive()) {
            motorPower = - this.gamepad1.left_stick_y;
            test_motor.setPower(motorPower);
            
            telemetry.addData("Encoder Value", test_motor.getCurrentPosition());
            telemetry.addData("Status", "Running");
            telemetry.update();

        }

Para obter mais informações sobre a programação de encoders, consulte a página "Using Encoders" (Usando Encoders). Para obter mais informações sobre a métrica "counts per revolution" (contagens por revolução) e como usá-la, consulte a página "Encoders" (Encoders).

Programando sensores

Noções básicas do sensor de toque

O objetivo desta seção é abordar alguns dos conceitos básicos de programação de um dispositivo digital, ou sensor de toque, dentro do ambiente de programação Blocks.

Antes de programar com um sensor de toque ou outro dispositivo digital, é importante entender o que é um dispositivo digital e quais são as aplicações comuns para esses dispositivos. Visite a página "Digital Sensors" (Sensores Digitais) para obter mais informações.

Programando um dispositivo digital

Modifique seu modo operacional para adicionar o código relacionado ao dispositivo digital. Isso pode ser feito limpando as modificações de código atuais ou adicionando o código do dispositivo digital ao seu modo operacional.

A informação proveniente de dispositivos digitais vem em dois estados, também conhecidos como estados binários. A forma mais comum de utilizar essa informação é através de uma instrução condicional, como uma instrução if/else. A linha test_touch.getState(); coleta o estado binário FALSO/VERDADEIRO do sensor de toque e atua como condição para a instrução if/else.

if (test_touch.getState()){
    //Touch Sensor is not pressed 
} else {
    //Touch Sensor is pressed 
        }

O código acima destaca a estrutura básica da instrução if/else para um dispositivo digital. O estado FALSO/VERDADEIRO de um sensor de toque REV corresponde a se o botão no sensor de toque está pressionado ou não. Quando o botão não está pressionado, o estado do sensor de toque é verdadeiro. Quando o botão é pressionado, o estado do sensor de toque é falso. Essa condição é refletida pelos comentários no código.

A maneira mais básica de usar um dispositivo digital é usar a telemetria para exibir informações, como o status do botão do sensor de toque. Para fazer isso, vamos criar uma variável de string chamada touchStatus. Essa variável será criada dentro do modo de operação (op mode).

String é um conjunto de caracteres

public void runOpMode() {
        imu = hardwareMap.get(Gyroscope.class, "imu");
        test_color = hardwareMap.get(ColorSensor.class, "test_color");
        test_motor = hardwareMap.get(DcMotor.class, "test_motor");
        test_servo = hardwareMap.get(Servo.class, "test_servo");
        test_touch = hardwareMap.get(DigitalChannel.class, "test_touch");
        
        String touchStatus = "";

A linha String touchStatus = ""; declara que a variável touchStatus é uma variável de string vazia. Isso significa que touchStatus está atualmente armazenando uma string sem nenhum caractere.

public void runOpMode() {
        imu = hardwareMap.get(Gyroscope.class, "imu");
        test_color = hardwareMap.get(ColorSensor.class, "test_color");
        test_motor = hardwareMap.get(DcMotor.class, "test_motor");
        test_servo = hardwareMap.get(Servo.class, "test_servo");
        test_touch = hardwareMap.get(DigitalChannel.class, "test_touch");
        
        String touchStatus = "";
        
        telemetry.addData("Status", "Initialized");
        telemetry.update();
        
        // Wait for the game to start (driver presses PLAY)
        waitForStart();
        
        // run until the end of the match (driver presses STOP)
        while (opModeIsActive()) {
        
            if (test_touch.getState()){
                //Touch Sensor is not pressed 
            } else {
                //Touch Sensor is pressed 
                        }
            telemetry.addData("Status", "Running");
            telemetry.update();

        }
    }
}

Agora, a variável touchStatus está vazia, mas para este exemplo, ela deverá ser alterada para refletir o status do sensor de toque. Para fazer isso, touchStatus deve ser definida como "Não Pressionado" ou "Pressionado".

 if (test_touch.getState()){
    //Touch Sensor is not pressed  
    touchStatus = "Not Pressed";
    
 } else {
    //Touch Sensor is pressed 
    touchStatus = "Pressed";
                        }

Para exibir as informações atribuídas ao touchStatus, é necessário utilizar a telemetria. Na seção de fundamentos de programação, a linha telemetry.addData() foi discutida brevemente. Essa chamada de método recebe um parâmetro de chave e variável, e exibe as informações no Driver Station. A chave é uma string, ou uma linha de texto, que deve definir a variável. Neste caso, a telemetry.addData() está sendo usada para exibir alterações na variável touchStatus, então "Touch Status" seria uma boa chave. O parâmetro será a variável touchStatus. Adicione esta linha acima da linha telemetry.update(); no loop while.

telemetry.addData("Touch Sensor", touchStatus);

Dispositivos digitais como interruptores de limite

Um dos usos mais comuns para um dispositivo digital, como um sensor de toque, é utilizá-lo como um interruptor de limite. A finalidade de um interruptor de limite é interromper um mecanismo, como um braço ou elevador, antes que ele ultrapasse suas limitações físicas. Nesta aplicação, a alimentação precisa ser cortada do motor quando o limite é atingido. Programar um interruptor de limite requer a mesma lógica if/else aplicada na seção anterior. Se o estado do sensor de toque for verdadeiro (não está pressionado), o motor terá energia. Caso contrário (está pressionado), o motor não terá energia.

 if (test_touch.getState()){
    //Touch Sensor is not pressed  
     test_motor.setPower(0.3);
    
} else {
    //Touch Sensor is pressed 
    test_motor.setPower(0);
                        }

O bloco de código acima introduz os conceitos básicos de um interruptor de limite. Assim como na maioria dos sensores, é bom ter telemetria que atualiza a Estação do Motorista sobre o status do sensor. Considere o seguinte código:

public void runOpMode() {
        imu = hardwareMap.get(Gyroscope.class, "imu");
        test_color = hardwareMap.get(ColorSensor.class, "test_color");
        test_motor = hardwareMap.get(DcMotor.class, "test_motor");
        test_servo = hardwareMap.get(Servo.class, "test_servo");
        test_touch = hardwareMap.get(DigitalChannel.class, "test_touch");
        
        String touchStatus = "";
        
        telemetry.addData("Status", "Initialized");
        telemetry.update();
        
        // Wait for the game to start (driver presses PLAY)
        waitForStart();
        
        // run until the end of the match (driver presses STOP)
        while (opModeIsActive()) {
        
         if (test_touch.getState()){
            //Touch Sensor is not pressed  
           test_motor.setPower(0.3);
           touchStatus = "Not Pressed";
    
          } else {
            //Touch Sensor is pressed 
            test_motor.setPower(0);
            touchStatus = "Pressed";
                        }
                        
            telemetry.addData("Touch Sensor:", touchStatus);
            telemetry.addData("Status", "Running");
            telemetry.update();

        }
    }
}

Olá robô - Controle do Robô (OnBot Java)

Olá robô - Controle do Robô (OnBot Java)

Controle do robô


Compreendidos os princípios básicos de controle de atuadores e obtenção de feedback de sensores a partir do Olá Robô - Test Bed, é hora de começar a configurar e programar nosso robô para controle Teleoperado e Autônomo!

Seção Objetivos da seção
Criando um robô básico Apresenta um potencial robô para trabalhar, assim como o arquivo de configuração utilizado nas seções seguintes.
Noções básicas de transmissão Diferenças entre drivetrains diferencial e omnidirecional e seu impacto nos tipos de controle teleoperado.

Antes de continuar, é recomendado completar, no mínimo, um drivetrain. Existem algumas opções diferentes dependendo do kit que está sendo utilizado. Para este guia, o Class Bot V2 é utilizado. Consulte o guia de montagem para obter instruções completas de montagem para o Class Bot V2!

Criar um robô básico

A imagem abaixo destaca os principais componentes de hardware do Class Bot V2. Esses componentes são importantes para entender o processo de configuração.

module

A seção de Configuração do Olá Robô concentrou-se na configuração dos componentes no Test Bed. Para avançar nas seções de programação do Controle do Robô, é necessário criar um novo arquivo de configuração para os componentes no robô. É sua escolha quais nomes de variáveis você deseja atribuir ao seu robô, mas, para referência, este guia usará os seguintes nomes para cada componente de hardware.

Componente de Hardware Tipo de Hardware Nome
Motor direito REV Robotics UltraPlanetary HD Hex Motor right motor
Motor esquerdo REV Robotics UltraPlanetary HD Hex Motor left motor
Braço motor REV Robotics Core Hex Motor arm
Garra servo Servo claw
Sensor de toque REV Touch Sensor touch

Noções básicas de transmissão

Antes de continuar, é importante entender o comportamento mecânico de diferentes drivetrains. As duas categorias mais comuns de drivetrains são Diferencial e Omnidirecional. O drivetrain do Class Bot é um drivetrain diferencial. A tabela abaixo destaca as principais características desses dois tipos de drivetrains.

module

Tração diferencial Omnidirecional
Tipo mais comum de transmissão Pode se mover em qualquer direção
Se move ao longo de um eixo central Varia a força em cada roda para se mover lateralmente ou linearmente
Aplica mais potência de um dos lados para para mudar a direção Programação mais complexa
Pode ter diferentes nomes (4WD, 6WD, West Coast..) Precisa de mais de dois motores

Tipos de controle teleoperado

Existem várias maneiras de controlar um robô teleoperado. Ao usar o REV Control System, isso é feito com um dispositivo de Driver Station e gamepads. Existem várias maneiras de usar um controlador para movimentar um drivetrain diferencial. Duas das formas convencionais são Tank Drive e Arcade Drive.

Tração Tank

Para o Tank Drive, cada lado do drivetrain diferencial é mapeado para seu próprio joystick. Alterar a posição de cada joystick permite que o drivetrain vire e mude sua direção. Existe um código de exemplo no aplicativo Controlador do Robô para controlar um drivetrain diferencial dessa maneira.

Arcade Drive

Para o Arcade Drive, cada lado do drivetrain diferencial é controlado por um único joystick. Alterar a posição do joystick muda a potência aplicada a cada lado do drivetrain, permitindo um comando específico. Os controles de Arcade Drive geralmente têm o movimento esquerda/direita do joystick configurado para girar o robô em torno do seu eixo, com o movimento para frente/para trás fazendo o robô avançar e retroceder. Mais informações sobre Arcade Drive podem ser encontradas nas próximas seções.

Com o robô configurado e uma compreensão básica de drivetrains e tipos de controle teleoperado, podemos avançar para a programação do drivetrain para movimentar o robô.

Olá robô - Controle do Robô (OnBot Java)

Navegação do robô - OnBot Java


Introdução a navegação do robô

Como indicado na seção Olá Robô - Controle do Robô, o controle de robôs assume muitas formas diferentes. Um dos tipos de controle a ser considerado para robôs com drivetrains é a navegação do robô.

A navegação do robô como conceito depende do tipo de drivetrain e do tipo de modo de operação. Por exemplo, o código para controlar um drivetrain mecanum difere do código usado para controlar um drivetrain diferencial. Também há diferença entre codificar para direção teleoperada, com um gamepad, e codificar para autonomia, onde cada movimento do robô deve ser definido dentro do código.

A próxima seção passa pelos fundamentos da programação para um drivetrain diferencial, bem como como configurar um código de drivetrain teleoperado no estilo arcade. Os conceitos e a lógica destacados nesta seção serão aplicáveis à seção de controle autônomo Elapsed Time.

Seções Objetivos da seção
Noções básicas de programação de transmissão O que considerar quando estiver programando uma transmissão e como aplicar.

Noções básicas de programação de transmissão

Programação de motores de transmissão

Comece criando um Op Mode chamado de DualDrive.

Visite a seção OnBot Java para obter mais informações sobre como criar um modo operacional (op mode). O op mode abaixo concentra-se apenas no mapeamento de hardware dos motores relevantes do sistema de transmissão.

package org.firstinspires.ftc.teamcode;

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

@TeleOp
public class DualDrive extends LinearOpMode {
  private DcMotor rightmotor;
  private DcMotor leftmotor;

  @Override
  public void runOpMode() {

        rightmotor = hardwareMap.get(DcMotor.class, "rightmotor");
        leftmotor = hardwareMap.get(DcMotor.class, "leftmotor");
        
        waitForStart();

        while (opModeIsActive()) {

        }
    }
}

Dado que o foco desta seção é criar um sistema de transmissão funcional no código, vamos começar adicionando rightmotor.setPower(1); e leftmotor.setPower(1); ao loop while do op mode.

while (opModeIsActive()) {
        rightmotor.setPower(1);
        leftmotor.setPower(1);
        }

Antes de prosseguir, tente executar o código conforme está e considere as seguintes perguntas:

Quando os motores funcionam em velocidades diferentes, eles giram em torno do ponto central de pivô. No entanto, os motores estão ambos configurados com uma potência (ou ciclo de trabalho) de 1?

Os motores de corrente contínua são capazes de girar em duas direções diferentes, dependendo do fluxo de corrente: no sentido horário e no sentido anti-horário. Ao usar um valor de potência positivo, o Control Hub envia corrente para o motor para fazê-lo girar no sentido horário. Com a classe Bot e o código atual, ambos os motores estão atualmente configurados para girar no sentido horário. No entanto, se você colocar o robô em blocos e executar o código novamente, verá que os motores giram em direções opostas. Com a maneira espelhada como os motores são montados no sistema de transmissão, um motor é naturalmente o inverso do outro. Por que o motor inverso faz o robô girar em círculos? Tanto a velocidade quanto a direção de rotação das rodas afetam a direção geral em que o robô se move. Neste caso, ambos os motores foram designados para ter a mesma potência e direção, mas a forma como os motores transferem o movimento para as rodas faz com que o robô gire em vez de avançar.

Consulte a seção de Introdução ao Movimento da REV Robotics para obter mais informações sobre a mecânica de transferência de movimento e potência.

Na caixa de informações anterior, foi solicitado que você determinasse em que direção o robô girava. O robô pivota na direção do motor invertido. Por exemplo, quando o motor direito é o motor invertido, o robô irá pivotar para a direita. Se o motor esquerdo for o motor invertido, o robô pivotará para a esquerda.

O efeito dos motores do sistema de transmissão no movimento]

module

Para a classe Bot, em que o robô pivota para a direita, o motor direito será invertido. Adicione a linha rightmotor.setDirection(DcMotorSimple.Direction.REVERSE); ao op mode, logo abaixo das declarações de variáveis.

public void runOpMode() {
        float x;
        double y;

        rightmotor = hardwareMap.get(DcMotor.class, "rightmotor");
        leftmotor = hardwareMap.get(DcMotor.class, "leftmotor");

        rightmotor.setDirection(DcMotorSimple.Direction.REVERSE);
        
        waitForStart();

        while (opModeIsActive()) {
              rightmotor.setPower(1);
              leftmotor.setPower(1);
            }
   }

Adicionar a linha de código rightmotor.setDirection(DcMotorSimple.Direction.REVERSE); inverte a direção do motor direito. Agora, ambos os motores consideram a mesma direção como a direção para a frente.

Estilo Arcade de condução

Lembre-se de que quando os motores estavam girando em direções opostas, o robô girava em círculos. Essa mesma lógica será usada para controlar o robô usando o estilo arcade de controle mencionado na seção Hello Robot - Autonomous Robot.

Programando com controle

Para começar, crie duas variáveis double chamadas drive e turn.

public void runOpMode() {
        double x;
        double y;

        rightmotor = hardwareMap.get(DcMotor.class, "rightmotor");
        leftmotor = hardwareMap.get(DcMotor.class, "leftmotor");

        rightmotor.setDirection(DcMotorSimple.Direction.REVERSE);
        
        waitForStart();

Atribua a y o valor -gamepad1.right_stick_y;, que corresponde ao eixo y do joystick direito.

Lembre-se de que os valores positivos/negativos inseridos pelo eixo y do gamepad são inversos dos valores positivos/negativos do motor.

Atribua x como x = gamepad1.right_stick_x;, que é o eixo x do joystick direito do gamepad. O eixo x do joystick não precisa ser invertido.

while (opModeIsActive()) {
        x = gamepad1.right_stick_x;
        y = -gamepad1.right_stick_y;
        
        rightmotor.setPower(1);
        leftmotor.setPower(1);
        }

Definir x = gamepad1.right_stick_x; e y = -gamepad1.right_stick_y; atribui valores do joystick do gamepad a x e y. Como mencionado anteriormente, o joystick fornece valores ao longo de um sistema de coordenadas bidimensional. y recebe o valor do eixo y e x recebe o valor do eixo x. Ambos os eixos geram valores entre -1 e 1.

Para entender melhor, considere a seguinte tabela. A tabela mostra o valor esperado gerado ao mover o joystick completamente em uma direção, ao longo do eixo. Por exemplo, quando o joystick é empurrado completamente na direção para cima, os valores das coordenadas são (0,1).

A tabela abaixo assume que o valor de y foi invertido no código

module

Agora que você tem uma compreensão melhor de como o movimento físico do gamepad afeta as entradas numéricas fornecidas ao seu sistema de controle, é hora de considerar como controlar o sistema de propulsão usando o joystick.

Lembre-se, da seção de Programação dos Motores da Transmissão, que a velocidade e a direção de um motor desempenham um papel importante na forma como a transmissão se move.

Os resultados numéricos para setPower determinam a velocidade e direção dos motores. Por exemplo, quando ambos os motores são ajustados para 1, eles se movem na direção para frente a toda velocidade (ou 100% do ciclo de trabalho). Assim como nos gamepads, o valor numérico para setPower está em uma faixa de -1 a 1. O valor absoluto do número atribuído determina a porcentagem do ciclo de trabalho. Como exemplo, 0,3 e -0,3 indicam ambos que o motor está operando com um ciclo de trabalho de 30%. O sinal do número indica a direção em que o motor está girando. Para entender melhor, considere o seguinte gráfico.

module

Quando um motor recebe um valor de setPower entre -1 e 0, o motor girará na direção que considera ser reversa. Quando um motor recebe um valor entre 0 e 1, ele girará para frente.

Na seção de Programação dos Motores da Transmissão, discutiu-se que um robô gira quando os motores estão se movendo em direções opostas. No entanto, isso tem mais a ver com a velocidade e direção. Numericamente, uma transmissão diferencial vai girar para a direita quando o valor de setPower para o motor direito for menor do que o do motor esquerdo. Isso é exemplificado no seguinte exemplo.

module

Quando ambos os motores estão configurados como rightmotor.setPower(1); leftmotor.setPower(1);, o robô irá se mover a toda velocidade em linha reta. No entanto, quando o rightmotor está se movendo na mesma direção, mas a uma velocidade menor, como rightmotor.setPower(0.3); leftmotor.setPower(1);, o robô irá virar ou girar para a direita. Isso provavelmente resultará em um movimento de arco que não é tão acentuado quanto um pivô completo. Em contraste, quando o rightmotor está configurado para velocidade máxima, mas na direção oposta à do leftmotor, o robô gira para a direita. Portanto, matematicamente, o seguinte é considerado verdadeiro:

Expressão Resultado
rightmotor.setPower = leftmotor.setPower Frente ou trás
rightmotor.setPower > leftmotor.setPower Rotação para esquerda
rightmotor.setPower < leftmotor.setPower Rotação para direita

Como mencionado anteriormente, gamepad1.right_stick_y e gamepad1.right_stick_x enviam valores para o sistema de controle a partir do joystick do gamepad. Em contraste, a função setPower interpreta informações numéricas definidas no código e envia a corrente apropriada para os motores para ditar o comportamento dos motores.

Em um sistema de direção de arcade, as seguintes entradas (direções) do joystick precisam corresponder às seguintes saídas (valores de potência do motor).

module

Para obter as saídas expressas na tabela acima, os valores do gamepad devem ser atribuídos a cada motor de maneira significativa, onde princípios algébricos podem ser usados para determinar as duas fórmulas necessárias para obter os valores. No entanto, as fórmulas estão fornecidas abaixo.

leftMotor = y - x
rightMotor = y + x

Em vez de setPower(1);, ambos os motores podem ser configurados com as fórmulas mencionadas anteriormente. Por exemplo, o motor direito pode ser configurado como rightmotor.setPower(y - x);

while (opModeIsActive()) {
        x = gamepad1.right_stick_x;
        y = -gamepad1.right_stick_y;
        
        rightmotor.setPower(y-x);
        leftmotor.setPower(y+x);
        }

Com isso, você agora possui um controle remoto funcional com sistema de direção arcade. A partir daqui, você pode começar a adicionar o mapeamento de hardware para outras peças do hardware do robô. Abaixo está um esboço do código esperado para a classe Bot com um mapeamento completo de hardware.

package org.firstinspires.ftc.teamcode;

import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
import com.qualcomm.robotcore.hardware.Blinker;
import com.qualcomm.robotcore.hardware.Servo;
import com.qualcomm.robotcore.hardware.Gyroscope;
import com.qualcomm.robotcore.hardware.DigitalChannel;
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;
import com.qualcomm.robotcore.util.ElapsedTime;

@TeleOp

public class DualDrive extends LinearOpMode {
    private Blinker control_Hub;
    private DcMotor arm;
    private Servo claw;
    private Gyroscope imu;
    private DcMotor leftmotor;
    private DcMotor rightmotor;
    private DigitalChannel touch;


    @Override
    public void runOpMode() {
        double x;
        double y;

        control_Hub = hardwareMap.get(Blinker.class, "Control Hub");
        arm = hardwareMap.get(DcMotor.class, "arm");
        claw = hardwareMap.get(Servo.class, "claw");
        imu = hardwareMap.get(Gyroscope.class, "imu");
        leftmotor = hardwareMap.get(DcMotor.class, "leftmotor");
        rightmotor = hardwareMap.get(DcMotor.class, "rightmotor");
        touch = hardwareMap.get(DigitalChannel.class, "touch");
        
        rightmotor.setDirection(DcMotorSimple.Direction.REVERSE);

        telemetry.addData("Status", "Initialized");
        telemetry.update();
        
        // Wait for the game to start (driver presses PLAY)
        waitForStart();

        // run until the end of the match (driver presses STOP)
        while (opModeIsActive()) {
              x = gamepad1.right_stick_x;
              y = -gamepad1.right_stick_y;
              
              rightmotor.setPower(y-x);
              leftmotor.setPower(y+x);
        
            telemetry.addData("Status", "Running");
            telemetry.update();

        }
    }
Olá robô - Controle do Robô (OnBot Java)

Tempo Decorrido - OnBot Java


Introdução ao tempo decorrido

Uma maneira de criar um código autônomo é usar um temporizador para definir quais ações devem ocorrer quando. Dentro do SDK, as ações podem ser programadas para um temporizador usando o ElapsedTime. Os temporizadores consistem em duas categorias principais: contagem regressiva e contagem progressiva. Na maioria das aplicações, um temporizador é considerado um dispositivo que conta regressivamente a partir de um intervalo de tempo especificado, como o temporizador em um telefone ou micro-ondas. No entanto, alguns temporizadores, como cronômetros, contam progressivamente a partir de zero. Esses tipos de temporizadores medem o tempo decorrido. O ElapsedTime é um temporizador de contagem progressiva, registrando o tempo decorrido desde o início de um evento definido, como o início de um cronômetro. Neste caso, é o tempo decorrido desde quando o temporizador é instanciado ou redefinido no código. O temporizador ElapsedTime começa a contar o tempo decorrido a partir do ponto de sua criação dentro de um código. Por exemplo, nesta seção, o ElapsedTime será criado (ou instanciado) na seção de código que ocorre quando o modo operacional é inicializado. Não há opção de parar o temporizador ElapsedTime. Em vez disso, a função reset() pode ser usada dentro do seu código para reiniciar o temporizador em vários intervalos. Depois que o temporizador é redefinido, o tempo decorrido pode ser consultado chamando métodos como time(), seconds() ou milliseconds(). O tempo fornecido pelos métodos consultados pode ser usado em loops para determinar quanto tempo uma ação específica deve ocorrer.

Para obter mais informações sobre o objeto ElapsedTime, consulte a documentação do Java (Java Docs).

Seções Objetivos das seções
Noções básicas de programação com tempo Aprender a lógica para fazer um código autônomo por tempo

Programação com tempo decorrido

Para começar, crie um novo modo operacional (op mode) chamado HelloWorld_ElapsedTime utilizando o exemplo BasicOpMode_Linear. Existem outras características que você pode selecionar para facilitar as coisas ao começar a desenvolver seus modos operacionais autônomos. Por exemplo, como você pode se lembrar, selecionar "Setup Code for Configured Hardware" cria as referências necessárias para o mapa de hardware. Outra opção que você pode escolher é configurar o código como um modo operacional autônomo. Isso adiciona a anotação @Autonomous, que distingue o código como um modo operacional autônomo na aplicação Driver Station.

Ao criar um modo operacional (op mode), é necessário decidir se ele deve ser configurado como modo autônomo. Para aplicações com duração inferior a 30 segundos, geralmente necessárias para jogabilidade competitiva, é recomendado alterar o tipo de op mode para autônomo. Para aplicações com duração superior a 30 segundos, configurar o código como o tipo de op mode autônomo limitará o tempo de execução do seu código autônomo a 30 segundos. Se você planeja ultrapassar os 30 segundos incorporados no 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.

module

A seleção das características discutidas acima permitirá que você comece com o seguinte código:

package org.firstinspires.ftc.teamcode;

import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
import com.qualcomm.robotcore.hardware.AnalogInput;
import com.qualcomm.robotcore.hardware.Gyroscope;
import com.qualcomm.robotcore.hardware.ColorSensor;
import com.qualcomm.robotcore.hardware.Servo;
import com.qualcomm.robotcore.hardware.DigitalChannel;
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

public class HelloWorld_ElapsedTime extends LinearOpMode {
    private DcMotor leftMotor;
    private DcMotor rightMotor;
    private DcMotor arm;
    private Servo claw;
    private DigitalChannel touch;
    private Gyroscope imu; 

    @Override
    public void runOpMode() {
        imu = hardwareMap.get(Gyroscope.class, "imu");
        leftMotor = hardwareMap.get(DcMotor.class, "leftmotor");
        rightMotor = hardwareMap.get(DcMotor.class, "rightmotor");
        arm = hardwareMap.get(DcMotor.class, "arm");
        claw = hardwareMap.get(Servo.class, "claw");
        touch = hardwareMap.get(DigitalChannel.class, "touch");
        
        telemetry.addData("Status", "Initialized");
        telemetry.update();
        
        // Wait for the game to start (driver presses PLAY)
        waitForStart();

        // run until the end of the match (driver presses STOP)
        while (opModeIsActive()){
            telemetry.addData("Status", "Running");
            telemetry.update();
        }
    }
}

Como o foco desta seção é o Tempo Decorrido, é necessário criar uma variável de ElapsedTime e uma instância de ElapsedTime. Para fazer isso, a seguinte linha é necessária:

private ElapsedTime     runtime = new ElapsedTime();

A linha acima realiza duas ações. Uma variável privada ElapsedTime chamada "runtime" é criada. Uma vez que "runtime" é criado e definido como uma variável ElapsedTime, ele pode armazenar as informações e dados de tempo relevantes. A outra parte da linha runtime = new ElapsedTime(); cria uma instância do objeto temporizador ElapsedTime e a atribui à variável "runtime".

Adicione essa linha ao op mode junto com as outras variáveis privadas.

public class HelloWorld_ElapsedTime extends LinearOpMode {
    private DcMotor leftMotor;
    private DcMotor rightMotor;
    private DcMotor arm;
    private Servo claw;
    private DigitalChannel touch;
    private Gyroscope imu; 
    private ElapsedTime     runtime = new ElapsedTime();

O objetivo deste exemplo é realizar uma série de ações em intervalos de tempo, como avançar por três segundos. Outra maneira de pensar é que o robô avança enquanto o temporizador ElapsedTime for menor ou igual a três segundos, ou seja, runtime.seconds() <= 3.0. Para este exemplo em particular, a melhor maneira de atingir esse objetivo é usar um loop while. Substitua o loop while padrão do op mode pelo seguinte loop:

       waitForStart();
       while (runtime.seconds() <= 3.0) {

        }

É importante saber que, dentro de um modo operacional linear, um loop while deve sempre ter a condição Booleana opModeIsActive(). Essa condição garante que o loop while será encerrado quando o botão de parada for pressionado.

Os loops while executam quando a condição é verdadeira e param quando a condição é falsa. Neste caso, o loop while deve iniciar apenas se ambas as condições (opModeIsActive() e runtime.seconds() <= 3.0) forem verdadeiras. O loop while deve terminar quando runtime.seconds() > 3 for maior que três segundos ou quando o botão de parada na estação do piloto for pressionado. Para realizar isso, o operador lógico && precisa ser utilizado.

O operador && é um operador lógico em Java. Este símbolo é equivalente a "e" em Java. Utilizar isso em uma instrução condicional requer que ambas as declarações precisem ser verdadeiras para que a condição geral seja verdadeira.

       waitForStart();
       while (opModeIsActive() && (runtime.seconds() <= 3.0)) {

        }

Lembre-se de que o temporizador ElapsedTime começa a contar quando é instanciado ou resetado. Como o temporizador está sendo instanciado quando a variável runtime está sendo criada, e as criações de variáveis estão ocorrendo antes do comando waitForStart(); ser executado; o temporizador começará a contar quando o modo operacional for inicializado, em vez de quando o modo operacional for iniciado. Isso pode causar problemas na consistência do desempenho do robô, dependendo do atraso entre a inicialização e o início.

Considere o seguinte cenário: Em um ambiente de competição, equipes frequentemente são obrigadas a inicializar seu robô antes do início de uma partida. Isso significa que um robô pode permanecer na fase de inicialização por alguns segundos a alguns minutos. Se um código autônomo é baseado no uso de um temporizador ElapsedTime que começa ao ser instanciado, quanto mais tempo um robô passa na fase de inicialização, menos provável é que ele funcione conforme esperado.

Para evitar problemas decorrentes de um atraso de tempo entre a inicialização e o início, é possível adicionar um reset do temporizador ao código. Adicione a linha runtime.reset(); entre o comando waitForStart(); e o loop while.

       waitForStart();
       runtime.reset();
       while (opModeIsActive() && (runtime.seconds() <= 3.0)) {

        }

Agora que o temporizador está resetado, vamos em frente e adicionar o código relacionado aos motores. Se você se lembra do artigo "Programming Drivetrain Motors", os motores no trem de força (drivetrain) se espelham entre si. A natureza espelhada da montagem dos motores faz com que eles girem em direções opostas. Para corrigir essa discrepância, a direção do motor direito precisa ser invertida. Adicione as seguintes linhas de código ao op mode acima do comando waitForStart();

rightMotor.setDirection(DcMotor.Direction.REVERSE);

Agora, dentro do loop while, adicione as linhas leftmotor.setPower(1); e rightmotor.setPower(1); para definir ambos os motores para funcionar em velocidade máxima na direção para frente.

package org.firstinspires.ftc.teamcode;

import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
import com.qualcomm.robotcore.hardware.AnalogInput;
import com.qualcomm.robotcore.hardware.Gyroscope;
import com.qualcomm.robotcore.hardware.ColorSensor;
import com.qualcomm.robotcore.hardware.Servo;
import com.qualcomm.robotcore.hardware.DigitalChannel;
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;
import com.qualcomm.robotcore.util.ElapsedTime;


@Autonomous

public class HelloWorld_ElapsedTime extends LinearOpMode {
    private DcMotor leftMotor;
    private DcMotor rightMotor;
    private DcMotor arm;
    private Servo claw;
    private DigitalChannel touch;
    private Gyroscope imu; 
    private ElapsedTime     runtime = new ElapsedTime();

    @Override
    public void runOpMode() {
        imu = hardwareMap.get(Gyroscope.class, "imu");
        leftMotor = hardwareMap.get(DcMotor.class, "leftmotor");
        rightMotor = hardwareMap.get(DcMotor.class, "rightmotor");
        arm = hardwareMap.get(DcMotor.class, "arm");
        claw = hardwareMap.get(Servo.class, "claw");
        touch = hardwareMap.get(DigitalChannel.class, "touch");
        
        rightMotor.setDirection(DcMotor.Direction.REVERSE);
        
        telemetry.addData("Status", "Initialized");
        telemetry.update();
        // Wait for the game to start (driver presses PLAY)
        
        waitForStart();
        // run until the end of the match (driver presses STOP)
  
        runtime.reset();
        while (opModeIsActive() && (runtime.seconds() <= 3.0)) {
            leftMotor.setPower(1);
            rightMotor.setPower(1);
        }

Agora você tem o código básico necessário para fazer com que seu robô avance por três segundos. Isso deve proporcionar a você uma noção básica de codificação com ElapsedTime. Outras ações, como abrir e fechar uma garra ou levantar um braço, podem ser codificadas em seu programa autônomo.

Como aconselhado nas seções anteriores, é benéfico adicionar telemetria a determinado código para obter os dados de feedback que você deseja ou precisa. Para este exemplo, a telemetria mostrará quantos segundos se passaram para cada etapa da jornada do robô.

while (opModeIsActive() && (runtime.seconds() <= 3.0)) {
    leftMotor.setPower(1);
    rightMotor.setPower(1);
    telemetry.addData("Leg 1", runtime.seconds());
    telemetry.update();
        }

Para este guia específico, o objetivo final é testar a precisão de um robô avançando do ponto A para o ponto B e, em seguida, recuando de volta para o ponto A. Para fazer isso, é necessário escrever outra seção de código com base no temporizador. Uma maneira de fazer isso é copiar o loop while que você já criou e fazer as edições necessárias, como alternar a direção de energia para os motores.

runtime.reset();
while (opModeIsActive() && (runtime.seconds() <= 3.0)) {
    leftMotor.setPower(1);
    rightMotor.setPower(1);
    telemetry.addData("Leg 1", runtime.seconds());
    telemetry.update();
        }

runtime.reset();
while (opModeIsActive() && (runtime.seconds() <= 3.0)) {
    leftMotor.setPower(-1);
    rightMotor.setPower(-1);
    telemetry.addData("Leg 2", runtime.seconds());
    telemetry.update();
        }

Observe que um runtime.reset(); adicional foi adicionado ao código acima. A outra opção para um segundo while loop teria envolvido adicionar uma condição adicional ao while loop, como:

A escolha de redefinir o temporizador antes de iniciar uma nova etapa da jornada do robô foi feita para reduzir a quantidade de alterações de código que podem ser necessárias durante os testes do código.

Exemplo de código completo]

package org.firstinspires.ftc.teamcode;

import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
import com.qualcomm.robotcore.hardware.AnalogInput;
import com.qualcomm.robotcore.hardware.Gyroscope;
import com.qualcomm.robotcore.hardware.ColorSensor;
import com.qualcomm.robotcore.hardware.Servo;
import com.qualcomm.robotcore.hardware.DigitalChannel;
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;
import com.qualcomm.robotcore.util.ElapsedTime;


@Autonomous

public class HelloWorld_ElapsedTime extends LinearOpMode {
    private DcMotor leftMotor;
    private DcMotor rightMotor;
    private DcMotor arm;
    private Servo claw;
    private DigitalChannel touch;
    private Gyroscope imu; 
    private ElapsedTime     runtime = new ElapsedTime();

    @Override
    public void runOpMode() {
        imu = hardwareMap.get(Gyroscope.class, "imu");
        leftMotor = hardwareMap.get(DcMotor.class, "leftmotor");
        rightMotor = hardwareMap.get(DcMotor.class, "rightmotor");
        arm = hardwareMap.get(DcMotor.class, "arm");
        claw = hardwareMap.get(Servo.class, "claw");
        touch = hardwareMap.get(DigitalChannel.class, "touch");
        leftMotor.setDirection(DcMotor.Direction.FORWARD); // Set to REVERSE if using AndyMark motors
        rightMotor.setDirection(DcMotor.Direction.REVERSE);
        
        telemetry.addData("Status", "Initialized");
        telemetry.update();
        // Wait for the game to start (driver presses PLAY)
        waitForStart();

        // run until the end of the match (driver presses STOP)
  
        runtime.reset();
        while (opModeIsActive() && (runtime.seconds() <= 3.0)) {
            leftMotor.setPower(1);
            rightMotor.setPower(1);
            telemetry.addData("Leg 1", runtime.seconds());
            telemetry.update();
        }

        runtime.reset();
        while (opModeIsActive() && (runtime.seconds() <= 3.0)) {
            leftMotor.setPower(-1);
            rightMotor.setPower(-1);
            telemetry.addData("Leg 2", runtime.seconds());
            telemetry.update();
        }

        }
    }
Olá robô - Controle do Robô (OnBot Java)

Controle de braço - OnBot Java


Introdução ao controle de braço

O controle de robôs assume muitas formas diferentes. Agora que você passou pela programação de um drivetrain, podemos aplicar esses conceitos ao controle de outros mecanismos. Como este guia utiliza a classe Bot, o foco será nos conceitos básicos de controlar o seu mecanismo principal, um braço de articulação única.

module

Controlar um braço requer um processo mental diferente daquele que você usou para controlar o drivetrain. Enquanto o drivetrain usa o movimento de rotação dos motores para percorrer uma distância linear, um braço gira em torno de um ponto central, ou junta. Ao trabalhar com um braço, você precisará ter cautela com as limitações físicas do robô, incluindo capacidade de carga, amplitude de movimento e outras forças que possam ser aplicadas.

Nesta seção, você aprenderá a usar os controles do D-pad do gamepad e o Sensor de Toque instalado para controlar o braço. No entanto, o foco desta seção é usar o código para limitar a amplitude de movimento do braço.

Seção Objetivos da seção
Noções básicas de programação sobre o braço Introdução à codificação de um braço para controle teleoperado e trabalho com um interruptor de limite.
Programando um braço para uma posição Utilizando codificadores de motor para mover um braço para uma posição específica, como de 45 graus para 90 graus.
Utilizando limites para controlar a faixa do motor Trabalhando com os fundamentos do controle de braço, codificador de motor e interruptores de limite para controlar a amplitude de movimento de um braço.

Noções básicas de programação sobre o braço

Comece criando um modo operacional básico chamado HelloRobot_ArmControl.

Para obter mais informações sobre como criar um modo operacional (op mode), consulte a seção Test Bed - Onbot Java.

Diferentemente do joystick, que envia valores correspondentes à posição do joystick, o Dpad no gamepad envia valores booleanos FALSE/TRUE. Para determinar como o braço se move quando você pressiona DpadUp ou DpadDown, é necessário usar uma instrução if/else if. Crie uma instrução if/else if semelhante à abaixo:

while (opModeIsActive()) {
   if(gamepad1.dpad_up){
                
                }
   else if (gamepad1.dpad_down){
            
                } 
     }            

Agora que a estrutura básica está no lugar, podemos adicionar os blocos necessários para ditar a direção do braço. A melhor prática é fazer com que o braço se mova para cima quando DpadUp é selecionado e para baixo quando DpadDown é selecionado. Para fazer isso, vamos adicionar arm.setPower(); a cada parte executável da instrução if/else if.

Lembre-se de que o valor atribuído a setPower dita a direção e a velocidade do motor. Entre o motor e a engrenagem no robô classe, um valor positivo moverá o braço para cima, enquanto um valor negativo moverá o braço para baixo. Se você não tem certeza sobre a direção do seu motor, crie o seguinte código e teste para garantir que o motor esteja se comportando conforme o esperado:

if(gamepad1.dpad_up){
       arm.setPower(0.2);         
            }
else if (gamepad1.dpad_down){
       arm.setPower(-0.2); 
            }   

Iniciar com um ciclo de trabalho mais baixo, como o 0,2 mostrado no código acima, permitirá testes mais fáceis ao tomar decisões para o braço. Mais tarde, neste guia, alteraremos para um ciclo de trabalho mais alto.

Guarde o modo de operação e tente executar o código. Considere as seguintes perguntas.

Atualmente, a lógica da instrução if/else if declara que quando gamepad1.dpad_up é verdadeiro (foi pressionado), o motor irá funcionar na direção para frente (ou, neste caso, para cima) a 20% de ciclo de trabalho. Se gamepad1.dpad_down for verdadeiro, o motor irá funcionar reversamente a 20% de ciclo de trabalho. Se você executou o código até este ponto, pode ter notado que mesmo quando você soltou o Dpad, o motor continuou a funcionar na direção selecionada. A instrução if/else if atual diz ao robô quando o motor deve se mover e em que direção, mas nada diz ao motor para parar, assim o braço continua a se mover sem limites.

Para corrigir isso, edite a instrução if/else if para incluir uma ação a ser realizada se nenhuma das condições do gamepad for verdadeira. Como queremos que o braço pare de se mover se nenhuma das condições do gamepad for atendida, vamos usar arm.setPower(0); para parar o motor.

if(gamepad1.dpad_up){
       arm.setPower(0.2);         
            }
else if (gamepad1.dpad_down){
       arm.setPower(-0.2); 
            }   
else { 
       arm.setPower(0); 
            }    

Tente salvar e executar novamente o modo de operação. Preste atenção na velocidade do braço subindo em comparação com descendo. A velocidade parece a mesma?

Trabalhar com um braço introduz diferentes fatores a serem considerados em comparação com o que você viu anteriormente com trens de acionamento. Por exemplo, você notou alguma diferença nas velocidades ao mover o braço para cima ou para baixo? Ao contrário do trem de acionamento, onde o efeito da gravidade impacta os motores de maneira consistente em qualquer direção, a gravidade desempenha um papel significativo na velocidade do motor do braço.

Adicionando um interruptor de limite

Outra consideração a ser feita são as limitações físicas do mecanismo do braço. Certos mecanismos podem ter uma limitação física, e quando essa limitação é ultrapassada, há o risco de danificar o mecanismo ou outro componente do robô. Existem algumas maneiras de limitar o mecanismo com sensores que ajudarão a reduzir a possibilidade de um mecanismo ultrapassar suas limitações físicas. Nesta seção, vamos nos concentrar no uso de um interruptor de limite para restringir a faixa de movimento do braço.

Esta seção pressupõe que você tenha um conhecimento básico sobre interruptores de limite a partir da seção de Testes e da artigo sobre Sensores Digitais.

Como você pode se lembrar da seção Testes, os interruptores de limite usam lógica booleana para indicar quando um limite foi atingido. Os interruptores de limite geralmente se apresentam na forma de sensores digitais, como o Sensor de Toque, já que os sensores digitais reportam um sinal booleano de ligado/desligado para o sistema, assim como um interruptor de luz.

Se você estiver usando um robô da Classe Bot, seu robô deve ter um Sensor de Toque montado na frente do chassi. Você também deve ter instalado um Limit Switch Bumper. Juntos, esses itens formam um sistema de interruptor de limite. Ao utilizar o sistema de interruptor de limite, você pode evitar que o braço do seu robô Classe Bot ultrapasse o limite físico inferior, ou o que será conhecido como nossa posição inicial. Vamos começar a programar!

Antes de prosseguir com o código, certifique-se de que o mecanismo está interagindo com e pressionando o Sensor de Toque. Se você estiver usando a Classe Bot, isso envolve garantir que o chassi esteja pressionando ativamente o Sensor de Toque quando o braço descer.

No trecho "Banco de testes - Onbot Java", você aprendeu como criar um programa básico de interruptor de limite, semelhante ao exemplo abaixo.

 if (touch.getState()){
    //Touch Sensor is not pressed  
     arm.setPower(0.2);
    
} else {
    //Touch Sensor is pressed 
    arm.setPower(0);
                        }

Se você se recorda da seção inicial sobre interruptores de limite, o Sensor de Toque opera em um estado binário de FALSO/VERDADEIRO. Quando o sensor de toque não está pressionado, touch.getState() retorna verdadeiro; quando o sensor de toque está pressionado, touch.getState() retorna falso. A lógica do código afirma que quando o sensor de toque não está pressionado, o motor funciona com um ciclo de trabalho de 20%.

Em vez de fazer o motor funcionar com um ciclo de trabalho de 20% quando o Sensor de Toque não está pressionado e parar quando o sensor é pressionado, queremos controlar o braço usando o gamepad ainda. Para fazer isso, podemos aninhar a instrução if/else if do Gamepad dentro da instrução if/else do Interruptor de Limite.

Para esta próxima parte, estaremos utilizando a instrução if/else if criada nas seções Basics of Programming and Arm. Daqui para frente, essa lógica básica de código será referida como a instrução if/else if do Gamepad. O código do interruptor de limite será conhecido como a instrução if/else do Interruptor de Limite. Ambas as partes do código serão referenciadas novamente.

if(gamepad1.dpad_up){
       arm.setPower(0.2);         
            }
else if (gamepad1.dpad_down){
       arm.setPower(-0.2); 
            }   
else { 
       arm.setPower(0); 
            } 

Segundo código:

if(touch.getState()){
      if(gamepad1.dpad_up){
            arm.setPower(0.2);         
                 }
      else if (gamepad1.dpad_down){
            arm.setPower(-0.2); 
                 }   
      else { 
            arm.setPower(0); 
                 }  
       }
  else {
      arm.setPower(0);
       } 

Salve o modo operacional e execute-o. O que acontece quando o Sensor de Toque é pressionado?

Uma das características comuns de um interruptor de limite, como o sensor de toque, é a capacidade de redefinir para o seu estado padrão. Se você pressionar o sensor de toque com o dedo, pode notar que assim que liberar a pressão que está aplicando, o sensor de toque voltará ao seu estado padrão de "não pressionado". No entanto, é necessário liberar a pressão para conseguir isso.

Certifique-se de que o mecanismo está efetivamente interagindo com o Sensor de Toque. Para o Bot da Classe, talvez seja necessário ajustar o Sensor de Toque para que o Limit Switch Bumper esteja interagindo com ele de forma mais consistente.

O código no bloco de informações acima determina que quando o Sensor de Toque é pressionado, o motor do braço é definido como zero. Isso funcionaria em um mecanismo onde o Sensor de Toque pode retornar ao seu estado padrão por conta própria. No entanto, uma vez que o braço pressiona o Sensor de Toque, o peso do mecanismo impedirá que o Sensor de Toque retorne ao seu estado padrão. A combinação do peso do mecanismo e a lógica do código no bloco de informações significa que, uma vez que o braço atinge seu limite, ele não será capaz de se mover novamente.

Para remediar isso, uma ação para mover o braço na direção oposta ao limite precisa ser adicionada à instrução "else". Como o Sensor de Toque é um limite inferior para o braço, o braço precisará se mover para cima (ou o motor na direção para a frente) para se afastar do sensor de toque. Para fazer isso, podemos criar uma instrução if/else semelhante à nossa instrução if/else do gamepad. Em vez de ter as operações normais do gamepad, quando o Sensor de Toque e o DpadUp são pressionados, o braço se move para longe do Sensor de Toque. Uma vez que o Sensor de Toque não relata mais falso, as operações normais do gamepad retornam e o braço pode se mover em qualquer direção novamente.

if(touch.getState()){
      if(gamepad1.dpad_up){
            arm.setPower(0.2);         
                 }
      else if (gamepad1.dpad_down){
            arm.setPower(-0.2); 
                 }   
      else { 
            arm.setPower(0); 
                 }  
       }
  else {
       if(gamepad1.dpad_up){
            arm.setPower(0.2);         
                 }
       else{
            arm.setPower(0);
       } 

Programando um braço para uma posição

Na seção de Navegação por Encoder, foi introduzido o conceito de mover o motor para uma posição específica com base em ticks do encoder. O processo destacado na Navegação por Encoder focou em como converter de ticks do encoder para rotações e, consequentemente, para uma distância linear. Um procedimento semelhante pode ser utilizado para mover o braço para uma posição específica. No entanto, ao contrário do drivetrain, o braço não segue um caminho linear. Em vez de converter para uma distância linear, faz mais sentido converter os ticks do encoder em um ângulo medido em graus.

Na imagem abaixo, são apresentadas duas posições potenciais para o braço do ClassBot. Uma das posições - destacada em azul abaixo - é a posição em que o braço encontra o limite do sensor de toque. Devido ao limite, esta posição será nossa posição padrão ou de início. No guia de construção do ClassBot, sabe-se que a extrusão que suporta a bateria está em um ângulo de 45 graus. Como o braço está aproximadamente paralelo a essas extrusões quando está na posição inicial, podemos estimar que o ângulo padrão do braço é aproximadamente 45 graus.

module

O objetivo desta seção é determinar a quantidade de ticks do encoder necessários para mover o braço de sua posição inicial para uma posição em torno de 90 graus. Existem algumas maneiras diferentes de realizar isso. Uma estimativa pode ser feita movendo o braço para a posição desejada e registrando o retorno de telemetria da Estação do Motorista. Outra opção é realizar os cálculos matemáticos para encontrar a quantidade de ticks do encoder que ocorrem por grau movido. Siga esta seção para percorrer ambas as opções e determinar qual é a melhor para a sua equipe.

Estimando a posição do braço

Para estimar a posição do braço utilizando telemetria e testando, vamos começar com uma programação if/else com controle

if(gamepad1.dpad_up){
       arm.setPower(0.2);         
            }
else if (gamepad1.dpad_down){
       arm.setPower(-0.2); 
            }   
else { 
       arm.setPower(0); 
            } 

Por enquanto, você pode comentar o código relacionado ao interruptor de limite.

Dentro do loop while, adicione a linha telemetry.addData("Arm Test", arm.getCurrentPosition()); e telemetry.update();.

while(opModeIsActive){
    if(gamepad1.dpad_up){
       arm.setPower(0.2);         
            }
     else if (gamepad1.dpad_down){
       arm.setPower(-0.2); 
            }   
     else { 
       arm.setPower(0); 
            } 
     telemetry.addData("Arm Test", arm.getCurrentPosition());
     telemetry.update();
          
 }

Guarde o modo operacional (op mode) e execute-o. Utilize os comandos do gamepad para mover o braço para a posição de 90 graus. Assim que o braço estiver devidamente posicionado, leia as informações de telemetria na Driver Station para determinar a contagem do codificador em relação à posição do braço.

module

Recordem-se da seção Conceitos Básicos de Encoder, na qual a posição do encoder é definida como 0 cada vez que o Control Hub é ligado. Isso significa que, se o braço estiver em uma posição diferente da posição inicial quando o Control Hub for ligado, essa posição se tornará zero em vez da posição inicial. O número fornecido na imagem acima não é necessariamente uma contagem precisa do encoder para a posição de 90 graus. Para obter a leitura mais precisa do encoder para o seu robô, certifique-se de que a posição inicial seja registrada como 0 contagens do encoder. Para aumentar ainda mais a precisão, considere realizar várias execuções de teste antes de decidir sobre o número de contagens.

Recordem-se de que, para executar RUN_TO_POSITION, as seguintes três linhas de código precisam ser adicionadas a ambas as seções do bloco if/else if do Gamepad.

arm.setTargetPosition(0);
arm.setMode(DcMotor.RunMode.RUN_TO_POSITION);
arm.setPower(0);

Quando DpadUp for pressionado, o braço deve se mover para a posição de 90 graus. Quando DpadDown for pressionado, o braço deve voltar à posição inicial. Para fazer isso, defina a primeira linha arm.setTargetPosition(0); igual ao número de ticks necessários para o seu braço atingir 90 graus. Para este exemplo, vamos usar 83 ticks.

Como queremos que DpadDown retorne o braço à posição inicial, manter arm.setTargetPosition(0); definido como 0 nos permitirá realizar isso. Defina ambas as linhas arm.setPower(0); como 0,5.

if(gamepad1.dpad_up){
     arm.setTargetPosition(83);
     arm.setMode(DcMotor.RunMode.RUN_TO_POSITION);
     arm.setPower(0.5);
            }
else if (gamepad1.dpad_down){
      arm.setTargetPosition(0);
      arm.setMode(DcMotor.RunMode.RUN_TO_POSITION);
      arm.setPower(0.5);
            } 

Observação: o código acima tem uma nome de arquivo "Target Position if/else if", esse código vai ser referênciado de novo

Recordem-se de que a posição alvo (target position) dita em que direção o motor se move, assumindo o controle da direcionalidade anteriormente controlada por arm.setPower(); Portanto, ambos os blocos podem ser definidos com um valor positivo, já que eles controlarão a velocidade.

Se você tentar executar este código, pode perceber que o braço oscila na posição de 90 graus. Quando esse comportamento está presente, você também deve notar a saída de telemetria para as contagens do encoder flutuando. RUN_TO_POSITION é um Controle em Malha Fechada, o que significa que, se o braço não atingir perfeitamente a posição alvo, o motor continuará a oscilar até conseguir. Quando os motores continuam a oscilar e nunca atingem completamente a posição alvo, isso pode ser um sinal de que os fatores que determinam as tolerâncias e outros aspectos do loop fechado não estão ajustados para esse motor ou mecanismo específico. Existem maneiras de ajustar o motor, mas por enquanto queremos nos concentrar em trabalhar com o braço e expandir sobre como limites e posições funcionam em relação ao mecanismo.

Calculando a posição alvo

Na introdução inicial ao "run to position", você trabalhou nos cálculos necessários para converter os ticks por rotação de um motor em ticks por milímetro movido. Agora, queremos nos concentrar em como converter os ticks por rotação do motor em ticks por grau movido. A partir da seção anterior, você deve ter uma estimativa aproximada da quantidade de ticks necessários para chegar à posição de 90 graus. O objetivo desta seção é trabalhar na obtenção de uma posição mais exata.

Para começar, você precisará de algumas das mesmas variáveis que usamos em Navegação com Encoder.

Ticks por revolução

Recordem-se de que os ticks por revolução do eixo do encoder são diferentes dos ticks por revolução do eixo que controla um mecanismo. Vimos isso na seção de Navegação com Encoder, quando os ticks por revolução no motor eram diferentes dos ticks por revolução da roda. À medida que o movimento é transmitido de um motor para um mecanismo, a resolução dos ticks do encoder muda.

Para mais informação sobre o efeito da transmissão em um mecanismo, consulte a seguinte seção:

Reduçã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 terão informações sobre a quantidade de ticks por revolução.

Visite o site da fabricante dos seus motores e encoders para saber as contagens

Nas especificações do Core Hex tem dois tipos diferentes de Contagens por revolução do encoder:

No motor, temos o número de contagens do encoder no eixo em que o encoder está instalado. Esse número é equivalente aos 28 counts por revolução que usamos para o HD Hex Motor. Os 288 counts "na saída" levam em consideração a mudança na resolução após o movimento ser transmitido do motor para a caixa de engrenagens embutida de 72:1. Vamos usar o valor 288 como ticks por revolução para que não seja necessário considerar a caixa de engrenagens em nossa variável total de redução de engrenagens.

Redução total

Como incorporamos a redução de engrenagens da caixa de engrenagens do motor nos ticks por revolução, o foco principal desta seção é calcular a redução de engrenagens da junta do braço. O eixo do motor aciona uma engrenagem de 45 dentes que transmite movimento para uma engrenagem de 125 dentes. A relação total de engrenagens é 125D:45D. Para calcular a redução de engrenagens para este conjunto de engrenagens, podemos simplesmente dividir 125 por 45.

125/45 = 2.777778

Para recapitular, para o Robô V2 as seguinte informações são verídicas:

Descrição Valor
Ticks por revolução 288 ticks
Redução total 2.777778

Vamos criar duas variáveis agora que temos essa informação:

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

Adicione as variáveis COUNTS_PER_MOTOR_REV e GEAR_REDUCTION ao op mode abaixo de onde as variáveis de hardware são criadas.

public class HelloRobot_ArmControl extends LinearOpMode {
    private DcMotor arm;
    
    static final double     COUNTS_PER_MOTOR_REV    = 288; 
    static final double     GEAR_REDUCTION    = 2.7778;   

Agora que essas duas variáveis foram definidas, podemos usá-las para calcular outras duas variáveis: a quantidade de contagens do encoder por rotação da engrenagem acionada de 125 dentes e o número de contagens por grau movido.

Calcular as contagens por revolução da engrenagem de 125 dentes (ou COUNTS_PER_GEAR_REV) é a mesma fórmula usada na Navegação com Encoder para nossa variável COUNTS_PER_WHEEL_REV. Portanto, para obter essa variável, podemos multiplicar COUNTS_PER_MOTOR_REV por GEAR_REDUCTION.

static final double  COUNTS_PER_GEAR_REV    = COUNTS_PER_MOTOR_REV * GEAR_REDUCTION;

Para calcular o número de contagens por grau movido (ou COUNTS_PER_DEGREE), divida a variável COUNTS_PER_GEAR_REV por 360.

static final double     COUNTS_PER_DEGREE    = COUNTS_PER_GEAR_REV/360;

Adicione essas variáveis ao Op Mode.

public class HelloRobot_ArmControl extends LinearOpMode {
    private DcMotor arm;
    
    static final double     COUNTS_PER_MOTOR_REV    = 288; 
    static final double     GEAR_REDUCTION    = 2.7778;   
    static final double     COUNTS_PER_GEAR_REV    = COUNTS_PER_MOTOR_REV * GEAR_REDUCTION;
    static final double     COUNTS_PER_DEGREE    = COUNTS_PER_GEAR_REV/360;

Finalmente, precisamos criar uma variável não constante que atuará como nossa posição. Crie uma variável chamada armPosition acima do comando waitForStart();.

public void runOpMode() {
        arm = hardwareMap.get(DcMotor.class, "arm");
        
        int armPosition;
        
        waitForStart();

Adicione essa variável à seção if(gamepad1.dpad_up) da instrução if/else if, já que essa seção indica a posição de 90 graus. Para chegar à posição de 90 graus, o braço precisa se mover aproximadamente 45 graus. Defina a posição do braço como COUNTS_PER_DEGREE vezes 45.

Recordem-se de que setTargetPosition() requer um número inteiro como seu parâmetro. Ao definir armPosition, lembre-se de adicionar a linha (int) na frente da variável double. No entanto, você precisa ter cautela em relação a possíveis erros de arredondamento. Como COUNTS_PER_MM faz parte de uma equação, é recomendável converter para um número inteiro após encontrar o resultado da equação.

armPosition = (int)(COUNTS_PER_DEGREE * 45);
while (opModeIsActive()) {

   if(gamepad1.dpad_up){
         armPosition = (int)(COUNTS_PER_DEGREE * 45);
         arm.setTargetPosition(83);
         arm.setMode(DcMotor.RunMode.RUN_TO_POSITION);
         arm.setPower(0.4);
                 }

Defina a posição alvo para armPosition:

if(gamepad1.dpad_up){
     armPosition = (int)(COUNTS_PER_DEGREE * 45);
     arm.setTargetPosition(armPosition);
     arm.setMode(DcMotor.RunMode.RUN_TO_POSITION);
     arm.setPower(0.4);
            }
else if (gamepad1.dpad_down){
      arm.setTargetPosition(0);
      arm.setMode(DcMotor.RunMode.RUN_TO_POSITION);
      arm.setPower(0.4);
            } 

Poderíamos alterar para o que armPosition é igual na parte do gamepad1.dpad_down da instrução if/else if, como por exemplo:

else if (gamepad1.dpad_down){
  armPosition = (int)(COUNTS_PER_DEGREE * 0);
  arm.setTargetPosition(armPosition);
  arm.setTargetPosition(armPosition);
  arm.setPower(0.4);
  } 

Neste caso, estaríamos constantemente redefinindo armPosition para atender às necessidades das posições que desejamos criar. Uma vez que, no momento, temos apenas duas posições - a posição inicial e a posição de 90 graus - não é necessário. No entanto, é uma boa prática criar uma variável em situações como essa. Se quisermos adicionar outra posição posteriormente, podemos editar facilmente a variável para atender às nossas necessidades.

Utilizando limites para controlar a faixa de um movimento

Nas seções anteriores, você trabalhou em alguns dos fundamentos para restringir o alcance de movimento de um braço. A partir dessas seções, você deveria ter a base necessária para realizar um controle básico do braço. No entanto, existem outras maneiras criativas de usar posições de encoder e limites para expandir o controle sobre o braço.

Esta seção abordará dois tipos adicionais de controle. O primeiro tipo de controle que exploraremos é a ideia de "soft limits" (limites suaves). Na seção de Adição de um Interruptor de Limite, discutimos o conceito de limites físicos de um mecanismo; no entanto, pode haver momentos em que você precisa limitar o alcance de movimento de um braço sem instalar um limite físico. Para fazer isso, pode-se usar código baseado em posição para criar um intervalo para o braço.

Depois de ter uma ideia básica de como criar limites suaves, exploraremos como usar um interruptor de limite (como um sensor de toque) para redefinir o alcance de movimento. Esse tipo de controle reduz o risco de ficar preso fora do intervalo pretendido, o que pode afetar o comportamento esperado do seu robô.

Para definir os limites suaves, usaremos alguma lógica básica que estabelecemos em seções anteriores, com algumas alterações editadas. Comece com um Op Mode básico e adicione as variáveis constantes da seção anterior (calculando a posição alvo).

@TeleOp

public class Basic extends LinearOpMode {
    private DcMotor  arm;

    static final double     COUNTS_PER_MOTOR_REV    = 288; 
    static final double     GEAR_REDUCTION    = 2.7778;   
    static final double     COUNTS_PER_GEAR_REV    = COUNTS_PER_MOTOR_REV * GEAR_REDUCTION;
    static final double     COUNTS_PER_DEGREE    = COUNTS_PER_GEAR_REV/360;
    
    @Override
    public void runOpMode() {
        arm = hardwareMap.get(DcMotor.class, "arm");
        
        
        waitForStart();
    
        while (opModeIsActive()) {
            telemetry.addData("Status", "Running");
            telemetry.update();

        }
    }
}

Depois, nós precisamos criar nossos limites superior e inferior. Portanto, crie duas novas variáveis (int), uma chamada de minPosition e a outra de maxPosition. Adicione ambos na parte de inicialização do Op Mode, acima do waitForStart();.

public void runOpMode() {
        arm = hardwareMap.get(DcMotor.class, "arm");
        
        int minPostion;
        int maxPosition;
        waitForStart();
    

Agora nós queremos que a minPosition defina a posição inicial, e que a maxPosition defina a posição de 90°. Defina a minPosition igual a 0 e defina maxPosition igual a COUNTS_PER_DEGREE vezes 45.

Lembre-se de que é necessário converter os tipos primitivos

int minPostion = 0;
int maxPosition = (int)(COUNTS_PER_DEGREE *45);

Uma lógica de if/else if precisa ser adicionada no controle do braço, para isso nós podemos usar a mesma lógica que utilizamos em noções básicas da programação de um braço.

while(opModeIsActive()){
     if(gamepad1.dpad_up){
            arm.setPower(0.5);         
            }
     else if (gamepad1.dpad_down){
            arm.setPower(-0.5); 
            }   
     else { 
            arm.setPower(0); 
            }   
     } 

Para definir o limite, precisamos editar nossa instrução if/else if para que os limites sejam incorporados. Se DpadUp for selecionado e a posição do braço for menor que a maxPosition, então o braço se moverá para a maxPosition. Se DpadDown for selecionado e a posição do braço for maior que a minPosition, então o braço se moverá em direção à minPosition.

while (opModeIsActive()) {
    if (gamepad1.dpad_up && arm.getCurrentPosition() < maxPosition) {
            arm.setPower(0.5);
            } 
    else if (gamepad1.dpad_down && arm.getCurrentPosition() > minPosition) {
            arm.setPower(-0.5);
            } 
    else {
            arm.setPower(0);
            }
    }

A configuração atual do código interromperá o motor em qualquer ponto em que as condições para alimentar o motor não sejam atendidas. Dependendo de fatores como o peso do mecanismo e qualquer carga que ele esteja suportando, quando o motor para, o braço pode cair abaixo da maxPosition. Reserve um tempo para testar o código e confirmar se ele se comporta da maneira que você espera.

Substítuindo limites

Um dos benefícios de ter um limite suave é a capacidade de ultrapassar esse limite. Como a posição zero dos ticks do encoder é determinada pela posição do braço quando o Control Hub é ligado; se não prestarmos atenção à posição do braço no momento da inicialização, o alcance de movimento do braço é afetado. Por exemplo, se precisarmos reiniciar o Control Hub enquanto o braço estiver na posição de 90 graus, a posição de 90 graus será igual a 0 ticks do encoder. Uma maneira de contornar isso é criar uma substituição para o alcance de movimento.

Existem várias maneiras diferentes de criar uma substituição desse tipo; no nosso caso, vamos usar um botão e um sensor de toque para ajudar a redefinir nosso intervalo.

Comece editando a instrução if/else if para adicionar outra condição else if. Use a linha gamepad1.a como condição. Adicione a linha arm.setPower(-0.5); como a ação correspondente.

while (opModeIsActive()) {
    if (gamepad1.dpad_up && arm.getCurrentPosition() < maxPosition) {
            arm.setPower(0.5);
            } 
    else if (gamepad1.dpad_down && arm.getCurrentPosition() > minPosition) {
            arm.setPower(-0.5);
            } 
    else if(gamepad1.a){
            arm.setPower(-0.5);
    else {
            arm.setPower(0);
            }
    }

Agora que temos essa alteração em vigor, quando o botão A é pressionado, o braço se moverá em direção à posição inicial. Quando o braço alcançar e pressionar o sensor de toque, queremos utilizar STOP_AND_RESET_ENCODER.

Podemos criar outra instrução if que se concentra em realizar essa parada e reinicialização quando o sensor de toque é pressionado. Como o sensor de toque relata true quando não está pressionado e false quando está, precisaremos usar o operador lógico not !.

O operador ! pode ser usado em instruções binárias condicionais quando você precisa inverter se algo é verdadeiro ou falso. Por exemplo, uma instrução if é ativada quando algo é verdadeiro, mas quando touch.getState(); relata verdadeiro, significa que não está pressionado. No nosso caso, queremos que esta instrução if seja ativada quando o sensor de toque está pressionado, portanto, precisamos usar o operador not.

if (!touch.getState()) {
          arm.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
        }

Portanto, se o sensor de toque retornar falso (ou estiver pressionado), o modo de execução do motor STOP_AND_RESET_ENCODER será ativado, fazendo com que o encoder do motor seja redefinido para 0 ticks.

Agora que este código está concluído, experimente testá-lo!

@TeleOp

public class HelloRobot_ArmControl extends LinearOpMode {
    private DcMotor arm;
    private Servo claw;
    private Gyroscope imu;
    private DcMotor leftmotor;
    private DcMotor rightmotor;
    private DigitalChannel touch;
    
    static final double     COUNTS_PER_MOTOR_REV    = 288; 
    static final double     GEAR_REDUCTION    = 2.7778;   
    static final double     COUNTS_PER_GEAR_REV    = COUNTS_PER_MOTOR_REV * GEAR_REDUCTION;
    static final double     COUNTS_PER_DEGREE    = COUNTS_PER_GEAR_REV/360;

    

    @Override
    public void runOpMode() {
        arm = hardwareMap.get(DcMotor.class, "arm");
        claw = hardwareMap.get(Servo.class, "claw");
        imu = hardwareMap.get(Gyroscope.class, "imu");
        leftmotor = hardwareMap.get(DcMotor.class, "leftmotor");
        rightmotor = hardwareMap.get(DcMotor.class, "rightmotor");
        touch = hardwareMap.get(DigitalChannel.class, "touch");
        
        int minPostion = 0;
        int maxPosition = (int)(COUNTS_PER_DEGREE *45);
        
        waitForStart();
            
        // run until the end of the match (driver presses STOP)
        while (opModeIsActive()) {
            if (gamepad1.dpad_up && arm.getCurrentPosition() < maxPosition) {
                arm.setPower(0.5);
                    } 
            else if (gamepad1.dpad_down && arm.getCurrentPosition() > minPosition) {
                arm.setPower(-0.5);
                    } 
            else if (gamepad1.a) {
                arm.setPower(-0.5);
                    } 
            else {
                arm.setPower(0);
                }
            if (!touch.getState()) {
              arm.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
                    }
        telemetry.addData("Arm Test", arm.getCurrentPosition());
        telemetry.update(); 
        }
    }
}
Olá robô - Controle do Robô (OnBot Java)

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 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:

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:

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:

Crie a variável COUNTS_PER_MM 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;
    static final double     COUNTS_PER_MM           = COUNTS_PER_WHEEL_REV / WHEEL_CIRCUMFERENCE_MM;

COUNTS_PER_WHEEL_REV será criado como uma variável separada de COUNTS_PER_MM, pois é usado no cálculo de uma velocidade alvo.

Movendo para um distância alvo

Agora que você criou as variáveis constantes necessárias para calcular a quantidade de pulsos por milímetro movido, você pode usá-las para definir uma distância alvo. Por exemplo, se você quiser que o robô se mova para frente dois pés, converter de pés para milímetros e multiplicar pelo COUNTS_PER_MM fornecerá a quantidade de pulsos necessários para alcançar essa distância.

Crie mais duas variáveis chamadas leftTarget e rightTarget. Essas variáveis podem ser alteradas e editadas em seu código para indicar às rodas motrizes as posições para as quais devem ir. Em vez de colocá-las com as variáveis constantes, crie essas variáveis dentro do modo de operação (op mode) mas acima do comando waitForStart();.

A função setTargetPosition(); aceita um tipo de dado inteiro (int) como parâmetro, em vez de um tipo duplo (double). Já que tanto leftTarget quanto rightTarget serão usados para definir a posição alvo, crie ambas as variáveis como variáveis int.

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);
        
        int leftTarget;
        int rightTarget;
        
        waitForStart();

Atualmente, o principal fator de distância é COUNTS_PER_MM; no entanto, você pode querer percorrer uma distância que está no sistema imperial, como 2 pés (ou 24 polegadas). A distância alvo, nesse caso, precisará ser convertida para milímetros. Para converter de pés para milímetros, utilize a seguinte fórmula:

d(mm) = d(ft) * 304.8

Se você converter 2 pés para milímetros, o resultado será 609,6 milímetros. Para fins deste guia, arredondemos isso para 610 milímetros. Multiplique 610 milímetros pela variável COUNTS_PER_MM para obter o número de pulsos necessários para mover o robô 2 pés. Como a intenção é fazer com que o robô se mova em linha reta, defina tanto leftTarget quanto rightTarget para serem iguais a 610 * COUNTS_PER_MM.

Como mencionado anteriormente, a função setTargetPosition(); exige que seu parâmetro seja do tipo de dado inteiro. As variáveis leftTarget e rightTarget foram definidas como inteiros, no entanto, a variável COUNTS_PER_MM é do tipo double. Uma vez que esses são dois tipos de dados diferentes, é necessário fazer uma conversão de tipos de dados. Neste caso, COUNTS_PER_MM precisa ser convertido para um inteiro. Isso é tão simples quanto adicionar a linha (int) na frente da variável double. No entanto, é preciso ter cautela com possíveis erros de arredondamento. Como COUNTS_PER_MM faz parte de uma equação, é recomendável converter para um inteiro após encontrar o resultado da equação. O exemplo de como fazer isso é mostrado abaixo.

int leftTarget = (int)(610 * COUNTS_PER_MM);
int rightTarget = (int)(610 * COUNTS_PER_MM);

Edite as linhas setTargetPosition(); para que ambos os motores sejam configurados para a posição alvo apropriada. Para fazer isso, adicione as variáveis leftTarget e rightTarget aos seus motores respectivos.

leftmotor.setTargetPosition(leftTarget);
rightmotor.setTargetPosition(rightTarget);

Tente executar o código e observar o comportamento do robô. Considere algumas das seguintes perguntas:

Definindo a velocidade

A velocidade é um controle em malha fechada dentro do SDK que utiliza as contagens do encoder para determinar a potência/aproximada velocidade necessária para que os motores alcancem a velocidade definida. Ao trabalhar com configurações de encoder, é recomendável definir uma velocidade em vez de um nível de potência, pois oferece um maior controle.

Para definir uma velocidade, é importante entender a velocidade máxima em RPM que o seu motor é capaz de atingir. No caso do Bot Classe V2, os motores são capazes de atingir no máximo 300 RPM. Com um conjunto de rodas motrizes, é provável que você obtenha um melhor controle configurando a velocidade para um valor inferior ao máximo. Neste caso, vamos definir a velocidade para 175 RPM.

Lembre-se que a função setVelocity é medida em ticks por segundo

Crie uma nova variável TPS. Adicione TPS ao Op Mode abaixo de onde rightTarget está definido.

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);
        
        int leftTarget = (int)(610 * COUNTS_PER_MM);
        int rightTarget = (int)(610 * COUNTS_PER_MM);
        double TPS;
        
        waitForStart();

Como RPM representa a quantidade de rotações por minuto, é necessário fazer uma conversão de RPM para ticks por segundo. Para fazer isso, divida o RPM por 60 para obter a quantidade de rotações por segundo. Em seguida, multiplique as rotações por segundo por COUNTS_PER_WHEEL_REV para obter a quantidade de ticks por segundo.

TPS = 175/69 * CPW R
double TPS = (175/60) * COUNTS_PER_WHEEL_REV

Substítua as funções setPower(); para setVelocity();. Adicione TPS como parâmetro setVelocity();

waitForStart();

leftmotor.setTargetPosition(leftTarget);
rightmotor.setTargetPosition(rightTarget);

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

leftmotor.setVelocity(TPS);
rightmotor.setVelocity(TPS);

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

Tente compilar o código. Você obteve erros?

Com o estado atual do código você vai conseguir os seguintes erros:

motor ex error.png

Isso ocorre porque a função setVelocity(); é uma função da interface DcMotorEx. A interface DcMotorEx é uma extensão da interface DcMotor, que fornece funcionalidades avançadas do motor, como acesso a funções de controle em malha fechada. Para utilizar setVelocity();, as variáveis do motor precisam ser alteradas para DcMotorEx. Para fazer isso, tanto a criação das variáveis privadas dos motores quanto o mapeamento de hardware precisam ser alterados para DcMotorEx.

public class HelloWorld_EncoderAuton extends LinearOpMode {
    private DcMotorEx leftmotor;
    private DcMotorEx rightmotor;
public void runOpMode() {
        leftmotor = hardwareMap.get(DcMotorEx.class, "leftmotor");
        rightmotor = hardwareMap.get(DcMotorEx.class, "rightmotor");

Como DcMotorEx é uma extensão de DcMotor, as funções específicas de DcMotor podem ser utilizadas por variáveis definidas como DcMotorEx.

Depois de fazer essas alterações, o código básico para mover dois pés está concluído! O código abaixo é a versão finalizado do código. Nesta versão, outros componentes de hardware e a telemetria foram adicionados.

@Autonomous

public class HelloWorld_EncoderAuton extends LinearOpMode {
    private DcMotorEx leftmotor;
    private DcMotorEx rightmotor;
    private DcMotor arm;
    private Servo claw;
    private DigitalChannel touch;
    private Gyroscope imu; 
    
    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;
    static final double     COUNTS_PER_MM           = COUNTS_PER_WHEEL_REV / WHEEL_CIRCUMFERENCE_MM;
    
    @Override
    public void runOpMode() {
        imu = hardwareMap.get(Gyroscope.class, "imu");
        leftmotor = hardwareMap.get(DcMotorEx.class, "leftmotor");
        rightmotor = hardwareMap.get(DcMotorEx.class, "rightmotor");
        arm = hardwareMap.get(DcMotor.class, "arm");
        claw = hardwareMap.get(Servo.class, "claw");
        touch = hardwareMap.get(DigitalChannel.class, "touch");
        
        rightmotor.setDirection(DcMotor.Direction.REVERSE);

        leftmotor.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
        rightmotor.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
        
        
        int leftTarget = (int)(610 * COUNTS_PER_MM);
        int rightTarget = (int)(610 * COUNTS_PER_MM);
        double TPS = (175/ 60) * COUNTS_PER_WHEEL_REV;
        
        waitForStart();
        
        
        leftmotor.setTargetPosition(leftTarget);
        rightmotor.setTargetPosition(rightTarget);
        
        leftmotor.setMode(DcMotor.RunMode.RUN_TO_POSITION);
        rightmotor.setMode(DcMotor.RunMode.RUN_TO_POSITION);
        
        leftmotor.setVelocity(TPS);
        rightmotor.setVelocity(TPS);
        
        while (opModeIsActive() && (leftmotor.isBusy() && rightmotor.isBusy())) {
            telemetry.addData("left", leftmotor.getCurrentPosition());
            telemetry.addData("right", rightmotor.getCurrentPosition());
            telemetry.update();
        }
    }
}

Melhorando a transmissão utilizando RUN_TO_POSITION

Na seção de Navegação do Robô - OnBot Java, foi discutido o mecanismo de setPower();. setPower(); dita em que direção e velocidade um motor se move. Em um drivetrain, isso determina se o robô se move para frente, para trás ou vira.

No modo RUN_TO_POSITION, os contadores do codificador (ou setTargetPosition();) são usados em vez de setPower(); para determinar a direção do motor. Se o valor da posição alvo for maior que a posição atual do codificador, o motor se move para frente. Se o valor da posição alvo for menor que a posição atual do codificador, o motor se move para trás.

Como a velocidade e a direção impactam como um robô vira, setTargetPosition(); e setVelocity(); precisam ser editados para fazer com que o robô vire. Considere o código a seguir:

int leftTarget = (int)(610 * COUNTS_PER_MM);
int rightTarget = (int)(-610 * COUNTS_PER_MM);
double TPS = (100/ 60) * COUNTS_PER_WHEEL_REV;
       
        
waitForStart();
        
        
leftmotor.setTargetPosition(leftTarget);
rightmotor.setTargetPosition(rightTarget);
        
leftmotor.setMode(DcMotor.RunMode.RUN_TO_POSITION);
rightmotor.setMode(DcMotor.RunMode.RUN_TO_POSITION);
        
leftmotor.setVelocity(TPS);
rightmotor.setVelocity(TPS);

O rightTarget foi alterado para ser uma posição alvo negativa. Supondo que o codificador comece em zero devido a STOP_AND_RESET_ENCODER, isso faz com que o robô vire para a direita. A velocidade permanece a mesma para ambos os motores. Se você tentar executar este código, pode notar que o robô gira em torno de seu centro de rotação. Para obter uma curva mais ampla, altere a velocidade de modo que o motor direito esteja funcionando a uma velocidade menor do que o motor esquerdo. Ajuste a velocidade e a posição alvo conforme necessário para obter a curva desejada.

Para obter mais informações sobre como a direção e a velocidade impactam o movimento de um robô, consulte a explicação de setPower(); na seção de Navegação do Robô.

O código a seguir mostra como adicionar uma curva ao programa após o robô se mover para frente por 2 pés. Após o robô atingir a meta de 2 pés, há uma chamada para STOP_AND_RESET_ENCODERS, o que reduzirá a necessidade de calcular qual posição alcançar após atingir uma posição.

@Autonomous

public class HelloWorld_EncoderAuton extends LinearOpMode {
    private DcMotorEx leftmotor;
    private DcMotorEx rightmotor;
    private DcMotor arm;
    private Servo claw;
    private DigitalChannel touch;
    private Gyroscope imu; 
    
    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;
    static final double     COUNTS_PER_MM           = COUNTS_PER_WHEEL_REV / WHEEL_CIRCUMFERENCE_MM;
    
    @Override
    public void runOpMode() {
        imu = hardwareMap.get(Gyroscope.class, "imu");
        leftmotor = hardwareMap.get(DcMotorEx.class, "leftmotor");
        rightmotor = hardwareMap.get(DcMotorEx.class, "rightmotor");
        arm = hardwareMap.get(DcMotor.class, "arm");
        claw = hardwareMap.get(Servo.class, "claw");
        touch = hardwareMap.get(DigitalChannel.class, "touch");

        rightmotor.setDirection(DcMotor.Direction.REVERSE);

        leftmotor.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
        rightmotor.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
        
        // TPS variable split to change velocity for each motor when necessary
        
        int leftTarget = (int)(610 * COUNTS_PER_MM);
        int rightTarget = (int)(610 * COUNTS_PER_MM);
        double LTPS = (175/ 60) * COUNTS_PER_WHEEL_REV;         
        double RTPS = (175/ 60) * COUNTS_PER_WHEEL_REV;
        
        waitForStart();
        
        
        leftmotor.setTargetPosition(leftTarget);
        rightmotor.setTargetPosition(rightTarget);
        
        leftmotor.setMode(DcMotor.RunMode.RUN_TO_POSITION);
        rightmotor.setMode(DcMotor.RunMode.RUN_TO_POSITION);
        
        leftmotor.setVelocity(LTPS);
        rightmotor.setVelocity(RTPS);
        
        //wait for motor to reach position before moving on 
        while (opModeIsActive() && (leftmotor.isBusy() && rightmotor.isBusy())) {
            telemetry.addData("left", leftmotor.getCurrentPosition());
            telemetry.addData("right", rightmotor.getCurrentPosition());
            telemetry.update();
        }
        // Reset encoders to zero 
        leftmotor.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
        rightmotor.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
        
        // changing variables to match new needs 
        
        leftTarget = (int)(300 * COUNTS_PER_MM);
        rightTarget = (int)( -300 * COUNTS_PER_MM);
        LTPS = (100/ 60) * COUNTS_PER_WHEEL_REV;         
        RTPS = (70/ 60) * COUNTS_PER_WHEEL_REV;
        
        leftmotor.setTargetPosition(leftTarget);
        rightmotor.setTargetPosition(rightTarget);
        
        leftmotor.setMode(DcMotor.RunMode.RUN_TO_POSITION);
        rightmotor.setMode(DcMotor.RunMode.RUN_TO_POSITION);
        
        leftmotor.setVelocity(LTPS);
        rightmotor.setVelocity(RTPS);
        
        //wait for motor to reach position before moving on 
        while (opModeIsActive() && (leftmotor.isBusy() && rightmotor.isBusy())) {
            telemetry.addData("left", leftmotor.getCurrentPosition());
            telemetry.addData("right", rightmotor.getCurrentPosition());
            telemetry.update();
        }
    }
}

Olá robô - Controle do Robô (Blocos Java)

Olá robô - Controle do Robô (Blocos Java)

Controle do robô


Compreendidos os princípios básicos de controle de atuadores e obtenção de feedback de sensores a partir do Olá Robô - Test Bed, é hora de começar a configurar e programar nosso robô para controle Teleoperado e Autônomo!

Seção Objetivos da seção
Criando um robô básico Apresenta um potencial robô para trabalhar, assim como o arquivo de configuração utilizado nas seções seguintes.
Noções básicas de transmissão Diferenças entre drivetrains diferencial e omnidirecional e seu impacto nos tipos de controle teleoperado.

Antes de continuar, é recomendado completar, no mínimo, um drivetrain. Existem algumas opções diferentes dependendo do kit que está sendo utilizado. Para este guia, o Class Bot V2 é utilizado. Consulte o guia de montagem para obter instruções completas de montagem para o Class Bot V2!

Criar um robô básico

A imagem abaixo destaca os principais componentes de hardware do Class Bot V2. Esses componentes são importantes para entender o processo de configuração.

module

A seção de Configuração do Olá Robô concentrou-se na configuração dos componentes no Test Bed. Para avançar nas seções de programação do Controle do Robô, é necessário criar um novo arquivo de configuração para os componentes no robô. É sua escolha quais nomes de variáveis você deseja atribuir ao seu robô, mas, para referência, este guia usará os seguintes nomes para cada componente de hardware.

Componente de Hardware Tipo de Hardware Nome
Motor direito REV Robotics UltraPlanetary HD Hex Motor right motor
Motor esquerdo REV Robotics UltraPlanetary HD Hex Motor left motor
Braço motor REV Robotics Core Hex Motor arm
Garra servo Servo claw
Sensor de toque REV Touch Sensor touch

Noções básicas de transmissão

Antes de continuar, é importante entender o comportamento mecânico de diferentes drivetrains. As duas categorias mais comuns de drivetrains são Diferencial e Omnidirecional. O drivetrain do Class Bot é um drivetrain diferencial. A tabela abaixo destaca as principais características desses dois tipos de drivetrains.

module

Tração diferencial Omnidirecional
Tipo mais comum de transmissão Pode se mover em qualquer direção
Se move ao longo de um eixo central Varia a força em cada roda para se mover lateralmente ou linearmente
Aplica mais potência de um dos lados para para mudar a direção Programação mais complexa
Pode ter diferentes nomes (4WD, 6WD, West Coast..) Precisa de mais de dois motores

Tipos de controle teleoperado

Existem várias maneiras de controlar um robô teleoperado. Ao usar o REV Control System, isso é feito com um dispositivo de Driver Station e gamepads. Existem várias maneiras de usar um controlador para movimentar um drivetrain diferencial. Duas das formas convencionais são Tank Drive e Arcade Drive.

Tração Tank

Para o Tank Drive, cada lado do drivetrain diferencial é mapeado para seu próprio joystick. Alterar a posição de cada joystick permite que o drivetrain vire e mude sua direção. Existe um código de exemplo no aplicativo Controlador do Robô para controlar um drivetrain diferencial dessa maneira.

Arcade Drive

Para o Arcade Drive, cada lado do drivetrain diferencial é controlado por um único joystick. Alterar a posição do joystick muda a potência aplicada a cada lado do drivetrain, permitindo um comando específico. Os controles de Arcade Drive geralmente têm o movimento esquerda/direita do joystick configurado para girar o robô em torno do seu eixo, com o movimento para frente/para trás fazendo o robô avançar e retroceder. Mais informações sobre Arcade Drive podem ser encontradas nas próximas seções.

Com o robô configurado e uma compreensão básica de drivetrains e tipos de controle teleoperado, podemos avançar para a programação do drivetrain para movimentar o robô.