hello,
I'm working on a project with Teensy 4.1. I want to send data quickly (with 60fps = every 16.6ms) to Teensy via Serial Port and control 4 Stepper Motors and 16 Servos with the data. The Serial Data is send from the Blender (animation program) with a small Python Add-on.
In the first successfull tests I did, I simply sent 20 csv-values via Serial. It was fast, worked pretty well, but was not flexible or nicely coded.
To be more flexible, I rewrote this part, sending a JSON-Package, receive it (non-blocking, works so far) and parse it with the ArduinoJSON 7 library. The bottleneck seems to be the deserializeJson() function, it takes ~69us and blocks out the stepper motors heavily.
That's the JSON-Data, send via Serial with 500000 Baud:
(I already shortened the JSON data to a minimal version, it reduces the speed by ~20%. But still not where I would need it.)
The code of the parser is as follows:
receivedChars holds the data you see above.
Of course, parsing the data ~640 characters takes a bit time and 69us is in another context fast. I see different options:
1) Speed up deserialization (how?)
2) Run the stepper motors with timer interrupts (how?) - currently I'm using AccelStepper lib which relies on a smooth main-loop. So the parsing function could block, but the stepper motors continue to run.
3) Not sending all data at once, but sending tiny packages for each Stepper or Servo and putting it back together on the microcontroller.
4) any more ideas?
Thank you for your thoughts and all the best,
clockdiv
I'm working on a project with Teensy 4.1. I want to send data quickly (with 60fps = every 16.6ms) to Teensy via Serial Port and control 4 Stepper Motors and 16 Servos with the data. The Serial Data is send from the Blender (animation program) with a small Python Add-on.
In the first successfull tests I did, I simply sent 20 csv-values via Serial. It was fast, worked pretty well, but was not flexible or nicely coded.
To be more flexible, I rewrote this part, sending a JSON-Package, receive it (non-blocking, works so far) and parse it with the ArduinoJSON 7 library. The bottleneck seems to be the deserializeJson() function, it takes ~69us and blocks out the stepper motors heavily.
That's the JSON-Data, send via Serial with 500000 Baud:
JSON:
{"Frame":567,"Mode":"MANUAL","AnimationData":[{"Type":"STEPPER","ID":0,"Position":123},{"Type":"STEPPER","ID":1,"Position":456},{"Type":"STEPPER","ID":2,"Position":789},{"Type":"SERVO","ID":0,"Position":987},{"Type":"SERVO","ID":1,"Position":654},{"Type":"SERVO","ID":2,"Position":321},{"Type":"SERVO","ID":3,"Position":987},{"Type":"SERVO","ID":4,"Position":654},{"Type":"SERVO","ID":5,"Position":321},{"Type":"SERVO","ID":6,"Position":987},{"Type":"SERVO","ID":7,"Position":654},{"Type":"SERVO","ID":8,"Position":321},{"Type":"SERVO","ID":9,"Position":987},{"Type":"SERVO","ID":10,"Position":654},{"Type":"SERVO","ID":11,"Position":321}]}
(I already shortened the JSON data to a minimal version, it reduces the speed by ~20%. But still not where I would need it.)
The code of the parser is as follows:
receivedChars holds the data you see above.
C++:
bool SerialDataHandler::parseJSONData()
{
// Part 1 takes ~69us
current_micros = micros(); // start stopwatch
newData = false;
DeserializationError error = deserializeJson(doc, receivedChars); // BOTTLENECK
// Test if parsing succeeds
if (error)
{
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
return false;
}
stopwatch_micros = micros() - current_micros; // stop stopwatch
Serial.printf("\n\nJSON parse time part 1: %luus (blocking!!)\n", stopwatch_micros);
// Part 2 takes ~14us, itearating the array could be rewritten to non-blocking,
// but the bottleneck above is more urgent
current_micros = micros(); // start stopwatch
uint32_t frame = doc["Frame"];
const char *mode_ = doc["Mode"];
JsonArray frameDataJson = doc["AnimationData"].as<JsonArray>();
uint8_t stepperIndex = 0;
uint8_t servoIndex = 0;
for (JsonVariant actuator : frameDataJson)
{
const char *type = actuator["Type"];
uint8_t id = actuator["ID"];
uint32_t position = actuator["Position"];
if (strcmp(type, "STEPPER") == 0)
{
frameData.stepperTargets[stepperIndex].id = id;
frameData.stepperTargets[stepperIndex].position = position;
stepperIndex++;
}
else if (strcmp(type, "SERVO") == 0)
{
frameData.servoTargets[servoIndex].id = id;
frameData.servoTargets[servoIndex].position = position;
servoIndex++;
}
}
frameData.stepperCount = stepperIndex;
frameData.servoCount = servoIndex;
stopwatch_micros = micros() - current_micros; // stop stopwatch
Serial.printf("\n\nJSON parse time part 2: %luus (blocking)\n", stopwatch_micros);
// finished here, only serial output from here:
Serial.printf("Frame: %ld", frame);
Serial.print("\nMode:");
Serial.println(mode_);
Serial.printf("%d Steppers:\n", frameData.stepperCount);
for (int i = 0; i < frameData.stepperCount; i++)
{
Serial.print(frameData.stepperTargets[i].id);
Serial.print(", ");
Serial.println(frameData.stepperTargets[i].position);
}
Serial.printf("%d Servos:\n", frameData.servoCount);
for (int i = 0; i < frameData.servoCount; i++)
{
Serial.print(frameData.servoTargets[i].id);
Serial.print(", ");
Serial.println(frameData.servoTargets[i].position);
}
return true;
}
Of course, parsing the data ~640 characters takes a bit time and 69us is in another context fast. I see different options:
1) Speed up deserialization (how?)
2) Run the stepper motors with timer interrupts (how?) - currently I'm using AccelStepper lib which relies on a smooth main-loop. So the parsing function could block, but the stepper motors continue to run.
3) Not sending all data at once, but sending tiny packages for each Stepper or Servo and putting it back together on the microcontroller.
4) any more ideas?
Thank you for your thoughts and all the best,
clockdiv