Using new software for X4

After being away for some time I picked up programming again. From the new ToTem board software I see functions as DC.A.setDecelerationTime(time). I presumed they are used on the X4 board itself. Is it possible to use these kuind of calls remotely? I have an ESP32 system which I used to control speed, turn, etc.

Thnx

After being away for some time I picked up programming again

Great choice. It can be fun sometimes :+1:

I presumed they are used on the X4 board itself

Yes, this function is integral part of RoboBoard motor control drivers and only available inside Arduino sketch, running on RoboBoard itself.

Are you controlling X4 remotely using “Totem.BLE” (from TotemLibrary)? It is an old implementation with limited set of controls (we have plans to improve it). The good thing - it is basically the same as Totem Android or iOS app, so you can use TotemApp. - Totem Documentation and receive custom data with “function” feature. I’ll post an example how to do that.

RoboBoard X4 Arduino sketch:

void appEvent(int evt, int value) {
  // If disconnected from RoboBoard - stop all motors
  if (evt == TotemApp.evtDisconnect) {
    DC.brake();
    return;
  }
  // Accept only "functionA"
  if (evt != TotemApp.evtFunctionA) return;
  // Unpack data from 32-bit "value" variable
  int cmd = value & 0xFF;         // Function type to call
  int port = (value >> 8) & 0xFF; // Motor port (A,B,C,D)
  value = value >> 16;            // Value sent to the function (16-bit)
  // Execute selected command
  switch (cmd) {
    case 0: DC[port].brake(value); break;
    case 1: DC[port].spin(value); break;
    case 2: DC[port].tone(value); break;
    case 3: DC[port].setAccelerationTime(value); break;
    case 4: DC[port].setDecelerationTime(value); break;
  }
}
void setup() {
  TotemApp.addEvent(appEvent); // Register events from Totem.BLE
}
void loop() {

}

Remote ESP32 Arduino sketch:

#include <Totem.h>
// Define object that will be used to access X4 board.
// It will become active when BLE connection is established.
TotemModule rb_x4(04);
// Function wrapper to send custom commands to X4
class RoboBoardX4 {
  void write(int cmd, int port, int value) {
    // Pack parameters to 32-bit "value"
    value <<= 16;
    value |= (port & 0xFF) << 8;
    value |= (cmd & 0xFF);
    rb_x4.write("functionA", value); // Send to X4
  }
public:
  // port: 0-A, 1-B, 2-C, 3-D
  void brake(int port, int value = 100) { write(0, port, value); }
  void spin(int port, int value) { write(1, port, value); }
  void tone(int port, int value) { write(2, port, value); }
  void setAccelerationTime(int port, int value) { write(3, port, value); }
  void setDecelerationTime(int port, int value) { write(4, port, value); }
} X4;
// Arduino setup function
void setup() {
    Serial.begin(115200);
    Totem.BLE.begin(); // Start Bluetooth Low Energy interface
    Serial.println("Searching for Totem board...");
    // Start scanning for Totem board. It's representing a Totem robot.
    TotemRobot robot = Totem.BLE.findRobot(); // Wait until connected to first found Totem robot
    // Print connected robot name
    Serial.print("Connected to: ");
    Serial.println(robot.getName());
    // Proceed to loop
}
// Arduino loop function
void loop() {
    X4.spin(0, 100);
    Serial.println("Spin DC A 100%");
    delay(1000);
    X4.brake(0);
    Serial.println("Brake DC A");
    delay(1000);
    X4.tone(0, 1000); // 1kHz
    Serial.println("Beep DC A");
    delay(500);
    X4.tone(0, 0); // Stop beep
    delay(500);
    X4.setAccelerationTime(0, 1000); // Set acceleration to 1 second
    X4.setDecelerationTime(0, 500); // Set deceleration to 0.5 seconds
    X4.spin(0, 100);
    Serial.println("Accelerate DC A to 100% power in 1s");
    delay(2000);
    X4.spin(0, 0);
    Serial.println("Decelerate DC A to 0% power in 0.5s");
    delay(2000);
    // Turn off accelerations
    X4.setAccelerationTime(0, 0);
    X4.setDecelerationTime(0, 0);
}

This way you can use any X4 feature remotely. Just add additional commands you need. There are “functionB”, “functionC”, “functionD” if additional separation of controls is required (e.g. servo control).

It’s working. Now implement it in my own application.

Thanx for your help
Gerard

1 Like

I now have integrated this solution with my project and iit is functioning as should. Now I have the following issue for which I cannot find a solution.

I want to connect an extra sensor to the GPIO pins on the X4. I know I kan make it working with a function running inside the X4. But how can I sent the results to my ESP32 controller??

Thanx

Extended previous example with data sending from X4 → ESP32

Important: A bit of “hack” is required to access some internal RoboBoard functionality. Will implement a proper way to do it later (with furter software releases).
For now, update this file path to match your file system. It should point to this file inside “TotemLibrary”:

#include "C:\Users\NAME\Documents\Arduino\libraries\TotemArduino\src\core\TotemBUS.h"

Example demonstrates sending 32bit integer, string or data array.

RoboBoard X4 Arduino sketch:

void appEvent(int evt, int value) {
  // If disconnected from RoboBoard - stop all motors
  if (evt == TotemApp.evtDisconnect) {
    DC.brake();
    return;
  }
  // Accept only "functionA"
  if (evt != TotemApp.evtFunctionA) return;
  // Unpack data from 32-bit "value" variable
  int cmd = value & 0xFF;         // Function type to call
  int port = (value >> 8) & 0xFF; // Motor port (A,B,C,D)
  value = value >> 16;            // Value sent to the function (16-bit)
  // Execute selected command
  switch (cmd) {
    case 0: DC[port].brake(value); break;
    case 1: DC[port].spin(value); break;
    case 2: DC[port].tone(value); break;
    case 3: DC[port].setAccelerationTime(value); break;
    case 4: DC[port].setDecelerationTime(value); break;
  }
}
// Update path to match your local file system
#include "C:\Users\USER\Documents\Arduino\libraries\TotemArduino\src\core\TotemBUS.h"
int BleServerService_sendFrame(TotemBUS::Frame frame, uint16_t number, uint16_t serial);
// Functions to send data back to ESP32
int sendValue(int command, int value) {
  return BleServerService_sendFrame(TotemBUS::respond(command, value), 4, 0);
}
int sendData(int command, uint8_t *data, uint32_t len) {
  return BleServerService_sendFrame(TotemBUS::respond(command, {(const char*)data, len}), 4, 0);
}
int sendString(int command, const char *str) {
  return BleServerService_sendFrame(TotemBUS::respond(command, {str, strlen(str)}), 4, 0);
}

IOButton button(BUTTON_BUILTIN);
void buttonEvent(int evt) {
  // Send X4 button state to ESP32
  if (evt == Button.evtPress) { sendValue(11, 1); }
  else if (evt == Button.evtRelease) { sendValue(11, 0); }
}
void setup() {
  TotemApp.addEvent(appEvent); // Register events from Totem.BLE
  button.addEvent(buttonEvent); // Regiser button event
}
void loop() {
  // Send value (integer) to ESP32
  sendValue(55, 1000);
  printf("send command:55 value:1000 to ESP32\n");
  delay(1000);
  // Send string to ESP32
  sendString(66, "This is a text");
  printf("send command:66 string:'This is a text' to ESP32\n");
  delay(1000);
  // Send byte array to ESP32
  uint8_t arr[] = {1, 2, 3, 4, 5, 0};
  sendData(77, arr, sizeof(arr));
  printf("send command:77 bytes: 1,2,3,4,5,0 to ESP32\n");
  delay(1000);
}

Remote ESP32 Arduino sketch:

#include <Totem.h>
// Define object that will be used to access X4 board.
// It will become active when BLE connection is established.
TotemModule rb_x4(04);
// Function wrapper to send custom commands to X4
class RoboBoardX4 {
  void write(int cmd, int port, int value) {
    // Pack parameters to 32-bit "value"
    value <<= 16;
    value |= (port & 0xFF) << 8;
    value |= (cmd & 0xFF);
    rb_x4.write("functionA", value); // Send to X4
  }
public:
  // port: 0-A, 1-B, 2-C, 3-D
  void brake(int port, int value = 100) { write(0, port, value); }
  void spin(int port, int value) { write(1, port, value); }
  void tone(int port, int value) { write(2, port, value); }
  void setAccelerationTime(int port, int value) { write(3, port, value); }
  void setDecelerationTime(int port, int value) { write(4, port, value); }
} X4;
// Receive data from RoboBoard X4
void onSendFromX4(ModuleData data) {
  if (data.getHashCmd() == 11) { // Buton was pressed on RoboBoard X4
    printf("==>> BUTTON: %d\n", data.getInt());
  }
  else if (data.isInt()) { // Used "sendValue"
    // Get command and value
    int command = data.getHashCmd();
    int value = data.getInt();
    // Print command and value
    printf("==>> Got command: %d value: %d\n", command, value);
  }
  else if (data.isString()) { // Used "sendData" or "sendString"
    // Get command and string
    int command = data.getHashCmd();
    const char *str = data.getString();
    // Get data array
    uint8_t *dataPtr; int dataLen;
    data.getData(dataPtr, dataLen);
    // Print string & data array (choose the one you need)
    printf("==>> Got command: %d string: %s\nBytes(%d): ", command, str, dataLen);
    for (int i=0; i<dataLen; i++) {
      printf(" %d", dataPtr[i]);
    }
    printf("\n");
  }
}

// Arduino setup function.
void setup() {
    // put your setup code here, to run once:
    Serial.begin(115200);
    Totem.BLE.begin(); // Start Bluetooth Low Energy interface
    Serial.println("Searching for Totem board...");
    // Start scanning for Totem board. It's representing a Totem robot.
    TotemRobot robot = Totem.BLE.findRobot(); // Wait until connected to first found Totem robot
    // Print connected robot name
    Serial.print("Connected to: ");
    Serial.println(robot.getName());
    // Register data receiver from RoboBoard X4
    rb_x4.attachOnData(onSendFromX4);
    // Proceed to loop    
}
// Arduino loop function
void loop() {
    X4.spin(0, 50); // Send to X4
    Serial.println("Spin DC A 50%");
    delay(1000);
    X4.brake(0); // Send to X4
    Serial.println("Stop DC A");
    delay(1000);
}

Arnas,

I implemented this code in my program and it is working fine. Now implement the real thing.

Thanx for your help
Gerard

1 Like

May you share what project you are working on? It’s interesting approach to run all control logic on external processor remotely.
Maybe some things could be taken into account, when designing RoboBoard software (for your particular use case).

My project has two parts: one is the user Interface based on an Adafruit ESP32 Feather with display and two analog joysticks for speed and steer direction. This is what I call the client. The second part is the X4 board (the server) which will receive commands from the client and reports back speed, distance (from sensor) and the like. Together whit the main programs I developed several libraries to help in structuring the code in a reasonable pieces of code. Purpose is to have the client send the configuration to the server like min/max speeds of motors/servos acceleration and deceleration times etc. Everything is where possible driven by tables which makes the main application understandable and easy.

Does this make sense?

Regardes, Gerard

Yes, this is actually a good use of remote control feature. Made a note to add public example code, other people may find this useful.

One thing I would suggest (if it fits you needs, or you already doing this way):
Instead of sending motor spin power - just send a raw joystick axis position. Allow for X4 (server) to run motor control logic.

The advantages:

  • Both motors will update their spin power at the same moment (sending individual values over Bluetooth - there may be a delay between 7 - 45ms)
  • You will be able to use any remote controller with joysticks (your current one, Totem app with joystick widgets, PS3 or PS4 gamepad).
  • By running all control logic inside server - you can have many clients, without a need to know specifics about motor configuration and etc. If something has to be changed - it is done inside server and clients won’t need an update.
  • You can have multiple different robots (servers) with their own specific control logic, but only single one client, that works with all of them.

Another thing - for precise movement control with joystick, you need to decrease sensitivity at low speed. This is done by applying quadratic function to joystick axis.

An example code to do all this with Totem software:

int stickLeftY = 0;
int stickRightX = 0;

void appEvent(int evt, int value) {
  // If disconnected from RoboBoard - stop all motors
  if (evt == TotemApp.evtDisconnect) {
    stickLeftY = 0;
    stickRightX = 0;
    return;
  }
  // Receive updated axis position
  if (evt == TotemApp.evtFunctionA) stickLeftY = value;
  if (evt == TotemApp.evtFunctionB) stickRightX = value;
}

void setup() {
  TotemApp.addEvent(appEvent); // Register events from Totem.BLE
  // Configure drivetrain to use DC A and B ports  
  Drivetrain.setWheelLeft(DC.A);
  Drivetrain.setWheelRight(DC.B);
  // Use skid steer control logic
  Drivetrain.setDriveTank();
}
void loop() {
  // Run drive logic
  // 1. Convert [-128:127] axis position to percentage [-100:100]%
  // 2. Apply quadratic filter to lower joystic sensitivity
  // 3. Apply movement power and direction to motors
  int drive = Joystick::getAxis(stickLeftY); // Forward / Backward
  int turn = Joystick::getAxis(3, stickRightX); // Left / Right
  Drivetrain.driveTurn(turn, turn); // Update motors
}

Documentation: Drivetrain. - Totem Documentation

Actually I converted the analog joystick value from whatever it is (0 thru 4096) to a percentage -100% thru 100% which is then send to the X4 board. This one will apply whatever is needed like min/max values for speed or steering servo.

The client is not aware of the actual robot involved neither does the X4 know what UI is used. The 32 bit value send to/from X4 always has cmd code, port code and value for port, where cmd code is unique and identifies what has to be done. As I do not like duplicating code I have created a small ‘library’ which is shred between client and X4 program-code.

I think this is in line with your suggestions.

I implemented all these in my application and it is working as should. However I have the following issues:

  • When I connect an HC-SR04 distance sensor to GPIOA/GPIOB and an I2C OLED display to GPIOC/GPIOD the distance sensor is not working. Without the I2C display it is OK. Did I miss something?
  • I control the motor/servo with DriveTrain. However for some functions like setAccelerationTime I have to use DC.A.setAccelerationTime(value); Is this going to change so everything is controlled by DriveTrain (making things easier).

Thanx in advance.

When I connect an HC-SR04 distance sensor to GPIOA/GPIOB and an I2C OLED display to GPIOC/GPIOD the distance sensor is not working. Without the I2C display it is OK. Did I miss something?

HC-SR04 works by measuring pulse time on echo pin. This contains multiple instructions, so maybe somethings gets unintentionally delayed.

Can you show minimal code and Arduino libraries used?

I control the motor/servo with DriveTrain. However for some functions like setAccelerationTime I have to use DC.A.setAccelerationTime(value); Is this going to change so everything is controlled by DriveTrain (making things easier).

The Drivetrain module is basically a wrapper like:
Drivetrain.drive() → “some control logic” → DC.A.spin(), DC.B.spin()

Also, DC module has more configuration like: setAutobrake, setFrequency, setRange, setFastDecay. Adding it to Drivetrain would introduce repeating of the same API and (probably) some confusion.

Also, in future there could be added support for other kind of motors (e.g. Stepper). It will have different functionality that won’t be compatible with DC.

So current idea was to do like this:

void setup() {
  // Configure DC port A
  DC.A.setFrequency(50);
  DC.A.setRange(10, 90);
  DC.A.setAccelerationTime(300);
  DC.A.setDecelerationTime(150);
  // Assign DC A to Drivetrain
  Drivetrain.setWheelLeft(DC.A);
}

But your point is valid. Acceleration control should work on any kind of motor. Also adding it to Drivetrain would allow to focus on whole robot acceleration, without thinking of individual motor config.
This Drivetrain module is still evolving and some additional features are planned. Will think how to improve its usability.

About the distance sensor. It is working OK in a small test program. I will investigate further with the original software.

Keep you informed.

Added new functions:

  • TotemApp.sendValue()
  • TotemApp.sendData()
  • TotemApp.sendString()
  • TotemApp.addOverride()bool onCmdValue(int cmd, int value)
  • TotemApp.addOverride()bool onCmdData(int cmd, const char *data, int len)

Now data transmission ir more flexible:
RoboBoard X4:

void appEvent(int evt, int value) {
  if (evt == TotemApp.evtConnect) { printf("TotemApp connected\n"); }
  if (evt == TotemApp.evtDisconnect) { printf("TotemApp disconnected\n"); }
}
bool commandValueEvent(int cmd, int value) {
  // Received value from ESP32
  printf("==>> Got cmd: %x, value: %d\n", cmd, value);
  return false; // Ignore "cmd" in RoboBoard firmware
}
bool commandDataEvent(int cmd, const char *data, int len) {
  // Received data / string from ESP32
  printf("==>> Got cmd: %x, len: %d, data:", cmd, len);
  for (int i=0; i<len; i++) { printf(" %02x", data[i]); }
  printf("\n");
  return false; // Ignore "cmd" in RoboBoard firmware
}
void setup() {
  TotemApp.addEvent(appEvent); // Register events from Totem.BLE
  TotemApp.addOverride(commandValueEvent); // Receive "value" commands
  TotemApp.addOverride(commandDataEvent); // Receive "data" commands
}
void loop() {
  // Send value (integer) to ESP32
  TotemApp.sendValue(0x55, 1000);
  delay(100);
  // Send string to ESP32
  TotemApp.sendString(0x66, "This is a text");
  delay(100);
  // Send byte array to ESP32
  uint8_t arr[] = {1, 2, 3, 4, 5, 0};
  TotemApp.sendData(0x77, arr, sizeof(arr));
  delay(1000);
}

ESP32:

#include <Totem.h>
// RoboBoard X4 remote control object
TotemModule rb_x4(04);
// Receive data from RoboBoard
void onSendFromX4(ModuleData data) {
  int cmd = data.getHashCmd();
  if (data.isInt()) { // Used "sendValue"
    // Get value
    int value = data.getInt();
    // Print command and value
    printf("==>> Got cmd: %x value: %d\n", cmd, value);
  }
  else if (data.isString()) { // Used "sendData" or "sendString"
    // Get get pointer to data
    const char *str = data.getString();
    // Get data array
    uint8_t *dataPtr; int dataLen;
    data.getData(dataPtr, dataLen);
    // Print string & data array (choose the one you need)
    printf("==>> Got command: %d string: %s\nBytes(%d): ", cmd, str, dataLen);
    for (int i=0; i<dataLen; i++) {
      printf(" %02x", dataPtr[i]);
    }
    printf("\n");
  }
}
// Arduino setup function.
void setup() {
    Totem.BLE.begin(); // Start Bluetooth Low Energy interface
    printf("Searching for Totem board...\n");
    // Start scanning for Totem board. It's representing a Totem robot.
    TotemRobot robot = Totem.BLE.findRobot(); // Wait until connected to first found Totem robot
    // Print connected robot name
    printf("Connected to: %s\n", robot.getName().c_str());
    // Register data receiver from RoboBoard X4
    rb_x4.attachOnData(onSendFromX4);
}
// Arduino loop function
void loop() {
    rb_x4.write(0x11, 55); // Send value to RoboBoard
    static char msg[] = "Hello 0";
    msg[6]++;
    rb_x4.write(0x22, msg); // Send string to RoboBoard
    uint8_t arr[] = {7, 8};
    rb_x4.write(0x33, arr, sizeof(arr)); // Send data to RoboBoard
    delay(1000);
}

Arnas,

Thanx for the update. Will implement these in my application as it is easier to work with.

Let you know the result

Kind regards

I am trying to implement these features. However I get compiler errors. Is there another version of the Totem_Library? I have version 1.1.2

Regards, Gerard

Can you post compile error you are getting?

Compiles for me with Arduino IDE:

  • Roboboard code: 2.0.14-totem.2, Board: RoboBoard X4
  • ESP32 code: ESP32 Arduino core 2.0.14, Totem Library 1.1.2, Board: ESP32 Dev Module

running from Arduino 1.8.19. IDE 2.3.2 gives same errors
Code on esp32 runs ok.

Code compilation on X4 gives:
Arduino:1.8.19 (Mac OS X), Board:“RoboBoard X4, 921600, Disabled, Totem App, Default”

/Users/gerardvandenberg/Projects/Arduino/scetches/trc2/x4/x4.ino: In function ‘void setup()’:
x4:32:24: error: invalid conversion from ‘bool ()(int, const char, int)’ to ‘bool ()(int, int)’ [-fpermissive]
TotemApp.addOverride(commandDataEvent); // Receive “data” commands
^~~~~~~~~~~~~~~~
In file included from /Users/gerardvandenberg/Library/Arduino15/packages/totemmaker/hardware/esp32/2.0.14-totem.1/tools/totem/include/private/totem-sdk.h:23,
from /Users/gerardvandenberg/Library/Arduino15/packages/totemmaker/hardware/esp32/2.0.14-totem.1/cores/esp32/Arduino.h:225,
from sketch/x4.ino.cpp:1:
/Users/gerardvandenberg/Library/Arduino15/packages/totemmaker/hardware/esp32/2.0.14-totem.1/tools/totem/include/private/totem-totemapp.h:58:29: note: initializing argument 1 of 'void _Totem::TotemAppClass::addOverride(bool (
)(int, int))’
void addOverride(bool (func)(int cmd, int value));
~^~~~~~~~~~~~~~~~~~~
/Users/gerardvandenberg/Projects/Arduino/scetches/trc2/x4/x4.ino: In function ‘void loop()’:
x4:40:12: error: ‘class _Totem::TotemAppClass’ has no member named ‘sendValue’
TotemApp.sendValue(0x55, 1000);
^~~~~~~~~
x4:45:12: error: ‘class _Totem::TotemAppClass’ has no member named ‘sendString’
TotemApp.sendString(0x66, “This is a text”);
^~~~~~~~~~
x4:51:12: error: ‘class _Totem::TotemAppClass’ has no member named ‘sendData’
TotemApp.sendData(0x77, arr, sizeof(arr));
^~~~~~~~
exit status 1
invalid conversion from 'bool (
)(int, const char*, int)’ to ‘bool (*)(int, int)’ [-fpermissive]

Some ore info of RobocoardX4:

14:15:00.327 → Revision: 1.1
14:15:00.327 → Version : 2.0.14-totem.1
14:15:00.327 → Driver : 1.52