Embedded, Real-Time, and Concurrent ProgrammingIntroduction and ContextEmbedded systems are computing systems integrated into products or physical processes rather than operated as general-purpose desktop computers. They range from low-cost sensor nodes and motor controllers to avionics computers, medical devices, automotive control units, and industrial robots. Their software must interact with hardware, operate within limited memory and energy budgets, respond within specified time bounds, and often execute several logically independent activities at once. C remains central to this domain because it provides predictable low-level access to memory and peripherals, efficient compiled code, a mature toolchain, and portability across microcontrollers and processors. At the same time, C places substantial responsibility on the programmer. Undefined behaviour, data races, incorrect interrupt handling, stack exhaustion, and unbounded execution time can cause failures that are difficult to reproduce and potentially hazardous. Embedded, real-time, and concurrent programming are related but distinct ideas. Embedded programming concerns software integrated with hardware. Real-time programming concerns correctness in relation to time. Concurrent programming concerns multiple activities whose executions overlap or are coordinated. A system may be embedded without being real-time, real-time without being safety-critical, or concurrent without using multiple processors. Learning ObjectivesAfter studying this chapter, the reader should be able to: - Explain the architecture and constraints of embedded C systems.
- Distinguish hard, firm, and soft real-time requirements.
- Analyse task timing using execution-time, deadline, latency, and schedulability concepts.
- Design interrupt-driven and timer-driven C programs safely.
- Explain concurrency, race conditions, critical sections, atomicity, and synchronization.
- Compare polling, interrupts, cooperative scheduling, and real-time operating systems.
- Apply robust design practices to a practical embedded case.
- Evaluate reliability, safety, security, and maintainability implications of embedded C.
1. Embedded-System FoundationsAn embedded system normally contains a processor, memory, input/output interfaces, timing resources, and application software. A microcontroller often integrates the processor core, flash memory, RAM, timers, analogue-to-digital converters, communication peripherals, and interrupt controller on one chip. A typical execution path is: Physical process → sensor → peripheral interface → driver → application logic → actuator → physical process The C program is therefore part of a cyber-physical system. Its correctness depends not only on algorithms but also on electrical timing, hardware registers, interrupt behaviour, compiler rules, and communication protocols. 1.1 Resource constraintsImportant constraints include: - Memory: Flash stores program code and read-only data; RAM stores variables, buffers, stacks, and run-time state.
- Energy: Battery-powered systems may need sleep modes, low-frequency operation, and efficient communication.
- Computation: A processor may have limited clock frequency, no floating-point unit, and strict response deadlines.
- Physical interfaces: Inputs may be noisy, asynchronous, analogue, or subject to switch bounce.
- Reliability: Systems may operate unattended for years and must recover from faults.
- Toolchain dependence: Startup code, linker scripts, compiler options, and hardware abstraction layers affect behaviour.
1.2 Memory-mapped I/O and volatile objectsMany microcontrollers expose peripheral registers at fixed memory addresses. A C implementation may represent a register as a volatile object: #define GPIO_OUT (*(volatile unsigned int *)0x40020014u)
void led_on(void) {
GPIO_OUT |= (1u << 5);
}The volatile qualifier informs the compiler that the object can change for reasons not visible in the current flow of C code, such as hardware activity or an interrupt service routine. It prevents certain optimisations that would incorrectly remove or combine accesses. volatile does not make an operation atomic, does not provide mutual exclusion, and does not establish a complete memory-ordering policy for multicore concurrency. For example, counter++ usually consists of read, modify, and write operations; an interrupt or another thread can intervene between them. Atomic types, interrupt masking, locks, or carefully designed single-writer protocols may be required.
1.3 Startup and execution environmentUnlike a hosted desktop program, an embedded program may begin at a reset vector. Startup code commonly: - establishes the stack pointer;
- copies initialised data from non-volatile memory to RAM;
- clears zero-initialised data;
- configures clocks and low-level hardware;
- invokes
main() or an RTOS kernel.
The linker script determines where code, constants, data, stacks, and peripheral regions are placed. A C programmer must therefore understand the boundary between the language standard and implementation-defined hardware behaviour. 2. Real-Time ComputingA real-time system is one in which the time at which a result is produced is part of its correctness. A numerically correct braking command delivered after the required interval may be a system failure. 2.1 Core timing terminology- Release time: The instant at which a task becomes ready.
- Execution time: Processor time required by a task under specified conditions.
- Response time: Time from release until completion.
- Deadline: Latest acceptable completion time.
- Period: Minimum or nominal interval between successive releases of a periodic task.
- Jitter: Variation in release, execution, or completion time.
- Latency: Delay between an event and the beginning or completion of its response.
- WCET: Worst-case execution time, determined under a stated hardware and software configuration.
For a task released at time r and completing at time f, its response time is: R = f − r where R, r, and f are measured in consistent time units. A deadline requirement is satisfied when R ≤ D, where D is the relative deadline measured from release. 2.2 Hard, firm, and soft deadlinesA hard real-time deadline must never be missed, as in a protection relay or airbag controller. A firm real-time result has no value after its deadline, but occasional misses may be tolerated if they do not create unacceptable danger. A soft real-time task loses value progressively after its deadline, as in multimedia processing. The classification is a system requirement, not merely a property of a programming language. C can be used in all three classes, but achieving hard real-time behaviour requires bounded execution, controlled interrupts, verified hardware, and disciplined scheduling. 2.3 Utilisation and schedulabilityFor a periodic task i with worst-case execution time Cᵢ and period Tᵢ, processor utilisation is: U = ∑(i=1 to n) Cᵢ / Tᵢ Cᵢ and Tᵢ must use the same units, such as milliseconds, and U is dimensionless. This represents the fraction of processor capacity demanded by the periodic tasks, assuming one processor and no overhead. Interrupt, context-switch, blocking, and operating-system overhead must be included in a realistic analysis. For n independent periodic tasks scheduled by rate-monotonic priority assignment, a sufficient classic bound is: U ≤ n(2^(1/n) − 1) This bound assumes fixed priorities assigned in inverse period order, independent tasks, deadlines equal to periods, negligible overhead, and preemptive scheduling. It is sufficient but not necessary: a task set exceeding the bound may still be schedulable. For n approaching infinity, the bound approaches approximately 0.693. Worked example. Three periodic tasks have execution times and periods (in milliseconds): (1, 10), (2, 20), and (3, 50). Their utilisation is: U = 1/10 + 2/20 + 3/50 = 0.10 + 0.10 + 0.06 = 0.26 The three-task sufficient bound is 3(2^(1/3) − 1) ≈ 0.780. Since 0.26 ≤ 0.780, the task set passes this sufficient test under the stated assumptions. This conclusion does not prove correctness if interrupt overhead, blocking, cache effects, or inaccurate WCET estimates are ignored. 2.4 Response-time reasoningFor fixed-priority preemptive scheduling, a commonly used response-time recurrence for task i is: Rᵢ^(k+1) = Cᵢ + Bᵢ + ∑(j∈hp(i)) ⌈Rᵢ^(k) / Tⱼ⌉ Cⱼ Here Cᵢ is the task's execution time, Bᵢ is its maximum blocking time, hp(i) is the set of higher-priority tasks, Tⱼ is the period of higher-priority task j, and ⌈x⌉ is the ceiling function. The iteration begins with Rᵢ⁽⁰⁾ = Cᵢ + Bᵢ and continues until it converges or exceeds the deadline. The equation assumes a suitable fixed-priority preemptive model and must be extended when release jitter, non-preemptive sections, interrupts, or multiprocessor effects matter. 3. C Techniques for Embedded Systems3.1 Fixed-width and bounded dataUse <stdint.h> types such as uint16_t and int32_t when exact widths matter. Use <stdbool.h> for Boolean values. Avoid assuming that int has a particular size. Explicitly consider signed overflow, integer promotions, endianness, alignment, and conversion between signed and unsigned types. Dynamic allocation is often restricted or prohibited in hard real-time systems because allocation time may be unbounded, fragmentation can accumulate, and failure handling is difficult. Static allocation, fixed-size pools, and bounded queues are more predictable. 3.2 Interrupt service routinesAn interrupt service routine (ISR) should be short, deterministic, and limited to urgent work. A common pattern is: #include <stdint.h>
#include <stdbool.h>
static volatile bool sample_ready = false;
static volatile uint16_t sample_value = 0;
void ADC_IRQHandler(void) {
sample_value = ADC_DATA_REGISTER;
sample_ready = true;
}
int main(void) {
hardware_init();
for (;;) {
if (sample_ready) {
uint16_t local;
disable_interrupts();
local = sample_value;
sample_ready = false;
enable_interrupts();
process_sample(local);
}
}
}The exact correctness of this example depends on the target architecture and interrupt functions. If a multi-byte value cannot be read atomically, interrupts must be controlled or an atomic protocol used. A better design may use a ring buffer when samples can arrive faster than the main loop processes them. ISRs should generally avoid blocking, long loops, formatted I/O, uncontrolled dynamic allocation, and calls to non-reentrant library functions. They should acknowledge the interrupt source according to the device manual; otherwise the ISR may execute repeatedly. 3.3 Polling, interrupts, and DMAPolling repeatedly checks a device status flag. It is simple and predictable for slow devices, but wastes CPU time and may miss events if the polling interval is too long. Interrupts allow the processor to perform other work and respond to asynchronous events, but introduce latency, priority interactions, and shared-state problems. Direct memory access (DMA) transfers data between peripherals and memory with limited CPU involvement, improving throughput but requiring careful buffer ownership, cache management on some processors, and completion synchronisation. 3.4 Timers and periodic executionA timer can generate an interrupt at a configured interval. If a clock has frequency f and a timer divides it by prescaler p, then a simple tick interval for a reload value N is architecture-dependent; the exact expression must follow the timer's counting convention and whether N or N+1 clock periods are counted. This is an important example of why hardware manuals, not generic formulas, determine register configuration. Software should avoid accumulating drift. Instead of scheduling the next event by “current time plus period” after variable execution, maintain an absolute next-release time: next_release += PERIOD_TICKS;
delay_until(next_release); This preserves the intended phase, provided missed-release behaviour is explicitly defined. 4. Concurrency in CConcurrency means that multiple activities make progress during overlapping intervals. Activities may be ISRs, foreground loops, RTOS tasks, processes, or threads. Parallelism is simultaneous execution on multiple hardware contexts; concurrency does not require parallelism. 4.1 Shared state and race conditionsA race condition occurs when the result depends on the timing or interleaving of accesses. Consider: balance = balance + amount; This is a read-modify-write sequence. If two tasks execute it concurrently, one update may overwrite the other. The programmer must identify shared objects and specify ownership, access protocol, and allowable interleavings. A critical section is a region that must not be interleaved with conflicting operations. Mutual exclusion can be implemented using interrupt masking, mutexes, atomic operations, or lock-free algorithms. Interrupt masking is appropriate only for very short sections and only when its effect on latency is acceptable. 4.2 Atomicity, visibility, and orderingThese are different properties: - Atomicity: An operation appears indivisible.
- Visibility: An update becomes observable by another execution context.
- Ordering: Operations are observed in an intended sequence.
The C11 standard provides <stdatomic.h> for atomic objects and memory orders. A simple atomic flag can coordinate producer and consumer code, but the design must ensure that associated data is published with appropriate ordering. volatile alone does not replace atomic operations or synchronisation. 4.3 Communication patternsCommon patterns include: - Single-producer, single-consumer ring buffer: Efficient when exactly one context writes and one reads.
- Message queue: Transfers discrete messages and clarifies ownership.
- Shared data with mutex: Suitable for complex state, but introduces blocking and possible priority inversion.
- Event flags: Represent one or more conditions without transferring large data.
- Double buffering: One buffer is filled while another is processed; ownership changes at defined boundaries.
A ring buffer of capacity N commonly uses head and tail indices. If one slot is deliberately left unused, the buffer is empty when head equals tail and full when advancing head would equal tail. Index arithmetic must be bounded and carefully defined for integer wraparound. 4.4 Deadlock and priority inversionDeadlock requires conditions such as mutual exclusion, hold-and-wait, no pre-emption, and circular wait. Prevention strategies include global lock ordering, reducing lock scope, avoiding nested locks, and using timeouts where appropriate. Priority inversion occurs when a high-priority task waits for a lock held by a low-priority task while medium-priority work prevents the low-priority task from running. Priority inheritance temporarily raises the lock holder's priority; priority ceiling protocols provide an alternative with stronger analytical properties. These mechanisms reduce risk but do not remove the need for bounded critical sections. 5. Scheduling Architectures5.1 Superloop architectureA superloop repeatedly executes modules: for (;;) {
read_inputs();
run_control();
service_communications();
update_outputs();
}It has low overhead and is suitable for small systems. However, the worst-case time of the loop can grow as features are added, and a slow module delays every other function. 5.2 Cooperative schedulingIn cooperative scheduling, tasks run until they voluntarily yield or return. It is simple and avoids arbitrary pre-emption, but one faulty or long-running task can block all others. Each task must be designed to execute in bounded increments. 5.3 Pre-emptive real-time operating systemsAn RTOS generally provides tasks or threads, priorities, timers, queues, semaphores, mutexes, and interrupt integration. Pre-emption improves responsiveness but adds context-switch overhead and introduces more complex synchronisation. The kernel does not automatically make a system real-time; unbounded blocking, excessive logging, priority errors, and inaccurate timing analysis remain possible. A practical architecture often follows this division: - ISRs capture urgent hardware events.
- ISRs place data or set events.
- High-priority tasks perform bounded control actions.
- Lower-priority tasks handle diagnostics, logging, and communication.
- A watchdog detects failures in progress or scheduling.
6. Safety, Reliability, and SecurityEmbedded software may control physical energy, personal data, transportation, or medical treatment. Requirements engineering should identify hazards, failure modes, safe states, and evidence needed for verification. Important practices include: - defensive range and validity checks;
- explicit timeout handling;
- watchdog design that detects genuine loss of progress;
- fail-safe outputs and startup defaults;
- static analysis and compiler warnings;
- unit, integration, hardware-in-the-loop, stress, and fault-injection tests;
- code review and traceability from requirements to tests;
- controlled configuration and reproducible builds;
- protection against buffer overflows, integer errors, unsafe parsing, and unauthorised updates.
Standards such as IEC 61508, ISO 26262, IEC 62304, DO-178C, and MISRA C may be relevant depending on sector and assurance level. Their applicability must be determined by the project and regulator; following a coding guideline alone does not establish safety. Security is increasingly important because connected devices can be attacked through update mechanisms, communication interfaces, debug ports, or supply-chain components. Secure boot, authenticated updates, least privilege, key protection, input validation, and vulnerability management should be designed from the beginning. 7. Applied Case: Illustrative Motor-Protection ControllerBackgroundConsider a fictional but technically representative three-phase motor controller. It measures current every 1 ms, detects over-current conditions, updates a control output every 5 ms, communicates diagnostics over a bus every 100 ms, and records faults in non-volatile memory. ProblemThe initial implementation used a long polling loop. Bus traffic occasionally delayed current sampling, and a fault could take several milliseconds to reach the shutdown output. Diagnostic logging also blocked the control loop while writing memory. AnalysisThe protection function is hard real-time: a missed deadline can damage equipment. Control is firm real-time, while diagnostics are soft real-time. The design team measured worst-case execution times, including interrupt and driver overhead, rather than relying on average measurements. It also identified shared state between the sampling ISR, control task, and diagnostic task. Approach and solution- A timer-triggered ADC conversion captures samples at 1 ms intervals.
- The ADC ISR places samples into a bounded single-producer, single-consumer buffer.
- A high-priority control task processes samples and updates the output every 5 ms.
- A hardware comparator independently asserts a shutdown signal for severe over-current, reducing dependence on software response time.
- Diagnostics run at lower priority and communicate through a queue.
- Fault records are buffered in RAM and written to non-volatile memory outside the protection path.
- A watchdog is serviced only after sampling, control, and communication health checks succeed.
- Timing tests include maximum bus traffic, worst-case sensor patterns, flash-write interference, and deliberate task starvation.
OutcomeThe architecture separates urgent protection from non-critical logging, bounds the principal queues, and makes ownership of data explicit. It also provides a hardware safety layer. The design still requires verification of ADC timing, buffer overflow policy, watchdog behaviour, and the actual worst-case response under all specified operating conditions. Learning pointsThe case demonstrates that real-time design is an architectural discipline. Increasing processor speed may conceal but does not eliminate race conditions, unbounded blocking, or unsafe fault handling. Hardware and software mechanisms should be combined when the consequence of a missed deadline is severe. 8. Contemporary Developments and Critical EvaluationModern microcontrollers increasingly include multicore processors, hardware security modules, caches, wireless interfaces, and machine-learning accelerators. These capabilities improve performance but complicate timing analysis. Caches, speculative execution, shared buses, and interrupt migration can make WCET estimation more difficult. Measurement alone is not proof of a worst case; it must be combined with structural analysis, controlled assumptions, and suitable assurance evidence. C23 and modern toolchains improve language facilities, diagnostics, and portability, but compiler conformance and embedded-library support vary. Static analysis, formal verification of selected components, model-based development, and property-based testing are increasingly used alongside conventional testing. Artificial intelligence may assist code review, test generation, or anomaly detection, but generated code requires expert review, traceability, licensing assessment, and security validation. It cannot replace timing analysis or safety evidence. A mature engineering process therefore combines: - requirements with measurable timing and failure criteria;
- architecture based on explicit ownership and bounded execution;
- language and coding rules suited to the target;
- analysis of scheduling and synchronisation;
- automated testing and static analysis;
- hardware-in-the-loop and fault testing;
- secure development and controlled deployment;
- monitoring and maintenance throughout the device life cycle.
Chapter SummaryEmbedded C programming joins software with hardware under constraints of memory, energy, timing, and reliability. Real-time correctness requires results to be delivered by their deadlines, and task utilisation or response-time analysis can provide evidence of schedulability when their assumptions are satisfied. Interrupts, timers, DMA, and RTOS facilities support responsive designs, but each introduces overhead and interaction that must be analysed. Concurrency creates risks involving race conditions, atomicity, visibility, ordering, deadlock, and priority inversion. Safe designs make data ownership explicit, minimise critical sections, use suitable atomic or locking mechanisms, and transfer work from ISRs to bounded tasks. Superloops, cooperative schedulers, and pre-emptive RTOS architectures are valid alternatives for different scales and assurance needs. Professional embedded development extends beyond writing C statements. It includes hardware-aware design, WCET and deadline analysis, defensive programming, testing, secure updates, standards-based assurance, and explicit treatment of failure. The most dependable systems separate safety-critical paths from diagnostics, combine hardware and software protection, and verify behaviour under worst-case conditions. Key TermsEmbedded system: A computer system integrated into a larger product or physical process. Real-time system: A system whose correctness depends on both result and timing. Hard deadline: A deadline that must not be missed. Soft deadline: A deadline after which utility decreases rather than becoming immediately zero. Worst-case execution time: An upper execution-time estimate under stated assumptions. Response time: Time from task release to task completion. Jitter: Variation in timing from one execution or event to another. Interrupt service routine: Code invoked in response to an interrupt request. Polling: Repeatedly checking a device or condition under program control. DMA: Direct memory access transfer performed with limited processor involvement. Concurrency: Overlapping progress of multiple activities. Parallelism: Simultaneous execution on multiple hardware execution units. Race condition: Behaviour whose result depends on the timing of concurrent accesses. Critical section: Code that must be protected from conflicting interleavings. Atomicity: The property that an operation appears indivisible. Mutex: A mutual-exclusion synchronisation object. Deadlock: Permanent waiting caused by cyclic resource dependencies. Priority inversion: Blocking of a higher-priority activity by lower-priority work. Schedulability: The ability of a task set to meet its timing requirements. Watchdog: A timer-based mechanism used to detect lack of system progress. Review Questions5 Short Answer Questions- Why is
volatile useful for memory-mapped registers, and why does it not by itself provide mutual exclusion? - Distinguish hard, firm, and soft real-time deadlines.
- Define WCET, response time, and jitter.
- State two disadvantages of polling compared with interrupt-driven I/O.
- What is priority inversion, and how can priority inheritance help?
5 Long Answer Questions- Explain the design and limitations of a timer-driven interrupt architecture in embedded C.
- Discuss how atomicity, visibility, and ordering differ in concurrent programming.
- Compare superloop, cooperative, and pre-emptive RTOS architectures for a medium-complexity controller.
- Derive the utilisation of a periodic task set and explain the assumptions behind the rate-monotonic sufficient bound.
- Discuss how safety, security, testing, and coding standards contribute to dependable embedded software.
3 Analytical / Application Questions- A controller has periodic tasks with (C, T) values of (1 ms, 8 ms), (2 ms, 20 ms), and (4 ms, 40 ms). Calculate total utilisation and explain what additional information is needed before claiming that the system is schedulable.
- Design a producer-consumer architecture for sensor samples arriving in an ISR and processed by an RTOS task. Specify buffer ownership, overflow behaviour, and synchronisation.
- Analyse the motor-protection case and identify two functions that should remain independent of diagnostic logging. Justify the decision using deadline and failure-consequence reasoning.
Multiple Choice Questions with AnswersQuestion 1Which statement best describes a hard real-time requirement? A. The result must be correct and completed before its deadline. B. The result is useful only when processor utilisation is below 50%. C. The result may always be delayed if its numerical value is correct. D. The task must execute on a multicore processor. Correct Answer: A. The result must be correct and completed before its deadline. Question 2What is the primary purpose of the volatile qualifier for a hardware register? A. To make every compound operation atomic B. To prevent all data races C. To inform the compiler that accesses may have externally visible changes D. To allocate the register in non-volatile memory Correct Answer: C. To inform the compiler that accesses may have externally visible changes Question 3For periodic tasks with execution times Cᵢ and periods Tᵢ, which expression represents processor utilisation? A. ∑ Tᵢ/Cᵢ B. ∑ Cᵢ/Tᵢ C. ∑ Cᵢ×Tᵢ D. ∑(Cᵢ−Tᵢ) Correct Answer: B. ∑ Cᵢ/Tᵢ Question 4What is a major advantage of moving substantial work out of an interrupt service routine? A. It guarantees that no task can be pre-empted. B. It reduces interrupt latency and makes timing easier to analyse. C. It eliminates the need for shared-state protection. D. It makes dynamic memory allocation deterministic. Correct Answer: B. It reduces interrupt latency and makes timing easier to analyse. Question 5Which situation is a race condition? A. A task executes entirely before another task is released. B. Two activities access shared state and the result depends on their interleaving. C. A timer is configured with a fixed period. D. A compiler reports an unused variable. Correct Answer: B. Two activities access shared state and the result depends on their interleaving. Question 6Which mechanism is specifically intended to provide mutual exclusion? A. Mutex B. ADC C. DMA channel D. Linker script Correct Answer: A. Mutex Question 7Three tasks have utilisation values 0.10, 0.10, and 0.06. What is their total utilisation? A. 0.16 B. 0.20 C. 0.26 D. 0.36 Correct Answer: C. 0.26 Question 8Which condition can produce priority inversion? A. A low-priority task holds a resource required by a high-priority task. B. A high-priority task has the shortest period. C. An ISR reads a read-only constant. D. A task completes before its deadline. Correct Answer: A. A low-priority task holds a resource required by a high-priority task. Question 9Why can unrestricted dynamic memory allocation be problematic in hard real-time software? A. It always uses less memory than static allocation. B. Its timing and long-term fragmentation behaviour may be difficult to bound. C. It prevents the use of interrupts. D. It makes all variables volatile. Correct Answer: B. Its timing and long-term fragmentation behaviour may be difficult to bound. Question 10In the illustrative motor controller, why was a hardware over-current comparator valuable? A. It replaced every software function. B. It provided an independent rapid shutdown path for a severe fault. C. It increased diagnostic logging bandwidth. D. It guaranteed that the RTOS could not deadlock. Correct Answer: B. It provided an independent rapid shutdown path for a severe fault.
|