rtos

Thursday, October 13, 2022

Guide to use ProtocolBuffers (nanopb) in IoT Devices.

 What is Protocol Buffer?

    I hope the reader is familiar with JSON format. It's a widely used data format for communicating between two different programs. Many mobiles and web applications use JSON for data exchange. Because it's easy to use and human-readable. Many IoT applications are also using JSON for data exchange. But there is a good tool that can help to cut down the data packets size sends over the internet.  It's called Protocol Buffers. 

Nanopb is an ANSI-C library for encoding and decoding messages in Google's Protocol Buffers format with minimal requirements for RAM and code space. It is primarily suitable for 32-bit microcontrollers.

Protocol buffers, usually referred to as Protobuf, is a protocol developed by Google to allow serialization and deserialization of structured data. Google developed it with the goal to provide a better way, compared to XML and JSON, to make systems communicate. So they focused on making it simpler, smaller, faster, and more maintainable than XML and JSON. 

Microcontrollers use Protocol Buffers in IoT devices. It decreases the packet size, and reduces delivery time. 

How do Protocol Buffers works?

The developer writes the Data structure schema in a .proto file. Then that file will be passed to the python script. Python script will generate a C header and C source file for encoding and decoding the data structure just now the developer described in the .proto file.  User needs to use these C files inside their project directory to encode or decode. for more information refer to https://github.com/nanopb/nanopb

Process Overview

Image:     .proto file to c data structure converter

How to Install protocol buffers and dependencies?

This example is focused Linux system

     Below mentioned are the Dependencies:

  • Python
  • Python Protobuf-Library
  • Protoc
If python is not installed on the computer, Install it manually, and then it's easy to install other dependencies. 
  • Once the Python is installed then type the below command 
pip install --upgrade protobuf grpcio-tools

If it's successfully installed then good to go.  

  • Clone the nanopb GitHub directory

git clone https://github.com/nanopb/nanopb.git

How to use protocol buffers?

  • Once the installation is completed create a new directory inside nanopb.  I prefer location nanopb/examples.

            ex: directory named "sample" is created and its path looks like -> nanopb/examples/sample.

  • Create a file name sample.proto
    • "sample"  is the name taken for example but you can name it related to your project.

What is a <sample.proto> file is all about?

If you observe the basic C structure, It needs definition before we can use it in the main program. Just like that, .proto is a file where the developer writes the data structure. It almost looks like C but with a little different syntax. 

Ex:

Step 1: Creating a structure.

Type this in the sample.proto file 

syntax = "proto3";
message Example {
    int32 lucky_number = 1;
}

Here,

        syntax="proto3"

This tells the Protocol Buffer compiler to use protocol version 3. If it's not specified then by default it takes proto2 version. 

    message Example

 "message" is the keyword used in the protocol buffer to create a structured schema followed by the user-defined message name. Here "Example" is the user-defined name. 

    int32 lucky_number=1

just like uint32_t in C, we can use int32 to create a 32-bit integer variable in our code to store some sensor data or any other use desired values. Number 1 is used to identify the fields by Protobuf and it should be unique for a structure. There are number of data types supported by Protobuf. 

click here to check different types of Data Types supported in Protobuf 

https://jpa.kapsi.fi/nanopb/docs/concepts.html#data-types

Step 2: Generating C files.

Type the below command to generate C files in the terminal under the examples/sample directory.

python ../../generator/nanopb_generator.py sample.proto

It will generate 2 files sample.ph.c and sample.ph.h.
You can open sample.ph.h file and check for the C structure. It will be something like the one below. 

/* Struct definitions */
typedef struct _Example { 
    int32_t lucky_number;
} Example;

This is exactly the C structure.

Step 2: Adding Protobuf to your project.

To Use the protobuf in your project you need the below-mentioned files 

pb_encode.h/c

pb_decode.h/c

pb_common.h/c

pb.h

sample.ph.h/c  <message> same as the name of the .proto file

Physically Add all these files into your project directory or include a path to your project build system. message.ph.h/c files are generated from the previous command and the remaining files can be found on the root directory of the protobuf GitHub directory. 

Include these files in the header of the C file where messages needs to to encoded or decoded.

#include "sample.ph.h"

#include "pb_encode.h"

#include "pb_decode.h"

If you use only encoding on your code then include only pb_encode.h file.

Example of Encoding and Decoding:

Once the protobuf generates the message.ph.h/c files copy and paste all the file names mentioned in <adding protobuf to your example> section in the root directory or include the path to your build system.

Since many embedded systems are platform dependent we are going to check encoding and decoding in basic C which is same in many C based embedded developments. 

Create a c file named main.c in the root directory where all of your files are pasted. You can used those files stand alone in any other directory and there is no dependency for that since it all independent C file. But all files should be present in the same directory or path should be included in build system. once the files are included the project contains below files. 

pb_encode.h/c

pb_decode.h/c

pb_common.h/c

pb.h

sample.ph.h/c

main.c :( C file to implement Encode Decode logics)

#include <stdio.h>
#include "pb_encode.h"
#include "pb_decode.h"
#include "sample.pb.h"
int main()
{
/* This is the buffer where we will store our message. */
uint8_t buffer[128];
size_t message_length;
bool status;
/* Encode our message */
{
/* Allocate space on the stack to store the message data.
*
* Nanopb generates struct definitions for all the messages.
* - check out the contents of sample.pb.h!
* It is a good idea to always initialize your structures
* so that you do not have garbage data from RAM in there.
*/
SampleMessage message = SampleMessage_init_zero;
/* Create a stream that will write to our buffer. */
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
/* Fill in the lucky number */
message.lucky_number = 13;
/* Now we are ready to encode the message! */
status = pb_encode(&stream, SampleMessage_fields, &message);
message_length = stream.bytes_written;
/* Then just check for any errors.. */
if (!status)
{
printf("Encoding failed: %s\n", PB_GET_ERROR(&stream));
return 1;
}
}
/* Now we could transmit the message over network, store it in a file or
* wrap it to a pigeon's leg.
*/
/* But because we are lazy, we will just decode it immediately. */
{
/* Allocate space for the decoded message. */
SampleMessage message = SampleMessage_init_zero;
/* Create a stream that reads from the buffer. */
pb_istream_t stream = pb_istream_from_buffer(buffer, message_length);
/* Now we are ready to decode the message. */
status = pb_decode(&stream, SampleMessage_fields, &message);
/* Check for errors... */
if (!status)
{
printf("Decoding failed: %s\n", PB_GET_ERROR(&stream));
return 1;
}
/* Print the data contained in the message. */
printf("Your lucky number was %d!\n", (int)message.lucky_number);
}
return 0;
}
view raw main.c hosted with ❤ by GitHub

Copy and pase the above code in main.c file. Comments sections in the code explains code very well. 

Once the files are ready now lets compile the code and run.

Run in the files workspace directory :

gcc */c -o main

this will compile and generate executable file. 

To run the code type below command 

./main

output will be 

Your lucky number was 13!

Tuesday, October 11, 2022

How to use Zephyr ADC with NRF9160DK?

Technical Terms:

1. What is meant by 8-channel ADC?

        8 Channel ADC in Microcontroller means 

    • 8 pins of microcontrollers can be used to receive that many analog signals which can in turn be digitized.
    • However and usually there is one ADC in the microcontroller. NRF9160 ADC
    • The inputs at the pin are selected and connected to ADC via. Analog Multiplexer (part of microcontroller or ADC) one at a time
    • ADC delivers the digital code of the analog signal at the channel or pin number, which is connected to its input at a given instance.
Analog-to-digital converters (ADCs) need a reference voltage (VREF) input in order to operate properly. ADCs convert analog inputs that can vary from zero volts up to a maximum voltage level which is called the reference voltage.
    When we use the internal reference 0.6V as the ADC reference voltage, we cannot pass a voltage of more than 0.6V to ADC pins directly. If we do so the ADC output will be undesired(sometimes it will show 1024 in if it's 10 bit and sometimes it may be reduced to lower just like variable overflow). To increase/decrease the input range above the reference voltage Gain is used.  

Gain is nothing but performing multiplication or division with the actual input voltage before passing it to the ADC converter. Check this image of ADC

You can see the Gain block present between the Input voltage and the ADC circuit. We can configure this Gain to which number to be multiplied or to be divided from the Input voltage in order to provide proper value to the ADC that matches the Reference voltage. 

Example: 

Assume we chose the Internal reference voltage which is 0.6 Volt.  According to theory, we cannot pass voltage above 0.6V to ADC Channels. Let's say you want to read the AA battery voltage which is 3.6 Max Voltage (for simplicity without a voltage divider). You need to use Gain in order to make ADC work in the range of up to 3.6V. You can select 1/6 gain. By doing so it will divide the incoming voltage by 6 and pass it to ADC. 

Assume you passed an input voltage is 3.6V to ADC Pin, and you selected gain as 1/6. then the Gain part in the circuit divides the 3.6V by 0.6V. output it 0.6V. Which is exactly not greater than the internal reference voltage. 

 Zephyr ADC Supports the below gain configurations.


/** @brief ADC channel gain factors. */
enum adc_gain {
ADC_GAIN_1_6, /**< x 1/6. */
ADC_GAIN_1_5, /**< x 1/5. */
ADC_GAIN_1_4, /**< x 1/4. */
ADC_GAIN_1_3, /**< x 1/3. */
ADC_GAIN_1_2, /**< x 1/2. */
ADC_GAIN_2_3, /**< x 2/3. */
ADC_GAIN_1, /**< x 1. */
ADC_GAIN_2, /**< x 2. */
ADC_GAIN_3, /**< x 3. */
ADC_GAIN_4, /**< x 4. */
ADC_GAIN_6, /**< x 6. */
ADC_GAIN_8, /**< x 8. */
ADC_GAIN_12, /**< x 12. */
ADC_GAIN_16, /**< x 16. */
ADC_GAIN_24, /**< x 24. */
ADC_GAIN_32, /**< x 32. */
ADC_GAIN_64, /**< x 64. */
ADC_GAIN_128, /**< x 128. */
};



If you select 1/6 as Gain then 
Input range = (0.6 V)/(1/6) = 3.6 V
You can pass up to 3.6V and the gain will divide by 1.6 and pass it to ADC


If you select 2 as Gain then 
Input range = (0.6 V)/(2) = 0.3 V

Convert the raw value to Volts

  •     Remember the Gain value Input range? We are going to use that now. 
Actual_Voltage = input_raw_value * (input_volt_range / resolution)
input_raw_value: Value read from adc_read(adc_dev, &sequence);function
input_volt_range: maximum input voltage range ex: if the gain is 1/6 then the max value is 0.6/(1/6) = 3.6.  here 0.6 is an internal reference voltage and 1/6 is the gain.
resolution10-bit ADC max value. 

Find the code in the Git repo

 https://github.com/manudevappa/zephyr_adc_sample

Guide to use ProtocolBuffers (nanopb) in IoT Devices.

 What is Protocol Buffer?      I hope the reader is familiar with JSON format. It's a widely used data format for communicating between...