[C++] reset and get encoders? rotate degrees abs./rel.?

thank you matt, most things are more clear now, especially about the timing of the offset function.
Nontheless a reset function by which one wouldn’t have to write a function line as long enough to sink a ship would make coding actually much more easier… :wink:
Perhaps you could implement a shorter way to reset to a certain arbitrary value, without error, implicitely reading the current enc value?

But about this get enc function I still don’t understand what you mean, how it actually works and which values are doing and representing what exactly:

int get_motor_encoder(uint8_t port, int32_t &value);

(hell, what a forum’s editor… :-/ )

You can see the methods here for reference. The one is just a wrapper to provide a simplified interface for the other.

You could do something like this:

int32_t value;
if(BP.get_motor_encoder(port, value) == ERROR_NONE){
  // Encoder value was read successfully, and is stored in the "value" variable.
}else{
  // Error reading encoder value. Don't trust the value stored in the "value" variable.
}

Or you could do something like this:

int32_t value = BP.get_motor_encoder(port);
// Encoder value should now be stored in the "value" variable. No way to confirm.

See the full list of error values here. For get_motor_encoder, the possible errors are as follows:

  • ERROR_NONE - No error (valid data).
  • ERROR_SPI_FILE - SPI file error (for some reason the linux drivers weren’t able to use the SPI bus).
  • ERROR_SPI_RESPONSE - No/Invalid data received from the BrickPi3.

so IIUYC,
int get_motor_encoder(uint8_t port, int32_t &value);
does not poll and return an enc value at all ?

But I still do not understand what the values mean - it’s very obscured and ambiguous IMO… :-/

int get_motor_encoder(uint8_t port, int32_t &value);

reads the encoder value. The method returns the error (ideally equal to ERROR_NONE which is 0) so the user program can ensure a successful read, and it returns the encoder value through the pass-by-reference parameter.

Re-read my last post, including the comments I put in the code.

ok, it returns an error… but then it should be called instead
int get_motor_encoder_error(uint8_t port, int32_t &value);

because “get_motor_encoder” would connote to return an encoder value, not an error between 2 values.

Nonetheless I still do not understand which those 2 values are by which sequence:
as an error between 2 values I would expect

int get_motor_encoder_error(uint8_t port, int32_t value1, int32_t value2 );

So I must admit that I do not understand your comments so far.

If you want an inline function that returns the encoder value on success or an unspecified (should be 0) value on error, use this:

int32_t value = BP.get_motor_encoder(port);
// Encoder value should now be stored in the "value" variable. No way to confirm.

If you want to read the encoder value and ensure that the value was actually read successfully, use this:

int32_t value;
if(BP.get_motor_encoder(port, value) == ERROR_NONE){
  // Encoder value was read successfully, and is stored in the "value" variable.
}else{
  // Error reading encoder value. Don't trust the value stored in the "value" variable.
}

ok, perhaps I got now the idea behind this…

1st,
is it true that normally encoder readings are supposed to be untrustful and vague?

So as one cannot trust the values one will have to read also an error code additionally to confirm that the reading was successful and then one can trust the value, is that correct?

So finally this latter method would be then the only valid way - because of which worth is an untrustful and vague value supposed to be?

How often does this false encoder reading happen statistically?

is this mostly due to SPI data transmission errors?
.
.
2nd,
as I am no C++ programmer at all, just ANSI C…:
is it possible to pass something like
this. or this::
instead of BP as a function parameter?
the line
BP.offset_motor_encoder(PORT_A, BP.get_motor_encoder(PORT_A));
is so incredible long, with a “this” one could more easily make a wrapper around it,
especially extremely precious when having multiple instances for multiple stacked shields:

BP.offset_motor_encoder(PORT_A, BP.get_motor_encoder(PORT_A));
=>
BP.offset_motor_encoder(PORT_A, this::get_motor_encoder(PORT_A));
=>
#define resetEnc(p)  offset_motor_encoder(p, this::get_motor_encoder(p))
#define setEnc(p,v)  offset_motor_encoder(p, this::get_motor_encoder(p)+v)
=> that would provide now:
BP.resetEnc(PORT_A);     // resets enc to a new 0 (zero) value
BP.setEnc(PORT_A, 180);  // sets enc to a new arbitrary encoder value of 180°

I haven’t ever noticed errors when reading motor encoders, but that’s not to say they didn’t or couldn’t happen. Reading the motor encoders seems very reliable. Some things I can think of that might cause errors are:

  • Other software running on the RPi that is trying to use SPI at the same time as the BrickPi3 program is running.
  • A hardware problem such as the BrickPi3 not properly seated on the RPi, or other electronics connected to the SPI pins interfering with the SPI signals.
  • A drivers/firmware problem. While extensively tested, the drivers/firmware could have bugs that cause errors when reading values (note that the C++ drivers have only been tested minimally).

I don’t expect there to be errors when reading encoder values, but I give the user program access to the errors so they can be dealt with if necessary.

If you wanted to add a “reset_motor_encoder” method to the BrickPi3 class in the C++ drivers, you wouldn’t use “BP.” or “this::” since it’s part of the same class. The method would look something like this:

int BrickPi3::reset_motor_encoder(uint8_t port, int32_t &value){
  int32_t value = 0;
  // assign error to the error value returned by get_motor_encoder, and if not 0:
  if(int error = get_motor_encoder(port, value)){
    return error;
  }
  return offset_motor_encoder(port, value);
}

and you would call it with something like this:

int32_t OldEncoderValue; // Variable to store the previous/offset encoder value.
BP.reset_motor_encoder(PORT_A, OldEncoderValue); // Returns the error code for verification.
// Encoder is now reset, and the previous value is now stored in OldEncoderValue.

Untested so far, but I added reset_motor_encoder and set_motor_encoder to the C++ drivers. You can see here. To test, instead of cloning from the main fork, when you do the update, clone from my fork using this:

sudo git clone http://www.github.com/mattallen37/BrickPi3.git /home/pi/Dexter/BrickPi3

Please test both methods and report back. See here for the documentation in the header file.

thank you matt, that was pretty quick :sunglasses:

I’ll download your fork ASAP and test it - thanks or your efforts! :grin:

do IUYC, the OldEncoderValue is just for the case when the reset failed, in order to put things back to the previous state, but with no error it can be dropped then afterwards, correct?

OldEncoderValue is a way of letting the program know what the encoder value was before resetting (how much the offset was to set the encoder to 0). If you don’t need the program to know that, you can leave that parameter empty, and instead use this:

BP.reset_motor_encoder(PORT_A);

@matt,
the 1st test with reset_motor_encoder was successful, I’ll keep on testing though.

BTW, just 1 question back, about int and int32_t:
why do you use either ones, different from each other, instead of always int or always in32_t ?

then, 2nd,
as you mentioned above, currently there is no function to “rotate_relative_degrees”, just “rotate_to_position”, IIUC, opposite to Python.
Admittedly, for a workaround one could take sort of
rotate_rel_degrees(degrees)=rotate_to_position(get(current_pos) + degrees)
but that’s still quite cumbersome.
Will it be possible to have also a C function for
(Python) motorRotateDegree
additionally?

and last, but not least, 3rd,
about encoder values:
I know that Lego encoders provide a 720 ticks/rev resolution , but Lego works wit 360 ticks/rev.
How is it working for the BrickPi3?
360ticks res is absolutely fine and convenient, and as it appears that 360ticks res is also used by the motor example (CMIIW). Nonetheless, I observed some posts about Python which seem to deal with a 720ticks res.

I use int for values that are signed, but don’t need to be more than 16 bit (I’ve only ever used systems where int is either 16, 32, or 64 bits). Other than on 8-bit systems (where int is usually 16-bit), int usually seems to be the native variable size (32 bits on a 32 bit system). int works well for error codes, but since it’s size varies between systems, I tend to avoid it when the specific size is critical to the application.

I use int32_t if I want a variable that is specifically signed 32 bit (it’s definition doesn’t change between systems). This is ideal for passing values that are natively signed 32 bit (such as the encoder values).

set_motor_position in Python and in C++ sets the motor’s absolute target. If you want a relative target, you could set the absolute target to the current position plus the relative target.

The BrickPi+ has 720 ticks per revolution of mindstorms motors. The BrickPi3 has 360 ticks per revolution of mindstorms motors (1 degree precision).

1 Like

yes, I know that on 32 bit systems int has the same size as int32_t, and either one is always signed.
But on 64 bit systems (Raspberry Pi 3 on 64 bit Raspbian) it is probably 64 bits large, and that will make a difference.

About relative rotation, this feature is available for Python as far as I saw. So as stated, I know about this workaround:

Admittedly, for a workaround one could take sort of
rotate_rel_degrees(degrees)=rotate_to_position(get(current_pos) + degrees)
but that’s still quite cumbersome.

and so my question actually was

Will it be possible to have also a C function for
(Python) motorRotateDegree
additionally?

Finally this issue is similar to the set or reset issue:
Having that simplified function, one would not be forced to first poll the current rotation state before one was able to turn a relative angle.

Regarding the size of int, for the error codes it doesn’t matter if it’s 16, 32, or 64 bit, which is why I didn’t use e.g. int32_t for it.

motorRotateDegree is for BrickPi+. There is no method in brickpi3.py to rotate a motor to a relative position. Regarding the BrickPi3:

set_motor_position in Python and in C++ sets the motor’s absolute target.

See set_motor_position_relative here (I just added it). After the changes to the drivers it compiles without errors, but I haven’t tried running it. Please test and report. Remember when you do the update to clone from my fork since it hasn’t yet been pulled into the main (DexterInd) fork.

1 Like

I tried the following test code a couple of times:
New functions work fine + accurate, no issues!

Great work, thank you!

(updated:)

// motor test program: 
// turn relative and absolute degrees
// version 0.0.4

#include "BrickPi3.cpp" // for BrickPi3
#include <stdio.h>      // for printf
#include <unistd.h>     // for usleep
#include <signal.h>     // for catching exit signals

BrickPi3 BP;

void exit_signal_handler(int signo);


int main(){
    
  signal(SIGINT, exit_signal_handler); // register the exit function for Ctrl+C
  
  int32_t EncoderA, EncoderB, EncoderC, EncoderD; 
  int32_t relpos=0;
  
  BP.detect(); // Make sure that the BrickPi3 is communicating and that the firmware is compatible with the drivers.
  
  // Reset the encoders
  BP.reset_motor_encoder(PORT_A);
  BP.reset_motor_encoder(PORT_B);
  BP.reset_motor_encoder(PORT_C);
  BP.reset_motor_encoder(PORT_D);

 
  // Read the encoders   
  EncoderA = BP.get_motor_encoder(PORT_A);
  EncoderB = BP.get_motor_encoder(PORT_B);
  EncoderC = BP.get_motor_encoder(PORT_C);
  EncoderD = BP.get_motor_encoder(PORT_D);
  // print encoder vaues after reset
  printf("Encoder A: %6d  B: %6d  C: %6d  D: %6d\n", EncoderA, EncoderB, EncoderC, EncoderD);
  
  while(true){
     
    relpos = rand() %100 ;
    relpos -= 50;
    relpos *=10; // -500 < relpos < +500;
    printf("\nrelpos=%6d\n", relpos);
    
    BP.set_motor_position_relative(PORT_A, relpos);
    BP.set_motor_position_relative(PORT_B, relpos);  
    BP.set_motor_position_relative(PORT_C, relpos);        
    
    // Delay
    sleep(2);

    EncoderA = BP.get_motor_encoder(PORT_A);    
    
    // Use the encoder value from motor A to control motor D
    BP.set_motor_position(PORT_D, EncoderA);    
    
    // Delay  
    sleep(2);
    
    EncoderA = BP.get_motor_encoder(PORT_A);
    EncoderB = BP.get_motor_encoder(PORT_B);
    EncoderC = BP.get_motor_encoder(PORT_C);
    EncoderD = BP.get_motor_encoder(PORT_D);
    
    // Display the encoder values
    printf("Encoder A: %6d  B: %6d  C: %6d  D: %6d\n", EncoderA, EncoderB, EncoderC, EncoderD);
    
    // Delay  
    sleep(2);
  }
}

// Signal handler that will be called when Ctrl+C is pressed to stop the program
void exit_signal_handler(int signo){
  if(signo == SIGINT){
    BP.reset_all();    // Reset everything so there are no run-away motors
    exit(-2);
  }
}