Exemplo n.º 1
0
/**
 * Common initialization code for Encoders.
 * This code allocates resources for Encoders and is common to all constructors.
 *
 * The counter will start counting immediately.
 *
 * @param reverseDirection If true, counts down instead of up (this is all
 * relative)
 * @param encodingType either k1X, k2X, or k4X to indicate 1X, 2X or 4X
 * decoding. If 4X is
 * selected, then an encoder FPGA object is used and the returned counts will be
 * 4x the encoder
 * spec'd value since all rising and falling edges are counted. If 1X or 2X are
 * selected then
 * a counter object will be used and the returned value will either exactly
 * match the spec'd count
 * or be double (2x) the spec'd count.
 */
void Encoder::InitEncoder(bool reverseDirection, EncodingType encodingType) {
  m_encodingType = encodingType;
  switch (encodingType) {
    case k4X: {
      m_encodingScale = 4;
      if (m_aSource->StatusIsFatal()) {
        CloneError(*m_aSource);
        return;
      }
      if (m_bSource->StatusIsFatal()) {
        CloneError(*m_bSource);
        return;
      }
      int32_t status = 0;
      m_encoder = initializeEncoder(
          m_aSource->GetModuleForRouting(), m_aSource->GetChannelForRouting(),
          m_aSource->GetAnalogTriggerForRouting(),
          m_bSource->GetModuleForRouting(), m_bSource->GetChannelForRouting(),
          m_bSource->GetAnalogTriggerForRouting(), reverseDirection, &m_index,
          &status);
      wpi_setErrorWithContext(status, getHALErrorMessage(status));
      m_counter = nullptr;
      SetMaxPeriod(.5);
      break;
    }
    case k1X:
    case k2X: {
      m_encodingScale = encodingType == k1X ? 1 : 2;
      m_counter = std::make_unique<Counter>(m_encodingType, m_aSource,
                                              m_bSource, reverseDirection);
      m_index = m_counter->GetFPGAIndex();
      break;
    }
    default:
      wpi_setErrorWithContext(-1, "Invalid encodingType argument");
      break;
  }

  HALReport(HALUsageReporting::kResourceType_Encoder, m_index, encodingType);
  LiveWindow::GetInstance().AddSensor("Encoder",
                                       m_aSource->GetChannelForRouting(), this);
}
Exemplo n.º 2
0
/**
 * Create an instance of a DigitalInput.
 * Creates a digital input given a channel. Common creation routine for all
 * constructors.
 */
void DigitalInput::InitDigitalInput(uint32_t channel)
{
	m_table = NULL;
	char buf[64];

	if (!CheckDigitalChannel(channel))
	{
		snprintf(buf, 64, "Digital Channel %d", channel);
		wpi_setWPIErrorWithContext(ChannelIndexOutOfRange, buf);
		return;
	}
	m_channel = channel;

	int32_t status = 0;
	allocateDIO(m_digital_ports[channel], true, &status);
	wpi_setErrorWithContext(status, getHALErrorMessage(status));

	LiveWindow::GetInstance()->AddSensor("DigitalInput", channel, this);
	HALReport(HALUsageReporting::kResourceType_DigitalInput, channel);
}
Exemplo n.º 3
0
/**
* Request one of the 8 interrupts asynchronously on this digital input.
* Request interrupts in asynchronous mode where the user's interrupt handler
* will be
* called when the interrupt fires. Users that want control over the thread
* priority
* should use the synchronous method with their own spawned thread.
* The default is interrupt on rising edges only.
*/
void InterruptableSensorBase::RequestInterrupts(
    InterruptHandlerFunction handler, void *param) {
  if (StatusIsFatal()) return;
  uint32_t index = m_interrupts->Allocate("Async Interrupt");
  if (index == std::numeric_limits<uint32_t>::max()) {
    CloneError(*m_interrupts);
    return;
  }
  m_interruptIndex = index;

  // Creates a manager too
  AllocateInterrupts(false);

  int32_t status = 0;
  requestInterrupts(m_interrupt, GetModuleForRouting(), GetChannelForRouting(),
                    GetAnalogTriggerForRouting(), &status);
  SetUpSourceEdge(true, false);
  attachInterruptHandler(m_interrupt, handler, param, &status);
  wpi_setErrorWithContext(status, getHALErrorMessage(status));
}
Exemplo n.º 4
0
/**
 * Update the alarm hardware to reflect the current first element in the queue.
 * Compute the time the next alarm should occur based on the current time and
 * the
 * period for the first element in the timer queue.
 * WARNING: this method does not do synchronization! It must be called from
 * somewhere
 * that is taking care of synchronizing access to the queue.
 */
void Notifier::UpdateAlarm() {
  if (timerQueueHead != nullptr) {
    int32_t status = 0;
    // This locking is necessary in order to avoid two things:
    //  1) Race condition issues with calling cleanNotifer() and
    //     updateNotifierAlarm() at the same time.
    //  2) Avoid deadlock by making it so that this won't block waiting
    //     for the mutex to unlock.
    // Checking refcount as well is unnecessary, but will not hurt.
    if (halMutex.try_lock() && refcount != 0) {
      if (m_notifier)
        updateNotifierAlarm(m_notifier,
                            (uint32_t)(timerQueueHead->m_expirationTime * 1e6),
                            &status);
      halMutex.unlock();
    }
    wpi_setStaticErrorWithContext(timerQueueHead, status,
                                  getHALErrorMessage(status));
  }
}
Exemplo n.º 5
0
uint16_t CANTalon::GetStickyFaults() const {
  uint16_t retval = 0;
  int val;
  CTR_Code status = CTR_OKAY;

  /* temperature */
  val = 0;
  status = m_impl->GetStckyFault_OverTemp(val);
  if (status != CTR_OKAY)
    wpi_setErrorWithContext(status, getHALErrorMessage(status));
  retval |= (val) ? CANSpeedController::kTemperatureFault : 0;

  /* voltage */
  val = 0;
  status = m_impl->GetStckyFault_UnderVoltage(val);
  if (status != CTR_OKAY)
    wpi_setErrorWithContext(status, getHALErrorMessage(status));
  retval |= (val) ? CANSpeedController::kBusVoltageFault : 0;

  /* fwd-limit-switch */
  val = 0;
  status = m_impl->GetStckyFault_ForLim(val);
  if (status != CTR_OKAY)
    wpi_setErrorWithContext(status, getHALErrorMessage(status));
  retval |= (val) ? CANSpeedController::kFwdLimitSwitch : 0;

  /* rev-limit-switch */
  val = 0;
  status = m_impl->GetStckyFault_RevLim(val);
  if (status != CTR_OKAY)
    wpi_setErrorWithContext(status, getHALErrorMessage(status));
  retval |= (val) ? CANSpeedController::kRevLimitSwitch : 0;

  /* fwd-soft-limit */
  val = 0;
  status = m_impl->GetStckyFault_ForSoftLim(val);
  if (status != CTR_OKAY)
    wpi_setErrorWithContext(status, getHALErrorMessage(status));
  retval |= (val) ? CANSpeedController::kFwdSoftLimit : 0;

  /* rev-soft-limit */
  val = 0;
  status = m_impl->GetStckyFault_RevSoftLim(val);
  if (status != CTR_OKAY)
    wpi_setErrorWithContext(status, getHALErrorMessage(status));
  retval |= (val) ? CANSpeedController::kRevSoftLimit : 0;

  return retval;
}
Exemplo n.º 6
0
/**
 * Free the resources for a timer event.
 * All resources will be freed and the timer event will be removed from the
 * queue if necessary.
 */
Notifier::~Notifier()
{
	{
		::std::unique_lock<ReentrantMutex> sync(queueSemaphore);
		DeleteFromQueue();

		// Delete the static variables when the last one is going away
		if (!(--refcount))
		{
			int32_t status = 0;
			cleanNotifier(m_notifier, &status);
			wpi_setErrorWithContext(status, getHALErrorMessage(status));
		}
	}

	// Acquire the semaphore; this makes certain that the handler is 
	// not being executed by the interrupt manager.
	takeSemaphore(m_handlerSemaphore);
	// Delete while holding the semaphore so there can be no race.
	deleteSemaphore(m_handlerSemaphore);
}
Exemplo n.º 7
0
/**
 * Set the bounds on the PWM pulse widths.
 * This sets the bounds on the PWM values for a particular type of controller.
 * The values
 * determine the upper and lower speeds as well as the deadband bracket.
 * @param max The max PWM pulse width in ms
 * @param deadbandMax The high end of the deadband range pulse width in ms
 * @param center The center (off) pulse width in ms
 * @param deadbandMin The low end of the deadband pulse width in ms
 * @param min The minimum pulse width in ms
 */
void PWM::SetBounds(double max, double deadbandMax, double center,
                    double deadbandMin, double min) {
  // calculate the loop time in milliseconds
  int32_t status = 0;
  double loopTime =
      getLoopTiming(&status) / (kSystemClockTicksPerMicrosecond * 1e3);
  wpi_setErrorWithContext(status, getHALErrorMessage(status));

  if (StatusIsFatal()) return;

  m_maxPwm = (int32_t)((max - kDefaultPwmCenter) / loopTime +
                       kDefaultPwmStepsDown - 1);
  m_deadbandMaxPwm = (int32_t)((deadbandMax - kDefaultPwmCenter) / loopTime +
                               kDefaultPwmStepsDown - 1);
  m_centerPwm = (int32_t)((center - kDefaultPwmCenter) / loopTime +
                          kDefaultPwmStepsDown - 1);
  m_deadbandMinPwm = (int32_t)((deadbandMin - kDefaultPwmCenter) / loopTime +
                               kDefaultPwmStepsDown - 1);
  m_minPwm = (int32_t)((min - kDefaultPwmCenter) / loopTime +
                       kDefaultPwmStepsDown - 1);
}
Exemplo n.º 8
0
/**
 * TODO documentation (see CANJaguar.cpp)
 */
void CANTalon::ConfigNeutralMode(NeutralMode mode) {
  CTR_Code status = CTR_OKAY;
  switch (mode) {
    default:
    case kNeutralMode_Jumper: /* use default setting in flash based on
                                 webdash/BrakeCal button selection */
      status = m_impl->SetOverrideBrakeType(
          CanTalonSRX::kBrakeOverride_UseDefaultsFromFlash);
      break;
    case kNeutralMode_Brake:
      status = m_impl->SetOverrideBrakeType(
          CanTalonSRX::kBrakeOverride_OverrideBrake);
      break;
    case kNeutralMode_Coast:
      status = m_impl->SetOverrideBrakeType(
          CanTalonSRX::kBrakeOverride_OverrideCoast);
      break;
  }
  if (status != CTR_OKAY) {
    wpi_setErrorWithContext(status, getHALErrorMessage(status));
  }
}
Exemplo n.º 9
0
/**
 * Free the resources for a timer event.
 * All resources will be freed and the timer event will be removed from the
 * queue if necessary.
 */
Notifier::~Notifier() {
  {
    std::lock_guard<priority_recursive_mutex> sync(queueMutex);
    DeleteFromQueue();
  }

  // Delete the static variables when the last one is going away
  if (refcount.fetch_sub(1) == 1) {
    int32_t status = 0;
    {
      std::lock_guard<priority_mutex> sync(halMutex);
      if (m_notifier) {
        cleanNotifier(m_notifier, &status);
        m_notifier = nullptr;
      }
    }
    wpi_setErrorWithContext(status, getHALErrorMessage(status));
  }

  // Acquire the mutex; this makes certain that the handler is
  // not being executed by the interrupt manager.
  std::lock_guard<priority_mutex> lock(m_handlerMutex);
}
Exemplo n.º 10
0
/**
 * Relay constructor given a channel.
 *
 * This code initializes the relay and reserves all resources that need to be
 * locked. Initially the relay is set to both lines at 0v.
 * @param channel The channel number (0-3).
 * @param direction The direction that the Relay object will control.
 */
Relay::Relay(uint32_t channel, Relay::Direction direction)
    : m_channel(channel), m_direction(direction) {
  std::stringstream buf;
  Resource::CreateResourceObject(relayChannels,
                                 dio_kNumSystems * kRelayChannels * 2);
  if (!SensorBase::CheckRelayChannel(m_channel)) {
    buf << "Relay Channel " << m_channel;
    wpi_setWPIErrorWithContext(ChannelIndexOutOfRange, buf.str());
    return;
  }

  if (m_direction == kBothDirections || m_direction == kForwardOnly) {
    buf << "Forward Relay " << m_channel;
    if (relayChannels->Allocate(m_channel * 2, buf.str()) == ~0ul) {
      CloneError(*relayChannels);
      return;
    }

    HALReport(HALUsageReporting::kResourceType_Relay, m_channel);
  }
  if (m_direction == kBothDirections || m_direction == kReverseOnly) {
    buf << "Reverse Relay " << m_channel;
    if (relayChannels->Allocate(m_channel * 2 + 1, buf.str()) == ~0ul) {
      CloneError(*relayChannels);
      return;
    }

    HALReport(HALUsageReporting::kResourceType_Relay, m_channel + 128);
  }

  int32_t status = 0;
  setRelayForward(m_relay_ports[m_channel], false, &status);
  setRelayReverse(m_relay_ports[m_channel], false, &status);
  wpi_setErrorWithContext(status, getHALErrorMessage(status));

  LiveWindow::GetInstance().AddActuator("Relay", 1, m_channel, this);
}
Exemplo n.º 11
0
/**
 * Create a Notifier for timer event notification.
 * @param handler The handler is called at the notification time which is set
 * using StartSingle or StartPeriodic.
 */
Notifier::Notifier(TimerEventHandler handler, void *param)
{
	if (handler == NULL)
		wpi_setWPIErrorWithContext(NullParameter, "handler must not be NULL");
	m_handler = handler;
	m_param = param;
	m_periodic = false;
	m_expirationTime = 0;
	m_period = 0;
	m_nextEvent = NULL;
	m_queued = false;
	m_handlerSemaphore = initializeSemaphore(SEMAPHORE_FULL);
	{
		::std::unique_lock<ReentrantMutex> sync(queueSemaphore);
		// do the first time intialization of static variables
		if (refcount == 0)
		{
			int32_t status = 0;
			m_notifier = initializeNotifier(ProcessQueue, &status);
			wpi_setErrorWithContext(status, getHALErrorMessage(status));
		}
		refcount++;
	}
}
Exemplo n.º 12
0
/**
 * Slow down the PWM signal for old devices.
 *
 * @param mult The period multiplier to apply to this channel
 */
void PWM::SetPeriodMultiplier(PeriodMultiplier mult) {
  if (StatusIsFatal()) return;

  int32_t status = 0;

  switch (mult) {
    case kPeriodMultiplier_4X:
      setPWMPeriodScale(m_pwm_ports[m_channel], 3,
                        &status);  // Squelch 3 out of 4 outputs
      break;
    case kPeriodMultiplier_2X:
      setPWMPeriodScale(m_pwm_ports[m_channel], 1,
                        &status);  // Squelch 1 out of 2 outputs
      break;
    case kPeriodMultiplier_1X:
      setPWMPeriodScale(m_pwm_ports[m_channel], 0,
                        &status);  // Don't squelch any outputs
      break;
    default:
      wpi_assert(false);
  }

  wpi_setErrorWithContext(status, getHALErrorMessage(status));
}
Exemplo n.º 13
0
/**
 * Check if the FPGA outputs are enabled. The outputs may be disabled if the
 * robot is disabled
 * or e-stopped, the watchdog has expired, or if the roboRIO browns out.
 * @return True if the FPGA outputs are enabled.
 */
bool DriverStation::IsSysActive() const {
  int32_t status = 0;
  bool retVal = HALGetSystemActive(&status);
  wpi_setErrorWithContext(status, getHALErrorMessage(status));
  return retVal;
}
Exemplo n.º 14
0
/**
 * TODO documentation (see CANJaguar.cpp)
 * Configures the soft limit enable (wear leveled persistent memory).
 * Also sets the limit switch overrides.
 */
void CANTalon::ConfigLimitMode(LimitMode mode) {
  CTR_Code status = CTR_OKAY;
  switch (mode) {
    case kLimitMode_SwitchInputsOnly: /** Only use switches for limits */
      /* turn OFF both limits. SRX has individual enables and polarity for each
       * limit switch.*/
      status = m_impl->SetForwardSoftEnable(false);
      if (status != CTR_OKAY) {
        wpi_setErrorWithContext(status, getHALErrorMessage(status));
      }
      status = m_impl->SetReverseSoftEnable(false);
      if (status != CTR_OKAY) {
        wpi_setErrorWithContext(status, getHALErrorMessage(status));
      }
      /* override enable the limit switches, this circumvents the webdash */
      status = m_impl->SetOverrideLimitSwitchEn(
          CanTalonSRX::kLimitSwitchOverride_EnableFwd_EnableRev);
      if (status != CTR_OKAY) {
        wpi_setErrorWithContext(status, getHALErrorMessage(status));
      }
      break;
    case kLimitMode_SoftPositionLimits: /** Use both switches and soft limits */
      /* turn on both limits. SRX has individual enables and polarity for each
       * limit switch.*/
      status = m_impl->SetForwardSoftEnable(true);
      if (status != CTR_OKAY) {
        wpi_setErrorWithContext(status, getHALErrorMessage(status));
      }
      status = m_impl->SetReverseSoftEnable(true);
      if (status != CTR_OKAY) {
        wpi_setErrorWithContext(status, getHALErrorMessage(status));
      }
      /* override enable the limit switches, this circumvents the webdash */
      status = m_impl->SetOverrideLimitSwitchEn(
          CanTalonSRX::kLimitSwitchOverride_EnableFwd_EnableRev);
      if (status != CTR_OKAY) {
        wpi_setErrorWithContext(status, getHALErrorMessage(status));
      }
      break;

    case kLimitMode_SrxDisableSwitchInputs: /** disable both limit switches and
                                               soft limits */
      /* turn on both limits. SRX has individual enables and polarity for each
       * limit switch.*/
      status = m_impl->SetForwardSoftEnable(false);
      if (status != CTR_OKAY) {
        wpi_setErrorWithContext(status, getHALErrorMessage(status));
      }
      status = m_impl->SetReverseSoftEnable(false);
      if (status != CTR_OKAY) {
        wpi_setErrorWithContext(status, getHALErrorMessage(status));
      }
      /* override enable the limit switches, this circumvents the webdash */
      status = m_impl->SetOverrideLimitSwitchEn(
          CanTalonSRX::kLimitSwitchOverride_DisableFwd_DisableRev);
      if (status != CTR_OKAY) {
        wpi_setErrorWithContext(status, getHALErrorMessage(status));
      }
      break;
  }
}
Exemplo n.º 15
0
AnalogTrigger::~AnalogTrigger()
{
	int32_t status = 0;
	cleanAnalogTrigger(m_trigger, &status);
	wpi_setErrorWithContext(status, getHALErrorMessage(status));
}
Exemplo n.º 16
0
/**
 * Set the feedforward value of the currently selected profile.
 *
 * @param f Feedforward constant for the currently selected PID profile.
 * @see SelectProfileSlot to choose between the two sets of gains.
 */
void CANTalon::SetF(double f) {
  CTR_Code status = m_impl->SetFgain(m_profile, f);
  if (status != CTR_OKAY) {
    wpi_setErrorWithContext(status, getHALErrorMessage(status));
  }
}
Exemplo n.º 17
0
/**
 * Set the Izone to a nonzero value to auto clear the integral accumulator
 * 		when the absolute value of CloseLoopError exceeds Izone.
 *
 * @see SelectProfileSlot to choose between the two sets of gains.
 */
void CANTalon::SetIzone(unsigned iz) {
  CTR_Code status = m_impl->SetIzone(m_profile, iz);
  if (status != CTR_OKAY) {
    wpi_setErrorWithContext(status, getHALErrorMessage(status));
  }
}
Exemplo n.º 18
0
void CANTalon::ClearStickyFaults()
{
	CTR_Code status = m_impl->ClearStickyFaults();
	wpi_setErrorWithContext(status, getHALErrorMessage(status));
}
Exemplo n.º 19
0
/**
 * If sensor and motor are out of phase, sensor can be inverted
 * (position and velocity multiplied by -1).
 * @see GetPosition and @see GetSpeed.
 */
void CANTalon::SetSensorDirection(bool reverseSensor) {
  CTR_Code status = m_impl->SetRevFeedbackSensor(reverseSensor ? 1 : 0);
  if (status != CTR_OKAY) {
    wpi_setErrorWithContext(status, getHALErrorMessage(status));
  }
}
Exemplo n.º 20
0
/**
 * Set the sample rate per channel for all analog channels.
 * The maximum rate is 500kS/s divided by the number of channels in use.
 * This is 62500 samples/s per channel.
 * @param samplesPerSecond The number of samples per second.
 */
void AnalogInput::SetSampleRate(float samplesPerSecond) {
    int32_t status = 0;
    setAnalogSampleRate(samplesPerSecond, &status);
    wpi_setGlobalErrorWithContext(status, getHALErrorMessage(status));
}
Exemplo n.º 21
0
/**
 * Set the accumulator's deadband.
 * @param
 */
void AnalogInput::SetAccumulatorDeadband(int32_t deadband) {
    if (StatusIsFatal()) return;
    int32_t status = 0;
    setAccumulatorDeadband(m_port, deadband, &status);
    wpi_setErrorWithContext(status, getHALErrorMessage(status));
}
Exemplo n.º 22
0
/**
 * Set the center value of the accumulator.
 *
 * The center value is subtracted from each A/D value before it is added to the
 * accumulator. This
 * is used for the center value of devices like gyros and accelerometers to
 * take the device offset into account when integrating.
 *
 * This center value is based on the output of the oversampled and averaged
 * source from the accumulator
 * channel. Because of this, any non-zero oversample bits will affect the size
 * of the value for this field.
 */
void AnalogInput::SetAccumulatorCenter(int32_t center) {
    if (StatusIsFatal()) return;
    int32_t status = 0;
    setAccumulatorCenter(m_port, center, &status);
    wpi_setErrorWithContext(status, getHALErrorMessage(status));
}
Exemplo n.º 23
0
/**
 * Select the feedback device to use in closed-loop
 */
void CANTalon::SetFeedbackDevice(FeedbackDevice device) {
  CTR_Code status = m_impl->SetFeedbackDeviceSelect((int)device);
  if (status != CTR_OKAY) {
    wpi_setErrorWithContext(status, getHALErrorMessage(status));
  }
}
Exemplo n.º 24
0
/**
 * Select the feedback device to use in closed-loop
 */
void CANTalon::SetStatusFrameRateMs(StatusFrameRate stateFrame, int periodMs) {
  CTR_Code status = m_impl->SetStatusFrameRate((int)stateFrame, periodMs);
  if (status != CTR_OKAY) {
    wpi_setErrorWithContext(status, getHALErrorMessage(status));
  }
}
Exemplo n.º 25
0
/**
 * Check if the system is browned out.
 * @return True if the system is browned out
 */
bool DriverStation::IsSysBrownedOut() const {
  int32_t status = 0;
  bool retVal = HALGetBrownedOut(&status);
  wpi_setErrorWithContext(status, getHALErrorMessage(status));
  return retVal;
}
Exemplo n.º 26
0
/**
 * Get the current sample rate for all channels
 *
 * @return Sample rate.
 */
float AnalogInput::GetSampleRate() {
    int32_t status = 0;
    float sampleRate = getAnalogSampleRate(&status);
    wpi_setGlobalErrorWithContext(status, getHALErrorMessage(status));
    return sampleRate;
}
Exemplo n.º 27
0
/**
 * Get the number of averaging bits previously configured.
 * This gets the number of averaging bits from the FPGA. The actual number of
 * averaged samples is 2^bits.
 * The averaging is done automatically in the FPGA.
 *
 * @return Number of bits of averaging previously configured.
 */
uint32_t AnalogInput::GetAverageBits() const {
    int32_t status = 0;
    int32_t averageBits = getAnalogAverageBits(m_port, &status);
    wpi_setErrorWithContext(status, getHALErrorMessage(status));
    return averageBits;
}
Exemplo n.º 28
0
/**
 * Clear the accumulator for I gain.
 */
void CANTalon::ClearIaccum() {
  CTR_Code status = m_impl->SetParam(CanTalonSRX::ePidIaccum, 0);
  if (status != CTR_OKAY) {
    wpi_setErrorWithContext(status, getHALErrorMessage(status));
  }
}
InterruptSource::InterruptSource(uint32_t channel) : m_channel(channel) {
  int32_t status = 0;
  allocateDIO(m_digital_ports[channel], true, &status);
  wpi_setErrorWithContext(status, getHALErrorMessage(status));
}
Exemplo n.º 30
0
/**
 * Set the number of oversample bits.
 * This sets the number of oversample bits. The actual number of oversampled
 * values is 2^bits.
 * Use oversampling to improve the resolution of your measurements at the
 * expense of sampling rate.
 * The oversampling is done automatically in the FPGA.
 *
 * @param bits Number of bits of oversampling.
 */
void AnalogInput::SetOversampleBits(uint32_t bits) {
    if (StatusIsFatal()) return;
    int32_t status = 0;
    setAnalogOversampleBits(m_port, bits, &status);
    wpi_setErrorWithContext(status, getHALErrorMessage(status));
}