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.
Experiment with intelligence beyond the cloud.
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.
Can this image classifier maintain its intelligence while becoming small enough for the edge?
Run a fully quantized INT8 image-classification model directly on embedded hardware.
Can this image classifier maintain its intelligence while becoming small enough for the edge?
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.
Explore the ideas, systems and connections that shape technology — choose any node to begin your journey.
Practical guides to embedded systems and engineering.
Deploying neural networks and intelligent decision loops on raw silicon targets.
The hidden choreography that prepares raw silicon for your first line of C.
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.
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.
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 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.
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.
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.
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:
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.
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.
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.
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.
PrajnaEdge is a technology company exploring the space between understanding technology, experimenting with ideas, and turning them into things that can be experienced.
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.
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.
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 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.
Software that runs directly on hardware without an operating system.
"Every embedded application begins long before main()."
An Operating System manages hardware and software resources so complex applications can work efficiently.
"When one loop is no longer enough to carry the burden."
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.
Welcome to PrajnaEdge (prajnaedge.dev), an independent engineering and technology platform created and maintained by Devaharsha Meesarapu. By using this website, you agree to these terms.
Educational & Research Focus: PrajnaEdge publishes interactive technical explorations, architectural models, and simulation walk-throughs covering embedded systems, computer architecture, operating systems, and edge artificial intelligence. All materials, interactive tools, and code demonstrations are provided solely for educational and conceptual understanding.
Hardware & Firmware Disclaimer: Embedded programming interacts directly with hardware registers, physical voltages, and precise timing. While all writeups and code demonstrations are prepared with care, they are provided "as is" without warranty of any kind. You are responsible for reviewing component datasheets, circuit schematics, and electrical ratings before deploying code to physical microcontrollers or custom hardware.
Intellectual Property: All original articles, custom SVG architectures, interactive simulators, curriculum sequences, and source code are the intellectual property of Devaharsha Meesarapu (© 2026 PrajnaEdge. All rights reserved). Non-commercial educational study and citation are welcome with proper attribution. Direct republication, unauthorized mirroring, or mass scraping of content is not permitted.
PrajnaEdge is committed to user privacy and minimal data collection. We do not sell, rent, or monetize your personal information.
localStorage purely to remember your interface preferences on your device (such as sound preferences for animations and tutorial display states). No personal identity data is stored in localStorage.
To help support platform operation and hosting, PrajnaEdge displays advertisements served by third-party advertising partners, including Google AdSense.
We use Google Analytics (measurement tag: G-6Y8ZVQB1V0) to evaluate anonymous, aggregate usage trends across our technical writeups.
Depending on your jurisdiction (including rights under GDPR, CCPA/CPRA, and applicable privacy laws), you have the right to request access to, correction of, or deletion of any personal communications you have submitted. We do not sell personal data.
For any questions regarding these terms, privacy practices, or data inquiries, contact the creator directly: