Lectura y Escritura al Hardware#

Cuando se utiliza el SDK de FTC ®, hay una variedad de clases de hardware incorporados que se pueden utilizar para comunicarse con el hardware del robot, tales como motores de corriente continua, servos, y sensores.

Creación e Instanciación de Objetos Hardware#

Lo primero que se necesita para crear correctamente un objeto es importar su clase. En Android Studio, si se hace referencia a la clase sin haberla importado se puede pulsar Alt+Enter para importarla automáticamente. Una vez importada, el siguiente paso es crear el objeto:

private DcMotor liftMotor;

Después de crear el objeto, hay que instanciarlo. Parte de la superclase OpMode es algo llamado hardwareMap. hardwareMap se utiliza en el FTC SDK para instanciar objetos en lugar de llamar a un constructor.

Contiene toda la información introducida en la configuración del Robot Controller, como los nombres del hardware y en qué puerto está conectado. He aquí un ejemplo de instanciación del motor que hemos creado anteriormente:

liftMotor = hardwareMap.get(DcMotor.class, "Lift Motor");

Sea cual sea el sensor que estés utilizando, pasarás esa clase al lugar donde está DcMotor.class. Por ejemplo, si el liftMotor fuera un Servo, se pasaría Servo.class en su lugar.

Para el segundo argumento, pasas el nombre que tenga el dispositivo en la configuración del Robot Controller. Entonces hardwareMap irá a buscar en qué puerto está conectado el dispositivo con ese nombre, lo que permite acceder al hardware.

Ejemplos de Uso de Componentes Comunes de Hardware#

DC Motor#

DcMotor leftMotor = hardwareMap.get(DcMotor.class, "Left Motor");
DcMotor rightMotor = hardwareMap.get(DcMotor.class, "Right Motor");

DcMotor elevatorMotor = hardware.get(DcMotor.class, "Elevator Motor");
DcMotor intakeMotor = hardware.get(DcMotor.class, "Intake Motor");

Después de instanciar un DcMotor, hay algunas variables que puede establecer para afectar a cómo funciona el motor DC. La primera de ellas es la dirección:

leftMotor.setDirection(DcMotor.Direction.REVERSE);
rightMotor.setDirection(DcMotor.Direction.FORWARD);

Cambiar la dirección del motor hace exactamente lo que debería esperarse, cambia la dirección. Si se aplica una potencia de 1 al motor mientras está en modo de avance, girará en una dirección. Si está en marcha atrás, una potencia de 1 lo hará girar en el otro sentido. Si miras el eje del motor hacia ti, hacia adelante es en el sentido contrario a las agujas del reloj (con la excepción de los motores NeveRest).

A continuación, hay dos comportamientos de potencia cero que se pueden ajustar:

leftMotor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
rightMotor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.FLOAT);

Cambiar esta variable afecta a cómo se comporta el Motor DC mientras se aplica una potencia de 0. BRAKE hará que el motor intente frenarse a sí mismo si se está moviendo (NO hará que el motor mantenga su posición si no se está moviendo), mientras que FLOAT hace que el motor se deslice hasta detenerse, dejando que la fricción haga todo el trabajo.

Por último, existen cuatro modos de funcionamiento diferentes que pueden utilizarse con DC Motors:

leftMotor.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
rightMotor.setMode(DcMotor.RunMode.RUN_USING_ENCODER);

elevatorMotor.setMode(DcMotor.RunMode.RUN_TO_POSITION);
intakeMotor.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);

Es importante tener en cuenta que los valores del encoder se pueden leer en cualquiera de estos modos siempre que el encoder esté correctamente conectado. Estos modos sólo cambian la forma en que el motor reacciona a estos valores del codificador. La documentación de REV Robotics tiene una explicación de los cuatro modos de ejecución.

Advertencia

RUN_TO_POSITION puede ser una forma conveniente de controlar un mecanismo de un solo motor, ya que descarga todo el trabajo de control; sin embargo, dado que cada motor se trata de forma independiente, no es aconsejable utilizarlo en mecanismos con múltiples motores, especialmente en un chasis.

Encoders#

Término

Encoder#

Un encoder es un dispositivo que sigue (generalmente) el movimiento de rotación alrededor de un eje.

Existen encoders absolutos y relativos. Un encoder absoluto informará exactamente del ángulo en el que se encuentra el eje en comparación con su «cero» absoluto. Un encoder relativo informará de cuánto ha girado el :término: eje <Shaft> desde que comenzó el seguimiento (por ejemplo, cuando se inicia el autónomo). Los encoder relativos tendrán una salida en cuadratura, mientras que los encoders absolutos suelen tener salidas analógicas o i2c.

Los encoders se utilizan para ayudar a encontrar la posición en la que se encuentra el robot, o uno de sus mecanismos.

Aunque todos los motores legales FTC contienen codificadores de cuadratura relativa incorporados, deben cablearse por separado y no son necesarios para su uso. Se pueden utilizar encoders externos y conectarlos a un puerto de encoder siempre que utilicen el protocolo de comunicación de cuadratura.

Para acceder a los encoders es necesario llamar a un método del objeto DcMotor, getCurrentPosition(), que devuelve la posición actual del encoder conectado al puerto. Este número puede ser arbitrario al principio de un opmode, y no se reinicia a 0 a menos que se use STOP_AND_RESET_ENCODERS o se desconecte y vuelva la alimentación al expansion hub.

Importante

No existe una terminología estandarizada real cuando se trata de codificadores de cuadratura. El SDK utiliza «CPR» o recuentos por revolución, por defecto. También puede ver algunas hojas de datos lista «PPR» o pulsos por revolución. Un pulso puede ser equivalente a entre 1 y 4 «recuentos» SDK. Tenga cuidado al leer las hojas de datos.

Advertencia

Los codificadores con altos números de conteos por revolución, como el REV Through Bore Encoder, pueden perder pasos si se conectan a los puertos 1 o 2. Además, las llamadas a getVelocity() en un objeto DcMotorEx pueden desbordarse con codificadores de recuentos altos por revolución, debido a que el número devuelto sólo es un entero con signo de 16 bits.

Servo#

Servo relicServo = hardwareMap.get(Servo.class, "Release Servo");

Después de instanciar un Servo, hay dos funciones principales que se pueden llamar: setPosition() y getPosition().

releaseServo.setPosition(0.75);
telemetry.addData("Release Servo Target", releaseServo.getPosition());

setPosition() establece la posición del servo. El SDK utilizará un bucle de control integrado con el potenciómetro del servo para conducir el servo a esa posición y mantener esa posición. setPosition() toma un doble entre 0 y 1, donde el 0 del servo es el límite inferior de rotación y el 1 del servo es el límite superior de rotación. Todo lo que está entre es directamente proporcional, por lo que 0.5 es el medio, 0.75 es 3/4 del camino, etc.

getPosition() no devuelve la posición actual del servo, sino su posición actual de destino. Si una variable para la posición actual del destino del servo se almacena correctamente, esta función no debería ser necesaria.

Servo de rotación continua#

CRServo intakeServo = hardwareMap.get(CRServo.class, "Intake Servo");

Un CRServo tiene un método principal; setPower(). Esto funciona de manera muy similar a DcMotor `` setPower(), lo que significa que pasándole 0 hace que se detenga, pasándole 1 hace que avance a toda velocidad, pasándole -1 hace que retroceda a toda velocidad, y todo lo demás.

intakeServo.setPower(0.75);

IO digital#

DigitalChannel digitalDevice = hardwareMap.get(DigitalChannel.class, "digital device");

Un DigitalChannel tiene un par de métodos principales. setMode() se utiliza para establecer el puerto como un puerto OUTPUT o INPUT, getState() devuelve el estado actual del puerto (sólo funciona en modo INPUT), y setState() establece el estado del puerto (sólo funciona en modo OUTPUT).

Truco

Los puertos digitales se inician por defecto en modo ENTRADA «INPUT»

Peligro

Los puertos digitales se ponen hacia ARRIBA para evitar que floten. Esto significa que hay una resistencia entre el puerto y 3.3V para que el puerto se lea ALTO por defecto cuando no hay nada conectado. Como resultado, los dispositivos digitales DEBEN conectar el pin digital a tierra cuando está cerrado, y dejarlo desconectado cuando está abierto. Para los finales de carrera, esto significa conectar un cable a tierra y el otro al puerto digital. Conectar esto incorrectamente (conectando 3.3V al puerto digital) puede causar inestabilidad y puede hacer que tu expansion hub se bloquee.

Entrada analógica#

AnalogInput analogInput = hardwareMap.get(AnalogInput.class, "analog input");

Una AnalogInput «Entrada analoga» tiene un método principal: getVoltage() que se utiliza para obtener el voltaje de entrada actual al puerto.

Nota

Aunque getMaxVoltage() devuelve 3.3v, los puertos de entrada analógica del control y expansion hub pueden manejar con seguridad hasta 5v.

Nota sobre la velocidad de llamada del hardware#

Cada llamada de hardware que realices, (ya sea ajustar la potencia de un motor, ajustar una posición del servo, leer un valor de encoder, etc.) tardará aproximadamente 3 milisegundos en ejecutarse, excepto las llamadas I2C que pueden tardar más de 7ms. Esto se debe a que entre bastidores, el SDK puede necesitar hacer múltiples llamadas al hardware para realizar la operación I2C.

Nota

Cuando se utiliza un Control Hub, puede ver tiempos de llamada de hardware considerablemente más rápidos porque el Control Hub utiliza una conexión UART directa a la placa Lynx en lugar de ir a través de USB y un intermediario FTDI como sucede cuando se utiliza un teléfono.

Estos tiempos pueden parecer rápidos, pero se acumulan rápidamente. Considere un bucle de control para conducir hacia adelante para N recuentos del codificador mientras se mantiene el rumbo utilizando la IMU. Esto requeriría 5 llamadas normales de hardware (4 de ajuste de potencia + 1 de lectura del codificador) y una llamada I2C (IMU), lo que significa que el ciclo del bucle tardaría aproximadamente 22ms en ejecutarse, y por lo tanto se ejecutaría a aproximadamente 45Hz.

Esto significa que es fundamental minimizar la cantidad de llamadas al hardware para que los bucles de control funcionen con rapidez. Por ejemplo, no lea un sensor más de una vez por bucle. En su lugar, léalo una vez y almacene el valor en una variable por si necesita utilizarlo de nuevo en otros puntos del mismo ciclo de bucle.

El uso de una llamada de hardware de lectura masiva puede ayudar con este problema. Una lectura masiva tarda los mismos 3 ms en ejecutarse que cualquier otra llamada de hardware normal, pero devuelve muchos más datos. Para poder utilizar las lecturas masivas, debe estar ejecutando SDK v5.4 o superior. Consulte /docs/tutoriales/software/lecturas-masivas para más información.