Completed: / exercises

Data structures

Learning Objectives: Using data structures in C and their use cases in embedded systems programming.
A data structure (English struct) is a logical structure/combination/aggregation of several variables, where related information can be grouped for unified handling. Data structures are a very common concept in programming, and they also have use cases in embedded programming, so let's take a look at how they are used in C.
In C, the word reserved for defining a data structure is struct, and a structure is defined as a block as follows. The members of a data structure in C can be of any variable types.
struct structure_type_name { 
   member declarations;
}
For example, let's define a data structure of type point, which has x and y coordinates as members and a point name as a string. Then, we define another structure rect, which consists of point type structures.
struct point {
   uint16_t x; 
   uint16_t y; 
   char name[16]; 
}; 

struct rect {
   struct point max; 
   struct point min;
   struct point all_points[10]; 
}; 
When defining a data structure (struct), you are not initializing any variables. Defining a struct simply creates a new data type that describes the structure. Later, in your program, you can create variables of this struct type and initialize them. The initialization happens when you assign values to the actual variables, not during the struct definition.

Data Structures in a C Program

To use the members of a data structure in a program, two new operators are needed:
Example:
// Defining the structures
struct point {
   uint16_t x;
   uint16_t y;
}; 

struct rect {
   struct point max;
   struct point min;
   struct point all_points[10];
}; 

// Variables and initialization
struct point min_pt;
min_pt.x = 100;
min_pt.y = 100;

struct rect box;
box.max.x = 320;
box.max.y = 200;

printf("%d,%d", box.max.x, box.max.y);

Declaration and Initialization

Syntactically, the declaration and initialization of a data structure can be done like any variable. In this, C language syntax is flexible, and there are several ways to initialize a structure.
// Variable definition immediately after structure declaration. Followed by initialization.
// here point1
struct point {
   uint16_t x;
   uint16_t y;
   char name[20];
} point1;

point1.x = 320;
point1.y = 200;
char midpoint[] = "Midpoint";
// Use string library functions!
strncpy(point1.name, midpoint, strlen(midpoint));

// As a separate variable
struct point point2;
point2.x = 320;
point2.y = 200;
strncpy(point2.name, midpoint, strlen(midpoint));

struct point point3 = { 320, 200, "Midpoint" };
Please, observe how when initializing a string inside a structure, we need to use the strncpy function instead of a direct assignment. In C, strings are handled as arrays of characters, and the = operator cannot be used to assign values to arrays after their declaration. Instead, strncpy allows us to copy the contents of one string (such as "Midpoint") into the character array (name in this case) defined in the structure.
There are other ways to declare and initialize data structures, but the ones above are the clearest.
Data structures can also be "assigned" to each other, in which case each member is copied.
min_pt = max_pt;

Pointers and Data Structures

When used as function parameters, as we explained before, data structures are typically passed by pointers because when we pass an argument to a function, a copy of the data structure will be created, and we want to save memory.
To reference a member of a data structure through a pointer, we use the -> operator. Notice that the asterisk (*) operator is not used, because the arrow operator replaces it!
p1_ptr->point.x;// Equivalent to (*p1_ptr).point.x;
Below is a code example demonstrating its usage.
#include <stdio.h>
#include <inttypes.h>

struct point {
	uint16_t x;
	uint16_t y;
} point = { 160, 100 };

int main() {

   struct point *p1_ptr = &point;

   printf("x=%d y=%d\n", point.x, point.y);
   printf("x=%d y=%d\n", p1_ptr->x, p1_ptr->y);

   // Assign to the y member via pointer
   p1_ptr->y = 103;
   printf("x=%d y=%d\n", point.x, point.y);

   return 0;
}
When referencing members of nested structures with a pointer, you need to be aware of which member the pointer is exactly pointing to.
#include <stdio.h>
#include <inttypes.h>

struct rect {
   struct point {
      uint16_t x;
      uint16_t y;
   } point;
} r = { .point.x = 101, .point.y = 102 };

int main() {

   struct rect *p1_ptr = &r;

   // Use the arrow operator for pointer_r's members
   // Use the dot operator for point's members
   printf("x=%d y=%d)\n", p1_ptr->point.x, p1_ptr->point.y);

   return 0;
}
Note! Also check the string library function memcpy, which can be used to copy entire blocks of memory. In other words, the content of a data structure can be copied to another by copying the memory area allocated for the structure using pointers. The proximity to the hardware allows us to use this method.
#include <stdio.h>
#include <string.h>  // For memcpy

// Definition of the struct point
struct point {
    uint16_t x;
    uint16_t y;
    char name[20];
};

int main() {
    // Declare and initialize p1
    struct point p1 = {320, 200, "Midpoint"};

    // Declare another struct to copy into
    struct point p2;

    // Copy the contents of p1 into p2 using memcpy
    memcpy(&p2, &p1, sizeof(struct point));

    // Print both structs to show they are now identical
    printf("p1: x = %d, y = %d, name = %s\n", p1.x, p1.y, p1.name);
    printf("p2: x = %d, y = %d, name = %s\n", p2.x, p2.y, p2.name);

    return 0;
}

Comparing Data Structures

Comparing data structures is perhaps most clearly done using a custom function, where each member's values are compared using pointers.
Example: The function returns a pointer to the data structure that has the larger value in the x member.
#include <stdio.h>
#include <inttypes.h>

struct point {
	uint16_t x;
	uint16_t y;
};

// Function that returns a pointer to the data structure
struct point *which_is_larger(struct point *a, struct point *b);

int main() {

   struct point first = { 320, 200}, second = { 321, 201 };
   struct point *larger = which_is_larger(&first, &second);
   printf("x=%d y=%d\n", larger->x, larger->y);

   return 0;
}

struct point *which_is_larger(struct point *a, struct point *b) {

  if (a->x > b->x) {
      return a;
   } else {
      return b;
   }
}
This pointer stuff is starting to get pretty advanced...

Embedded Data Structures

As stated earlier, a data structure is a convenient way to logically group related information into a cohesive package. In embedded systems, such a package could consist of sensor data collected at the same moment or perhaps the configuration parameters of a peripheral device or the transmission settings for wireless communication. Since sensor data is often analyzed as a time series, it is essential to know the exact time of measurement. Many applications also use data collected simultaneously from multiple sensors in the implementation of more intelligent services, where the timestamp ties together different types of data.
No lexer for alias 'csv-formatted' found
struct sensor_data { uint32_t timestamp; float temperature; float humidity; };
And, when data is neatly packaged in a structure, it can easily be converted into a CSV-formatted string, which can then be wirelessly transmitted to an IoT backend system.
When we get to programming the hardware in this course, you will notice that many of the pre-made software components in the device's operating system actually expect their configuration parameters in the form of a data structure.
Let's practice. Create a struct called sensor_data. Add the following member variables
  • accelerometer, single precision floating point number, three axes x,y,z in array
  • gyro, single precision floating point number, three axes x,y,z in array
  • temperature, single precision floating point number
  • humidity, single precision floating point number
  • light, 16-bit unsigned integer
  • pressure, single precision floating point number
It might be good idea to consider different axis as an array of values for the particular sensor. In your answer just give the created struct.

Warning: You have not logged in. You cannot answer.

Custom Data Types

In addition to data structures, the C language offers the possibility to introduce custom data types by renaming or grouping standardized/derived data types. These custom types behave exactly like their original data types. The keyword reserved for this in C is typedef.
For example:
// Replace the type uint16_t with the name Length
typedef uint16_t Length;
Length len = 100;
printf("%d\n", len);
In embedded systems (and sometimes in C programs on workstations), typedef is often used to write code that is portable (transferrable) from one hardware platform to another. Now, when a program is built around custom data types, its functionality remains consistent even when the code is moved from one platform to another. On different platforms, suitable corresponding data types can simply be introduced with typedef. This feature is especially useful when working with more complex data structures, but sometimes renaming standardized data types can also be beneficial. For example, uint16_t provides a guaranteed 16-bit unsigned integer on all platforms.
This is exactly how we will do it in device programming part of the course. We will use both pre-made data structures and renamed data types because a large part of the libraries and other pre-defined configurations in the manufacturer's programming environment are based on them. The reason for this is, of course, compatibility and ease of transferring code from the manufacturer's devices to others, ensuring that all code works as expected in the manufacturer's embedded operating system.

In Conclusion

You don't need to stress about the various ways to declare and initialize data structures presented in the material. It's enough to learn one method and use it, as all are equally correct.
?