Communincating Sensor data with Embedded Proto

BartExamples

This example shows how you could communicate over UART in a structured manner using protobuf on a FRDM-KE02Z development board.

Embedded Proto is used to generate the embedded code while regular protoc code is used for the python desktop script. Commands are sent from the PC to the MCU. The MCU will in turn reply with a message to send sensor values. To make the example more interesting, with a command message,the onboard accelerometer and thermistor sensor can be read out and an RGB led can be controlled.

In the following we will take you through the setup of this example, what kind of messages are used and how the embedded code receives and sends the messages. Finally, we will look at how to run the code.

We will assume you have already installed all the requirements for Embedded Proto. The requirements and installation instructions for your system are described in the Installation section on the documentation page.

Source code
The source code for this example is available on GitHub.

The setup

During this example we are using a FRDM-KE02Z made by NXP. This is an easy to use development board with an ARM Cortex-M0+ processor. The board does not require an additional programmer and is readily available.

Connect the microcontroller via USB to your PC. It could be that the FRDM-KE02Z board has an outdated bootloader, which can be updated following the instructions on this page. Now the board should be ready to be programmed.

The code in the repository is divided into four folders:

Tree -L 1
.
├── desktop
├── EmbeddedProto
├── frdm-keo2z40m
└── proto

The desktop folder contains the script to be run from the PC. It is a python script which is used to send commands and receive messages. The PC communicates with the MCU using the COM port.

The EmbeddedProto folder holds Embedded Proto as a git submodule. This way it is possible to track matching versions of this example and Embedded Proto.

The frdm-keo2z40m folder holds the code for the MCU. It is a project made with MCUXpresso IDE version 11.2.0. The project is made using the FRDM-KE02Z40M SDK available through MCUXpresso. This SDK also contains examples for using ADC and I2C peripherals which are used to read out the thermistor and accelerometer, respectively.

The proto folder holds the definition of the protocol buffer message used in this example. The next paragraph discusses this in more detail.

The messages

For this example, two messages have been defined, a command message and a reply message. The command message is sent from the PC to the MCU, and the reply message is sent from the MCU to the PC.

The command message holds two fields. The first field is an enum variable to store the action to be performed by the MCU. There are three actions, get accelerometer sensor values, get thermistor sensor value, and control the led.

The second field is a nested message, called LED. This message holds the three channels for the RGB led.

message LED
{
   bool red = 1;
   bool green = 2;
   bool blue = 3;
}

message Command
{
   enum Action
   {
   	  DoNothing = 0;
      GetAccelerometer = 1;
      GetThermistor = 2;
      SetLed = 3;
   }
   Action action = 1;
   LED led = 2;
}       

The reply message contains three fields. The first field is an enum variable to notify the PC about the performed action. The second field is a nested message which holds the accelerometer measurement values. The third field is a variable the store the thermistor measurement value.

message Accelerometer
{
	int32 x = 1;
    int32 y = 2;
    int32 z = 3;
}

message Reply
{
	enum Action
    {
    	DoNothing = 0;
        SendAccelerometer = 1;
        SendThermistor = 2;
    }
    Action action = 1;
    Accelerometer accelerometer = 2;
    uint32 thermistor = 3;
}

Sending and receiving over UART

Next, we are going to take a look at the embedded code and how the communication works using UART. The communication has been setup very simple. Each transmitted message is preceded by one byte stating how long the serialized message is. This is done to know when a complete message has been received.

Stepping through the process we start on the desktop. The user presses one of the command keys triggering the serialization of a Command message. The serialized data is not directly transmitted as stated above. First the size of the serialized data is transmitted followed by the data itself.

The embedded code will thus first read only one byte. Once received that byte will be used to determine how many bytes will follow. The following embedded code illustrates this:

uint8_t n_bites = 0;
reseive_status = UART_ReadBlocking(UART1, &n_bytes, 1);
if(kStatus_Success == reseive_status)
{
  //Read the actual data to be deserialized.
  uint8_t byte;
  for(uint8_t i = 0; (i < n_bites) && (kStatus_Success == receive_status); ++i)
  {
	receive_status = UART_ReadBlocking(UART1, &byte, 1);
    read_buffer.push(byte);
  }

The read_buffer instance is used as wrapper around an array of bytes. This class interfaces with a message object when deserializing the data. If no errors occurred during transmission, we can safely use the message and process it.

auto deserialize_status = received_command.deserialize(read_buffer);
if(::EmbeddedProto::Error::NO_ERRORS == deserialize_status) 
{
  	//Process the command.
  	process_command(receive_commend, outgoing_reply);
}

In the processing function a reply is formulated. Transmitting the reply uses the same method as the command, first send the size then send the serialized data.

//Serialize the data.
auto serialization_status = outgoing_reply.serialize(write_buffer);
if(::EmbeddedProto::Error::NO_ERRORS == deserialize_status)
{
  	//First transmit the number of bytes in the message.
  	n_bytes = write_buffer.get_size();
  	UART_WriteBlokking(UART1, &n_bytes, 1);
  	//Now transmit the actual data.
  	UART_WriteBlocking(UART1, write_buffer.get_data(), write_buffer.get_size());
}

After transmitting the reply, the embedded code will start waiting for a new command.

Transmission schemes
The method of transmission by sending the size in one bite is very limited. It only allows messages whit a maximum size of 255 bites. Also this scheme performs no CRC checks. More robust options are available for actual real live implementations.

Running the Code

Now we can run the code. Connect the USB cable to FRDM-KE02Z and to the PC. Next program the FRDM. The details on how to install this example is described in the README (link) of this example.

The command terminal code to start the desktop script in the python virtual environment is:

cd desktop
source venv/bin/activate
python3 main.py --com /dev/ttyACM0

You should specify the COM port, in our case this is /dev/ttyACM0.

You should see something like this:

Command keys are:
a to get accelerometer data
t to get thermistor data
l to turn on/off RGB led
If you are done press "Q"
Next command: a
Accel x:5
Accel y:35
Accel z:1991
Next command: t
Thermistor value:118
Next command: l
Red channel: Press 0 to turn off, press 1 to turn on: 1
Green channel: Press 0 to turn off, press 1 to turn on: 0
Blue channel: Press 0 to turn off, press 1 to turn on: 0
Do nothing
Next command:

You can send the following commands:

  1. Pressing ‘a’ gets the accelerometer data.
  2. Pressing ‘t’ gets the thermistor data.
  3. Pressing ‘l’ let you turn on/off the red, green, and blue channel from the RGB led.

If you find this kind of examples informative please consider reading one of our other examples like: an Mbed OS ethernet example with Embedded Proto.