PrajnaEdge
A curiosphere for curious minds who want to understand, experiment with, and experience technology.

Technology is a system of connections.

Modern technology is built from layers that continuously interact with one another. At the physical level, electronic devices transform electrical signals into digital information. Digital logic turns that information into computation, while processors, memory and communication interfaces provide the machinery needed to execute instructions and move data. As these components become part of embedded systems, they begin to interact with the physical world through sensors, controllers, actuators and real-time software.

But computation does not exist in isolation. Operating systems coordinate hardware and software, firmware gives specialized machines their behaviour, and communication protocols allow independent systems to exchange information. At the same time, machine learning is moving beyond the cloud into edge and on-device systems, where models must operate within real constraints such as memory, processing power, latency and energy consumption.

PrajnaEdge explores these connections as one continuous technology landscape — and takes them beyond explanation. From computing foundations and embedded systems to intelligent machines and edge AI, ideas can be understood, experimented with, and eventually turned into technology that can be experienced in the real world.

To continue exploring
Products

Technology, made tangible.

PrajnaEdge products and technology experiences are currently in development.
In Development

Playground

Experiment with intelligence beyond the cloud.

AI inference performed closer to where data is generated — optimizing model representation to reduce latency, memory footprint, and bandwidth requirements.
Cloud AI Coming Soon

Model Training & Representation

Explore how neural networks learn representations from data before compression and deployment. Future experiments will allow adjusting learning parameters—such as learning rate, epoch counts, and batch sizes—to observe loss trajectories and decision boundaries.

Edge AI Computer Vision

Image Classification

Can this image classifier maintain its intelligence while becoming small enough for the edge?

On-device AI AI-Thinker ESP32-CAM

On-device Image Classification

Run a fully quantized INT8 image-classification model directly on embedded hardware.

Edge AI Playground

Image Classification

Can this image classifier maintain its intelligence while becoming small enough for the edge?

Choose an image

Upload an image
Supports JPG, JPEG, PNG
This classifier recognizes only Apple, Banana, and Orange. Other objects may be incorrectly classified as one of these classes.

Choose the model

Model size
4.91 MiB
Largest activation
~625 KiB
Test accuracy
99.11%
Measured model accuracy
Your image is processed locally in your browser.
On-device AI · Inference

On-device Image Classification

Run a fully quantized INT8 image-classification model directly on embedded hardware.

This experiment investigates preparing a compact INT8 TensorFlow Lite model for direct inference on resource-constrained embedded hardware. The model uses the same CNN architecture as the Edge AI exploration, converted to a full INT8 representation to classify three classes: Apple, Banana, and Orange.

Model size
1.23 MiB
INT8 quantized weights
Model accuracy
99.11%
Measured test accuracy
Target hardware
AI-Thinker ESP32-CAM
Embedded microcontroller target

Explore the ideas, systems and connections that shape technology — choose any node to begin your journey.

PrajnaEdge Navigation Tree
Embedded Systems Tree
View Books →
PrajnaEdge Library

PrajnaEdge Library

Practical guides to embedded systems and engineering.

CAN Bus Debugging: From Symptoms to Root Cause Book Cover
Automotive · Embedded Protocol Debugging

CAN Bus Debugging: From Symptoms to Root Cause

  • Select
  • United States
  • United Kingdom
  • Germany
  • France
  • Spain
  • Italy
  • Netherlands
  • Japan
  • Brazil
  • Canada
  • Mexico
  • Australia
  • India

Edge AI Demonstrations

Deploying neural networks and intelligent decision loops on raw silicon targets.

Sort:
Bare Metal

Before main()

The hidden choreography that prepares raw silicon for your first line of C.

Bare MetalBootloaderLinker ScriptReset VectorC Runtime

1. The Myth of the Beginning

To the software engineer, the universe of an application begins with a familiar and comforting signature:

int main(void) { ... }

It is the genesis of our code, the entry point for our debuggers, and the boundary line where high-level state starts its execution. We write our routines under the quiet assumption that the machine starts here, waiting as a clean, blank slate for our first instruction to call it to action.

But this starting line is an artificial boundary. The CPU did not begin at main(), nor does it understand the high-level syntax of a C function. Long before the first line of your main loop runs, the microcontroller has already completed a massive journey. It has left its analog reset state, evaluated physical boot flags, routed through vendor ROMs, set up stack space, copied segments across physical memories, and configured clock frequencies. Before C can exist, the machine must build the world C assumes already exists.

main() is not the beginning of computation. It is the final handoff from hardware reality to software abstraction.

2. Hardware Wakes: The Reset State

When power climbs the capacitive rails of a microcontroller, or the physical reset line is toggled, digital logic does not instantly begin computing. The core starts in an architecture-defined reset state where pipelines are flushed, registers are set to default values, and interrupts are globally disabled.

We have seen this moment before. In The First Instruction, we traced how analog supervisors release the CPU to fetch its initial Stack Pointer and Reset Vector. Here, we step back to examine the branching boot systems that govern this transition.

How the processor finds its first executable byte depends on its silicon architecture. There is no single universal embedded boot sequence. While a simple microcontroller might immediately point its Program Counter (PC) to a fixed vector table in Flash, a complex SoC or application processor might boot into an immutable on-chip Boot ROM, check boot pins, or load custom bootloader binaries from external media.

3. The Boot Decision Tree

Rather than following one mandatory sequence, microcontrollers and SoCs branch into different boot configurations depending on vendor design, boot configurations, and product architecture. A simple MCU boots directly into application code, while a production-grade secure system routes through multiple stages of software validation.

THE JOURNEY TO MAIN(): DEVICE-DEPENDENT BOOT PATHS
POWER ON / RESET Boot Config / Pin Latches / Hardware Defaults Simpler MCU Path Bootloader-Based Path Load Vector Table Pointer Fetch Reset Vector (Code Start) Vendor Boot ROM (On-Chip ROM, Immutable) Product Bootloader (Custom Boot Stage, OTA) Select & Validate App Entry Startup Code (C Runtime Setup) main()
Optional boot stages split basic microcontrollers from high-reliability multi-stage systems.

4. The Boot ROM Sentinel

The first software executed by a processor is not necessarily the application — and it is not necessarily a user-written bootloader.

In many modern microcontrollers and SoC designs, the physical reset vector points directly into on-chip Boot ROM. This is a small, immutable block of memory mask-programmed into the silicon by the chip vendor during manufacturing. The code within it is permanent; it cannot be modified by firmware updates or device failures.

When the Boot ROM runs, it acts as a sentinel. It inspects the state of physical boot pins (latching external voltage levels to determine the boot source) or reads specific memory-mapped registers. If it detects a recovery condition—such as a specific pin pulled low or an invalid signature in the primary flash sector—it launches an internal serial bootloader interface, listening on UART, USB, or CAN for new firmware. If the system is healthy, the Boot ROM locates the secondary boot stage or application vector table and branches to it.

5. Second-Stage Product Bootloaders

While simple microcontrollers often execute their application directly from Flash, commercial products frequently introduce a second-stage, programmable product bootloader. Unlike the Boot ROM, this bootloader is stored in writeable Flash memory and can be updated in the field.

The responsibilities of this stage are critical for product reliability. It acts as a gatekeeper, validating the integrity of the application image using checksums or verifying its authenticity through cryptographic signatures. It manages the swap logic between dual-image banks (A/B partitioning) to ensure that if a wireless OTA update fails mid-transmission, the system can safely roll back to a previously known good firmware image. If validation succeeds, it initiates the handoff.

6. The Handoff of Control

When a bootloader transfers control to the application, it does not simply invoke the application's main() as if it were a local subroutine. If the bootloader called main() directly, the application would inherit a contaminated execution context. Bootloader-owned interrupts might still be active, the stack pointer could be misaligned, and peripherals would remain in modified states.

The handoff is a clean break. The bootloader must: disable all of its active interrupts, clear pending flags in the interrupt controller, set the main stack pointer to the application's starting stack address, relocate the Vector Table base address (e.g. by modifying the Vector Table Offset Register VTOR in Cortex-M architectures), and finally jump to the application's entry address. The application must start as if the bootloader had never been there.

7. The Application Startup Phase

Once control is transferred to the application's entry address—which is the Reset Handler pointed to by the application's vector table—the execution shifts into the startup code. This code, usually provided by the silicon vendor or toolchain and written in assembly or low-level C, builds the runtime environment step-by-step.

The startup sequence follows a deterministic sequence to construct the C language environment:

void Reset_Handler(void) { /* 1. Low-level initialization */ SystemInit(); /* 2. Copy initialized data from Flash to RAM */ copy_data_segment(); /* 3. Zero-initialize BSS memory */ zero_bss_segment(); /* 4. Call static constructors and runtime libraries */ __libc_init_array(); /* 5. Transfer control to the application entry */ main(); }

8. Constructing the Memory World

When power is first applied, the cells of volatile Static RAM (SRAM) settle into arbitrary, electrically noisy states. Yet, C language code operates under a strict promise: global and static variables initialized to a value (like int speed = 100;) must begin execution with that exact value, and uninitialized objects (like static int fault_count;) must start at zero.

Startup code constructs this expected memory layout by migrating data segments from non-volatile storage (Flash) to volatile memory (RAM). The linker script defines two distinct addresses for initialized variables: the Load Memory Address (LMA), where the initial values reside permanently in Flash, and the Virtual Memory Address (VMA), where the variables will reside in RAM during runtime. The startup routine runs a copy loop to copy these bytes from LMA to VMA. It then runs a zeroing loop over the .bss section in RAM, clearing it to zero.

FLASH ↔ RAM DATA MIGRATION IN STARTUP
FLASH (Non-Volatile / Load Area) Vector Table (Pointers) .text (Executable Instructions) .rodata (Constants & Read-Only) .data initializers (Flash image) e.g. initial value of 'int speed = 100;' 1. COPY DATA RAM (Volatile / Execution Area) .data (Initialized Variables) Variables copied to RAM address .bss (Zero-Initialized) 2. ZERO LOOP (e.g. static int fault_count;) Heap (Grows Up →) Stack (← Grows Down) Stack Pointer (SP) initialized to RAM top
Startup code copies initial values to RAM and zeros out BSS segments before main() runs.
Startup code constructs the memory environment that the C program assumes already exists.

9. Stack Initialization and System Clocks

Before any C function can be invoked, the stack must be valid. The stack is the scratch area used for local variables, local execution contexts, and return addresses. If the program counter jumps to a C function before the Stack Pointer (SP) register is loaded with a valid RAM address, the first function call or stack allocation will push data into invalid memory space, crashing the processor immediately.

Different architectures handle stack pointer setup in different ways. In ARM Cortex-M processors, the hardware automatically loads the initial Stack Pointer value from the very first entry (offset 0) of the vector table during the reset cycle. In other architectures, the stack pointer must be explicitly loaded in assembly code inside the reset handler before any other operations occur.

Simultaneously, the system clock tree must be configured. At boot, the CPU runs from a slow, low-power internal default oscillator to guarantee startup. The reset code configures the clock multipliers (PLLs) and oscillators, adjusting Flash access wait-states in tandem to avoid instruction starvation. Order matters: scaling speed without wait states locks the bus.

10. The Language Runtime and main()

With memory structured, clocks stabilized, and stack space verified, the hardware environment is finally complete. However, if the project is written in C++, there remains one final software initialization step: static constructors.

Global C++ objects must have their constructors executed before main() starts. The toolchain compiles a list of pointers to these constructor functions into a dedicated section (like .init_array). The startup code iterates through this array, executing each constructor function in sequence. Finally, the program counter loads the address of the main() symbol. The application has begun.

The Genesis of the Loop

Every embedded application starts long before the code we write. Before the first statement of main() executes, a silent, complex dance of analog voltage detectors, memory transfers, and clock trees has already laid the foundation.

The code begins at main(). The machine became itself before the first line was read.

We write main() to dictate what the machine will do. The startup code executes to decide what the machine is.
System Tree Node Bare Metal

PrajnaEdge

Engineering concepts you don't just read — you experience.
Founded in 2026.

PrajnaEdge is a technology company exploring the space between understanding technology, experimenting with ideas, and turning them into things that can be experienced.

Our Mission

To make technology easier to explore, deeper to understand, and more exciting to experience.

Our Vision

To build a technology ecosystem where curiosity, experimentation and creation continuously lead to one another.

Where it began

Embedded Systems

PrajnaEdge began with Embedded Systems — exploring the foundations that connect hardware, software and intelligent computation.

The first technology universe is built around that foundation. The journey will expand as new ideas, experiments and products emerge.

PrajnaEdge is a technology company created by Devaharsha Meesarapu.

CREATOR PROFILE

Devaharsha Meesarapu

Embedded Systems • Firmware • Edge AI

I am the engineer behind the design, development, and content of PrajnaEdge. I build low-level systems where code directly controls hardware, bridging the gap between register-level silicon behavior and intelligent edge decision loops.

View Resume →

ABOUT ME

I am an Embedded Firmware Engineer focused on developing software for resource-constrained systems. My experience spans bare-metal firmware, device drivers, microcontroller peripherals, and communication protocols, working across the boundary between hardware and software.

My work has involved microcontroller-based systems, real-time behaviour, hardware interfaces, and communication technologies such as CAN, CAN FD, UART, SPI, and I²C. I am particularly interested in understanding systems from the lowest level upward—from registers and peripherals to intelligent edge systems.

ENGINEERING PHILOSOPHY

Engineering is not just about writing code; it is about managing constraints, timings, and physical hardware characteristics. True mastery of complex systems comes from understanding the interactions across different layers of the stack.

This conviction is why I built PrajnaEdge—to bridge the gap between conceptual theory and direct, register-level physical reality.

CONNECT

LinkedIn → GitHub →

Interactive Career Journey

BTech · ECE

Foundations

Where it all began — understanding the physical layer of computation. Circuits, signals, and systems gave me a mental model of how information moves through hardware.

⬡
Connects to Systems
Understanding circuits directly enables writing firmware that talks to peripherals at the register level.
What it is
BTech in Electronics and Communication
Undergraduate foundation covering analog & digital circuits, signal processing, microprocessors, and communication systems.
CircuitsSignal ProcessingMicroprocessorsVLSI
What I did
Core Engineering Fundamentals
Studied semiconductor physics, digital logic design, and embedded microcontrollers. Built prototypes using 8-bit MCUs.
8051Logic DesignPCB Basics
What I learned
The Hardware Mental Model
Every software abstraction sits on physical reality. Understanding silicon teaches you why timing, power, and noise are first-class engineering problems.
Let's Connect
Interested in embedded systems, AI, or building something meaningful? I'd love to hear from you.
Open to collaborations, research, and interesting engineering conversations.
Help Improve PrajnaEdge
Found something to improve? I'd love to hear your thoughts.

Bare Metal

Software that runs directly on hardware without an operating system.

Applications
↑
Operating Systems
YOU ARE HERE
Bare Metal
Processor
↑
Hardware

"Every embedded application begins long before main()."

Operating Systems

An Operating System manages hardware and software resources so complex applications can work efficiently.

Applications
↑
YOU ARE HERE
Operating Systems
Bare Metal
↑
Processor
↑
Hardware

"When one loop is no longer enough to carry the burden."

← Return to Systems Tree
Select Domain
Explore application domains branching from the Systems Tree
Automotive
Aerospace & Defence
Consumer
Embedded AI
Industrial
Medical
Multimedia
Network & Connectivity
Robotics
Explore Active Systems Tree →
← Return to Systems Tree
Select Depth
Examine computation through architectural depth layers
Architecture
Controller
Digital
Programming
Processor
Explore Processors → Explore Controllers →

Support PrajnaEdge

PrajnaEdge is an independent education platform built to make knowledge freely accessible.

If you find PrajnaEdge useful, you can support its continued development.

Your support helps fund the time, tools, infrastructure, and experimentation that go into building and maintaining PrajnaEdge.

Select Region
Select Amount
Select an amount to support PrajnaEdge.