⚙️

Learn C Programming for Robotics

A ground-up guide for high school students — master C and gain the skills to program real robots.

⚡ C Language 🤖 Robotics Focus 🔧 GCC Compiler 📚 10 Steps
📊 Your Progress 0 / 10 steps completed
1 2 3 4 5 6 7 8 9 10

🤔 Why Learn C for Robotics?

Blazing Fast
C compiles to native machine code — no interpreter in the way
🔧
Direct Hardware Control
Read/write memory, pins, and registers directly
🌍
Runs Everywhere
Arduino, Raspberry Pi, drones, industrial robots — all use C
🏭
Industry Standard
The most-used language in embedded systems and robotics
🧠
Teaches You How Computers Work
Memory, pointers, bits — C exposes the fundamentals
🚀
Foundation for Everything
C++, Python, Rust — all make more sense after learning C

🔍 C Decoder — What Does the Code Mean?

Every C symbol explained in plain English before you write a single line. Bookmark this!

📁#include — Import a header file
#include <stdio.h> #include <stdlib.h> #include "myrobot.h"
#include pastes the contents of another file into yours before compiling. <angle brackets> = system library. "quotes" = your own file. You need this to use printf, malloc, and most C functions.
Key headers: <stdio.h> print/read, <stdlib.h> memory, <stdint.h> int types, <math.h> math
🚪int main() — Program entry point
int main() { /* your code here */ return 0; }
Every C program starts by running main(). The int before it means main returns a number. return 0 at the end tells the OS "everything went fine". Non-zero means an error.
Without main(), your program won't compile — it's the required starting gate.
🏷️Data types — What kind of value?
int speed = 75; float angle = 45.5f; char mode = 'A'; uint8_t pwm = 200;
Unlike Python, C requires you to declare the type of every variable upfront. The type tells C how much memory to use. uint8_t (from stdint.h) is 1 byte, always — crucial in embedded systems where memory is tight.
Robotics tip: prefer int8_t, uint16_t, uint32_t over plain int for hardware code
🖨️printf() — Print output with format codes
printf("%d items\n", count); printf("%.2f cm\n", dist); printf("%s mode\n", name);
Format specifiers are placeholders that get replaced by real values: %d=integer, %f=float, %s=string, %c=char. \n = newline. %.2f = float with 2 decimal places.
printf("Speed: %d%%\n", 75) → prints: Speed: 75%
📌* and & — Pointers and addresses
int speed = 75; int* ptr = &speed; // & = address *ptr = 100; // * = dereference printf("%d", speed); // 100
& gets the memory address of a variable. * declares a pointer (stores an address) or dereferences one (reads/writes the value at that address). Pointers are C's superpower — and the thing beginners find hardest. Take your time here!
Used in robotics to pass large data structures without copying them in memory
➡️-> — Access struct member via pointer
Robot* r = &my_robot; r->speed = 75; // same as: (*r).speed = 75; // but cleaner
When you have a pointer to a struct, use -> (the "arrow operator") to access its fields. It's shorthand for dereferencing the pointer and then accessing the member. You'll use this constantly when working with robot state objects.
sensors->front_dist reads front_dist from a SensorData pointer
📦typedef struct — Create a custom type
typedef struct { float x, y; int heading; } Position;
A struct groups related variables under one name. typedef lets you use Position directly instead of writing struct Position every time. Perfect for modelling robot state, sensor readings, or motor configs as a single unit.
Position robot = {1.0f, 2.5f, 90}; — robot at (1, 2.5) facing 90°
🧠malloc / free — Dynamic memory
int* arr = malloc(10 * sizeof(int)); arr[0] = 42; free(arr); // always free!
malloc requests memory from the heap at runtime. You must free it when done — C won't do it for you. Forgetting = memory leak. In robotics, dynamic memory is used for sensor logs, message queues, and data buffers that change size at runtime.
Rule of thumb: every malloc must have exactly one matching free
🔢Bitwise operators — Manipulate individual bits
flags |= (1 << 3); // set bit 3 flags &= ~(1 << 3); // clear bit 3 flags ^= (1 << 2); // toggle bit 2
Hardware registers are controlled bit by bit. |=OR(set), &=AND(check/clear), ^=XOR(toggle), ~=NOT(flip all), <<=shift left. This is how you turn GPIO pins on/off at the register level — essential for embedded robotics.
1 << 3 = 0b00001000 = "only bit 3 is on"
📐#define — Preprocessor constant or macro
#define MAX_SPEED 255 #define TRIG_PIN 24 #define SQ(x) ((x)*(x))
#define is handled before compilation — the preprocessor does a find-and-replace on your code. It's great for naming magic numbers, pin numbers, and simple macros. Unlike variables, #define constants use no memory at runtime. No semicolon at the end!
#define LED_PIN 13 — everywhere you write LED_PIN, the compiler sees 13
📏sizeof() — How many bytes does this use?
sizeof(int) // 4 bytes sizeof(float) // 4 bytes sizeof(Robot) // size of struct malloc(10 * sizeof(int))
sizeof() returns the number of bytes a type or variable occupies. Always use it with malloc instead of hardcoding numbers — malloc(10 * sizeof(int)) is safe on every platform, while malloc(40) could be wrong on a different CPU.
Critical for robotics: microcontrollers often have 2KB of RAM — every byte counts!
🔒const — A value that must not change
const float PI = 3.14159f; const int TICKS = 512; void fn(const char* s);
const tells the compiler "this value must never change". Unlike #define, const variables have a type, are checked by the compiler, and can be inspected with a debugger. Using const in function parameters means "I promise not to modify what this pointer points to" — safe and clear.
void print_sensors(const SensorData* s) — read-only, won't corrupt your data
🌱
You don't need to memorise all of this! Every professional C programmer keeps a reference nearby — that's completely normal. The goal is to recognise these patterns when you see them in the steps below. C takes a little more setup than Python, but once it clicks, you'll understand how computers and robots actually work at a deep level. You've got this!

Your 10-Step C Programming Journey

1
Setup & Your First C Program
Install GCC, compile, and run Hello World
🔧
Unlike Python, C is a compiled language — you write code, run it through the compiler (GCC), and get a fast executable binary. This binary is what the robot's processor actually runs.
  • Linux/Raspberry Pi: sudo apt install gcc
  • Mac: brew install gcc or install Xcode Command Line Tools
  • Windows: Install WSL (Windows Subsystem for Linux) or MinGW
Every C program must have exactly one main() function — it's the front door the OS uses to start your program.
⚡ C Concept — Compiled vs Interpreted
Python is interpreted — a program reads and runs your code line by line each time. C is compiled — your code gets translated into machine instructions once, producing a binary that runs directly on the CPU with no middleman. That's why C can be 10–100× faster than Python for the same task, which matters enormously in time-critical robotics applications.
step1_hello.c
/* Step 1: Setup & Your First C Program
   Compile:  gcc step1_hello.c -o step1_hello
   Run:      ./step1_hello                      */

#include <stdio.h>   // Standard I/O — gives us printf()

int main() {
    printf("Hello, Robot World!\n");
    printf("C is compiled, fast, and runs on everything.\n");
    printf("Let's learn to build robots!\n");

    return 0;   // 0 = success; any other number = error
}
🧪 Quick Check — Step 1

What gcc flag specifies the name of the output executable?

What does `return 0` at the end of main() signal to the operating system?

2
Variables & Data Types
Declare types, use fixed-width integers for hardware
🏷️
C requires you to declare the type of every variable before using it. The type tells the compiler how much memory to allocate and what operations are valid.
  • int — whole numbers (4 bytes on most systems)
  • float — decimal numbers (~7 digits precision)
  • double — high-precision decimals (~15 digits)
  • char — a single character or 1-byte integer
  • uint8_t / int16_t — fixed-width types from <stdint.h> (essential for hardware!)
In robotics and embedded systems, always prefer fixed-width types like uint8_t over plain int — the size of int can vary between processors!
⚡ C Concept — Static Typing & sizeof()
C is statically typed — every variable's type is fixed at compile time and cannot change. This lets the compiler catch type errors before your robot even turns on. Use sizeof(type) to check how many bytes anything uses: sizeof(int) = 4, sizeof(char) = 1, sizeof(double) = 8. On a microcontroller with only 2KB of RAM, knowing this is critical!
step2_types.c
/* Step 2: Variables & Data Types
   Compile: gcc step2_types.c -o step2_types  */

#include <stdio.h>
#include <stdint.h>   // Fixed-width integer types

int main() {
    /* Basic types */
    int    motor_speed  = 75;         // whole number
    float  sensor_angle = 45.5f;      // decimal  (f suffix = float literal)
    double precise_dist = 3.14159265; // high-precision decimal
    char   drive_mode   = 'A';         // single character

    /* Fixed-width types — use these for hardware code! */
    uint8_t  pwm_duty   = 200;    // 0–255       (1 byte, unsigned)
    int16_t  encoder    = -1024;  // -32768–32767 (2 bytes, signed)
    uint32_t timestamp  = 0;      // 0–4 billion (4 bytes, unsigned)

    /* Print them — notice the format specifiers */
    printf("Motor speed: %d%%\n",     motor_speed);
    printf("Sensor angle: %.1f deg\n", sensor_angle);
    printf("Drive mode: %c\n",         drive_mode);
    printf("PWM duty: %u\n",           pwm_duty);
    printf("Encoder ticks: %d\n",      encoder);

    /* Check sizes — important on microcontrollers! */
    printf("\nsizeof(int)=%zu  sizeof(float)=%zu  sizeof(uint8_t)=%zu\n",
           sizeof(int), sizeof(float), sizeof(uint8_t));

    return 0;
}
🧪 Quick Check — Step 2

Which type is best for a PWM duty-cycle value that must be 0–255 and always exactly 1 byte?

What operator tells you how many bytes a type or variable occupies?

3
Operators & Bitwise Operations
Math, comparisons, and hardware-level bit manipulation
🔢
C has all the standard math operators, plus bitwise operators that manipulate individual bits. Bitwise operations are essential for robotics — hardware registers, GPIO pins, sensor flags, and communication protocols all work at the bit level.
  • Arithmetic: + - * / % (% = remainder/modulo)
  • Comparison: == != < > <= >= (returns 0 or 1)
  • Logical: && || ! (AND, OR, NOT)
  • Bitwise: & | ^ ~ << >> (operate on individual bits)
C has no true boolean type (until C99's stdbool.h). Zero means false, any non-zero value means true — a classic C gotcha!
⚡ C Concept — Bitwise Operators for Hardware
Hardware chips use registers — blocks of bits where each bit controls one feature. For example, bit 3 of a register might mean "motor A is enabled". flags |= (1 << 3) sets bit 3 without touching any other bits. flags &= ~(1 << 3) clears it. flags ^= (1 << 3) toggles it. This is the exact pattern used in every Arduino and embedded C driver ever written.
step3_operators.c
/* Step 3: Operators & Bitwise Operations
   Compile: gcc step3_operators.c -o step3_operators  */

#include <stdio.h>
#include <stdint.h>

int main() {
    /* ── Arithmetic ────────────────────────────────── */
    int speed = 100;
    printf("Half speed : %d\n", speed / 2);   // 50
    printf("Remainder  : %d\n", speed % 3);   // 1
    speed++;                                  // increment (speed = 101)
    speed -= 10;                             // speed = 91

    /* ── Comparison ────────────────────────────────── */
    int is_fast = (speed > 50);   // 1 = true
    int is_zero = (speed == 0);   // 0 = false
    printf("is_fast=%d  is_zero=%d\n", is_fast, is_zero);

    /* ── Bitwise — GPIO / hardware registers ──────── */
    uint8_t flags = 0b00000000;   // all bits off

    flags |= (1 << 3);            // SET   bit 3 → 0b00001000
    printf("After set    : 0x%02X\n", flags);

    flags &= ~(1 << 3);           // CLEAR bit 3 → 0b00000000
    printf("After clear  : 0x%02X\n", flags);

    flags ^= (1 << 2);            // TOGGLE bit 2 → 0b00000100
    printf("After toggle : 0x%02X\n", flags);

    /* Check if a specific bit is set */
    int bit2_on = (flags >> 2) & 1;
    printf("Bit 2 is on : %d\n", bit2_on);

    return 0;
}
🧪 Quick Check — Step 3

What does `flags |= (1 << 3)` do to the variable flags?

What does the % (modulo) operator compute?

4
Control Flow
if / else, switch / case, and the ternary operator
🔀
Control flow lets your program make decisions. A robot constantly evaluates its environment and chooses a response — that logic is built from these constructs.
  • if / else if / else — branch on any boolean condition
  • switch / case — clean way to handle a fixed set of states (perfect for robot state machines!)
  • Ternary cond ? a : b — compact single-line if/else
The switch / case pattern is ideal for robot state machines — IDLE, DRIVING, TURNING, AVOIDING, ERROR are all clean case labels.
⚡ C Concept — Don't Forget break in switch!
In C, switch cases fall through to the next case unless you write break. This is different from Python and a very common beginner bug. Without break at the end of case 1, the code will keep running into case 2, which is almost never what you want. Always add break (or return) to every case.
step4_control.c
/* Step 4: Control Flow
   Compile: gcc step4_control.c -o step4_control  */

#include <stdio.h>

int main() {
    /* ── if / else if / else ───────────────────────── */
    int distance = 15;   // cm

    if (distance > 30) {
        printf("✅ Path clear — moving forward\n");
    } else if (distance > 10) {
        printf("⚠️  Obstacle nearby — slowing down\n");
    } else {
        printf("🛑 Too close! Stopping.\n");
    }

    /* ── switch / case — robot state machine ───────── */
    int state = 2;

    switch (state) {
        case 0:  printf("State: IDLE\n");      break;
        case 1:  printf("State: DRIVING\n");    break;
        case 2:  printf("State: AVOIDING\n");   break;
        case 3:  printf("State: CHARGING\n");    break;
        default: printf("State: UNKNOWN\n");     break;
    }

    /* ── Ternary: cond ? value_if_true : value_if_false */
    int  speed   = 75;
    char* status = (speed > 50) ? "fast" : "slow";
    printf("Robot is moving: %s\n", status);

    return 0;
}
🧪 Quick Check — Step 4

What happens if you forget `break` at the end of a switch case in C?

When x = 3, what does the expression `x > 5 ? 'big' : 'small'` evaluate to?

5
Loops
for, while, do-while — and the infinite control loop
🔁
Loops let your program repeat actions — essential for reading sensors continuously, sweeping servo motors, or running the main robot control loop forever.
  • for — best when you know how many times to repeat
  • while — repeats while a condition is true
  • do-while — always runs at least once, then checks the condition
  • break — immediately exits the loop
  • continue — skip to the next iteration
Real robot programs use while(1) as the main control loop — it runs forever until you cut power. This is standard in embedded C!
⚡ C Concept — while(1) is the Robot Heartbeat
In embedded systems, while(1) { ... } is the main control loop — it reads sensors, makes decisions, and drives motors on every iteration. Unlike application software that runs and exits, robots need to run continuously. On microcontrollers there's no OS to return to, so the infinite loop is intentional and correct. The loop frequency (how fast it runs) is often carefully tuned for the application.
step5_loops.c
/* Step 5: Loops
   Compile: gcc step5_loops.c -o step5_loops  */

#include <stdio.h>

int main() {
    /* ── for loop — count from 0 to 4 ─────────────── */
    printf("for loop:\n");
    for (int i = 0; i < 5; i++) {
        printf("  Sensor reading %d\n", i);
    }

    /* ── while loop — run until condition is false ─── */
    printf("while loop:\n");
    int distance = 50;
    while (distance > 20) {
        printf("  Moving... distance = %d cm\n", distance);
        distance -= 8;
    }
    printf("  Stopped! Distance = %d cm\n", distance);

    /* ── do-while — runs body FIRST, checks after ─── */
    printf("do-while loop:\n");
    int attempts = 0;
    do {
        printf("  Attempt %d\n", ++attempts);
    } while (attempts < 3);

    /* ── break and continue ─────────────────────────── */
    printf("break/continue:\n");
    for (int i = 0; i < 10; i++) {
        if (i == 3) continue;   // skip i=3
        if (i == 6) break;      // stop at i=6
        printf("  i = %d\n", i);
    }

    /* ── Main robot control loop (simulation) ───────── */
    printf("\nSimulating robot loop (5 ticks):\n");
    int tick = 0;
    while (1) {                  // runs forever on real hardware
        printf("  [tick %d] read sensors → decide → drive\n", tick++);
        if (tick >= 5) break;   // simulation exit only
    }

    return 0;
}
🧪 Quick Check — Step 5

What does `while(1)` do in C?

What keyword immediately exits the innermost loop or switch?

6
Functions & Prototypes
Write reusable code blocks and declare them properly
🧩
Functions are the building blocks of well-organised C programs. Each robot behaviour (drive, turn, sense, log) should be its own function. C requires you to tell the compiler about a function before you use it — this is called a prototype.
  • Return type — what type the function gives back (void = nothing)
  • Parameters — inputs the function needs, with their types
  • Prototype — a declaration before main() so the compiler knows the function exists
Use void as the return type when a function just does an action and doesn't need to send a value back.
⚡ C Concept — Prototypes & Header Files
In real projects, function prototypes live in .h header files. The actual code lives in .c files. Other files #include the header to use your functions without seeing their implementation. This is how all large robot codebases (ROS, ArduPilot, etc.) are organised — headers define the interface, source files define the behaviour.
step6_functions.c
/* Step 6: Functions & Prototypes
   Compile: gcc step6_functions.c -o step6_functions  */

#include <stdio.h>

/* ── Prototypes (declarations before main) ─────────
   The compiler needs to know the signature before use.  */
float calc_speed(int ticks, float seconds);
void  print_status(const char* label, float value);
int   clamp(int value, int min, int max);

int main() {
    float speed = calc_speed(512, 0.5f);
    print_status("Wheel speed (cm/s)", speed);

    int raw_input = 300;
    int safe_speed = clamp(raw_input, 0, 255);
    printf("Raw: %d  Clamped: %d\n", raw_input, safe_speed);

    return 0;
}

/* ── Function definitions (after main is fine) ───── */

float calc_speed(int ticks, float seconds) {
    float wheel_circumference = 20.1f;    // cm (6.4 cm diameter)
    float rotations = ticks / 512.0f;    // 512 encoder ticks per revolution
    return (rotations * wheel_circumference) / seconds;
}

void print_status(const char* label, float value) {
    printf("[STATUS] %s: %.2f\n", label, value);
}

int clamp(int value, int min, int max) {
    if (value < min) return min;
    if (value > max) return max;
    return value;
}
🧪 Quick Check — Step 6

What is a function prototype in C?

What return type means a function performs an action and returns nothing?

7
Arrays & Strings
Collections of values, and how C handles text
📋
Arrays store multiple values of the same type in a contiguous block of memory. They're used heavily in robotics for sensor buffers, waypoint lists, lookup tables, and PWM duty-cycle maps.
  • Arrays start at index 0 — the last valid index is length - 1
  • C strings are just char arrays ending with a null terminator '\0'
  • There is no bounds checking — going past the end is a dangerous bug called a buffer overflow!
Going out of bounds (accessing arr[10] when the array has 5 elements) is undefined behavior in C — it can corrupt memory silently. Always track your array sizes!
⚡ C Concept — Strings Are Just Char Arrays
C has no built-in string type. A string is a char array where the last element is '\0' (the null character, value 0) — this marks the end. strlen() counts characters up to (but not including) that null terminator. Functions like strcpy and strcat from <string.h> always write the null terminator for you — but you must ensure the destination array is large enough. Buffer overflows in C strings are one of the most common security vulnerabilities in history.
step7_arrays.c
/* Step 7: Arrays & Strings
   Compile: gcc step7_arrays.c -o step7_arrays  */

#include <stdio.h>
#include <string.h>   // strlen, strcpy, strcat, snprintf

int main() {
    /* ── Float array — sensor distance readings ─────── */
    float distances[5] = { 25.0f, 18.3f, 42.1f, 8.7f, 31.0f };
    int   count = 5;

    float min_dist = distances[0];
    for (int i = 1; i < count; i++) {
        if (distances[i] < min_dist)
            min_dist = distances[i];
    }
    printf("Closest obstacle: %.1f cm\n", min_dist);

    /* ── 2D array — motor speed table ───────────────── */
    int speed_table[3][2] = {
        { 100, 100 },   // forward
        {  60, -60 },  // turn left
        { -60,  60 }   // turn right
    };
    printf("Forward:    L=%d R=%d\n", speed_table[0][0], speed_table[0][1]);
    printf("Turn left:  L=%d R=%d\n", speed_table[1][0], speed_table[1][1]);

    /* ── C strings (char arrays) ─────────────────────── */
    char robot_name[32] = "Rover-01";
    printf("Name: %s  Length: %zu\n", robot_name, strlen(robot_name));

    char full_name[64];
    strcpy(full_name, robot_name);         // copy
    strcat(full_name, "-Alpha");           // append
    printf("Full name: %s\n", full_name);

    /* snprintf — safer than sprintf, prevents overflow */
    char log_msg[64];
    snprintf(log_msg, sizeof(log_msg),
             "Robot %s at %.1f cm", robot_name, min_dist);
    printf("%s\n", log_msg);

    return 0;
}
🧪 Quick Check — Step 7

What is the last valid index of `int arr[5]`?

What special character marks the end of a C string?

8
Pointers
C's most powerful (and most feared) feature
📌
A pointer is a variable that stores a memory address instead of a value. They're how C achieves "pass by reference" — letting functions modify the caller's variables, handle large data structures without copying them, and map directly to hardware registers.
  • &x — the address of variable x
  • int* p — declare a pointer to an int
  • *pdereference: read or write the value at the address
The rule: & gives you the address, * gets you the value. Read int* p as "p is a pointer to an int."
⚡ C Concept — Why Pointers Matter for Robotics
On a microcontroller, hardware registers (GPIO, PWM, timers) are just specific memory addresses. Writing to address 0x40021000 might turn on an LED. Pointers are how C accesses these addresses directly — no operating system needed. At a higher level, passing a pointer to a large sensor struct is much faster than copying the whole struct into a function — critical when your control loop must run in microseconds.
step8_pointers.c
/* Step 8: Pointers
   Compile: gcc step8_pointers.c -o step8_pointers  */

#include <stdio.h>

/* Pass by pointer — allows function to modify the caller's variable */
void set_speed(int* speed, int value) {
    *speed = value;       // write through the pointer
}

void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    /* ── Basic pointer mechanics ─────────────────────── */
    int  motor_speed = 0;
    int* ptr         = &motor_speed;   // ptr holds the address of motor_speed

    printf("Address : %p\n",  (void*)&motor_speed);
    printf("Value   : %d\n",  motor_speed);

    set_speed(&motor_speed, 75);    // pass address so function can modify it
    printf("After set_speed: %d\n", motor_speed);

    *ptr = 100;                      // write directly via pointer
    printf("After *ptr=100: %d\n", motor_speed);

    /* ── Pointers and arrays ─────────────────────────── */
    int  readings[4] = { 10, 20, 30, 40 };
    int* p            = readings;       // array name IS a pointer to element 0

    for (int i = 0; i < 4; i++) {
        printf("readings[%d] = %d  (via pointer: %d)\n",
               i, readings[i], *(p + i));
    }

    /* ── Swap demo ───────────────────────────────────── */
    int left = 60, right = 80;
    printf("Before swap: left=%d right=%d\n", left, right);
    swap(&left, &right);
    printf("After swap : left=%d right=%d\n", left, right);

    return 0;
}
🧪 Quick Check — Step 8

What does `&x` return when x is an int variable?

Given `int* p = &x;` — what does `*p = 99` do?

9
Structs & Custom Types
Group related data — model your robot's state
📦
A struct groups related variables under one name, creating a custom data type. Instead of passing 8 separate sensor variables into every function, you pass one SensorData struct. This mirrors how real robotics frameworks model the world.
  • struct.member — access a field when you have the struct directly
  • ptr->member — access a field through a pointer (most common in practice)
  • typedef — give the struct a clean type name so you don't write struct X everywhere
Model your robot as a collection of structs: SensorData, MotorState, Position, PIDController. Clean structs = clean code.
⚡ C Concept — Structs Enable State Machines
Real robots are implemented as state machines — a current state (IDLE, DRIVING, AVOIDING) plus data about that state. A struct holds all of this together: the state enum, position, sensor readings, and target coordinates all in one place. When you pass a Robot* pointer to every function, every part of your code can read and update the robot's world model consistently — a fundamental pattern in robotics software design.
step9_structs.c
/* Step 9: Structs & Custom Types
   Compile: gcc step9_structs.c -o step9_structs  */

#include <stdio.h>

/* ── Define custom types for robot components ───────── */

typedef struct {
    float front, left, right;   // distances in cm
    int   battery_pct;          // 0–100
} SensorData;

typedef struct {
    int left_speed;    // -255 to 255
    int right_speed;   // -255 to 255
} MotorState;

typedef enum {
    STATE_IDLE, STATE_DRIVING, STATE_AVOIDING, STATE_CHARGING
} RobotState;

typedef struct {
    SensorData  sensors;
    MotorState  motors;
    RobotState  state;
    float       x, y;     // position
} Robot;

/* ── Functions that operate on robot state ──────────── */
void print_sensors(const SensorData* s) {
    printf("Sensors | Front:%.1f Left:%.1f Right:%.1f Batt:%d%%\n",
           s->front, s->left, s->right, s->battery_pct);
}

void update_motors(Robot* robot, int l, int r) {
    robot->motors.left_speed  = l;
    robot->motors.right_speed = r;
    printf("Motors set → L:%d R:%d\n", l, r);
}

int main() {
    Robot rover = {
        .sensors = { 42.0f, 15.3f, 88.7f, 87 },
        .motors  = { 0, 0 },
        .state   = STATE_IDLE,
        .x = 0.0f, .y = 0.0f
    };

    print_sensors(&rover.sensors);
    printf("State: %d  Position: (%.1f, %.1f)\n",
           rover.state, rover.x, rover.y);

    rover.state = STATE_DRIVING;
    update_motors(&rover, 200, 200);

    /* Arrow operator: rover.sensors is a struct, not a pointer,
       so we use dot (.) not arrow (->) here */
    printf("Front distance: %.1f cm\n", rover.sensors.front);

    return 0;
}
🧪 Quick Check — Step 9

You have `Robot* r`. How do you access its `speed` field?

What does `typedef struct { float x; } Point;` let you write instead of `struct Point p;`?

10
Memory Management & Complete Program
malloc / free, and a full robot simulation in C
🏆
C gives you manual control of memory. This is one of its greatest strengths (and the source of many bugs). Understanding the stack vs heap is fundamental to writing reliable robot software.
  • Stack — automatic, fast, limited size. Local variables live here.
  • Heap — manual, flexible, large. malloc() allocates here.
  • malloc(n) — request n bytes from the heap. Returns a pointer (or NULL on failure).
  • free(ptr) — release heap memory. Every malloc must have exactly one free.
Always check that malloc didn't return NULL before using the pointer — on embedded systems with limited RAM, allocation can fail!
⚡ C Concept — Memory Leaks & Safe Patterns
A memory leak happens when you malloc but never free. The memory stays claimed until the program ends. For a robot running 24/7, even a small leak will eventually exhaust all RAM and crash the system. A double free (freeing the same pointer twice) corrupts the heap and causes unpredictable crashes. The safe pattern: set a pointer to NULL immediately after freeing it — then an accidental second free is a no-op instead of a crash.
step10_complete.c
/* Step 10: Memory Management & Complete Robot Program
   Compile: gcc step10_complete.c -o step10_complete -lm  */

#include <stdio.h>
#include <stdlib.h>   // malloc, free, exit
#include <string.h>   // memset
#include <math.h>     // sqrt (compile with -lm)

#define MAX_LOG      64
#define SAFE_DIST_CM 20.0f

/* ── Types ───────────────────────────────────────────── */
typedef struct { float front, left, right; int batt; } Sensors;
typedef struct { int left, right; }                       Motors;
typedef enum   { IDLE, DRIVING, AVOIDING }                 State;

typedef struct {
    float* readings;    // dynamic array on the heap
    int    count;
    int    capacity;
} SensorLog;

/* ── SensorLog helpers ───────────────────────────────── */
SensorLog* log_create(int capacity) {
    SensorLog* log = (SensorLog*)malloc(sizeof(SensorLog));
    if (!log) { fprintf(stderr, "Allocation failed!\n"); exit(1); }
    log->readings = (float*)malloc(sizeof(float) * capacity);
    if (!log->readings) { free(log); exit(1); }
    log->count = 0; log->capacity = capacity;
    return log;
}

void log_add(SensorLog* log, float v) {
    if (log->count < log->capacity)
        log->readings[log->count++] = v;
}

float log_average(const SensorLog* log) {
    if (log->count == 0) return 0.0f;
    float sum = 0.0f;
    for (int i = 0; i < log->count; i++) sum += log->readings[i];
    return sum / log->count;
}

void log_free(SensorLog* log) {
    if (!log) return;
    free(log->readings); log->readings = NULL;  // nullify after free!
    free(log);           log          = NULL;
}

/* ── Robot decision logic ────────────────────────────── */
void tick(Sensors* s, Motors* m, State* state, SensorLog* log) {
    log_add(log, s->front);

    switch (*state) {
        case IDLE:
            printf("  [IDLE]    Waiting for mission...\n");
            *state = DRIVING; break;
        case DRIVING:
            if (s->front > SAFE_DIST_CM) {
                m->left = m->right = 200;
                printf("  [DRIVING] Forward. Front=%.1fcm\n", s->front);
            } else {
                m->left = m->right = 0;
                *state = AVOIDING;
                printf("  [DRIVING] Obstacle! → AVOIDING\n");
            }
            break;
        case AVOIDING:
            m->left = -100; m->right = 100;   // spin right
            printf("  [AVOIDING] Turning. Front=%.1fcm\n", s->front);
            if (s->front > SAFE_DIST_CM) *state = DRIVING;
            break;
    }
}

int main() {
    printf("=== Robot Simulator (C) ===\n\n");

    Sensors  s = { 50.0f, 30.0f, 40.0f, 95 };
    Motors   m = { 0, 0 };
    State    state = IDLE;
    SensorLog* log = log_create(MAX_LOG);

    /* Simulate 6 ticks with a changing environment */
    float sim_front[] = { 50.0f, 40.0f, 15.0f, 10.0f, 35.0f, 60.0f };
    for (int t = 0; t < 6; t++) {
        s.front = sim_front[t];
        tick(&s, &m, &state, log);
    }

    printf("\n--- Session Summary ---\n");
    printf("Readings logged : %d\n", log->count);
    printf("Average distance: %.2f cm\n", log_average(log));

    log_free(log);   // free heap memory
    printf("Memory freed. Program complete.\n");
    return 0;
}
🧪 Quick Check — Step 10

What must you call for every successful malloc() call?

What is a memory leak?