| // Copyright (c) FIRST and other WPILib contributors. |
| // Open Source Software; you can modify and/or share it under the terms of |
| // the WPILib BSD license file in the root directory of this project. |
| |
| #include "hal/PWM.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| |
| #include "ConstantsInternal.h" |
| #include "DigitalInternal.h" |
| #include "HALInitializer.h" |
| #include "HALInternal.h" |
| #include "PortsInternal.h" |
| #include "hal/handles/HandlesInternal.h" |
| #include "mockdata/PWMDataInternal.h" |
| |
| using namespace hal; |
| |
| namespace hal::init { |
| void InitializePWM() {} |
| } // namespace hal::init |
| |
| static inline int32_t GetMaxPositivePwm(DigitalPort* port) { |
| return port->maxPwm; |
| } |
| |
| static inline int32_t GetMinPositivePwm(DigitalPort* port) { |
| if (port->eliminateDeadband) { |
| return port->deadbandMaxPwm; |
| } else { |
| return port->centerPwm + 1; |
| } |
| } |
| |
| static inline int32_t GetCenterPwm(DigitalPort* port) { |
| return port->centerPwm; |
| } |
| |
| static inline int32_t GetMaxNegativePwm(DigitalPort* port) { |
| if (port->eliminateDeadband) { |
| return port->deadbandMinPwm; |
| } else { |
| return port->centerPwm - 1; |
| } |
| } |
| |
| static inline int32_t GetMinNegativePwm(DigitalPort* port) { |
| return port->minPwm; |
| } |
| |
| static inline int32_t GetPositiveScaleFactor(DigitalPort* port) { |
| return GetMaxPositivePwm(port) - GetMinPositivePwm(port); |
| } ///< The scale for positive speeds. |
| |
| static inline int32_t GetNegativeScaleFactor(DigitalPort* port) { |
| return GetMaxNegativePwm(port) - GetMinNegativePwm(port); |
| } ///< The scale for negative speeds. |
| |
| static inline int32_t GetFullRangeScaleFactor(DigitalPort* port) { |
| return GetMaxPositivePwm(port) - GetMinNegativePwm(port); |
| } ///< The scale for positions. |
| |
| extern "C" { |
| |
| HAL_DigitalHandle HAL_InitializePWMPort(HAL_PortHandle portHandle, |
| const char* allocationLocation, |
| int32_t* status) { |
| hal::init::CheckInit(); |
| |
| int16_t channel = getPortHandleChannel(portHandle); |
| if (channel == InvalidHandleIndex) { |
| *status = RESOURCE_OUT_OF_RANGE; |
| hal::SetLastErrorIndexOutOfRange(status, "Invalid Index for PWM", 0, |
| kNumPWMChannels, channel); |
| return HAL_kInvalidHandle; |
| } |
| |
| uint8_t origChannel = static_cast<uint8_t>(channel); |
| |
| if (origChannel < kNumPWMHeaders) { |
| channel += kNumDigitalChannels; // remap Headers to end of allocations |
| } else { |
| channel = remapMXPPWMChannel(channel) + 10; // remap MXP to proper channel |
| } |
| |
| HAL_DigitalHandle handle; |
| |
| auto port = digitalChannelHandles->Allocate(channel, HAL_HandleEnum::PWM, |
| &handle, status); |
| |
| if (*status != 0) { |
| if (port) { |
| hal::SetLastErrorPreviouslyAllocated(status, "PWM or DIO", channel, |
| port->previousAllocation); |
| } else { |
| hal::SetLastErrorIndexOutOfRange(status, "Invalid Index for PWM", 0, |
| kNumPWMChannels, channel); |
| } |
| return HAL_kInvalidHandle; // failed to allocate. Pass error back. |
| } |
| |
| port->channel = origChannel; |
| |
| SimPWMData[origChannel].initialized = true; |
| |
| // Defaults to allow an always valid config. |
| HAL_SetPWMConfigMicroseconds(handle, 2000, 1501, 1500, 1499, 1000, status); |
| |
| port->previousAllocation = allocationLocation ? allocationLocation : ""; |
| |
| return handle; |
| } |
| void HAL_FreePWMPort(HAL_DigitalHandle pwmPortHandle, int32_t* status) { |
| auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM); |
| if (port == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return; |
| } |
| |
| SimPWMData[port->channel].initialized = false; |
| |
| digitalChannelHandles->Free(pwmPortHandle, HAL_HandleEnum::PWM); |
| } |
| |
| HAL_Bool HAL_CheckPWMChannel(int32_t channel) { |
| return channel < kNumPWMChannels && channel >= 0; |
| } |
| |
| void HAL_SetPWMConfigMicroseconds(HAL_DigitalHandle pwmPortHandle, int32_t max, |
| int32_t deadbandMax, int32_t center, |
| int32_t deadbandMin, int32_t min, |
| int32_t* status) { |
| auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM); |
| if (port == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return; |
| } |
| |
| port->maxPwm = max; |
| port->deadbandMaxPwm = deadbandMax; |
| port->deadbandMinPwm = deadbandMin; |
| port->centerPwm = center; |
| port->minPwm = min; |
| port->configSet = true; |
| } |
| |
| void HAL_GetPWMConfigMicroseconds(HAL_DigitalHandle pwmPortHandle, |
| int32_t* maxPwm, int32_t* deadbandMaxPwm, |
| int32_t* centerPwm, int32_t* deadbandMinPwm, |
| int32_t* minPwm, int32_t* status) { |
| auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM); |
| if (port == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return; |
| } |
| *maxPwm = port->maxPwm; |
| *deadbandMaxPwm = port->deadbandMaxPwm; |
| *deadbandMinPwm = port->deadbandMinPwm; |
| *centerPwm = port->centerPwm; |
| *minPwm = port->minPwm; |
| } |
| |
| void HAL_SetPWMEliminateDeadband(HAL_DigitalHandle pwmPortHandle, |
| HAL_Bool eliminateDeadband, int32_t* status) { |
| auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM); |
| if (port == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return; |
| } |
| port->eliminateDeadband = eliminateDeadband; |
| } |
| |
| HAL_Bool HAL_GetPWMEliminateDeadband(HAL_DigitalHandle pwmPortHandle, |
| int32_t* status) { |
| auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM); |
| if (port == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return false; |
| } |
| return port->eliminateDeadband; |
| } |
| |
| void HAL_SetPWMPulseTimeMicroseconds(HAL_DigitalHandle pwmPortHandle, |
| int32_t value, int32_t* status) { |
| auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM); |
| if (port == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return; |
| } |
| |
| SimPWMData[port->channel].pulseMicrosecond = value; |
| |
| DigitalPort* dPort = port.get(); |
| double speed = 0.0; |
| |
| if (value == kPwmDisabled) { |
| speed = 0.0; |
| } else if (value > GetMaxPositivePwm(dPort)) { |
| speed = 1.0; |
| } else if (value < GetMinNegativePwm(dPort)) { |
| speed = -1.0; |
| } else if (value > GetMinPositivePwm(dPort)) { |
| speed = static_cast<double>(value - GetMinPositivePwm(dPort)) / |
| static_cast<double>(GetPositiveScaleFactor(dPort)); |
| } else if (value < GetMaxNegativePwm(dPort)) { |
| speed = static_cast<double>(value - GetMaxNegativePwm(dPort)) / |
| static_cast<double>(GetNegativeScaleFactor(dPort)); |
| } else { |
| speed = 0.0; |
| } |
| |
| SimPWMData[port->channel].speed = speed; |
| |
| double pos = 0.0; |
| |
| if (value < GetMinNegativePwm(dPort)) { |
| pos = 0.0; |
| } else if (value > GetMaxPositivePwm(dPort)) { |
| pos = 1.0; |
| } else { |
| pos = static_cast<double>(value - GetMinNegativePwm(dPort)) / |
| static_cast<double>(GetFullRangeScaleFactor(dPort)); |
| } |
| |
| SimPWMData[port->channel].position = pos; |
| } |
| |
| void HAL_SetPWMSpeed(HAL_DigitalHandle pwmPortHandle, double speed, |
| int32_t* status) { |
| auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM); |
| if (port == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return; |
| } |
| if (!port->configSet) { |
| *status = INCOMPATIBLE_STATE; |
| return; |
| } |
| |
| if (std::isfinite(speed)) { |
| speed = std::clamp(speed, -1.0, 1.0); |
| } else { |
| speed = 0.0; |
| } |
| |
| DigitalPort* dPort = port.get(); |
| |
| // calculate the desired output pwm value by scaling the speed appropriately |
| int32_t rawValue; |
| if (speed == 0.0) { |
| rawValue = GetCenterPwm(dPort); |
| } else if (speed > 0.0) { |
| rawValue = |
| std::lround(speed * static_cast<double>(GetPositiveScaleFactor(dPort)) + |
| static_cast<double>(GetMinPositivePwm(dPort))); |
| } else { |
| rawValue = |
| std::lround(speed * static_cast<double>(GetNegativeScaleFactor(dPort)) + |
| static_cast<double>(GetMaxNegativePwm(dPort))); |
| } |
| |
| if (!((rawValue >= GetMinNegativePwm(dPort)) && |
| (rawValue <= GetMaxPositivePwm(dPort))) || |
| rawValue == kPwmDisabled) { |
| *status = HAL_PWM_SCALE_ERROR; |
| return; |
| } |
| |
| HAL_SetPWMPulseTimeMicroseconds(pwmPortHandle, rawValue, status); |
| } |
| |
| void HAL_SetPWMPosition(HAL_DigitalHandle pwmPortHandle, double pos, |
| int32_t* status) { |
| auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM); |
| if (port == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return; |
| } |
| if (!port->configSet) { |
| *status = INCOMPATIBLE_STATE; |
| return; |
| } |
| |
| if (pos < 0.0) { |
| pos = 0.0; |
| } else if (pos > 1.0) { |
| pos = 1.0; |
| } |
| |
| DigitalPort* dPort = port.get(); |
| |
| // note, need to perform the multiplication below as floating point before |
| // converting to int |
| int32_t rawValue = static_cast<int32_t>( |
| (pos * static_cast<double>(GetFullRangeScaleFactor(dPort))) + |
| GetMinNegativePwm(dPort)); |
| |
| if (rawValue == kPwmDisabled) { |
| *status = HAL_PWM_SCALE_ERROR; |
| return; |
| } |
| |
| HAL_SetPWMPulseTimeMicroseconds(pwmPortHandle, rawValue, status); |
| } |
| |
| void HAL_SetPWMDisabled(HAL_DigitalHandle pwmPortHandle, int32_t* status) { |
| auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM); |
| if (port == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return; |
| } |
| SimPWMData[port->channel].pulseMicrosecond = 0; |
| SimPWMData[port->channel].position = 0; |
| SimPWMData[port->channel].speed = 0; |
| } |
| |
| int32_t HAL_GetPWMPulseTimeMicroseconds(HAL_DigitalHandle pwmPortHandle, |
| int32_t* status) { |
| auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM); |
| if (port == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return 0; |
| } |
| |
| return SimPWMData[port->channel].pulseMicrosecond; |
| } |
| |
| double HAL_GetPWMSpeed(HAL_DigitalHandle pwmPortHandle, int32_t* status) { |
| auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM); |
| if (port == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return 0; |
| } |
| if (!port->configSet) { |
| *status = INCOMPATIBLE_STATE; |
| return 0; |
| } |
| |
| double speed = SimPWMData[port->channel].speed; |
| if (speed > 1) { |
| speed = 1; |
| } |
| if (speed < -1) { |
| speed = -1; |
| } |
| return speed; |
| } |
| |
| double HAL_GetPWMPosition(HAL_DigitalHandle pwmPortHandle, int32_t* status) { |
| auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM); |
| if (port == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return 0; |
| } |
| if (!port->configSet) { |
| *status = INCOMPATIBLE_STATE; |
| return 0; |
| } |
| |
| double position = SimPWMData[port->channel].position; |
| if (position > 1) { |
| position = 1; |
| } |
| if (position < 0) { |
| position = 0; |
| } |
| return position; |
| } |
| |
| void HAL_LatchPWMZero(HAL_DigitalHandle pwmPortHandle, int32_t* status) { |
| auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM); |
| if (port == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return; |
| } |
| |
| SimPWMData[port->channel].zeroLatch = true; |
| SimPWMData[port->channel].zeroLatch = false; |
| } |
| |
| void HAL_SetPWMAlwaysHighMode(HAL_DigitalHandle pwmPortHandle, |
| int32_t* status) { |
| auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM); |
| if (port == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return; |
| } |
| |
| SimPWMData[port->channel].pulseMicrosecond = 0xFFFF; |
| SimPWMData[port->channel].position = 0xFFFF; |
| SimPWMData[port->channel].speed = 0xFFFF; |
| } |
| |
| void HAL_SetPWMPeriodScale(HAL_DigitalHandle pwmPortHandle, int32_t squelchMask, |
| int32_t* status) { |
| auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM); |
| if (port == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return; |
| } |
| |
| SimPWMData[port->channel].periodScale = squelchMask; |
| } |
| |
| int32_t HAL_GetPWMLoopTiming(int32_t* status) { |
| return kExpectedLoopTiming; |
| } |
| |
| uint64_t HAL_GetPWMCycleStartTime(int32_t* status) { |
| return 0; |
| } |
| } // extern "C" |