Embedded Software Applications | Andrew Nicastro

This portfolio showcases a collection of embedded C programming projects that are focused on industrial control and real-time systems. Projects include PID controller simulations, VFD emulators, sensor and actuator integration, advanced debugging and fault simulation, and commissioning file systems. Each project demonstrates practical embedded software engineering techniques and concepts applied to real-world industrial automation challenges.

C Embedded Controls Projects

PID Controller Simulation

A C program that simulates a PID (Proportional-Integral-Derivative) controller for motor speed control. Demonstrates embedded control logic, floating-point calculations, and simulation of sensors/actuators in C.

PID Controller Simulation Screenshot
C Control Systems Real-time Simulation GCC
View Project

VFD Emulator

A C program that emulates the operation of a Variable Frequency Drive for motor speed control. Demonstrates industrial drive control concepts, state machines, and real-time simulation.

VFD Emulator Screenshot
C State Machines Industrial Control Motor Control
View Project

Sensor & Actuator Integration

A C program simulating an embedded control system that reads sensor inputs and controls actuators. Demonstrates hardware signal processing, ADC/DAC simulation, and real-time control logic.

Sensor & Actuator Integration Screenshot
C Hardware Interface I/O Systems Real-time Control
View Project

Debugging-Fault Simulation

A C program demonstrating advanced debugging techniques and fault simulation for embedded systems. Showcases comprehensive error handling, logging, state tracking, and recovery mechanisms.

Debugging-Fault Simulation Screenshot
C Error Handling Fault Simulation System Reliability
View Project

Excel Commissioning File System

A C program simulating a commissioning file system for embedded control systems. Demonstrates file I/O, CSV parsing, parameter validation, and data persistence commonly used in industrial applications.

Excel Commissioning File System Screenshot
C File I/O CSV Parsing Data Persistence
View Project

PID-Controlled Motor System Schematic

This schematic represents a PID-controlled motor system. The PID controller receives a desired speed input, called the setpoint, and compares it to the motor's actual speed from the feedback sensor. The PID calculates a control signal, which goes to the Variable Frequency Drive, or VFD, acting as the power amplifier to adjust the AC motor's voltage and frequency. The motor responds, and the sensor measures its speed, completing the feedback loop. Essentially, this shows how software and hardware interact to achieve precise motor control, which is what I demonstrated in my PID and VFD emulator projects. This schematic was made with KiCad.

PID-Controlled Motor System Schematic

Important C Concepts

Note: The content from all examples in this section was taken from geeksforgeeks.org.

Raw Pointers

A pointer is a variable that stores the memory address of another variable. Instead of holding a direct value, it holds the address where the value is stored in memory. It is the backbone of low-level memory manipulation in C. Accessing the pointer directly will just give us the address that is stored in the pointer. Here is an example of that:

Example 1: Basic Pointers
#include 
#include 

int main() {

    // Normal Variable
    int var = 10;

    // Pointer Variable ptr that
    // stores address of var
    int* ptr = &var;

    // Directly accessing ptr will
    // give us an address
    // This dereferences the pointer
    printf("%d", *ptr);

    return 0;
}

//here is the output without the '&' : 0x7fff67b3056c
Example 2: Sizeof Pointers
#include 

int main() {
    int *ptr1;
    char *ptr2;

    // Finding size using sizeof()
    printf("%zu\n", sizeof(ptr1));   //zu is used specifically for sizeof outputs
    printf("%zu", sizeof(ptr2));

    return 0;
}
Example 3: Null Pointers
/* Null pointers are those pointers that do not point to any memory location. They can be
created by assigning NULL value to a pointer. A pointer of any type can be assigned the
NULL value.
Null pointers are generally used to represent the absence of any address. This allows
us to check whether the pointer is pointing to any valid memory location by checking
if it is equal to NULL. */

#include 

int main() {
    // Null pointer
    int *ptr = NULL;

    return 0;
}
Example 4: Void Pointers
/* The void pointers in C are the pointers of type void. It means that they do not have
any associated data type. They are also called generic pointers as they can point to
any type and can be typecasted to any type. */

#include 

int main() {
    // Void pointer
    void *ptr;

    return 0;
}
Example 5: Wild Pointers
/* The wild pointers are pointers that have not been initialized with something yet.
These types of C-pointers can cause problems in our programs and can eventually
cause them to crash. If values are updated using wild pointers, they could cause
data abort or data corruption. */

int main() {

    // Wild Pointer
    int *ptr;

    return 0;
}
Example 6: Dangling Pointers
/* A pointer pointing to a memory location that has been deleted (or freed) is called a dangling pointer.
Such a situation can lead to unexpected behavior in the program and also serve as a source of bugs in C programs. */

#include 
#include 

int main() {

    // malloc asks the heap to give you enough memory to store one integer
    int* ptr = (int*)malloc(sizeof(int));
    
    // Here we can print the address of the un-initialized pointer, since it is a 
    // generic pointer it expects "void*"
    printf("%p\n", (void*)ptr);

    // After below free call, ptr becomes a dangling pointer
    free(ptr);
    printf("Memory freed\n");

    // removing Dangling Pointer
    ptr = NULL;

    return 0;
}
Example 7: Constant Pointers
/* In constant pointers, the memory address stored inside the pointer is constant
and cannot be modified once it is defined. It will always point to the same memory
address. */

#include 

int main() {

    int a = 90;
    int b = 50;

    // Creating a constant pointer
    int* const ptr = &a;
    
    // Trying to reassign it to b
    ptr = &b;

    return 0;
}

// OUTPUT

solution.c: In function ‘main’:
solution.c:11:9: error: assignment of read-only variable ‘ptr’
   11 |     ptr = &b;
      |  
Example 8: Function Pointer
/* A function pointer is a type of pointer that stores the address of a function,
allowing functions to be passed as arguments and invoked dynamically. It is useful
in techniques such as callback functions, event-driven programs. */

#include 

int add(int a, int b) {
    return a + b;
}

int main() {
  
    // Declare a function pointer that matches
  	// the signature of add() fuction
    int (*fptr)(int, int);

    // Assign address of add()
    fptr = &add;

    // Call the function via ptr
    printf("%d", fptr(10, 5));

    return 0;
}

// OUTPUT : 15
Example 9: Multilevel Pointers
/* In C, we can create multi-level pointers with any number of levels such as – ***ptr3,
****ptr4, ******ptr5 and so on. Most popular of them is double pointer
(pointer to pointer). It stores the memory address of another pointer.
Instead of pointing to a data value, they point to another pointer. */

#include 

int main() {
    int var = 10;
  
    // Pointer to int
    int *ptr1 = &var;
  
    // Pointer to pointer (double pointer)
    int **ptr2 = &ptr1;  

    // Accessing values using all three
    printf("var: %d\n", var);          
    printf("*ptr1: %d\n", *ptr1);
    printf("**ptr2: %d", **ptr2);

    return 0;
}

// OUTPUT 

var: 10
*ptr1: 10
**ptr2: 10
Example 10a: Pointers with Arrays - Part 1
#include 

int main() {
    int arr[5] = {10, 20, 30, 40, 50};

    // Pointer to the first element of the array
    int* ptr = arr;  // same as &arr[0]

    // Access elements using pointer arithmetic
    printf("First element: %d\n", *ptr);        // 10
    printf("Second element: %d\n", *(ptr + 1)); // 20
    printf("Third element: %d\n", *(ptr + 2));  // 30

    // You can also use the pointer like an array
    printf("Fourth element: %d\n", ptr[3]);     // 40
    printf("Fifth element: %d\n", ptr[4]);      // 50

    return 0;
}
Example 10b: Pointers with Arrays - Part 2
#include 

int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    int* ptr = arr;  // points to arr[0]

    // Increment pointer (move forward)
    printf("Start: %d\n", *ptr);   // 10
    ptr++;                         // move to arr[1]
    printf("After ++ : %d\n", *ptr); // 20

    // Decrement pointer (move backward)
    ptr--;                         // back to arr[0]
    printf("After -- : %d\n", *ptr); // 10

    // Add an offset
    ptr = ptr + 3;                 // move to arr[3]
    printf("ptr + 3: %d\n", *ptr);   // 40

    // Subtract an offset
    ptr = ptr - 2;                 // back to arr[1]
    printf("ptr - 2: %d\n", *ptr);   // 20

    return 0;
}

Arrow Operator

The operator -> in C is called the arrow operator (sometimes called the structure pointer operator). It automatically dereferences for you.

#include 

// Define a struct
struct Point {
    int x;
    int y;
};

int main() {
    struct Point p = {10, 20};   // Declare a struct variable
    struct Point* ptr = &p;      // Pointer to the struct

    // Access using the dot operator (direct variable)
    printf("Using dot: p.x = %d, p.y = %d\n", p.x, p.y);

    // Access using arrow operator (pointer to struct)
    printf("Using arrow: ptr->x = %d, ptr->y = %d\n", ptr->x, ptr->y);

    return 0;
}

Signed vs. Unsigned

Signed means the number can be negative or positive, allowing for a wider range of values including negative numbers. Unsigned means the number is only positive (including zero), which provides a larger positive range but cannot represent negative values. This distinction is crucial in C for data types like int, char, and others, affecting memory usage and potential overflow behavior.

#include 

int main() {
    signed int a = -5;     // signed allows negatives
    unsigned int b = 5;    // unsigned does NOT allow negatives

    printf("Signed a = %d\n", a);
    printf("Unsigned b = %u\n", b);

    // Danger zone: wrapping
    unsigned int c = 0;
    c = c - 1; // goes below zero, wraps around
    printf("Unsigned wrap-around: c = %u\n", c);

    return 0;
}

Restrict Keyword

The restrict keyword is a type qualifier that was introduced in the C99 standard. It is used to tell the compiler that a pointer is the only reference or access point to the memory it points to, allowing the compiler to make optimizations based on that information.

Example 1: Add Arrays
#include 

// Function that specifies its parameters as restricts
void add(int *restrict arr1, int *restrict arr2,
         int *restrict res, int n) {
    for (int i = 0; i < n; i++)
        res[i] = arr1[i] + arr2[i];
}

int main() {
    int arr1[] = {1, 2, 3, 4, 5};
    int arr2[] = {10, 20, 30, 40, 50};
    int res[5];
    int n = 5;

  	// Calling function add
    add(arr1, arr2, res, n);

    for (int i = 0; i < n; i++)
        printf("%d ", res[i]);

    return 0;
}

/*

Syntax of restrict
The syntax for declaring a pointer with the restrict keyword is as follows:

type* restrict name;

type: The data type of the pointer (e.g., int, char, float).
restrict: The keyword that marks the pointer as restricted.
name: The name of the pointer variable.

*/
Example 2: Multiply Arrays
#include 

void multiply_arrays(int *restrict arr1, int *restrict arr2, int *restrict result, int size) {
  
    // Loop through the arrays and multiply corresponding elements
    for (int i = 0; i < size; i++) {
      
        // Multiply and store the result in the result array
        result[i] = arr1[i] * arr2[i];  
    }
}

int main() {
  
    // Initialize two arrays with values
    int arr1[] = {1, 2, 3};
    int arr2[] = {4, 5, 6};
    int result[3];

    // Call the multiply_arrays function to multiply elements of arr1 and arr2
    multiply_arrays(arr1, arr2, result, 3);

    // Print the resulting array
    for (int i = 0; i < 3; i++) {
        printf("%d ", result[i]);
    }

    return 0;
}

Volatile Keyword

In C, the volatile keyword is used to inform the compiler that the value of a variable may change at any time, without any action being taken by the code in the program. It is particularly useful in embedded systems, signal handlers, and multi-threaded applications. In this article, we will learn how to use the volatile keyword in C.

Volatile Keyword in C
We can use the volatile keyword for different purposes like declaring some global variables, signal handlers, variables across shared threads, etc. When a variable is declared as volatile, it tells the compiler: The compiler must not assume the value of the variable remains constant between accesses, so it should read the value from memory every time it is used. The compiler must not reorder instructions in a way that changes the access order of the volatile variable.

#include 
#include 

// Volatile variable to be accessed by multiple threads
volatile int volVar = 0;

// Mutex for synchronization
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

// Function to increment the volatile variable
void* incValue(void* arg)
{
    for (int i = 0; i < 10; i++) {
        // Lock the mutex before accessing volVar
        pthread_mutex_lock(&mtx);
        volVar++;
        // Unlock the mutex after modifying volVar
        pthread_mutex_unlock(&mtx);
    }
    return NULL;
}

int main()
{
    pthread_t t1, t2;

// Create two threads to increment volVar
pthread_create(&t1, NULL, incValue, NULL);
pthread_create(&t2, NULL, incValue, NULL);

// Wait for both threads to finish
pthread_join(t1, NULL);
pthread_join(t2, NULL);

// Output the final value of volVar
printf("Final value of volVar: %d\n", volVar);

return 0;
}

/*

This program shows how to safely share a variable between threads in C. The variable volVar 
is marked volatile so the compiler doesn’t optimize its access since multiple threads may
change it. A mutex mtx is used so that only one thread can modify volVar at a time.
The function incValue() increments volVar ten times, locking and unlocking the mutex
around each increment to avoid race conditions. In main(), two threads are created that
both run incValue(). Each adds ten, so the final printed result is twenty. Without the
mutex, the threads could interfere with each other and the result would be wrong.
*/

Extern Keyword

In C, the extern keyword is used to declare a variable or a function whose definition is present in some other file. Basically, it extends the visibility of the variables and functions in C to multiple source files. Before going deeper into the topic, we need to know about a few terms: Declaration (a declaration only tells the compiler about the existence of an identifier and its type), Definition (in addition to the declaration, it also allocates the memory for that identifier), and Translation Unit (a single source file including all the header files used directly or indirectly in it). The usage syntax of extern is a multistep process. In the file where we want to use the variable/function, we have to provide extern declaration: extern var_type var_name; extern ret_type func_name (arg_types .... ).

First File
#include 

// Extern decalration that will prompt compiler to
// search for the variable ext_var in other files
extern int ext_var;
extern void func();

void printExt() {
  	printf("%d", ext_var);
}
int main() {
    
    printExt();
    
    printf("\n");
    
    func();
    
    return 0;
}
Second File (src.c)
#include 

void func() {
    printf("Hello");
}

// Definition of a variable
int ext_var = 22;

Malloc Keyword

Dynamic memory allocation techniques give programmer control of memory when to allocate, how much to allocate and when to de-allocate. Normal local variable defined in a function is stored in the stack memory. The limitations of such allocations are, size needs to known at compile time, we cannot change the size or delete the memory. With dynamic memory allocation, you allocate memory at runtime, giving you the ability to handle data of varying sizes. Dynamic resources are stored in the heap memory instead of the stack. The size of the array can be increased if more elements are to be inserted and decreased if less elements are inserted. The dynamically allocated memory stays there (if the programmer has not de-allocated it) even after the function call is over, so a function can return pointer to the allocated memory. In case of normal local variables/arrays, the memory/variables become invalid once the function call is over. malloc() The malloc() (stands for memory allocation) function is used to allocate a single block of contiguous memory on the heap at runtime. The memory allocated by malloc() is uninitialized, meaning it contains garbage values.

Example 1: Basic Malloc
/*
Assume that we want to create an array to store 5 integers. Since the size of int is 4 bytes, 
we need 5 * 4 bytes = 20 bytes of memory. This can be done as shown:
*/

#include 
#include 

int main() {
    int *ptr = (int *)malloc(20);
    
    // Populate the array
    for (int i = 0; i < 5; i++)
        ptr[i] = i + 1;
        
    // Print the array
    for (int i = 0; i < 5; i++)
        printf("%d ", ptr[i]);
    return 0;
}
Example 2: Malloc with sizeof and NULL Check
/*
In the previous malloc call, we hardcoded the number of bytes we need to store 5 integers. But
we know that the size of the integer in C depends on the architecture. So, it is better to 
use the sizeof operator to find the size of type you want to store.
Moreover, if there is no memory available, the malloc will fail and return NULL.
So, it is recommended to check for failure by comparing the ptr to NULL.

Syntax

malloc(size);
*/

#include 
#include 

int main() {
    int *ptr = (int *)malloc(sizeof(int) * 5);
    
    // Checking if failed or pass
    if (ptr == NULL) {
        printf("Allocation Failed");
        exit(0);
    }
    
    // Populate the array
    for (int i = 0; i < 5; i++)
        ptr[i] = i + 1;
        
    // Print the array
    for (int i = 0; i < 5; i++)
        printf("%d ", ptr[i]);
    return 0;
}

Calloc Keyword

The calloc() (stands for contiguous allocation) function is similar to malloc(), but it initializes the allocated memory to zero. It is used when you need memory with default zero values. Data can be inserted later.

Syntax

calloc(n, size);

#include 
#include 

int main() {
    int *ptr = (int *)calloc(5, sizeof(int));

    // Checking if failed or pass
    if (ptr == NULL) {
        printf("Allocation Failed");
        exit(0);
    }

    // No need to populate as already
    // initialized to 0

    // Print the array
    for (int i = 0; i < 5; i++)
        printf("%d ", ptr[i]);
    return 0;
}

Free Keyword

The free() function in C is used to deallocate memory that was previously allocated by malloc(), calloc(), or realloc(). It releases the allocated memory back to the system to avoid memory leaks and ensure efficient memory usage.

#include 

int main() {
    int *ptr = (int *)malloc(5 * sizeof(int));

    if (ptr == NULL) {
        return 1; // Allocation failed
    }

    // Use the allocated memory

    // Free the allocated memory
    free(ptr);

    return 0;
}

Realloc Keyword

The realloc() function is used to resize a previously allocated memory block. It allows you to change the size of an existing memory allocation without needing to free the old memory and allocate a new block. Suppose we initially allocate memory for 5 integers but later need to expand the array to hold 10 integers. We can use realloc() to resize the memory block. It is important to note that if realloc() fails and returns NULL, the original memory block is not freed, so you should not overwrite the original pointer until you've successfully allocated a new block. To prevent memory leaks, it's a good practice to handle the NULL return value carefully.

Example 1: Basic Realloc
#include 
#include 

int main() {
    int *ptr = (int *)malloc(5 * sizeof(int));

    // Reallocation
    int *temp = (int *)realloc(ptr, 10 * sizeof(int));

    // Only update the pointer if reallocation is successful
    if (temp == NULL)
        printf("Memory Reallocation Failed\n");
    else
        ptr = temp;

    return 0;
}
Example 2: Realloc with Shrinking
#include 
#include 

int main() {

    // Initially allocate memory for 5 integers
    int *ptr = (int *)malloc(5 * sizeof(int));

    // Check if allocation was successful
    if (ptr == NULL) {
        printf("Memory Allocation Failed\n");
        exit(0);
    }

    // Now, we need to store 8 elements so
    // Reallocate to store 8 integers
    ptr = (int *)realloc(ptr, 8 * sizeof(int));

    // Check if reallocation was successful
    if (ptr == NULL) {
        printf("Memory Reallocation Failed\n");
        exit(0);
    }

    // Assume we only use 5 elements now
    for (int i = 0; i < 5; i++) {
        ptr[i] = (i + 1) * 10;
    }

    // Shrink the array back to 5 elements
    ptr = (int *)realloc(ptr, 5 * sizeof(int));

    // Check if shrinking was successful
    if (ptr == NULL) {
        printf("Memory Reallocation Failed\n");
        exit(0);
    }

    for (int i = 0; i < 5; i++)
        printf("%d ", ptr[i]);

    // Finally, free the memory when done
    free(ptr);

    return 0;
}

Stack vs. Heap

Stack
Memory is fixed size and managed automatically.
Used for local variables and function calls.
Fast access, automatically cleaned up when function ends.

Heap
Memory is dynamic and manually managed.
Used for malloc/calloc/realloc.
Slower access, must free memory manually to avoid leaks.

#include 
#include 

void exampleFunction() {
    int stackVar = 10;  // Stored on the stack
    printf("Stack variable: %d\n", stackVar);
    // Automatically cleaned up when function ends
}

int main() {
    exampleFunction();

    // Heap allocation
    int *heapPtr = (int *)malloc(sizeof(int));
    if (heapPtr != NULL) {
        *heapPtr = 20;
        printf("Heap variable: %d\n", *heapPtr);
        free(heapPtr);  // Must manually free to avoid leak
    }

    return 0;
}

Preprocessors and Macros

Preprocessors are programs that process the source code before the actual compilation begins. They are not part of the compilation process but operate separately, allowing programmers to modify the code before compilation. It is the first step that the C source code goes through when being converted into an executable file. Main types of Preprocessor Directives are Macros, File Inclusion, Conditional Compilation and Other directives like #undef, #pragma, etc. Mainly these directives are used to replace a given section of C code with another C code. For example, if we write "#define PI 3.14", then PI is replaced with 3.14 by the preprocessor.

Example 1: Macros
/*
Macros are used to define constants or create functions that are substituted by the
preprocessor before the code is compiled. The two preprocessors #define and #undef are used
to create and remove macros in C.

#define token value
#undef token

where after preprocessing, the token will be expanded to its value in the program.

Example:

*/

#include 

// Macro Definition
#define LIMIT 5

int main(){
    for (int i = 0; i < LIMIT; i++) {
        printf("%d \n", i);
    }
    return 0;
}

// OUTPUT : 0 1 2 3 4

/*
A macro defined previously can be undefined using #undef preprocessor. For example, in the above code,
*/

// Undefine macro
#undef LIMIT
Example 2: Macros with Args
/*
Macros With Arguments
We can also pass arguments to macros. These macros work similarly to functions. For example,

#define foo(a, b) a + b
#define func(r) r * r

Let us understand this with a program:

*/

#include 

// macro with parameter
#define AREA(l, b) (l * b)

int main(){
    int a = 10, b = 5;

    // Finding area using above macro
    printf("%d", AREA(a, b));
    return 0;
}
Example 3: File Inclusion
/*
File inclusion allows you to include external files (header files, libraries, etc.) into the current program. This is typically done using the #include directive, which can include both system and user-defined files.

Syntax

There are two ways to include header files.

#include 
#include "filename"

The '<' and '>' brackets tell the compiler to look for the file in the standard directory while double quotes ( " " ) tell the compiler to search for the header file in the source file's directory.

Example:

*/

// Includes the standard I/O library
#include 

int main() {
    printf("Hello World");

    return 0;
}

Specifiers

Specifier Type Notes
Integer types
%dintSigned decimal
%iintSigned decimal (same as %d)
%uunsigned intUnsigned decimal
%ounsigned intOctal
%xunsigned intHexadecimal (lowercase)
%Xunsigned intHexadecimal (uppercase)
%ldlong intSigned long decimal
%luunsigned long intUnsigned long decimal
Floating point types
%ffloat / doubleDecimal notation
%efloat / doubleScientific notation (lowercase e)
%Efloat / doubleScientific notation (uppercase E)
%gfloat / double%f or %e, whichever is shorter
%Gfloat / double%f or %E, whichever is shorter
Character / string
%ccharSingle character
%schar*Null-terminated string
Pointer / address
%pvoid*Memory address (pointer)
Misc / special
%%N/APrints a literal %
// Finding size using sizeof()
printf("%zu\n", sizeof(ptr1)); // prints size of int pointer (usually 8 bytes on 64-bit)
printf("%zu", sizeof(ptr2));   // prints size of char pointer (usually also 8 bytes)

Struct vs. Union

Structs and unions are user-defined data types in C that group variables. A struct allocates separate memory for each member, allowing all members to be accessed independently. A union shares the same memory space for all members, so only one member can hold a value at a time. This example demonstrates how structs and unions differ in memory usage and access, using characters with either a personality string or a firmware version number.

In the character struct example, the union will only ever access one of its two members at once, demonstrating how memory is shared between the members and only one value is stored at a time.

#include 
#include 

typedef struct {
    int x;
    float y;
    char z;
} mystr;

typedef union {
    int x;
    float y;
    char z;
} myunion;



typedef struct {
    char *name;
    bool isRobot;
    
    union {
        char *personality;
        int firmware_version;
    };
} character;



void printCharacter(character* c){
    
    printf("Character: %s --", c -> name);
    
    if(c -> isRobot){
        printf("version %i", c -> firmware_version);
    }
    else {
         printf("%s", c -> personality);
    }
    printf("\n");
}





int main()
{
 
 character tarkus;
 character r2d2;
 
 tarkus.name = "Dark Iron Tarkus";
 tarkus.isRobot = false;
 tarkus.personality = "Strong and silent leader";
 
 r2d2.name = "R2";
 r2d2.isRobot = true;
 r2d2.firmware_version = 22;
 
 printCharacter(&tarkus);
 printCharacter(&r2d2);
 
    return 0;
}

C/C++ Reference Materials

C Reference

Comprehensive guide to C programming fundamentals, memory management, pointers, data structures, and embedded systems concepts. Essential reference for embedded software development.

C Embedded Systems Memory Management
View Repository

C++ Reference

Complete C++ reference covering object-oriented programming, STL, templates, and modern C++ features. Includes examples relevant to embedded systems and real-time applications.

C++ OOP STL Templates
View Repository