Can anyone help me figure out why this code returns “no robot code” when enable is pressed on the driver station after successfully compiling and deploying? Most of it is copied from CTRE, and worked when only CANTalon0 was enabled. I am at a loss, as there isn’t much here to not work. Any input is appreciated.
#include "WPILib.h"
#include "CANTalon.h" /* necessary as of FRC2017, comment out for earler seasons */
//#include <RobotDrive.h>
class Robot: public IterativeRobot {
private:
CANTalon talon0;
CANTalon talon1;
CANTalon talon2;
CANTalon talon3;
Joystick *joy0;
int _loops = 0; //Not needed
public:
Robot():
talon0(0), talon1(1), talon2(2), talon3(3), joy0(0)
{
}
void Disabled()
{
while(IsDisabled()){}
}
void RobotInit() {
/* first choose the sensor */
talon0.SetSafetyEnabled(false);
talon1.SetSafetyEnabled(false);
talon2.SetSafetyEnabled(false);
talon3.SetSafetyEnabled(false);
talon0.SetFeedbackDevice(CANTalon::QuadEncoder);
talon0.SetSensorDirection(false);
talon0.ConfigEncoderCodesPerRev(256), // if using FeedbackDevice.QuadEncoder
talon2.SetFeedbackDevice(CANTalon::QuadEncoder);
talon2.SetSensorDirection(false);
talon2.ConfigEncoderCodesPerRev(256), // if using FeedbackDevice.QuadEncoder
/* set the peak and nominal outputs, 12V means full */
talon0.ConfigNominalOutputVoltage(+0.0f, -0.0f);
talon0.ConfigPeakOutputVoltage(+12.0f, -12.0f);
talon1.ConfigNominalOutputVoltage(+0.0f, -0.0f);
talon1.ConfigPeakOutputVoltage(+12.0f, -12.0f);
talon2.ConfigNominalOutputVoltage(+0.0f, -0.0f);
talon2.ConfigPeakOutputVoltage(+12.0f, -12.0f);
talon3.ConfigNominalOutputVoltage(+0.0f, -0.0f);
talon3.ConfigPeakOutputVoltage(+12.0f, -12.0f);
/* set closed loop gains in slot0 */
talon0.SelectProfileSlot(0);
talon0.SetF(0.1097);
talon0.SetP(0.22);
talon0.SetI(0.0);
talon0.SetD(0.0);
talon2.SelectProfileSlot(0);
talon2.SetF(0.1097);
talon2.SetP(0.22);
talon2.SetI(0.0);
talon2.SetD(0.0);
/*set 1 and 3 to slave */
talon1.SetControlMode(CANSpeedController::kFollower);// Set to follow another
talon1.Set(0);// follow this talon
talon3.SetControlMode(CANSpeedController::kFollower);
talon3.Set(2);
}
/**
* This function is called periodically during operator control
*/
void TeleopPeriodic() {
/* get gamepad axis */
double leftYstick = joy0->GetAxis(Joystick::kYAxis);
double motorOutputR = talon0.GetOutputVoltage() / talon0.GetBusVoltage();
double motorOutputL = talon2.GetOutputVoltage() / talon2.GetBusVoltage();
/* while button1 is held down, closed-loop on target velocity */
if (joy0->GetRawButton(1)) {
/* Speed mode */
double targetSpeed = leftYstick * 1500.0; /* 1500 RPM in either direction */
talon0.SetControlMode(CANSpeedController::kSpeed);
talon0.Set(targetSpeed); /* 1500 RPM in either direction */
talon2.SetControlMode(CANSpeedController::kSpeed);
talon2.Set(targetSpeed); /* 1500 RPM in either direction */
} else {
/* Percent voltage mode */
talon0.SetControlMode(CANSpeedController::kPercentVbus);
talon0.Set(leftYstick);
talon2.SetControlMode(CANSpeedController::kPercentVbus);
talon2.Set(leftYstick);
}
/* print every ten loops, printing too much too fast is generally bad for performance */
if (++_loops >= 10) {
_loops = 0;
SmartDashboard::PutNumber("RMotor Out", motorOutputR);
SmartDashboard::PutNumber("LMotor Out", motorOutputL);
SmartDashboard::PutNumber("RMotor Speed", talon0.GetSpeed());
SmartDashboard::PutNumber("LMotor Speed", talon2.GetSpeed());
SmartDashboard::PutNumber("RMotor Error", talon0.GetClosedLoopError());
SmartDashboard::PutNumber("LMotor Error", talon2.GetClosedLoopError());
}
}
};
START_ROBOT_CLASS(Robot)
I’m no expert in C here, but I know in Java you should have a “if isEnabled() && isOperatorControl()” and I think this may be causing the error. Here’s a link to the WPILib page and I do see it there also. Seems a little strange thought that that would throw a no code error.
I would take a look at the most up to date version of this example from CTRE.
You only need the if (isEnabled() && isOperatorControl()) if you are using SampleRobot. Please do not use SampleRobot. Here is a copy of the warning text in the SampleRobot template:
WARNING: While it may look like a good choice to use for your code if you’re inexperienced, don’t. Unless you know what you are doing, complex code will be much more difficult under this system. Use IterativeRobot or Command-Based instead if you’re new.
Didn’t look at your code indepth, but if you look at your Driverstation output console, most likely you will see Null pointer errors.
Which means you are not instantiating the Talon controllers and Joysticks.
You need to use the “new” operator to instantiate each motor controller and joystick
something like
talon0 = new CANTalon(0);
joy = new Joystick(0);
for each controller, and similarly for the joystick.
“Robot Code” green while in disabled, then “No Robot Code” while enabled means the code crashed during run-time. So you have a run-time error crashing your code.
In this case, most likely a null pointer since the Motor controllers and Joysticks are not instantiated before using the dot operator on them.
The console will give you a trace to where this error is occurring.
Not in C++. This code will just segfault with no error message.
You need to use the “new” operator to instantiate each motor controller and joystick
This is not 100% correct. While this is one way of doing things (and the right way if using pointers), it is not the only valid way. The OP is using an initializer list to instantiate the motor controllers and joystick which, if done correctly, is perfectly valid.
The issue is that joy0 is a pointer not a reference. So initializing it to 0 is not creating a joystick object for “Joystick 0” it is initializing to a null pointer. When this pointer is accessed the code crashes.
joy0 should be changed to a reference and the pointer dereferences on it (->) should be changed to dot operators (.) for the minimal changes to make this code not crash.
Having said that, I would take a look at the updated CTRE examples as referenced above.
As-is, this calls the constructor for the CANTalon objects with the parameter you give it. But - joy0 is not a Joystick object, it’s a pointer (memory address) to a Joystick object. When you initialize joy0 with 0 in your constructor, you’re setting that pointer to memory address 0. Since your program doesn’t own that memory, the operating system kills it when you try to access joy0.
If you want to continue to use a pointer, you can initialize the Joystick using the new operator as NotInControl said. Alternatively, you could just remove the * (which makes that Joystick variable a pointer), and reference the Joystick as a normal object (dot vs -> ). This changes how the instance gets allocated in memory (stack vs heap) - either way will work the same for a single class robot program.
Thanks, I got it working both with a pointer and a “normal” object.
Bigger question, why use one over the other?
I’m not a C++ programmer, and the resource I used to learn didn’t explain why to use pointers, only that you can create a variable, or a pointer to one, which sounds like bypassing some functionality of the language. But in researching this problem yesterday, it seems pointers versus normal objects also determine which memory space the object is in (probably does not matter on a robot), and how long and where it exists - normal objects don’t exist outside the entity that created them. This probably doesn’t matter much on a robot, at least one using iterative robot. Command based might be a different story.
So it would appear the “safe” method is to create objects with pointers, to ensure they don’t blink out of existence just when you need them. Is this reasonable, or is there a better answer?
A pointer allocates in Heap Space, while a normal old variable allocates in the Scoped Stack Space.
In the Heap Space, the pointer is allocated some memory on the large heap (where most of the system memory is most of the time), and the system is told not to give that memory to anyone else until it is free’d, meaning it will stay there until you call free(), or until your program exits (the OS has some safety to not leave dangling heap memory). In this case, the pointer is just saying “I’ve got your memory, it’s over there, go use it”.
In the Stack Space, your variables are allocated into something called a stack frame. Stack frames start when your program does, starting with constants and static variables, and each time a new scope is called (usually entering a function, for example), a new stack frame is added. When that stack frame is exited (i.e. returning from a function), the stack frame is popped off, and its memory is free to be written over by the next stack frame.
When you call new <yourobject>(), you’re allocating on heap space. This is much more expensive than allocating on the stack, since the system has to find memory that isn’t taken up yet (allocating the memory is practical 0 time, but finding memory to allocate takes a while).
As long as the scope the variables were initialised in still exists, your variables won’t disappear.