Understanding Data in Embedded Systems: A Step-by-Step Guide
Quick Overview
Hey there, tech explorers! Ever wondered how your TV, smartphone, or cool watches actually do their thing? Well, surprise — it’s all about something called data! Think of it as the secret sauce that makes tech tick.
Today, we’re on a journey to unravel this data mystery in simple terms. Imagine it like a cool adventure, starting with the basics (what’s up with arrays?) and going on to the more interesting stuff, like how data does its behind-the-scenes dance. So, buckle up for a fun ride where we make tech talk easy and exciting. Ready? Let’s jump in!
Exploring Data in Embedded Systems
Finding Data in Embedded Systems
In tech, think of signals as the bottom layer, like the ground floor. Now, data is the layer above — it’s like the main currency in the software world. It’s what makes everything happen in our software.
In embedded systems, technically, data can be an array or a variable. From the drivers layer to the applications layer, data represents the most important unit. Your microcontroller registers are data. When you want to check an input state, you access the Input Data Register (IDR). When we talk about the internal peripherals of a microcontroller, data is the information transferred on these buses. For example, the CAN address or DLC, or even the payload are all data. Even if we switch to the OS internal kernel and verify the used semaphore, guess what? It’s data too.
In a nutshell, data is everywhere in embedded systems. As a definition, data is the amount of bits.
How Data Changes Hands
In the distance metrology world, the meter represents the principal unit. However, when dealing with larger distances, such as in this set {1705m, 85144m, 368445m}, we shift to kilometers as the main unit for clearer information. Similarly, in embedded systems, the bit is the principal unit. Yet, in certain use cases where the data size is larger, we use the byte to provide a more detailed representation. A byte is the sum of 8 bits and ranges between 0 and 255 when unsigned. When it’s an algebraic value, the byte becomes signed, ranging from -128 to 127, with the Most Significant Bit (MSB) representing the sign.
For some parameters, 1 byte is insufficient. Take, for instance, a variable representing the number of seconds in one hour (3600 seconds). In such cases, we have two ways to store this parameter: creating an array with 2 memory boxes, each having a type of 1 byte, or using a 16-bit memory box. The same principle applies to other parameters where a 16-bit memory is inadequate, leading us to use a 32-bit memory box or an array with 4 memory boxes, each having a type of 1 byte.
In summary, data can be stored in boxes with different types: signed or unsigned 8-bit, 16-bit, or 32-bit. The key is to understand that every bit in our data packet has an index, allowing us to access and manipulate its state.
What’s the Deal with data?
The goal of understanding what data is involves comprehending how to handle the pieces of information in these packets. Dealing with data in embedded systems can be represented in three actions:
- Write my data: I have a type of data (bit, byte, or more), and I want to save it into a packet.
- Read my data: I have a type of data (bit, byte, or more), and I want to read it into my context.
- Play with my data: I can manipulate my dataset based on the use case, such as sorting, finding the maximum value, getting the nearest value to zero, or extracting only positive values.
What Comes Next? I will now explain every necessary algorithm to effectively play with data.
Getting Specific Data: Easy Techniques
Grabbing Bits: A Simple Way
Let’s start by swimming into the smallest unit, the bit. For example, when working with an input state and seeking to retrieve its specific state, we can utilize the concept of bit shifting. All that is needed is the bit’s position, and the desired value becomes accessible. To filter out all other bits, we employ the logical AND operator.
Getting a Whole Byte: Easy Steps
Retrieving an entire byte from an array is a fundamental operation in programming languages. To access your byte, you must be aware of both the array’s address and the byte’s position. Even when dealing with matrices, these two parameters are essential. The most widely used method is to create a pointer with the type of your array and assign your array’s address to it. Subsequently, you can access your byte by adding the byte index to the pointer data.
Dealing with Fixed Data Sizes
Retrieving a single byte from an array is straightforward, but extracting a specific amount of data from an array can be challenging. To simplify the process, consider a scenario where you receive a complete packet via UART containing various sensor measurements. Suppose the temperature data, for instance, is stored in 2 bytes and is located in the 6th byte. In this case, we have four parameters: the input buffer, a buffer to store the temperature, the temperature’s location, and the temperature’s size. All that’s required is to copy the data from the specified location to the end of the temperature value (location + size).
The same principle applies to any other context involving reading bytes. Even if the bytes are not aligned, all you need to know is the storage location of each byte in your output buffer.
The last thing you need to be careful about is the endianness of your data structure.
Creating Custom Data Packets: A Simple Guide
Bit by Bit: Adding Specific Bits
Reading data is not the only operation you can perform. You can also update your bits by setting or resetting their states. All that is needed for this is the use of bitwise operators.
Before delving into bitwise operators, let’s review some fundamental concepts in binary logic. Consider two bits, named ‘A’ and ‘B.’ Each of these bits can only take on a binary value of 0 or 1. Let’s start with the AND operator. If we perform ‘A’ AND ‘B,’ the result is 1 only if both ‘A’ and ‘B’ are equal to 1. If either one or both are 0, the result will always be 0.
Now, moving on to the OR operator. If we perform ‘A’ OR ‘B,’ the result is 1 if either ‘A’ or ‘B’ (or both) equals 1. The result is 0 only when both ‘A’ and ‘B’ are 0.
Finally, the NOT operator can invert the value of a bit. For instance, if ‘A’ equals 0, NOT ‘A’ will be 1. Similarly, if ‘A’ and ‘B’ are both 0, the result of ‘A’ AND ‘B’ is 0, and the NOT of this result is 1.
// Remember this:
// A = 0 , B = 0 ⇒ A | B = 0 ; A&B = 0 ; ~A = 1 ; ~B = 1
// A = 0 , B = 1 ⇒ A | B = 1 ; A&B = 0 ; ~A = 1 ; ~B = 0
// A = 1 , B = 0 ⇒ A | B = 1 ; A&B = 0 ; ~A = 0 ; ~B = 1
// A = 1 , B = 1 ⇒ A | B = 1 ; A&B = 1 ; ~A = 0 ; ~B = 0
The challenge in setting or resetting a bit is to maintain the state of the other bits. When setting a bit at a specified position, we shift 1 to the specified position to the right and perform a bitwise OR operation with the byte field. Conversely, when resetting a bit, we shift 1 to the specified position to the right, apply a NOT operation to the value, and perform a bitwise AND operation with the byte field.
Byte by Byte: Putting Bytes Together
Similar to reading, writing a byte in a data structure is one of the fundamental programming tasks. All we need is the data address and the position to write our byte. To execute the write operation, we access the position using the byte index (data address + byte position).
Fixed Data Sizes: Easy Building Blocks
To understand how to incorporate my data into another data structure, envision this operation as constructing a wall with bricks. In this analogy, the wall represents the data structure, and each brick symbolizes your data. The crucial aspects are knowing where to place your data, understanding its size, and identifying the type of the wall.
To illustrate this concept further, let’s delve into a more intricate scenario. Consider that our data represents a car’s position on the Z-axis, and for precision, we use the data type float. Our ‘wall’ is a buffer intended to be sent via WebSocket to a web server, with the type being an array of uint8_t. The position of our data is at the 5th byte.
Utilizing the IEEE754 standard to convert our float, let’s take a value of ‘5300.5 mm,’ resulting in ‘0x45A5A400.’ Notably, a float is composed of 4 bytes. When storing this value in our data structure, we need to be mindful of the endianness. Each byte of the float position will be stored in one of our data frames, starting from the 5th byte.
A Handy Library for Simplified Data Management
Why You Need This Library
I created a small toolkit that performs various operations with bits, bytes, and data. It includes functions for setting, resetting, and retrieving bits, as well as reading and writing bytes. If you need to handle a bunch of data, this library has you covered. It’s written using only the ‘stdint.h’ library, making it easy to integrate into your project. To prevent confusion about the ‘int’ size type on certain controllers, this library uses ‘uintx_t’ types.
It’s useful for:
- People who work on small computer systems that don’t handle data structures well.
- Students learning about data in the C programming language.
- Anyone working on projects that talk to small computer parts and wants to know what they’re saying and how to work with the information.
How to Use It: A Simple Guide
To begin using the library, the first step is to include the header file in your source code.
// Include header file
#include "data.h"
Afterwards, you should initialize the library by calling the necessary functions into your context using the ‘init’ function:
// Declare your context
st_data_ctx_t st_data_;
// Init your module
vfn_data_init(&st_data_);
Whenever you need to handle a data buffer, you should add its address to the data context:
// Define a max size
#define cst_data_max_size 32
// Declare your data structure
uint8_t data_test[cst_data_max_size] = {};
// Set the data structure address to the data module
st_data_.vfn_data_set_ptr(&st_data_, data_test, cst_data_max_size);
Finally, you can utilize all the methods provided by this library on your data structure. For example, let’s set the second bit of the first byte:
// Declare used variables
static uint8_t bittestfield_byte = 0, bittestfield_bit = 0;
// Ask for Byte position
printf("Set bit command please set byte index ");
scanf("%u", &bittestfield_byte); printf("\\n");
// Ask for bit position
printf("Set bit command please set bit index ");
scanf("%u", &bittestfield_bit); printf("\\n");
// Print data structure before update
print_data_set(data_test,cst_data_max_size);
// Set bit
st_data_.vfn_data_set_bit(&st_data_,bittestfield_byte,bittestfield_bit);
// Print data structure after update
print_data_set(data_test,cst_data_max_size);
Output:
Key Points to Remember
To conclude this journey with data structures, let’s remember key points:
- Explored the world of data, the secret language powering tech gadgets.
- Unveiled the importance of data in embedded systems, where bits and bytes rule.
- Learned to play with data: grabbing bits, dealing with bytes, and creating custom packets.
- Navigated the challenge of endianness, ensuring data speaks the same language everywhere.
- Introduced a handy library for easy data management in small systems, C programming, and projects.
- Demystified tech talk, making it accessible and fun for everyone.
- Armed with new knowledge, ready to dive into the data universe and kickstart tech conversations!