BrickPi3 C++ drivers

BrickPi3 C++ drivers are now available here with a few examples. These are very new and not extensively tested. Please report any problems.

@HaWe

thank you very much for the examples, they look already quite promising!

I’m gladly looking forward to seeing examples about stacked HATs providing multiples of 4 IOs.

Additionally, it’s still very complicated to access the functions by multithreading.

To establish that by one self belated, then e.g. your example about motor controll will have to look sth weird like this:

/*
 *  https://www.dexterindustries.com/BrickPi/
 *  https://github.com/DexterInd/BrickPi3
 *
 *  Copyright (c) 2017 Dexter Industries
 *  Released under the MIT license (http://choosealicense.com/licenses/mit/).
 *  For more information, see https://github.com/DexterInd/BrickPi3/blob/master/LICENSE.md
 *
 *  This code is an example for reading the encoders of motors connected to the BrickPi3.
 *
 *  Hardware: Connect EV3 or NXT motor(s) to any of the BrickPi3 motor ports.
 *
 *  Results:  When you run this program, you should see the encoder value for each motor. By manually rotating motor A, the other motor(s) will be controlled. Motor B power will be controlled, Motor C speed will be controlled, and motor D position will be controlled.
 *
 *  Example compile command:
 *    g++ -o program "motors.c"
 *  Example run command:
 *    sudo ./program
 *
 */

#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
  
  BP.detect(); // Make sure that the BrickPi3 is communicating and that the firmware is compatible with the drivers.

  pthread_mutex_init (&mutex, NULL);


  // Reset the encoders
pthread_mutex_lock (mutex);
  BP.offset_motor_encoder(PORT_A, BP.get_motor_encoder(PORT_A));
pthread_mutex_unlock (mutex);
pthread_mutex_lock (mutex);
  BP.offset_motor_encoder(PORT_B, BP.get_motor_encoder(PORT_B));
pthread_mutex_unlock (mutex);
pthread_mutex_lock (mutex);
  BP.offset_motor_encoder(PORT_C, BP.get_motor_encoder(PORT_C));
pthread_mutex_unlock (mutex);
pthread_mutex_lock (mutex);
  BP.offset_motor_encoder(PORT_D, BP.get_motor_encoder(PORT_D));

  
  while(true){
    // Read the encoders
  
   pthread_mutex_lock (mutex);
   int32_t EncoderA = BP.get_motor_encoder(PORT_A);
pthread_mutex_unlock (mutex);
pthread_mutex_lock (mutex);
  int32_t EncoderB = BP.get_motor_encoder(PORT_B);
pthread_mutex_unlock (mutex);
pthread_mutex_lock (mutex);
    int32_t EncoderC = BP.get_motor_encoder(PORT_C);
pthread_mutex_unlock (mutex);
pthread_mutex_lock (mutex);
    int32_t EncoderD = BP.get_motor_encoder(PORT_D);
pthread_mutex_unlock (mutex);
 
    // Use the encoder value from motor A to control motors B, C, and D
pthread_mutex_lock (mutex);
    BP.set_motor_power(PORT_B, EncoderA < 100 ? EncoderA > -100 ? EncoderA : -100 : 100);
pthread_mutex_unlock (mutex);
pthread_mutex_lock (mutex);
    BP.set_motor_dps(PORT_C, EncoderA);
pthread_mutex_unlock (mutex);
pthread_mutex_lock (mutex);
    BP.set_motor_position(PORT_D, EncoderA);
   pthread_mutex_unlock (mutex);
 
    // Display the encoder values

pthread_mutex_lock (mutex);
    printf("Encoder A: %6d  B: %6d  C: %6d  D: %6d\n", EncoderA, EncoderB, EncoderC, EncoderD);

pthread_mutex_unlock (mutex);

    
    // Delay for 20ms
    usleep(20000);
  }
}

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

… and that would be really too cumbersome, even for that tiny example, but now imagine a bigger code of 4000 lines or more, doing some really smart things…

:dizzy_face:

perhaps you could use atomic variables and C++ methods
std::atomic< >
or wrapped mutexes around your sensor and motor functions? I think that would be really necessary…

In the info.c example, I commented out the functions for changing address and the second BrickPi3 instance, but you can use if for reference for stacking BrickPi3s. You can also refer to the Python stack example for reference (it’s has more comments, and it’s easier to understand).

If you have a block of multiple BrickPi3 methods, you can lock the mutex, do multiple things, and then unlock the mutex. Your example of locking and unlocking was excessive. Take a look at this modified version of the code you posted, which seems very reasonable:

/*
 *  https://www.dexterindustries.com/BrickPi/
 *  https://github.com/DexterInd/BrickPi3
 *
 *  Copyright (c) 2017 Dexter Industries
 *  Released under the MIT license (http://choosealicense.com/licenses/mit/).
 *  For more information, see https://github.com/DexterInd/BrickPi3/blob/master/LICENSE.md
 *
 *  This code is an example for reading the encoders of motors connected to the BrickPi3.
 *
 *  Hardware: Connect EV3 or NXT motor(s) to any of the BrickPi3 motor ports.
 *
 *  Results:  When you run this program, you should see the encoder value for each motor. By manually rotating motor A, the other motor(s) will be controlled. Motor B power will be controlled, Motor C speed will be controlled, and motor D position will be controlled.
 *
 *  Example compile command:
 *    g++ -o program "motors.c"
 *  Example run command:
 *    sudo ./program
 *
 */

#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
  
  pthread_mutex_init (&mutex, NULL);
  pthread_mutex_lock (mutex);
  
  BP.detect(); // Make sure that the BrickPi3 is communicating and that the firmware is compatible with the drivers.

  // Reset the encoders
  BP.offset_motor_encoder(PORT_A, BP.get_motor_encoder(PORT_A));
  BP.offset_motor_encoder(PORT_B, BP.get_motor_encoder(PORT_B));
  BP.offset_motor_encoder(PORT_C, BP.get_motor_encoder(PORT_C));
  BP.offset_motor_encoder(PORT_D, BP.get_motor_encoder(PORT_D));
  pthread_mutex_unlock (mutex);
  
  while(true){
    // Read the encoders
  
    pthread_mutex_lock (mutex);
    int32_t EncoderA = BP.get_motor_encoder(PORT_A);
    int32_t EncoderB = BP.get_motor_encoder(PORT_B);
    int32_t EncoderC = BP.get_motor_encoder(PORT_C);
    int32_t EncoderD = BP.get_motor_encoder(PORT_D);
 
    // Use the encoder value from motor A to control motors B, C, and D
    BP.set_motor_power(PORT_B, EncoderA < 100 ? EncoderA > -100 ? EncoderA : -100 : 100);
    BP.set_motor_dps(PORT_C, EncoderA);
    BP.set_motor_position(PORT_D, EncoderA);
    pthread_mutex_unlock (mutex);
 
    // Display the encoder values
    printf("Encoder A: %6d  B: %6d  C: %6d  D: %6d\n", EncoderA, EncoderB, EncoderC, EncoderD);
    
    // Delay for 20ms
    usleep(20000);
  }
}

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

I was uncertain about the timing if I uncomment several lines by a huge block. I was afraid there wasn’t a yield() in between to switch to other time slices then, intermediately unlocking the mutexes to give access to different threads, and then re-locking for the following one.

I also assumed that the encoder printf function also has to be locked, in case diffeent threads do different things with it, but perhaps I was wrong with this assumption.

OTOH, do you know a way to implement atomic methods for BrickPi3 IO access?
that would be the best away ever!
(edit: C++11 std::atomic most probably wouldn’t work for it though according to what I recently read)

By keeping a mutex locked for several BrickPi3 calls will for that very short time prevent other threads from continuing (if there are threads waiting to lock the mutex). The communication is fast enough that I don’t think it would be a problem to make several transactions before yielding the mutex to anther thread. If it does take too long, you can certainly use more mutex locks/unlocks.

The printf function doesn’t need to be locked, because it doesn’t use any resources shared with other threads (at least as far as the BrickPi3 is concerned).

@matt,
1st,

about your C examples: in https://github.com/DexterInd/BrickPi3/tree/master/Software/C :

I vaguely remember that I was asking in a certain post about your usage of a “string” variable

  • I falsely thought you have been using the reserved word
    string
    as a variable type of the C++ <cstring> class,
    instead, indeed you have been using
    char string[33];
    what actually was puzzling me because “string” was used here as a variable (expression) name, not as the reserved variable type (what it actually is).

Having said that, I would suggest you to change your expression “string” to “str” or just “s” or sth like that in all of your source code examples, because some people are used to compile C(++) files with g++ whilst #including <cstring> (like I am doing sometimes if unvoidable, especially when also #including <iostream> ), and then
char string[ ]; // ANSI "C string" variable expression named string
vs.
string astring; // C++ "<cstring> string" variable type named astring
would mess everything up when compiling.
.
.
.
2nd,

can you please provide a C++ API documentation, e.g. like it once was available for NXC, showing for all BrickPi3 C/C++ API commands the

command / function / expression name
parameters
return value
example code

PS,
also openCV is including <cstring> with it’s string class.
so again, please kindly always exchange
char string[]
or
char* string
by another variable expression instead of “string” !

The change from using “string” to using “str” has just been merged (see here).

Regarding C++ API documentation, for a short description of each method see the comments in BrickPi3.h here. Since the C++ drivers are only for advanced users, and I have a lot of other priorities, I don’t plan to extensively document these drivers (at least not anytime soon). You can use the examples as reference for writing your program, and you can refer to BrickPi3.h for a list of available methods.

thanks for considering and reworking the “string” thing - it will probably help avoiding compiling issues.

About C/C++: IMO C is just an additional different programming language, apart from Python.
Actually almost all of the current Python users are probably far more experienced with the BrickPi usage than e.g. me as a starter using C, and many others are already advanced users in even more sophisticated Python stuff, e.g. openCV.
So I actually do not claim C programmers to be sort of “advanced”. C is a simple programming language, much simpler than Python, if one finally learnt to start an IDE like Geany and compile by F9 and started the program by F5. That’s quite simple if not even simpler than to write and start a Python program IMO.
So I consider many C programmers for the Pi and the BrickPi not to be compellingly “advanced” and they are supposed to need also some tutorials like Python users do.

Nonetheless, having said that, it should not belittle your efforts for providing the C API. It’s great to have it availble now additionally for all those who dislike Python, so thank you very much for that. I will see how to get along with the C Header File.
Now I will only have to find a shop for Germany with reasonable pricing and especially shipping fees… :wink: