How to use¶
Platform support¶
Framework:
Arduino
This library is made to be used with:
ESP32 family
Teensy 4.X family
Library framing¶
To allow easy decoding for multiple messages on bus, bytes are added to the begining of the message. Two start bytes and message size are added. This allow to decode multiple chunks of data on the same bus, and to to distinguish between protobuf messages and other protocols.
start byte 1: 77
start byte 2: 88
The length of the message is added by nanopb.
constexpr uint8_t PROTOBUF_FRAME_START_BYTE_1 = 77U;
constexpr uint8_t PROTOBUF_FRAME_START_BYTE_2 = 88U;
Generate headers for custom messages¶
The protobuf headers are generated using nanopb protoc generator. A shell script is provided for easy generation. The basic Protobuf messages set is already generated and files can be found in the libDM_protobuf/autogen_protobuf folder.
If you need to add messages to the set, you can modify files present in the libDM_protobuf/message_definitions folder or add your own proto files following Google Protocol Buffers documentation.
Important
any .proto file found in the libDM_protobuf/message_definitions folder will be included in the generation process.
Attention
As all the protobuf messages are transmitted through hardware interfaces using a ProtoFrame, every message that has to transit through a hardware interface has to be included in the protoframe defined in the message_definitions/frame.proto file.
You can then use:
./generate_protobuf.sh
More informations about the protobuf langage can be found here: https://protobuf.dev/overview/
Necessary inclusions¶
If all the messages are included in the protoframe, you can just include the following files in your project:
#include "libDM_protobuf.hpp"
The inclusion of “autogen_protobuf/frame.pb.h” should be sufficient. If files have been added without the inclusion in the protoframe, you will have to include the corresponding .pb.h file in your code.
Message reception¶
1. Create a PROTOBUF_UTILS object¶
Important
API ref: PROTOBUF_UTILS
Example
PROTOBUF_UTILS protobuf(&streamObj, &myTimer);
2. (optional) Add a PROTOBUF_STREAM object¶
If no interface has already been specified, you can configure an interface and add it to the PROTOBUF_UTILS
object
Important
API ref: PROTOBUF_UTILS::addStream()
Example
1 // Initialize serial link
2 Serial2.setRxBufferSize(PROTOBUF_MAX_MESSAGE_SIZE);
3 Serial2.setTxBufferSize(PROTOBUF_MAX_MESSAGE_SIZE);
4 Serial2.begin(921600, SERIAL_8N1, pinProtocolRx, pinProtocolTx);
5 protobuf.addStream(&Serial2);
3. Get refs to the messages you want to receive and to the frame¶
To manipulate data, it is interesting to grab references of the ProtoFrame
Important
API ref: PROTOBUF_UTILS::getReceivedMsg()
, PROTOBUF_MSG::getFrame()
Example
1 // Get protobuf frame reference
2 PROTOBUF_MSG* protobufMsg = protobuf.getReceivedMsg();
3 ProtoFrame* protobufFrame = &protobufMsg->getFrame();
4. Call updateReceivedMessages(buffer, bufferLength) at every step¶
The PROTOBUF_UTILS::updateReceivedMessages()
has to be called periodically and feeded with the received buffer
on some serial interface.
It will update the ProtoFrame with the received informations
Important
With the example of a serial stream:
Example
1uint8_t receptionSerialBuffer[PROTOBUF_MAX_MESSAGE_SIZE];
2size_t size = Serial2.available();
3if (size > PROTOBUF_MIN_MESSAGE_SIZE)
4{
5 // Copy serial buffer to be able to use it for mavlink and protobuf
6 OP::UART::readBytesBuffer(&Serial2, receptionSerialBuffer, size);
7 protobuf.updateReceivedMessages(receptionSerialBuffer, size);
8}
Attention
As the size of the protoframe can be quite large, PROTOBUF_MAX_MESSAGE_SIZE can be quite large (more than 2000 bytes). Ensure stack can handle it or allocate one time the reception buffer in the heap.
5. Get the last ProtoFrame¶
The ProtoFrame fields have been automatically updated if received on some hardware interface.
To check if the message has been updated, you can use PROTOBUF_UTILS::isNewMsgReceived()
and grab the messages
received
Example
1 if (protobuf.isNewMsgReceived())
2 {
3 Serial.println("Message received received");
4 protobufMsg->setIsNew(false);
5
6 Serial.println(protobufFrame->timestamp);
7 // Inspect frame to see what has been sent:
8 if (protobufFrame->attitude.attitude_local.has_ypr)
9 {
10 Serial.print("Yaw = " + String(protobufFrame->attitude.attitude_local.ypr.yaw));
11 Serial.print(" Pitch = "
12 + String(protobufFrame->attitude.attitude_local.ypr.pitch));
13 Serial.println(" Roll = "
14 + String(protobufFrame->attitude.attitude_local.ypr.roll));
15 }
16 }
Note
The use of the has_xxx flags can help to determine what kind of messages inside the frame has been received.
Attention
Do not forget to set the isNew flag to false once the message has been read if all the consumers have used it or the
PROTOBUF_UTILS::isNewMsgReceived()
will always return true.
Message emission¶
1. Create a PROTOBUF_UTILS object¶
Important
API ref: PROTOBUF_UTILS
Example
PROTOBUF_UTILS protobuf(&streamObj, &myTimer);
2. Add a PROTOBUF_STREAM object¶
If no interface has already been specified, you can configure an interface and add it to the PROTOBUF_UTILS
object
Important
API ref: PROTOBUF_UTILS::addStream()
Example
1 // Initialize serial link
2 Serial2.setRxBufferSize(PROTOBUF_MAX_MESSAGE_SIZE);
3 Serial2.setTxBufferSize(PROTOBUF_MAX_MESSAGE_SIZE);
4 Serial2.begin(921600, SERIAL_8N1, pinProtocolRx, pinProtocolTx);
5 protobuf.addStream(&Serial2);
3. Get refs to the messages you want to send and to the frame¶
To manipulate data, it is interesting to grab references of the ProtoFrame
Important
API ref: PROTOBUF_UTILS::getMsgToSend()
, PROTOBUF_MSG::getFrame()
Example
1 // Get protobuf frame reference
2 PROTOBUF_MSG* protobufMsg = protobuf.getMsgToSend();
3 ProtoFrame* protobufFrame = &protobufMsg->getFrame();
4. Fill the ProtoFrame and send it¶
Attention
The use of nanopb imposes to set the has_xxx flags to true if the field has to be sent. Setting to top level message has_xxx to false will not send the message and all the subfields it contains. At reception their has_xxx flags will be read as false, even if they were set to true at emission. For enabling messages, the has_xxx flags have to be set to true at the top level message and all the subfields it contains until the last field to be sent has been reached. For example, to send accelerometer data:
1 ProtoFrame frame = ProtoFrame();
2 frame.has_sensors = true;
3 frame.sensors.has_accelerometer_raw = true;
4 frame.sensors.accelerometer_raw.has_xyz = true;
5 frame.sensors.accelerometer_raw.xyz.x = 0.1F;
6 frame.sensors.accelerometer_raw.xyz.y = 0.2F;
Important
API ref: PROTOBUF_UTILS::sendMessages()
, PROTOBUF_MSG::setIsReadyForSend()
,
PROTOBUF_MSG::resetFrame()
Example
1 uint8_t counterProtobufSend = 0U;
2
3 while (true)
4 {
5 delay(5);
6 counterProtobufSend += 1;
7 if (counterProtobufSend >= 10)
8 {
9 counterProtobufSend = 0;
10 // Reset all fields
11 protobufMsg->resetFrame(); // protobufFrame is resetted
12 // Set new values
13 // Set booleans for values we want to send (we want to send yaw pitch and not roll)
14 protobufFrame.has_attitude = true;
15 protobufFrame.attitude.has_attitude_local = true;
16 protobufFrame.attitude.attitude_local.has_ypr = true;
17 protobufFrame.attitude.attitude_local.ypr.has_yaw = true;
18 protobufFrame.attitude.attitude_local.ypr.has_pitch = true;
19 protobufFrame.attitude.attitude_local.ypr.has_roll = false; // not necessary
20
21 protobufFrame.attitude.attitude_local.ypr.yaw = 0.1;
22 protobufFrame.attitude.attitude_local.ypr.pitch = 0.2;
23 protobufFrame.attitude.attitude_local.ypr.roll = 0.3; // Will not be sent
24
25 // Send frame
26 protobufMsg->setIsReadyForSend(true); // for periodic send
27 protobuf.sendMessages();
28 }
29 }