| /*----------------------------------------------------------------------------*/ |
| /* Copyright (c) FIRST 2009. All Rights Reserved. */ |
| /* Open Source Software - may be modified and shared by FRC teams. The code */ |
| /* must be accompanied by the FIRST BSD license file in $(WIND_BASE)/WPILib. */ |
| /*----------------------------------------------------------------------------*/ |
| |
| #include "CANJaguar.h" |
| #define tNIRIO_i32 int |
| #include "ChipObject/NiFpga.h" |
| #include "CAN/JaguarCANDriver.h" |
| #include "CAN/can_proto.h" |
| #include "NetworkCommunication/UsageReporting.h" |
| #include "WPIErrors.h" |
| #include <stdio.h> |
| #include "LiveWindow/LiveWindow.h" |
| |
| #define swap16(x) ( (((x)>>8) &0x00FF) \ |
| | (((x)<<8) &0xFF00) ) |
| #define swap32(x) ( (((x)>>24)&0x000000FF) \ |
| | (((x)>>8) &0x0000FF00) \ |
| | (((x)<<8) &0x00FF0000) \ |
| | (((x)<<24)&0xFF000000) ) |
| |
| #define kFullMessageIDMask (CAN_MSGID_API_M | CAN_MSGID_MFR_M | CAN_MSGID_DTYPE_M) |
| |
| const INT32 CANJaguar::kControllerRate; |
| const double CANJaguar::kApproxBusVoltage; |
| |
| /** |
| * Common initialization code called by all constructors. |
| */ |
| void CANJaguar::InitCANJaguar() |
| { |
| m_transactionSemaphore = semMCreate(SEM_Q_PRIORITY | SEM_INVERSION_SAFE | SEM_DELETE_SAFE); |
| if (m_deviceNumber < 1 || m_deviceNumber > 63) |
| { |
| char buf[256]; |
| snprintf(buf, 256, "device number \"%d\" must be between 1 and 63", m_deviceNumber); |
| wpi_setWPIErrorWithContext(ParameterOutOfRange, buf); |
| return; |
| } |
| UINT32 fwVer = GetFirmwareVersion(); |
| if (StatusIsFatal()) |
| return; |
| // 3330 was the first shipping RDK firmware version for the Jaguar |
| if (fwVer >= 3330 || fwVer < 101) |
| { |
| char buf[256]; |
| if (fwVer < 3330) |
| { |
| snprintf(buf, 256, "Jag #%d firmware (%d) is too old (must be at least version 101 of the FIRST approved firmware)", m_deviceNumber, fwVer); |
| } |
| else |
| { |
| snprintf(buf, 256, "Jag #%d firmware (%d) is not FIRST approved (must be at least version 101 of the FIRST approved firmware)", m_deviceNumber, fwVer); |
| } |
| wpi_setWPIErrorWithContext(JaguarVersionError, buf); |
| return; |
| } |
| switch (m_controlMode) |
| { |
| case kPercentVbus: |
| case kVoltage: |
| // No additional configuration required... start enabled. |
| EnableControl(); |
| break; |
| default: |
| break; |
| } |
| m_safetyHelper = new MotorSafetyHelper(this); |
| |
| nUsageReporting::report(nUsageReporting::kResourceType_CANJaguar, m_deviceNumber, m_controlMode); |
| LiveWindow::GetInstance()->AddActuator("CANJaguar", m_deviceNumber, 0, this); |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param deviceNumber The the address of the Jaguar on the CAN bus. |
| */ |
| CANJaguar::CANJaguar(UINT8 deviceNumber, ControlMode controlMode) |
| : m_deviceNumber (deviceNumber) |
| , m_controlMode (controlMode) |
| , m_transactionSemaphore (NULL) |
| , m_maxOutputVoltage (kApproxBusVoltage) |
| , m_safetyHelper (NULL) |
| { |
| InitCANJaguar(); |
| } |
| |
| CANJaguar::~CANJaguar() |
| { |
| delete m_safetyHelper; |
| m_safetyHelper = NULL; |
| semDelete(m_transactionSemaphore); |
| m_transactionSemaphore = NULL; |
| } |
| |
| /** |
| * Set the output set-point value. |
| * |
| * The scale and the units depend on the mode the Jaguar is in. |
| * In PercentVbus Mode, the outputValue is from -1.0 to 1.0 (same as PWM Jaguar). |
| * In Voltage Mode, the outputValue is in Volts. |
| * In Current Mode, the outputValue is in Amps. |
| * In Speed Mode, the outputValue is in Rotations/Minute. |
| * In Position Mode, the outputValue is in Rotations. |
| * |
| * @param outputValue The set-point to sent to the motor controller. |
| * @param syncGroup The update group to add this Set() to, pending UpdateSyncGroup(). If 0, update immediately. |
| */ |
| void CANJaguar::Set(float outputValue, UINT8 syncGroup) |
| { |
| UINT32 messageID; |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| if (m_safetyHelper && !m_safetyHelper->IsAlive()) |
| { |
| EnableControl(); |
| } |
| |
| switch(m_controlMode) |
| { |
| case kPercentVbus: |
| { |
| messageID = LM_API_VOLT_T_SET; |
| if (outputValue > 1.0) outputValue = 1.0; |
| if (outputValue < -1.0) outputValue = -1.0; |
| dataSize = packPercentage(dataBuffer, outputValue); |
| } |
| break; |
| case kSpeed: |
| { |
| messageID = LM_API_SPD_T_SET; |
| dataSize = packFXP16_16(dataBuffer, outputValue); |
| } |
| break; |
| case kPosition: |
| { |
| messageID = LM_API_POS_T_SET; |
| dataSize = packFXP16_16(dataBuffer, outputValue); |
| } |
| break; |
| case kCurrent: |
| { |
| messageID = LM_API_ICTRL_T_SET; |
| dataSize = packFXP8_8(dataBuffer, outputValue); |
| } |
| break; |
| case kVoltage: |
| { |
| messageID = LM_API_VCOMP_T_SET; |
| dataSize = packFXP8_8(dataBuffer, outputValue); |
| } |
| break; |
| default: |
| return; |
| } |
| if (syncGroup != 0) |
| { |
| dataBuffer[dataSize] = syncGroup; |
| dataSize++; |
| } |
| setTransaction(messageID, dataBuffer, dataSize); |
| if (m_safetyHelper) m_safetyHelper->Feed(); |
| } |
| |
| /** |
| * Get the recently set outputValue setpoint. |
| * |
| * The scale and the units depend on the mode the Jaguar is in. |
| * In PercentVbus Mode, the outputValue is from -1.0 to 1.0 (same as PWM Jaguar). |
| * In Voltage Mode, the outputValue is in Volts. |
| * In Current Mode, the outputValue is in Amps. |
| * In Speed Mode, the outputValue is in Rotations/Minute. |
| * In Position Mode, the outputValue is in Rotations. |
| * |
| * @return The most recently set outputValue setpoint. |
| */ |
| float CANJaguar::Get() |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| switch(m_controlMode) |
| { |
| case kPercentVbus: |
| getTransaction(LM_API_VOLT_SET, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT16)) |
| { |
| return unpackPercentage(dataBuffer); |
| } |
| break; |
| case kSpeed: |
| getTransaction(LM_API_SPD_SET, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT32)) |
| { |
| return unpackFXP16_16(dataBuffer); |
| } |
| break; |
| case kPosition: |
| getTransaction(LM_API_POS_SET, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT32)) |
| { |
| return unpackFXP16_16(dataBuffer); |
| } |
| break; |
| case kCurrent: |
| getTransaction(LM_API_ICTRL_SET, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT16)) |
| { |
| return unpackFXP8_8(dataBuffer); |
| } |
| break; |
| case kVoltage: |
| getTransaction(LM_API_VCOMP_SET, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT16)) |
| { |
| return unpackFXP8_8(dataBuffer); |
| } |
| break; |
| } |
| return 0.0; |
| } |
| |
| /** |
| * Common interface for disabling a motor. |
| * |
| * @deprecated Call DisableControl instead. |
| */ |
| void CANJaguar::Disable() |
| { |
| DisableControl(); |
| } |
| |
| /** |
| * Write out the PID value as seen in the PIDOutput base object. |
| * |
| * @deprecated Call Set instead. |
| * |
| * @param output Write out the PercentVbus value as was computed by the PIDController |
| */ |
| void CANJaguar::PIDWrite(float output) |
| { |
| if (m_controlMode == kPercentVbus) |
| { |
| Set(output); |
| } |
| else |
| { |
| wpi_setWPIErrorWithContext(IncompatibleMode, "PID only supported in PercentVbus mode"); |
| } |
| } |
| |
| UINT8 CANJaguar::packPercentage(UINT8 *buffer, double value) |
| { |
| INT16 intValue = (INT16)(value * 32767.0); |
| *((INT16*)buffer) = swap16(intValue); |
| return sizeof(INT16); |
| } |
| |
| UINT8 CANJaguar::packFXP8_8(UINT8 *buffer, double value) |
| { |
| INT16 intValue = (INT16)(value * 256.0); |
| *((INT16*)buffer) = swap16(intValue); |
| return sizeof(INT16); |
| } |
| |
| UINT8 CANJaguar::packFXP16_16(UINT8 *buffer, double value) |
| { |
| INT32 intValue = (INT32)(value * 65536.0); |
| *((INT32*)buffer) = swap32(intValue); |
| return sizeof(INT32); |
| } |
| |
| UINT8 CANJaguar::packINT16(UINT8 *buffer, INT16 value) |
| { |
| *((INT16*)buffer) = swap16(value); |
| return sizeof(INT16); |
| } |
| |
| UINT8 CANJaguar::packINT32(UINT8 *buffer, INT32 value) |
| { |
| *((INT32*)buffer) = swap32(value); |
| return sizeof(INT32); |
| } |
| |
| double CANJaguar::unpackPercentage(UINT8 *buffer) |
| { |
| INT16 value = *((INT16*)buffer); |
| value = swap16(value); |
| return value / 32767.0; |
| } |
| |
| double CANJaguar::unpackFXP8_8(UINT8 *buffer) |
| { |
| INT16 value = *((INT16*)buffer); |
| value = swap16(value); |
| return value / 256.0; |
| } |
| |
| double CANJaguar::unpackFXP16_16(UINT8 *buffer) |
| { |
| INT32 value = *((INT32*)buffer); |
| value = swap32(value); |
| return value / 65536.0; |
| } |
| |
| INT16 CANJaguar::unpackINT16(UINT8 *buffer) |
| { |
| INT16 value = *((INT16*)buffer); |
| return swap16(value); |
| } |
| |
| INT32 CANJaguar::unpackINT32(UINT8 *buffer) |
| { |
| INT32 value = *((INT32*)buffer); |
| return swap32(value); |
| } |
| |
| /** |
| * Send a message on the CAN bus through the CAN driver in FRC_NetworkCommunication |
| * |
| * Trusted messages require a 2-byte token at the beginning of the data payload. |
| * If the message being sent is trusted, make space for the token. |
| * |
| * @param messageID The messageID to be used on the CAN bus |
| * @param data The up to 8 bytes of data to be sent with the message |
| * @param dataSize Specify how much of the data in "data" to send |
| * @return Status of send call |
| */ |
| INT32 CANJaguar::sendMessage(UINT32 messageID, const UINT8 *data, UINT8 dataSize) |
| { |
| static const UINT32 kTrustedMessages[] = { |
| LM_API_VOLT_T_EN, LM_API_VOLT_T_SET, LM_API_SPD_T_EN, LM_API_SPD_T_SET, |
| LM_API_VCOMP_T_EN, LM_API_VCOMP_T_SET, LM_API_POS_T_EN, LM_API_POS_T_SET, |
| LM_API_ICTRL_T_EN, LM_API_ICTRL_T_SET}; |
| INT32 status=0; |
| |
| for (UINT8 i=0; i<(sizeof(kTrustedMessages)/sizeof(kTrustedMessages[0])); i++) |
| { |
| if ((kFullMessageIDMask & messageID) == kTrustedMessages[i]) |
| { |
| UINT8 dataBuffer[8]; |
| dataBuffer[0] = 0; |
| dataBuffer[1] = 0; |
| // Make sure the data will still fit after adjusting for the token. |
| if (dataSize > 6) |
| { |
| // TODO: I would rather this not have to set the global error |
| wpi_setGlobalWPIErrorWithContext(ParameterOutOfRange, "dataSize > 6"); |
| return 0; |
| } |
| for (UINT8 j=0; j < dataSize; j++) |
| { |
| dataBuffer[j + 2] = data[j]; |
| } |
| FRC_NetworkCommunication_JaguarCANDriver_sendMessage(messageID, dataBuffer, dataSize + 2, &status); |
| return status; |
| } |
| } |
| FRC_NetworkCommunication_JaguarCANDriver_sendMessage(messageID, data, dataSize, &status); |
| return status; |
| } |
| |
| /** |
| * Receive a message from the CAN bus through the CAN driver in FRC_NetworkCommunication |
| * |
| * @param messageID The messageID to read from the CAN bus |
| * @param data The up to 8 bytes of data that was received with the message |
| * @param dataSize Indicates how much data was received |
| * @param timeout Specify how long to wait for a message (in seconds) |
| * @return Status of receive call |
| */ |
| INT32 CANJaguar::receiveMessage(UINT32 *messageID, UINT8 *data, UINT8 *dataSize, float timeout) |
| { |
| INT32 status = 0; |
| FRC_NetworkCommunication_JaguarCANDriver_receiveMessage(messageID, data, dataSize, |
| (UINT32)(timeout * 1000), &status); |
| return status; |
| } |
| |
| /** |
| * Execute a transaction with a Jaguar that sets some property. |
| * |
| * Jaguar always acks when it receives a message. If we don't wait for an ack, |
| * the message object in the Jaguar could get overwritten before it is handled. |
| * |
| * @param messageID The messageID to be used on the CAN bus (device number is added internally) |
| * @param data The up to 8 bytes of data to be sent with the message |
| * @param dataSize Specify how much of the data in "data" to send |
| */ |
| void CANJaguar::setTransaction(UINT32 messageID, const UINT8 *data, UINT8 dataSize) |
| { |
| UINT32 ackMessageID = LM_API_ACK | m_deviceNumber; |
| INT32 localStatus = 0; |
| |
| // If there was an error on this object and it wasn't a timeout, refuse to talk to the device |
| // Call ClearError() on the object to try again |
| if (StatusIsFatal() && GetError().GetCode() != -44087) |
| return; |
| |
| // Make sure we don't have more than one transaction with the same Jaguar outstanding. |
| semTake(m_transactionSemaphore, WAIT_FOREVER); |
| |
| // Throw away any stale acks. |
| receiveMessage(&ackMessageID, NULL, 0, 0.0f); |
| // Send the message with the data. |
| localStatus = sendMessage(messageID | m_deviceNumber, data, dataSize); |
| wpi_setErrorWithContext(localStatus, "sendMessage"); |
| // Wait for an ack. |
| localStatus = receiveMessage(&ackMessageID, NULL, 0); |
| wpi_setErrorWithContext(localStatus, "receiveMessage"); |
| |
| // Transaction complete. |
| semGive(m_transactionSemaphore); |
| } |
| |
| /** |
| * Execute a transaction with a Jaguar that gets some property. |
| * |
| * Jaguar always generates a message with the same message ID when replying. |
| * |
| * @param messageID The messageID to read from the CAN bus (device number is added internally) |
| * @param data The up to 8 bytes of data that was received with the message |
| * @param dataSize Indicates how much data was received |
| */ |
| void CANJaguar::getTransaction(UINT32 messageID, UINT8 *data, UINT8 *dataSize) |
| { |
| UINT32 targetedMessageID = messageID | m_deviceNumber; |
| INT32 localStatus = 0; |
| |
| // If there was an error on this object and it wasn't a timeout, refuse to talk to the device |
| // Call ClearError() on the object to try again |
| if (StatusIsFatal() && GetError().GetCode() != -44087) |
| { |
| if (dataSize != NULL) |
| *dataSize = 0; |
| return; |
| } |
| |
| // Make sure we don't have more than one transaction with the same Jaguar outstanding. |
| semTake(m_transactionSemaphore, WAIT_FOREVER); |
| |
| // Throw away any stale responses. |
| receiveMessage(&targetedMessageID, NULL, 0, 0.0f); |
| // Send the message requesting data. |
| localStatus = sendMessage(targetedMessageID, NULL, 0); |
| wpi_setErrorWithContext(localStatus, "sendMessage"); |
| // Caller may have set bit31 for remote frame transmission so clear invalid bits[31-29] |
| targetedMessageID &= 0x1FFFFFFF; |
| // Wait for the data. |
| localStatus = receiveMessage(&targetedMessageID, data, dataSize); |
| wpi_setErrorWithContext(localStatus, "receiveMessage"); |
| |
| // Transaction complete. |
| semGive(m_transactionSemaphore); |
| } |
| |
| /** |
| * Set the reference source device for speed controller mode. |
| * |
| * Choose encoder as the source of speed feedback when in speed control mode. |
| * |
| * @param reference Specify a SpeedReference. |
| */ |
| void CANJaguar::SetSpeedReference(SpeedReference reference) |
| { |
| UINT8 dataBuffer[8]; |
| |
| dataBuffer[0] = reference; |
| setTransaction(LM_API_SPD_REF, dataBuffer, sizeof(UINT8)); |
| } |
| |
| /** |
| * Get the reference source device for speed controller mode. |
| * |
| * @return A SpeedReference indicating the currently selected reference device for speed controller mode. |
| */ |
| CANJaguar::SpeedReference CANJaguar::GetSpeedReference() |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| getTransaction(LM_API_SPD_REF, dataBuffer, &dataSize); |
| if (dataSize == sizeof(UINT8)) |
| { |
| return (SpeedReference)*dataBuffer; |
| } |
| return kSpeedRef_None; |
| } |
| |
| /** |
| * Set the reference source device for position controller mode. |
| * |
| * Choose between using and encoder and using a potentiometer |
| * as the source of position feedback when in position control mode. |
| * |
| * @param reference Specify a PositionReference. |
| */ |
| void CANJaguar::SetPositionReference(PositionReference reference) |
| { |
| UINT8 dataBuffer[8]; |
| |
| dataBuffer[0] = reference; |
| setTransaction(LM_API_POS_REF, dataBuffer, sizeof(UINT8)); |
| } |
| |
| /** |
| * Get the reference source device for position controller mode. |
| * |
| * @return A PositionReference indicating the currently selected reference device for position controller mode. |
| */ |
| CANJaguar::PositionReference CANJaguar::GetPositionReference() |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| getTransaction(LM_API_POS_REF, dataBuffer, &dataSize); |
| if (dataSize == sizeof(UINT8)) |
| { |
| return (PositionReference)*dataBuffer; |
| } |
| return kPosRef_None; |
| } |
| |
| /** |
| * Set the P, I, and D constants for the closed loop modes. |
| * |
| * @param p The proportional gain of the Jaguar's PID controller. |
| * @param i The integral gain of the Jaguar's PID controller. |
| * @param d The differential gain of the Jaguar's PID controller. |
| */ |
| void CANJaguar::SetPID(double p, double i, double d) |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| switch(m_controlMode) |
| { |
| case kPercentVbus: |
| case kVoltage: |
| wpi_setWPIErrorWithContext(IncompatibleMode, "PID constants only apply in Speed, Position, and Current mode"); |
| break; |
| case kSpeed: |
| dataSize = packFXP16_16(dataBuffer, p); |
| setTransaction(LM_API_SPD_PC, dataBuffer, dataSize); |
| dataSize = packFXP16_16(dataBuffer, i); |
| setTransaction(LM_API_SPD_IC, dataBuffer, dataSize); |
| dataSize = packFXP16_16(dataBuffer, d); |
| setTransaction(LM_API_SPD_DC, dataBuffer, dataSize); |
| break; |
| case kPosition: |
| dataSize = packFXP16_16(dataBuffer, p); |
| setTransaction(LM_API_POS_PC, dataBuffer, dataSize); |
| dataSize = packFXP16_16(dataBuffer, i); |
| setTransaction(LM_API_POS_IC, dataBuffer, dataSize); |
| dataSize = packFXP16_16(dataBuffer, d); |
| setTransaction(LM_API_POS_DC, dataBuffer, dataSize); |
| break; |
| case kCurrent: |
| dataSize = packFXP16_16(dataBuffer, p); |
| setTransaction(LM_API_ICTRL_PC, dataBuffer, dataSize); |
| dataSize = packFXP16_16(dataBuffer, i); |
| setTransaction(LM_API_ICTRL_IC, dataBuffer, dataSize); |
| dataSize = packFXP16_16(dataBuffer, d); |
| setTransaction(LM_API_ICTRL_DC, dataBuffer, dataSize); |
| break; |
| } |
| } |
| |
| /** |
| * Get the Proportional gain of the controller. |
| * |
| * @return The proportional gain. |
| */ |
| double CANJaguar::GetP() |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| switch(m_controlMode) |
| { |
| case kPercentVbus: |
| case kVoltage: |
| wpi_setWPIErrorWithContext(IncompatibleMode, "PID constants only apply in Speed, Position, and Current mode"); |
| break; |
| case kSpeed: |
| getTransaction(LM_API_SPD_PC, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT32)) |
| { |
| return unpackFXP16_16(dataBuffer); |
| } |
| break; |
| case kPosition: |
| getTransaction(LM_API_POS_PC, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT32)) |
| { |
| return unpackFXP16_16(dataBuffer); |
| } |
| break; |
| case kCurrent: |
| getTransaction(LM_API_ICTRL_PC, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT32)) |
| { |
| return unpackFXP16_16(dataBuffer); |
| } |
| break; |
| } |
| return 0.0; |
| } |
| |
| /** |
| * Get the Intregral gain of the controller. |
| * |
| * @return The integral gain. |
| */ |
| double CANJaguar::GetI() |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| switch(m_controlMode) |
| { |
| case kPercentVbus: |
| case kVoltage: |
| wpi_setWPIErrorWithContext(IncompatibleMode, "PID constants only apply in Speed, Position, and Current mode"); |
| break; |
| case kSpeed: |
| getTransaction(LM_API_SPD_IC, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT32)) |
| { |
| return unpackFXP16_16(dataBuffer); |
| } |
| break; |
| case kPosition: |
| getTransaction(LM_API_POS_IC, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT32)) |
| { |
| return unpackFXP16_16(dataBuffer); |
| } |
| break; |
| case kCurrent: |
| getTransaction(LM_API_ICTRL_IC, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT32)) |
| { |
| return unpackFXP16_16(dataBuffer); |
| } |
| break; |
| } |
| return 0.0; |
| } |
| |
| /** |
| * Get the Differential gain of the controller. |
| * |
| * @return The differential gain. |
| */ |
| double CANJaguar::GetD() |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| switch(m_controlMode) |
| { |
| case kPercentVbus: |
| case kVoltage: |
| wpi_setWPIErrorWithContext(IncompatibleMode, "PID constants only apply in Speed, Position, and Current mode"); |
| break; |
| case kSpeed: |
| getTransaction(LM_API_SPD_DC, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT32)) |
| { |
| return unpackFXP16_16(dataBuffer); |
| } |
| break; |
| case kPosition: |
| getTransaction(LM_API_POS_DC, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT32)) |
| { |
| return unpackFXP16_16(dataBuffer); |
| } |
| break; |
| case kCurrent: |
| getTransaction(LM_API_ICTRL_DC, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT32)) |
| { |
| return unpackFXP16_16(dataBuffer); |
| } |
| break; |
| } |
| return 0.0; |
| } |
| |
| /** |
| * Enable the closed loop controller. |
| * |
| * Start actually controlling the output based on the feedback. |
| * If starting a position controller with an encoder reference, |
| * use the encoderInitialPosition parameter to initialize the |
| * encoder state. |
| * |
| * @param encoderInitialPosition Encoder position to set if position with encoder reference. Ignored otherwise. |
| */ |
| void CANJaguar::EnableControl(double encoderInitialPosition) |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize = 0; |
| |
| switch(m_controlMode) |
| { |
| case kPercentVbus: |
| setTransaction(LM_API_VOLT_T_EN, dataBuffer, dataSize); |
| break; |
| case kSpeed: |
| setTransaction(LM_API_SPD_T_EN, dataBuffer, dataSize); |
| break; |
| case kPosition: |
| dataSize = packFXP16_16(dataBuffer, encoderInitialPosition); |
| setTransaction(LM_API_POS_T_EN, dataBuffer, dataSize); |
| break; |
| case kCurrent: |
| setTransaction(LM_API_ICTRL_T_EN, dataBuffer, dataSize); |
| break; |
| case kVoltage: |
| setTransaction(LM_API_VCOMP_T_EN, dataBuffer, dataSize); |
| break; |
| } |
| } |
| |
| /** |
| * Disable the closed loop controller. |
| * |
| * Stop driving the output based on the feedback. |
| */ |
| void CANJaguar::DisableControl() |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize = 0; |
| |
| switch(m_controlMode) |
| { |
| case kPercentVbus: |
| setTransaction(LM_API_VOLT_DIS, dataBuffer, dataSize); |
| break; |
| case kSpeed: |
| setTransaction(LM_API_SPD_DIS, dataBuffer, dataSize); |
| break; |
| case kPosition: |
| setTransaction(LM_API_POS_DIS, dataBuffer, dataSize); |
| break; |
| case kCurrent: |
| setTransaction(LM_API_ICTRL_DIS, dataBuffer, dataSize); |
| break; |
| case kVoltage: |
| setTransaction(LM_API_VCOMP_DIS, dataBuffer, dataSize); |
| break; |
| } |
| } |
| |
| /** |
| * Change the control mode of this Jaguar object. |
| * |
| * After changing modes, configure any PID constants or other settings needed |
| * and then EnableControl() to actually change the mode on the Jaguar. |
| * |
| * @param controlMode The new mode. |
| */ |
| void CANJaguar::ChangeControlMode(ControlMode controlMode) |
| { |
| // Disable the previous mode |
| DisableControl(); |
| |
| // Update the local mode |
| m_controlMode = controlMode; |
| |
| nUsageReporting::report(nUsageReporting::kResourceType_CANJaguar, m_deviceNumber, m_controlMode); |
| } |
| |
| /** |
| * Get the active control mode from the Jaguar. |
| * |
| * Ask the Jag what mode it is in. |
| * |
| * @return ControlMode that the Jag is in. |
| */ |
| CANJaguar::ControlMode CANJaguar::GetControlMode() |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| getTransaction(LM_API_STATUS_CMODE, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT8)) |
| { |
| return (ControlMode)dataBuffer[0]; |
| } |
| return kPercentVbus; |
| } |
| |
| /** |
| * Get the voltage at the battery input terminals of the Jaguar. |
| * |
| * @return The bus voltage in Volts. |
| */ |
| float CANJaguar::GetBusVoltage() |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| getTransaction(LM_API_STATUS_VOLTBUS, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT16)) |
| { |
| return unpackFXP8_8(dataBuffer); |
| } |
| return 0.0; |
| } |
| |
| /** |
| * Get the voltage being output from the motor terminals of the Jaguar. |
| * |
| * @return The output voltage in Volts. |
| */ |
| float CANJaguar::GetOutputVoltage() |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| // Read the volt out which is in Volts units. |
| getTransaction(LM_API_STATUS_VOUT, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT16)) |
| { |
| return unpackFXP8_8(dataBuffer); |
| } |
| return 0.0; |
| } |
| |
| /** |
| * Get the current through the motor terminals of the Jaguar. |
| * |
| * @return The output current in Amps. |
| */ |
| float CANJaguar::GetOutputCurrent() |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| getTransaction(LM_API_STATUS_CURRENT, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT16)) |
| { |
| return unpackFXP8_8(dataBuffer); |
| } |
| return 0.0; |
| } |
| |
| /** |
| * Get the internal temperature of the Jaguar. |
| * |
| * @return The temperature of the Jaguar in degrees Celsius. |
| */ |
| float CANJaguar::GetTemperature() |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| getTransaction(LM_API_STATUS_TEMP, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT16)) |
| { |
| return unpackFXP8_8(dataBuffer); |
| } |
| return 0.0; |
| } |
| |
| /** |
| * Get the position of the encoder or potentiometer. |
| * |
| * @return The position of the motor in rotations based on the configured feedback. |
| */ |
| double CANJaguar::GetPosition() |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| getTransaction(LM_API_STATUS_POS, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT32)) |
| { |
| return unpackFXP16_16(dataBuffer); |
| } |
| return 0.0; |
| } |
| |
| /** |
| * Get the speed of the encoder. |
| * |
| * @return The speed of the motor in RPM based on the configured feedback. |
| */ |
| double CANJaguar::GetSpeed() |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| getTransaction(LM_API_STATUS_SPD, dataBuffer, &dataSize); |
| if (dataSize == sizeof(INT32)) |
| { |
| return unpackFXP16_16(dataBuffer); |
| } |
| return 0.0; |
| } |
| |
| /** |
| * Get the status of the forward limit switch. |
| * |
| * @return The motor is allowed to turn in the forward direction when true. |
| */ |
| bool CANJaguar::GetForwardLimitOK() |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| getTransaction(LM_API_STATUS_LIMIT, dataBuffer, &dataSize); |
| if (dataSize == sizeof(UINT8)) |
| { |
| return (*dataBuffer & kForwardLimit) != 0; |
| } |
| return 0; |
| } |
| |
| /** |
| * Get the status of the reverse limit switch. |
| * |
| * @return The motor is allowed to turn in the reverse direction when true. |
| */ |
| bool CANJaguar::GetReverseLimitOK() |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| getTransaction(LM_API_STATUS_LIMIT, dataBuffer, &dataSize); |
| if (dataSize == sizeof(UINT8)) |
| { |
| return (*dataBuffer & kReverseLimit) != 0; |
| } |
| return 0; |
| } |
| |
| /** |
| * Get the status of any faults the Jaguar has detected. |
| * |
| * @return A bit-mask of faults defined by the "Faults" enum. |
| */ |
| UINT16 CANJaguar::GetFaults() |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| getTransaction(LM_API_STATUS_FAULT, dataBuffer, &dataSize); |
| if (dataSize == sizeof(UINT16)) |
| { |
| return unpackINT16(dataBuffer); |
| } |
| return 0; |
| } |
| |
| /** |
| * Check if the Jaguar's power has been cycled since this was last called. |
| * |
| * This should return true the first time called after a Jaguar power up, |
| * and false after that. |
| * |
| * @return The Jaguar was power cycled since the last call to this function. |
| */ |
| bool CANJaguar::GetPowerCycled() |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| getTransaction(LM_API_STATUS_POWER, dataBuffer, &dataSize); |
| if (dataSize == sizeof(UINT8)) |
| { |
| bool powerCycled = (*dataBuffer != 0); |
| |
| // Clear the power cycled bit now that we've accessed it |
| if (powerCycled) |
| { |
| dataBuffer[0] = 1; |
| setTransaction(LM_API_STATUS_POWER, dataBuffer, sizeof(UINT8)); |
| } |
| |
| return powerCycled; |
| } |
| return 0; |
| } |
| |
| /** |
| * Set the maximum voltage change rate. |
| * |
| * When in PercentVbus or Voltage output mode, the rate at which the voltage changes can |
| * be limited to reduce current spikes. Set this to 0.0 to disable rate limiting. |
| * |
| * @param rampRate The maximum rate of voltage change in Percent Voltage mode in V/s. |
| */ |
| void CANJaguar::SetVoltageRampRate(double rampRate) |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| switch(m_controlMode) |
| { |
| case kPercentVbus: |
| dataSize = packPercentage(dataBuffer, rampRate / (m_maxOutputVoltage * kControllerRate)); |
| setTransaction(LM_API_VOLT_SET_RAMP, dataBuffer, dataSize); |
| break; |
| case kVoltage: |
| dataSize = packFXP8_8(dataBuffer, rampRate / kControllerRate); |
| setTransaction(LM_API_VCOMP_IN_RAMP, dataBuffer, dataSize); |
| break; |
| default: |
| return; |
| } |
| } |
| |
| /** |
| * Get the version of the firmware running on the Jaguar. |
| * |
| * @return The firmware version. 0 if the device did not respond. |
| */ |
| UINT32 CANJaguar::GetFirmwareVersion() |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| // Set the MSB to tell the 2CAN that this is a remote message. |
| getTransaction(0x80000000 | CAN_MSGID_API_FIRMVER, dataBuffer, &dataSize); |
| if (dataSize == sizeof(UINT32)) |
| { |
| return unpackINT32(dataBuffer); |
| } |
| return 0; |
| } |
| |
| /** |
| * Get the version of the Jaguar hardware. |
| * |
| * @return The hardware version. 1: Jaguar, 2: Black Jaguar |
| */ |
| UINT8 CANJaguar::GetHardwareVersion() |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| getTransaction(LM_API_HWVER, dataBuffer, &dataSize); |
| if (dataSize == sizeof(UINT8)+sizeof(UINT8)) |
| { |
| if (*dataBuffer == m_deviceNumber) |
| { |
| return *(dataBuffer+1); |
| } |
| } |
| // Assume Gray Jag if there is no response |
| return LM_HWVER_JAG_1_0; |
| } |
| |
| /** |
| * Configure what the controller does to the H-Bridge when neutral (not driving the output). |
| * |
| * This allows you to override the jumper configuration for brake or coast. |
| * |
| * @param mode Select to use the jumper setting or to override it to coast or brake. |
| */ |
| void CANJaguar::ConfigNeutralMode(NeutralMode mode) |
| { |
| UINT8 dataBuffer[8]; |
| |
| dataBuffer[0] = mode; |
| setTransaction(LM_API_CFG_BRAKE_COAST, dataBuffer, sizeof(UINT8)); |
| } |
| |
| /** |
| * Configure how many codes per revolution are generated by your encoder. |
| * |
| * @param codesPerRev The number of counts per revolution in 1X mode. |
| */ |
| void CANJaguar::ConfigEncoderCodesPerRev(UINT16 codesPerRev) |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| dataSize = packINT16(dataBuffer, codesPerRev); |
| setTransaction(LM_API_CFG_ENC_LINES, dataBuffer, dataSize); |
| } |
| |
| /** |
| * Configure the number of turns on the potentiometer. |
| * |
| * There is no special support for continuous turn potentiometers. |
| * Only integer numbers of turns are supported. |
| * |
| * @param turns The number of turns of the potentiometer |
| */ |
| void CANJaguar::ConfigPotentiometerTurns(UINT16 turns) |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| dataSize = packINT16(dataBuffer, turns); |
| setTransaction(LM_API_CFG_POT_TURNS, dataBuffer, dataSize); |
| } |
| |
| /** |
| * Configure Soft Position Limits when in Position Controller mode. |
| * |
| * When controlling position, you can add additional limits on top of the limit switch inputs |
| * that are based on the position feedback. If the position limit is reached or the |
| * switch is opened, that direction will be disabled. |
| * |
| * @param forwardLimitPosition The position that if exceeded will disable the forward direction. |
| * @param reverseLimitPosition The position that if exceeded will disable the reverse direction. |
| */ |
| void CANJaguar::ConfigSoftPositionLimits(double forwardLimitPosition, double reverseLimitPosition) |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| dataSize = packFXP16_16(dataBuffer, forwardLimitPosition); |
| dataBuffer[dataSize++] = forwardLimitPosition > reverseLimitPosition; |
| setTransaction(LM_API_CFG_LIMIT_FWD, dataBuffer, dataSize); |
| |
| dataSize = packFXP16_16(dataBuffer, reverseLimitPosition); |
| dataBuffer[dataSize++] = forwardLimitPosition <= reverseLimitPosition; |
| setTransaction(LM_API_CFG_LIMIT_REV, dataBuffer, dataSize); |
| |
| dataBuffer[0] = kLimitMode_SoftPositionLimits; |
| setTransaction(LM_API_CFG_LIMIT_MODE, dataBuffer, sizeof(UINT8)); |
| } |
| |
| /** |
| * Disable Soft Position Limits if previously enabled. |
| * |
| * Soft Position Limits are disabled by default. |
| */ |
| void CANJaguar::DisableSoftPositionLimits() |
| { |
| UINT8 dataBuffer[8]; |
| |
| dataBuffer[0] = kLimitMode_SwitchInputsOnly; |
| setTransaction(LM_API_CFG_LIMIT_MODE, dataBuffer, sizeof(UINT8)); |
| } |
| |
| /** |
| * Configure the maximum voltage that the Jaguar will ever output. |
| * |
| * This can be used to limit the maximum output voltage in all modes so that |
| * motors which cannot withstand full bus voltage can be used safely. |
| * |
| * @param voltage The maximum voltage output by the Jaguar. |
| */ |
| void CANJaguar::ConfigMaxOutputVoltage(double voltage) |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| m_maxOutputVoltage = voltage; |
| dataSize = packFXP8_8(dataBuffer, voltage); |
| setTransaction(LM_API_CFG_MAX_VOUT, dataBuffer, dataSize); |
| } |
| |
| /** |
| * Configure how long the Jaguar waits in the case of a fault before resuming operation. |
| * |
| * Faults include over temerature, over current, and bus under voltage. |
| * The default is 3.0 seconds, but can be reduced to as low as 0.5 seconds. |
| * |
| * @param faultTime The time to wait before resuming operation, in seconds. |
| */ |
| void CANJaguar::ConfigFaultTime(float faultTime) |
| { |
| UINT8 dataBuffer[8]; |
| UINT8 dataSize; |
| |
| // Message takes ms |
| dataSize = packINT16(dataBuffer, (INT16)(faultTime * 1000.0)); |
| setTransaction(LM_API_CFG_FAULT_TIME, dataBuffer, dataSize); |
| } |
| |
| /** |
| * Update all the motors that have pending sets in the syncGroup. |
| * |
| * @param syncGroup A bitmask of groups to generate synchronous output. |
| */ |
| void CANJaguar::UpdateSyncGroup(UINT8 syncGroup) |
| { |
| sendMessage(CAN_MSGID_API_SYNC, &syncGroup, sizeof(syncGroup)); |
| } |
| |
| |
| void CANJaguar::SetExpiration(float timeout) |
| { |
| if (m_safetyHelper) m_safetyHelper->SetExpiration(timeout); |
| } |
| |
| float CANJaguar::GetExpiration() |
| { |
| if (!m_safetyHelper) return 0.0; |
| return m_safetyHelper->GetExpiration(); |
| } |
| |
| bool CANJaguar::IsAlive() |
| { |
| if (!m_safetyHelper) return false; |
| return m_safetyHelper->IsAlive(); |
| } |
| |
| bool CANJaguar::IsSafetyEnabled() |
| { |
| if (!m_safetyHelper) return false; |
| return m_safetyHelper->IsSafetyEnabled(); |
| } |
| |
| void CANJaguar::SetSafetyEnabled(bool enabled) |
| { |
| if (m_safetyHelper) m_safetyHelper->SetSafetyEnabled(enabled); |
| } |
| |
| void CANJaguar::GetDescription(char *desc) |
| { |
| sprintf(desc, "CANJaguar ID %d", m_deviceNumber); |
| } |
| |
| /** |
| * Common interface for stopping the motor |
| * Part of the MotorSafety interface |
| * |
| * @deprecated Call DisableControl instead. |
| */ |
| void CANJaguar::StopMotor() |
| { |
| DisableControl(); |
| } |
| |
| void CANJaguar::ValueChanged(ITable* source, const std::string& key, EntryValue value, bool isNew) { |
| Set(value.f); |
| } |
| |
| void CANJaguar::UpdateTable() { |
| if (m_table != NULL) { |
| m_table->PutNumber("Value", Get()); |
| } |
| } |
| |
| void CANJaguar::StartLiveWindowMode() { |
| m_table->AddTableListener("Value", this, true); |
| } |
| |
| void CANJaguar::StopLiveWindowMode() { |
| m_table->RemoveTableListener(this); |
| } |
| |
| std::string CANJaguar::GetSmartDashboardType() { |
| return "Speed Controller"; |
| } |
| |
| void CANJaguar::InitTable(ITable *subTable) { |
| m_table = subTable; |
| UpdateTable(); |
| } |
| |
| ITable * CANJaguar::GetTable() { |
| return m_table; |
| } |
| |
| |