Sensors
A motor controller tells you what your motor is doing. A sensor tells you what your mechanism is doing. These are not the same thing — a slipping encoder, a jammed intake, or an arm that didn't reach its target won't show up in motor velocity data alone. This lesson covers every sensor category you'll use in FRC and how to read each one reliably.
By the end of this lesson, you will:
- Read position and velocity from a TalonFX integrated encoder and a CANcoder external encoder
- Wire and read a limit switch or beam break sensor using
DigitalInput - Read heading from a Pigeon 2 gyroscope and explain the difference between yaw, pitch, and roll
- Explain why sensors lie — and what mechanical and electrical conditions cause false readings
- Choose the right sensor for five common FRC mechanism problems
The Polling Model
FRC sensors are polled — your code reads them on request, every 20ms in periodic. They do not push data or fire interrupts the way microcontrollers sometimes do. This means you get one snapshot of the sensor state per loop cycle, and your code decisions are based on that snapshot until the next cycle. For a 50Hz loop this is fine for almost every FRC use case. It also means your code must read the sensor in the correct periodic method — not once in robotInit().
Sensor Explorer
FRC sensors fall into five categories. Click each one to see the WPILib class, construction, key API methods, and notes about what can go wrong with that sensor type on a real robot.
Encoder
External
Input
IMU
Sensor
Digital Input: Limit Switches and Beam Breaks
DigitalInput is the same class for both a limit switch and a beam break — both produce a binary true/false signal. The only difference is physics: a limit switch closes a circuit when physically depressed; a beam break interrupts an infrared beam to produce the same signal. Both plug into the roboRIO's DIO ports (0–9).
A limit switch stops a mechanism at a physical boundary — the bottom of an elevator, the fully-retracted position of a climber arm, the home position of an intake. Read it in periodic and use it to cut motor output before the mechanism hits the hard stop.
A beam break detects a game piece in the intake path — when the game piece interrupts the infrared beam, get() changes state. Use it to stop the intake roller automatically, confirm pick-up, and prevent jamming from over-running the intake.
The most common source of DigitalInput confusion is the "normally-open vs. normally-closed" wiring convention. A normally-open (NO) switch: unpressed = open circuit = get() returns true (internal pull-up). Pressed = closed = get() returns false. A normally-closed (NC) switch is the opposite. Check your physical sensor behavior before writing logic.
A normally-closed switch with a broken wire reads as "at limit" and stops the mechanism — a safe failure mode. A normally-open switch with a broken wire reads as "not at limit" and lets the mechanism drive past its boundary — a destructive failure mode. This is a hardware design decision, but the programmer should understand it when writing protection logic.
The CANcoder: Absolute Encoder Over CAN
Unlike the TalonFX's integrated encoder (which resets to zero every power cycle), the CANcoder reports an absolute position — it knows the shaft angle even after power loss. This makes it essential for mechanisms with a "home" position that matters across power cycles, like a swerve module's steer angle or an arm that needs to start at a known position.
The number-one swerve drive problem at competitions is a module that drives sideways or spins in circles instead of driving straight. Almost always, the root cause is a CANcoder magnet offset that's wrong — the encoder thinks the wheel is pointing forward when it's actually pointing 90 degrees off. This is checked during the mechanical assembly and the value is set in Phoenix Tuner X, then stored as a constant. If a robot that worked yesterday is now driving at an angle, the first thing to verify is whether a CANcoder magnet came loose or the offset was accidentally changed. Keep the magnet offset values in source control and in a physical log.
Gyroscopes: Heading and Tilt
A gyroscope (IMU — Inertial Measurement Unit) measures rotation. In FRC, the most common gyros are the Pigeon 2 (CTRE, CAN) and the navX2 (Kauai Labs, SPI/I2C). Both report yaw (heading), pitch, and roll. Yaw is used most: it's the robot's rotation around the vertical axis, which is how you implement field-oriented driving and straight-line autonomous.
Gyroscopes measure angular velocity and integrate over time. Any small error in each measurement accumulates. Most FRC gyros drift less than 1° per minute, which is negligible for a 2.5-minute match. More impactful: vibration from motors affects reading quality. Mount your Pigeon 2 on a firm structure away from drive motors, not on a flexible plate. A Pigeon mounted on a vibrating gearbox will drift noticeably during high-acceleration maneuvers. Check your yaw reading with the robot stationary: it should not creep more than a few tenths of a degree per second.
When Sensors Lie
Every sensor has failure modes. Knowing them prevents a 20-minute debugging session when the mechanism suddenly "doesn't work."
Which Sensor for Which Problem?
Select each scenario below to see the right sensor choice, why it fits, and what the code pattern looks like.
During robot inspections I ask teams to demonstrate their sensors on the Shuffleboard dashboard. Teams that can show me their encoder position, limit switch state, and gyro heading in real time — while the robot is disabled — are teams that can diagnose problems in the queue. Teams that only check sensors during matches are teams that don't notice a drifting encoder or a stuck limit switch until the mechanism fails mid-match. Add every sensor you use to robotPeriodic() dashboard output before your first competition. It costs you four lines of code and saves hours of debugging.
- Read all sensors in periodic methods, never once in
robotInit(). Sensor values change continuously. A position read inrobotInit()is stale within 20ms. The only initialization needed for sensors is their construction and configuration. - Publish every sensor to SmartDashboard in
robotPeriodic(). You cannot diagnose a sensor problem you cannot see. Live sensor readings on the dashboard are the most efficient debugging tool in FRC. - Physically verify sensor polarity before writing logic. Is
get()true or false when the limit switch is pressed? When the beam is blocked? When the arm is at home? Print and test before assuming. - Use a
Debounceron mechanical switches. Real switches bounce — they make and break contact rapidly over ~5ms when first pressed. A 40ms debounce window eliminates spurious triggers without perceptible delay. - Reset the gyro yaw in
autonomousInit(), notrobotInit(). Resetting at program start means the heading is relative to when the robot was powered on — which may not match the field orientation. Reset just before autonomous starts so the initial heading is consistent every run. - Verify CANcoder magnet offsets in Phoenix Tuner X before each event. Magnet offset is stored on the CANcoder itself. If a CANcoder was replaced or the magnet shifted, the offset must be recalibrated. Keep the calibration values in
Constants.javaas a cross-check.
Knowledge Check
Build a Sensor Dashboard
- Add a
DigitalInput bottomLimitwired to DIO port 0 and aDigitalInput beamBreakwired to DIO port 1. Construct both inrobotInit(). InrobotPeriodic(), publish both raw values to SmartDashboard. Manually press each switch and confirm the value changes as expected. - Add a
Pigeon2 gyroon CAN ID fromConstants.java. Publish yaw, pitch, and roll to SmartDashboard inrobotPeriodic(). Physically tilt the robot on each axis and confirm the correct value changes. Reset yaw inautonomousInit(). - Write a
private boolean isAtLowerLimit()method that wraps the rawbottomLimit.get()with the correct polarity (test physically first). Wrap it with aDebouncer(40ms window). Log the debounced value alongside the raw value to SmartDashboard and observe the difference when the switch bounces. - In
teleopPeriodic(), implement arm motor protection: ifisAtLowerLimit()returns true, zero any downward motor command. Allow upward commands to continue. IfbeamBreakindicates a game piece, stop the intake motor regardless of button state. - Add a
CANcoder steerEncoderif available. Publish its absolute position to SmartDashboard and manually rotate the shaft through 360°. Confirm the reading wraps correctly. Set the magnet offset so that 0.0 corresponds to your mechanism's "home" position. - Bonus: Build a "Sensor Health" panel on Shuffleboard with one indicator per sensor: green if the reading is within expected range, red if it looks wrong (encoder position NaN, gyro drifting >2°/s at rest, limit switch stuck). How would this panel help you in a 5-minute queue before a match?