Embedded C track

Lesson 01 · Part 1: Foundations (shared — verified with PC gcc)

Getting Started with Embedded C and Keil μVision

Embedded C, the `while(1)` skeleton and the build/debug flow

Part 1: Foundations (shared — verified with PC gcc)10 min readFree lesson

Although C programs running on a PC and C programs running on a microcontroller (MCU) use the same language, the way of thinking is different. On a small chip without an operating system, display, or keyboard, the code has to directly handle the hardware. This tutorial, as the first step, teaches what Embedded C is and how Keil μVision, the tool we will use throughout all 24 lessons, builds, debugs, and simulates code.

We do not handle actual boards (8051, STM32) yet. Instead, we focus on writing pure C logic that works anywhere and understanding the workflow of μVision and the concept of the simulator. To ensure you can follow along without a board, all examples are prepared so they can also be verified on a PC using gcc.

Learning Objectives

  • Explain the characteristics of embedded systems and Embedded C in comparison with general PC programming.
  • Distinguish between the two branches of Keil μVision (C51 / MDK-ARM) and what chips each targets.
  • Understand the standard skeleton of an embedded program (main + while(1) infinite loop).
  • Follow the flow in μVision: create a project → build (F7) → Debug/Simulation (Ctrl+F5).
  • Build and run a pure logic example with gcc to verify its operation.

Target Environment

  • Compiler: Keil C51 (for 8051) or Keil MDK-ARM / Arm Compiler 6 (for STM32) — for this lesson, just confirm installation.
  • Device: Pure logic — PC + gcc or μVision simulator (actual board not required).
  • Verification: μVision simulator, and gcc(pc_test.c) on PC.

Key Concepts

1) Embedded Systems and Embedded C

An embedded system is a computer built into a machine to perform a specific function. Microwaves, washing machines, drones, and small chips (MCUs) inside car ECUs are all examples. The difference is clear compared to PC programming.

Category PC Program Embedded Program
Operating System Present (Windows/Linux) Often absent (bare metal)
Program Termination Ends when main returns Does not end and keeps running as while(1)
Input/Output Screen, keyboard, files GPIO pins, sensors, registers
Resources Several GB of RAM Several KB ~ several hundred KB of RAM
Debugging Console output LED toggle, UART, debugger

Embedded C uses the same syntax as standard C, but it differs in that you directly handle memory addresses, manipulate hardware registers at the bit level, and have very limited resources.

2) Keil μVision — C51 vs MDK-ARM

Keil μVision is an integrated development environment (IDE) for embedded development. It allows code editing, compiling, debugging, and simulation all in one interface. The internal compiler varies depending on which chip you use.

Tool Target Chip Compiler Covered in this track
Keil C51 8051 series (e.g., AT89C52) C51 6~12 lessons
Keil MDK-ARM Arm Cortex-M (e.g., STM32F103) Arm Compiler 6 13~20, 24 lessons

When installed, both share the same μVision IDE, but you need to install the 8051 device pack and the STM32 device pack separately to select the corresponding chip.

3) Standard Skeleton of an Embedded Program

A PC program terminates when main ends, but an embedded program must keep running while the power is on. Therefore, it almost always includes an infinite loop.

int main(void)
{
    /*1. Initialization: Set clock, pins, peripherals (only once)*/

    while (1) {
        /*2. Main body: Read sensors, decide, output ... repeat infinitely*/
    }
    /*Never reaches here*/
}

Before main, there is the startup code that we do not write ourselves. It sets up the stack, initializes global variables, and then jumps to main. Keil includes this startup file in the device pack, so we only need to write from main onward.

4) Build → Debug → Simulation Flow

There are two key shortcut keys in μVision.

  • F7 (Build / Rebuild): Compiles and links the source to create an executable image.
  • Ctrl+F5 (Start/Stop Debug Session): Starts the debugger. In simulator mode, it mimics chip operation without an actual board.

Once in the debug session, you can follow the code and visually check register and variable values using F11 (step over) and F5 (run continuously).

5) What "Verification with a simulator" means

This track checks results using the μVision built-in simulator without actual hardware. The simulator mimics CPU instructions and peripheral operations in software. Therefore, the results should be in a visible form.

  • LED example → Observe pin toggling in the Logic Analyzer window
  • UART example → Check output text in the Serial window
  • Timer example → Observe the counter value increasing in the Register window

This session is pure logic, so the same logic can be immediately checked on a PC using gcc + printf instead of a board.

Getting Started with μVision (Installation & Overview for this session)

This session is not tied to a specific device, so step-by-step guides for creating device-specific projects will be provided in Session 6 (8051) and Session 14 (STM32). Here, the goal is to prepare the tools and get familiar with the interface.

Compiler & IDE Download Paths

The μVision IDE comes included in the compiler packages below. Download the one corresponding to the chip you want to use.

Tool Purpose (covered in session) Download Path
Keil MDK-Arm Arm Cortex-M such as STM32 (Sessions 13–20, 24) https://www.keil.com/demo/eval/arm.htm
Keil C51 8051 series, e.g., AT89C52 (Sessions 6–12) https://www.keil.com/demo/eval/c51.htm
Complete product list MDK / C51 / C251 / C166 on a single page https://www.keil.com/download/product/
MDK Community Edition (Free) Non-commercial Free License Activation Guide https://www.keil.arm.com/mdk-community/
  • On the download page, after filling out a simple registration form (name, email, etc.), you can download the installation file.
  • Without a license, you can still build and simulate all examples in this track using the Lite/Evaluation Edition (there is a code size limit, but it is sufficient for beginner examples). If you need larger code, activate the above MDK Community Edition (non-commercial free).
  • μVision is Windows-only. Use the If-it-s-macOS/Linux virtual machine, or follow parts 1–5 on a PC with gcc.

Things to Do After Installation

  1. Download and install MDK-Arm (for STM32) and C51 (for 8051) from the table above. (Both share the same μVision IDE)
  2. Run μVision and install the 8051 packs and STM32F1 series packs from the Pack Installer.
  3. Browse the menu: Project (create project), Debug (debug with Start/Stop), View → Serial Windows (UART output), View → Analysis Windows → Logic Analyzer (pin waveform).
  4. Create an empty project and check if it builds with F7. (Detailed steps are in parts 6 and 14)

Don’t have the board or μVision yet? That’s fine. First run this part's examples using "Verify on PC" below. C syntax and logic are the same in any environment.

Examples

Example 1 — main.c: Mimicking an Embedded Skeleton on a PC

On a real board, the GPIO pin would turn the LED on and off instead of the led variable. Here, we use a variable instead of a pin, and output the toggle result to printf to get a sense that "the state changes in an infinite loop." The infinite loop is hard to observe on a PC, so it is limited to 5 iterations.

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

/*
 *Lesson 01 Example — Imitating the basic structure of an embedded program on a PC.
 *On an actual board, the led variable is connected to a GPIO pin (LED),
 *here it is simulated with a variable and the status is printed with printf.
 */
int main(void)
{
    uint8_t led = 0;   /*0 = off, 1 = on*/
    uint8_t tick;

    printf("Hello, Embedded C!\n");

    /*
     *The embedded main usually operates with a while(1) infinite loop.
     *On a PC, running endlessly is hard to observe, so it was limited to 5 times.
     */
    for (tick = 0; tick < 5; tick++) {
        led = (uint8_t)!led;                 /*LED toggle: 0<->1*/
        printf("tick %u: LED = %s\n",
               (unsigned)tick, led ? "ON" : "OFF");
    }

    return 0;
}

Example 2 — pc_test.c: Separating Pure Logic Functions for Verification

If the "decision" of toggling is taken out into the toggle() function, the logic itself can be verified as a unit without hardware. This is a method often used when writing embedded code, and is the first exercise in separating hardware-dependent parts from pure logic.

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

/*Pure logic: flips the LED state. Hardware independent → can be verified on a PC.*/
static uint8_t toggle(uint8_t state)
{
    return (uint8_t)!state;
}

int main(void)
{
    uint8_t led = 0;
    uint8_t i;

    for (i = 0; i < 5; i++) {
        led = toggle(led);
        printf("tick %u: LED = %s\n", (unsigned)i, led ? "ON" : "OFF");
    }

    return 0;
}

Verification on a PC

Since this is pure logic, you can check it directly without a board.

gcc pc_test.c -o pc_test && ./pc_test

Expected output (stdout):

tick 0: LED = ON
tick 1: LED = OFF
tick 2: LED = ON
tick 3: LED = OFF
tick 4: LED = ON

If main.c is built in the same way, Hello, Embedded C! will be appended to the first line in the output.

gcc main.c -o main && ./main
Hello, Embedded C!
tick 0: LED = ON
tick 1: LED = OFF
tick 2: LED = ON
tick 3: LED = OFF
tick 4: LED = ON

Checking with a Simulator (Reference)

In μVision, put main.c into an empty project and enter debug mode (Ctrl+F5), then add led and tick to the Watch window and execute line by line with F11 to observe the values changing 0↔1. (However, printf in this example requires UART retargeting settings in the simulator, so confirming text output is much simpler on PC gcc. UART output is officially covered in parts 11 and 19.)

Common Mistakes

Q. Why doesn’t the embedded main return? I wrote return 0;.

A. On a PC, when main finishes, the OS cleans up the process. However, there is no OS running on a bare-metal MCU. If main returns, there is nowhere to go, and it enters undefined behavior. So in reality, embedded main ends with while(1) and never returns. This example deliberately ends for PC observation.

Q. We can just use int, so why use something like uint8_t?

A. The size of int may differ for each MCU (16-bit on 8051), and registers have a fixed width. uint8_t/uint16_t/uint32_t have the width in their names, making portability and intent clear. Detailed discussions about data types are covered in Part 2.

Q. gcc works well, so is this really embedded learning?

A. The logic in this lesson (toggle, infinite loop, data types) is the foundation of C, independent of hardware. After quickly mastering this foundation on a PC, from Part 6 onwards, the same concepts are applied to actual chip registers. The method of this track is "pure logic on PC, hardware control on a simulator."

Q. Installing μVision is too heavy and complicated.

A. You don’t need to install it right away. Parts 1-5 (basics) can all be followed using gcc. Take your time to install it before starting 8051 practice in Part 6.

Summary

  • Embedded C is C that handles hardware directly without relying on OS or abundant resources.
  • Keil μVision is divided into C51 (8051) and MDK-ARM (STM32), and they share the same IDE.
  • The skeleton of an embedded program is Initialization + while(1) infinite loop, and main does not return a value.
  • The μVision flow is F7 (build) → Ctrl+F5 (Debug/Sim) → observe the results in the window.
  • Pure logic is quickly verified with gcc, and hardware control is checked with the simulator.

Try it yourself

Solve the assignment below. The reference solution is shown below.

Example files

The runnable files used in this lesson. Copy them into your own project and experiment.

src/start_keil/main.cC
#include <stdio.h>
#include <stdint.h>

/*
 *Lesson 01 Example — Imitating the basic structure of an embedded program on a PC.
 *On an actual board, the led variable is connected to a GPIO pin (LED),
 *here it is simulated with a variable and the status is printed with printf.
 */
int main(void)
{
    uint8_t led = 0;   /*0 = off, 1 = on*/
    uint8_t tick;

    printf("Hello, Embedded C!\n");

    /*
     *The embedded main usually operates with a while(1) infinite loop.
     *On a PC, running endlessly is hard to observe, so it was limited to 5 times.
     */
    for (tick = 0; tick < 5; tick++) {
        led = (uint8_t)!led;                 /*LED toggle: 0<->1*/
        printf("tick %u: LED = %s\n",
               (unsigned)tick, led ? "ON" : "OFF");
    }

    return 0;
}
src/start_keil/pc_test.cC
#include <stdio.h>
#include <stdint.h>

/*Pure logic: flips the LED state. Hardware independent → can be verified on a PC.*/
static uint8_t toggle(uint8_t state)
{
    return (uint8_t)!state;
}

int main(void)
{
    uint8_t led = 0;
    uint8_t i;

    for (i = 0; i < 5; i++) {
        led = toggle(led);
        printf("tick %u: LED = %s\n", (unsigned)i, led ? "ON" : "OFF");
    }

    return 0;
}

Assignment

This assignment is all pure logic (PC verifiable). You can solve and grade it using only gcc without a board or μVision.

gcc main.c -o prog && ./prog

Problem 1 — Blink Count and ON Count

  • File name: homework_01/main.c
  • Key concepts: while(1) skeleton, toggle, counting
  • Platform: Common

Requirements

  • Toggle the LED 8 times (tick 0~7) and print the state at each tick. (Initial state is OFF 0)
  • After 8 toggles, print the number of times the LED was ON on the last line.
  • The output format must match the "Expected Output" below character by character.

Expected output

tick 0: LED = ON
tick 1: LED = OFF
tick 2: LED = ON
tick 3: LED = OFF
tick 4: LED = ON
tick 5: LED = OFF
tick 6: LED = ON
tick 7: LED = OFF
ON count = 4

Hint

  • Toggling from the initial state 0 makes the first tick ON.
  • Increment a separate counter variable by 1 each time the LED is ON.
  • ON occurs on even ticks (0,2,4,6) out of 8, so a total of 4 times.

Problem 2 — Alternating Two LEDs

  • File name: homework_02/main.c
  • Key concepts: state variable, mutually exclusive output
  • Platform: Common

Requirements

  • Handle two LEDs (A and B). If A is ON, B is OFF; if A is OFF, B is ON, always maintaining opposite states.
  • Initial state: A=ON, B=OFF.
  • Print both states for 6 ticks, and at the end of each tick, toggle A (B is always the opposite of A).

Expected output

tick 0: A = ON , B = OFF
tick 1: A = OFF, B = ON
tick 2: A = ON , B = OFF
tick 3: A = OFF, B = ON
tick 4: A = ON , B = OFF
tick 5: A = OFF, B = ON

Hint

  • Do not manage B separately; calculating from A like b = !a; always guarantees the opposite state.
  • To align the output, either adjust the width and use %-3s like ON (with 1 space at the end) and OFF, or handle the branching directly.

Check your answers

After solving it yourself, compare it with the reference solution below. The solution files include comments on key points and common mistakes.

Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

homework_01/main.cC
#include <stdio.h>
#include <stdint.h>

/*
 *Answer 1 — Blink Count and ON Count
 *
 *Key Points:
 *- If toggled from the initial state 0, the first tick (0) becomes ON.
 *- Increases on_count by 1 each time the LED turns ON.
 *
 *Common mistake:
 *- Printing the state 'before' toggling causes it to shift by one step. This problem is
 *It is the order of 'printing that state.' (Toggle → Count → Print)
 *- Mistaking the counter for the number of toggles (8). What is counted is the number of times it was 'ON'.
 */
int main(void)
{
    uint8_t led = 0;        /*0 = off, 1 = on*/
    uint8_t on_count = 0;   /*The number of times the LED was ON*/
    uint8_t tick;

    for (tick = 0; tick < 8; tick++) {
        led = (uint8_t)!led;            /*First, toggle*/
        if (led) {
            on_count++;                 /*Count if ON*/
        }
        printf("tick %u: LED = %s\n",
               (unsigned)tick, led ? "ON" : "OFF");
    }

    printf("ON count = %u\n", (unsigned)on_count);

    return 0;
}
homework_02/main.cC
#include <stdio.h>
#include <stdint.h>

/*
 *Correct answer 2 — Two LEDs alternately blinking
 *
 *Key Points:
 *- Instead of managing B separately, calculate b = !a each time to be the opposite of A.
 *This is a method that fundamentally prevents the two states from being out of sync.
 *- Output alignment: A is left-aligned with a width of 3 using "% -3s" ("ON "/"OFF") to match the comma position.
 *B is at the end of the line, so no padding is needed.
 *
 *Common mistake:
 *- A bug where toggling both A and B separately sometimes results in the same state simultaneously.
 *- A case where missing alignment spaces causes mismatch with the expected output at the character level.
 */
int main(void)
{
    uint8_t a = 1;   /*Start: A = ON*/
    uint8_t b;
    uint8_t tick;

    for (tick = 0; tick < 6; tick++) {
        b = (uint8_t)!a;                /*B is always the opposite of A*/
        printf("tick %u: A = %-3s, B = %s\n",
               (unsigned)tick,
               a ? "ON" : "OFF",
               b ? "ON" : "OFF");
        a = (uint8_t)!a;                /*Toggle A for the next tick*/
    }

    return 0;
}

Learn this lesson with a mentor

Work through the Embedded C track on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now