Rotation sensor bug & Workaround on VEXos 1.1.0

Environment

A V5 Brain with VEXos 1.1.0, a rotation sensor, and a SD card

Problem

If you read a file in the code, reverse the rotation sensor, then read the position value of that sensor, the return value is incorrect.

Which means, for example, if you run the code below, every time you start the program, you may get a value starting from 2875 or 33125 ( 36000 - x or x ).

Test code in PROS

#include "main.h"

extern "C" {
typedef   void          FIL;

FIL                  *vexFileOpen(const char *filename, const char *mode );
int32_t             vexAbsEncPositionGet( uint32_t index );
void                vexAbsEncReverseFlagSet( uint32_t index, bool value );
};

void initialize() {
    pros::lcd::initialize();
    vexFileOpen("/usd/anything1.config", "r");

    int port = 10;
    vexAbsEncReverseFlagSet(port-1, true);

    while (true) {
        pros::lcd::print(0, "%d", vexAbsEncPositionGet(port-1)); // get 1512 or 34488
        
        pros::delay(20);
    }
}

Test code in VEXcode

#include "vex.h"

using namespace vex;

extern "C" {
typedef   void          FIL;

FIL                  *vexFileOpen(const char *filename, const char *mode );
int32_t             vexAbsEncPositionGet( uint32_t index );
void                vexAbsEncReverseFlagSet( uint32_t index, bool value );
};

int main() {
    vexFileOpen("/usd/anything1.config", "r");
    wait(10, msec); // bruh

    int port = 10;
    vexAbsEncReverseFlagSet(port-1, true);

    while (true) {
        Brain.Screen.clearScreen();
        Brain.Screen.setCursor(3, 3);
        Brain.Screen.print("%d", vexAbsEncPositionGet(port-1));

        wait(200, msec);
    }
}

More Details

Run the code below with or without vexFileOpen and a 10msec delay after the line

// skip...

void initialize() {
    pros::lcd::initialize();
    vexFileOpen("/usd/anything1.config", "r"); // test subject
    pros::delay(10); // test subject

    int port = 10;
    vexAbsEncReverseFlagSet(port-1, true);

    while (true) {
        pros::lcd::print(0, "%d", vexAbsEncPositionGet(port-1)); // get 1512 or 34488
        pros::delay(20);
    }
}

Result:

vexFileOpen and delay
SD Card inserted: 34488 (always)
Without SD Card: 1512 (always)

vexFileOpen and no delay
SD Card inserted: 1512 or 34488
Without SD Card: 1512 (always)

no vexFileOpen and < 10msec delay
SD Card inserted: 34488 (always)
Without SD Card: 34488 (always)

no vexFileOpen and 50msec delay
SD Card inserted: 1512 (always)
Without SD Card: 1512 (always)

Workaround

Adding a 50 millisecond delay at the beginning.

In PROS:

void initialize() {
    pros::lcd::initialize();
    pros::delay(50);
	
    // your code
}

In VEXcode:

int main() {
    wait(50, msec);

    // your code
}
9 Likes

I’ll take a look at this at some point, but it seems to be very much an edge case and not something I’ve seen using the public VEXcode APIs.

VEXcode users should not be using the direct C calls (not should PROS users) and the reverse flag would usually be set as part of the rotation class constructor. However, root cause of the issue is probably the initialization sequence that the rotation sensor has to do when code is started, any absolute positions inside the sensor have to be reset and we need to wait for valid data to be returned after that happens, it can take some time for data to stabilize. Not sure why using SD card has an impact on that but, again, for VEXcode users using the C++ API it’s unlikely that a file would be opened before a rotation sensor instance created.

6 Likes

Using the direct C calls is just to show you it is nothing related to PROS or VEXcode. Here is an example in PROS:

void opcontrol() {
    pros::Controller master(pros::E_CONTROLLER_MASTER);

    FILE* settingfile = fopen("/usd/anything1.config", "r");

    // fclose(settingfile);  // uhhh

    pros::Rotation ahh(10);
    ahh.set_reversed(true);

    while (true) {
        pros::lcd::print(0, "%d", ahh.get_position());
        
        pros::delay(20);
    }
}

Also, I don’t think this is an edge case.

@jerrylum

I had a look into this and could reproduce your results, in reviewing the code it’s actually the expected behavior. Let me explain.

When a rotation sensor is initialized, the internal angle and absolute position are set to the same value. Angle is always in the range 0 to 359.99, position has no such limits but always starts out matching the angle.

The reverse flag does different things for those values, angle becomes 359.99 - angle (90 degrees would become 270) and the positive direction of rotation is reversed. Position is treated a little differently, lets say the current position is 3000 degrees, if the reverse flag is set after the code has been running for some time we do not change the actual position (ie. we don’t change +3000 degrees to be -3000 degrees), we simply recalculate an offset so it remains the same but counts in the opposite direction. This is the same as the behavior for the motor, setting the motor reverse flag in code does not change the motor position, only the direction it counts. If the reverse flag is set before rotation sensor initialization completes, the initial position will now be the same as the angle, so if reversed position was 270 degrees, the initial position will also be 270 degrees.

The code you posted has the reverse flag set sometimes after we have initialized the sensor and received messages (meaning position is not changed and matches the initial unreversed angle), and sometimes before (meaning position matches the reversed angle). It’s really nothing to do with the file reading, I could reproduce by setting an initial delay in the code to about 20 mS, but opening a file is probably a bit more variable in timing so could perhaps produce a slightly more random result.

it’s a tricky problem.
I can either leave as is, generally the reverse flag would be set in the rotation sensor constructor and cause initial position to match a reversed angle. or I can force position to always match an un-reversed angle which may confuse some users. I can’t solve the problem of marginal timing where each code run can set the flag sometimes before and sometimes after the sensor initialization completes.

4 Likes

Yikes! Race conditions are so much fun to solve…

2 Likes

What about changing the API to remove the ability to change the reversed flag? E.g. make is so the user may only set the reversed flag in the constructor, where you ensure the “proper” sequencing?

Admittedly this proposal would be a breaking change to the API, though in this case, users who call set_reversed would receive a compile-time error. I’m not sure I can think of a valid use case where one would want to change the reversed flag after constructing the object…but I may suffer a lack of imagination…

1 Like

sure I could do this for the VEXcode C++ API (although I probably won’t) but @jerrylum is probably using PROS, not sure what they offer.

It’s actually a bit more complicated than I explained, for many sensors we also handle the case where a sensor is unplugged and then plugged in again when user code is running. In the case of the rotation sensor, we try and restore the absolute position and reverse setting if this happens so user code can potentially keep running, so there are other factors at play.

As I said in an earlier post, for the majority of users this is an edge case they will never encounter.

I’m only looking at it today as EXP has the same issue (which is unsurprising as it’s using lots of V5 code) and I want to wrap up the vexos 1.0.0 release for that.

6 Likes

Thank you for your help!

Now I understand it’s the expected behavior so perhaps it might be better if we can leave it alone.

However, I also come up with a new workaround for this problem. It helps you set the reverse flag if and only if the sensor is ready to go.

bool safe_rotation_set_reverse_flag(uint8_t port, bool reverse) {
  static V5_DeviceType registry_types[V5_MAX_DEVICE_PORTS];
  vexDeviceGetStatus(registry_types);

  if (registry_types[port - 1] != kDeviceTypeAbsEncSensor)
    return false;

  uint16_t timeoutCount = 0;
  do {
    wait(5, msec); // for VEXcode
    // task_delay(5); // for PROS
    timeoutCount += 5;
    if (timeoutCount >= 1000)
      return false;
  } while (vexDeviceAbsEncStatusGet(vexDeviceGetByIndex(port - 1)) == 0);

  vexAbsEncReverseFlagSet(port - 1, reverse);
  return true;
}

In PROS, we’ll add a blocking constructor for the rotation sensor, something like Rotation(const std::uint8_t port, bool reverse), since we don’t have a constructor with reverse flag parameter now. By doing this, we can avoid this problem by using the constructor instead of the .set_reversed method.

2 Likes