Showing posts with label embedded. Show all posts
Showing posts with label embedded. Show all posts

Wednesday, June 17, 2026

Interrupt Deadlock

In embedded systems, there is a dangerous trap where standard thread safety fails completely, leaving your application permanently frozen. That trap is to use a mutex inside an interrupt handler. Consider this thread-safe function, where you protect shared state with a mutex:

void update_hardware(int data) {
    pthread_mutex_lock(&lock)
    global_hardware_buffer = data; // What if an interrupt hits right here?
    pthread_mutex_unlock(&lock);
}

If this function is called by an interrupt service routine (ISR) and a hardware interrupt fires right in the middle of that function:

  1. The Main Thread acquires the lock.
  2. The Interrupt hits. The CPU immediately freezes the main thread and jumps to your ISR.
  3. The Re-entry: The interrupt handler needs to log something, so it calls update_hardware().
  4. The Deadlock: The interrupt handler hits pthread_mutex_lock(). It sees the lock is busy, so it waits.

But who is it waiting for? It's waiting for the main thread to release the lock. Except the main thread is frozen underneath the interrupt handler, waiting for the interrupt to finish!

A common misconception is that the operating system's scheduler will see the interrupt handler is blocked, context-switch it out, let the main thread finish, and fix the mess. It can't. In embedded systems or Real-Time Operating Systems (RTOS), interrupts run at a higher execution priority than the scheduler itself. ISRs execute outside normal thread scheduling. If an ISR attempts to wait for a resource held by the interrupted thread, forward progress becomes impossible because the interrupted thread cannot run until the ISR completes. This is priority inversion taken to the extreme. The ISR (highest priority in the system) ends up waiting for a lower-priority thread that it itself has preempted.

To survive interrupts, your code cannot just be thread-safe, it must be reentrant. A reentrant function is completely self-contained. It never touches global variables, it never uses static buffers, and it never locks a mutex. It relies strictly on local variables allocated on the stack or parameters passed to it. Because it has no shared memory between calls, it can be interrupted at any instruction and safely called again without data corruption or deadlocks. Here is a reentrant function example:

// This function operates purely on local stack memory.
// It can be safely interrupted and re-entered at any microsecond.
int calculate_hardware_state(int current_state, int new_data) {
    int next_state;
    next_state = current_state + new_data;
    return next_state;
}

Also pay attention to macros because if a macro references a global variable, a static variable, or a hardcoded hardware register under the hood, any function using that macro instantly becomes non-reentrant:

int global_status = 0;
#define SET_STATUS_FLAG(mask) (global_status |= (mask))

Never use blocking synchronization primitives (like mutexes, malloc, or I/O) inside an interrupt handler or signal handler. If you must pass data between an interrupt and your main loop, stick to lock-free mechanisms like C11 atomics or volatile flags.

Wednesday, June 10, 2026

Choosing the Right CPU: Desktop vs. Industrial vs. Safety-Critical

We live in an era where a standard desktop processor has 24 cores and clock speeds past 5.5 GHz. Yet, if you walk into an automotive assembly line, you will see computers (PLCs) with processors running 100x slower, and being 10x more expensive than their desktop counterparts.

Why? Because in the world of computing, power is defined entirely by the problem you are trying to solve. We have to look past raw processing speed and examine three distinct operational philosophies: Throughput, Determinism, and Functional Safety.

1. The Desktop CPU

Desktop processors are designed to handle an unpredictable, highly dynamic workload. At any given moment, a desktop CPU might be asked to render a 3D video, compile a massive codebase, manage dozens of browser tabs, or decode high-definition audio.

To excel at this, desktop CPUs use general purpose operating systems like Windows or Linux, which rely on throughput-oriented schedulers. The OS slices up time and distributes it among running applications, trying to give everything a fair share. To squeeze out every drop of performance, the silicon itself relies on microarchitectural optimizations:

  • Out-of-Order Execution: The CPU dynamically rearranges the order of instructions to keep its execution pipelines full.
  • Speculative Execution & Branch Prediction: The processor literally guesses which path a piece of code will take before it even runs, executing the instructions ahead of time to hide latency.
  • Multi-Tiered Caches (L1/L2/L3): Large memory pools sit on the die to prevent the CPU from constantly waiting on slower system RAM.

However, this architecture is inherently non-deterministic. If a background cloud-sync app suddenly demands resources, or if a branch predictor guesses wrong, a task might take 50 milliseconds longer to execute on cycle two than it did on cycle one. In the consumer world, a dropped frame in a video game is an annoyance; in a physical system, a 50ms delay can be catastrophic.

2. The Industrial PLC CPU

Step inside a factory running a high-end programmable logic controller (PLC), like the Siemens SIMATIC S7-1500. Clock speeds range from tens to hundreds of megahertz, and memory is measured in megabytes rather than gigabytes. Yet, these processors are built for a completely opposing goal: Absolute Determinism.

An industrial CPU runs a Real-Time Operating System (RTOS). Instead of a fair share schedule, the RTOS uses strict, unyielding, priority-based cyclic execution. A PLC operates on a continuous loop:

  1. Read Inputs: Snapshot the state of every physical sensor.
  2. Execute Logic: Run the user control code sequentially.
  3. Write Outputs: Instantly update physical actuators, valves, and motors.

To guarantee that this cycle takes exactly the same amount of time down to the microsecond, industrial CPUs strip away the unpredictable optimization layers of desktop chips. There is no speculative execution, no out-of-order execution, and no virtual memory paging. Every task has a strict time budget monitored by a dedicated hardware watchdog timer. If a 1ms motion control loop fails to complete in exactly 1ms, the RTOS catches it, alerts the system, and can trigger a controlled shutdown.

Industrial CPUs are also physically engineered to survive decades in harsh environments. They are decoupled from fragile cooling fans, insulated against severe electromagnetic interference (EMI), and rated to maintain their precise timing clock cycles across extreme temperature swings (e.g., -25°C to +60°C).

3. The Safety-Critical CPU

While an industrial CPU guarantees when a command will execute, a safety-critical processor guarantees the mathematical integrity of the execution itself. Found in drive-by-wire automotive systems, avionics units complying with aerospace standards (like DO-254/DO-178C), or high-speed medical equipment, these processors are certified to standards like ISO 26262 (ASIL-D) or IEC 61508 (SIL-3).

The defining feature of a commercial safety-critical processor (such as the Texas Instruments Hercules™ or Infineon AURIX™ lines) is hardware level redundancy. Instead of using multiple cores to run different applications simultaneously, a safety-critical CPU pairs identical cores into a Dual-Core Lockstep (DCLS) configuration:

  • The Master and the Checker: Two physical hardware cores execute the exact same instruction stream, line-by-line, cycle-by-cycle.
  • Temporal Separation: To ensure that a localized physical event (like a voltage spike or a cosmic ray flipping a bit in memory) doesn't corrupt both cores simultaneously, the second core runs delayed by a micro-interval (typically 2 to 3 clock cycles).
  • Hardware Comparators: Independent hardware logic monitors the internal state and outputs of both cores. If a divergence of even a single bit is detected between the Master and the Checker, the comparator immediately strips power from the actuators or switches the system to a pre-defined, hardware-enforced "safe state."

In this realm, the code is heavily audited, features like Built-In Self-Tests (BIST) continuously sweep memory for corruption, and every single gate on the silicon must be mathematically traceable back to a design requirement.

Music: Ali Baba ve 7 Cüceler (arka jenerik)

Friday, July 11, 2025

Double-to-Int Conversion with Bit Shifting

We often need to pack a large numeric range into 32 bits. For instance, timestamps in microseconds over a 36 minute period exceed Integer.MAX_VALUE. By discarding the least significant bits (via right shift), we can fit the value and later we can recover it by the same amount of left shift. However, increasing the number of shifts decreases accuracy. What we need to optimize is to find the right amount of bit shift that covers the range and minimizes error. The following Java code investigates this:


Monday, December 30, 2024

Embedded Software: Simple vs. Complex

Embedded software is integral to modern technology, ranging from simple home appliances to advanced autonomous systems. It can be broadly classified into two categories: simple (Non-OS) and complex (OS-driven) embedded software.

Simple Embedded Software: When simplicity and low cost are priorities and an OS would be overkill

Examples:

  1. Power or temperature monitoring systems.
  2. Simple applications in household appliances like ovens and washing machines.
Characteristics:
  1. Typically designed for applications with few tasks.
  2. No operating system necessary.
  3. Software interacts directly with the microcontroller’s hardware (registers etc.), forcing rewrites if the hardware changes.
Advantages:
  1. Low power consumption, low cost.
  2. Can be developed by electronics engineers, no need for computer engineers because basic embedded programming knowledge is sufficient.
  3. Deterministic: By sidestepping the complexity of OS schedulers, simple systems achieve predictable performance.
  4. Fewer abstraction layers make verification and validation straightforward, which is a huge advantage for safety-critical certification.
Complex Embedded Software: When multi-tasking, file operations and networking necessitate an OS

Examples:

  1. IoT devices requiring seamless connectivity.
  2. Systems involving advanced sensor integration or navigation.
Characteristics:
  1. Runs on an operating system that manages tasks and system resources.
  2. Capable of handling multiple tasks and applications simultaneously.
  3. Safety-critical certification is difficult. To make it easier, safety-critical parts should be developed as separate, simpler modules.
Advantages:
  1. Less competition and higher profit margins, provided that you have a strong technical team.
  2. Requires computer engineers to lead the development because of increased software complexity. Besides embedded software courses, related concepts of algorithms, data structures, and operating systems are also a core parts of computer engineering but not electronics engineering.
  3. The OS abstracts low-level hardware management, enabling developers to focus on application logic. A POSIX-compliant application, for instance, can run on any POSIX-supporting OS with minimal changes.
  4. Easier for new developers to adapt and contribute due to less hardware dependency.
  5. A broad range of pre-existing libraries simplifies development.
  6. Operating systems provide abstraction layers (e.g., Linux Device Model), allowing drivers to expose standard interfaces while interacting with specific hardware.
  7. Simplifies adding new functionality (e.g. telemetry) or adapting to new hardware (e.g. new/different sensors).
  8. With minor modifications, software can be tested on a PC, speeding up testing with less effort (no need for electronic cards, power supplies, etc.) and reducing bugs.
Operating systems can also be categorized as either simple (e.g., FreeRTOS) or complex (e.g., real time Linux with ROS) - but let's leave that topic for another blog post.