Embedded, Real-Time, and Concurrent ProgrammingIntroduction and ContextEmbedded systems are computing systems designed for a specific function within a larger electrical or mechanical product. Examples include motor controllers, medical instruments, automotive braking systems, smart meters, industrial robots, and wearable devices. Unlike general-purpose applications, embedded programs must operate under constraints on processor speed, memory, energy, physical size, cost, and timing. A conventional desktop program may be judged mainly by whether it produces the correct result. An embedded program must also produce that result at the correct time, interact safely with hardware, recover from faults, and avoid exhausting limited resources. A temperature controller that calculates the correct heater command several seconds late may still be unsafe. Thus, embedded programming combines software design with electrical engineering, control, communication, and safety engineering. This chapter introduces the foundations required to program sensors, actuators, microcontrollers, interrupts, state machines, timers, bit-level interfaces, and concurrent tasks. Learning ObjectivesAfter studying this chapter, you should be able to: - Explain the structure and constraints of embedded systems.
- Interface digital and analog sensors and actuators with a microcontroller.
- Use bitwise operations to manipulate hardware registers safely.
- Distinguish polling, interrupts, and concurrent task execution.
- Design simple finite-state machines and timing logic.
- Estimate execution time, sampling requirements, and resource usage.
- Apply communication interfaces such as UART, SPI, and I²C.
- Identify latency, race conditions, memory, power, and safety risks.
1. Embedded-System ArchitectureA typical embedded system contains four interacting layers: - Physical layer: sensors, actuators, switches, motors, power electronics, and signal-conditioning circuits.
- Microcontroller layer: processor core, memory, timers, ADC, PWM units, GPIO, and communication peripherals.
- Firmware layer: device drivers, interrupt handlers, scheduling logic, control algorithms, and diagnostics.
- Application layer: the product-specific behavior, such as maintaining temperature or regulating motor speed.
A microcontroller usually contains a CPU, volatile data memory (RAM), non-volatile program memory (Flash or ROM), and peripherals on one integrated circuit. The CPU executes instructions, while peripherals perform specialized operations with less CPU involvement. Important resource constraints include: | Resource | Typical concern | Design response |
|---|
| CPU time | Tasks compete for processor cycles | Measure execution time and schedule priorities | | RAM | Stack, buffers, and variables may overflow memory | Use bounded buffers and avoid unnecessary copying | | Flash | Program and constant data have limited capacity | Remove unused code and select suitable data types | | Energy | Battery-operated products require low power | Sleep between events and reduce peripheral activity | | Timing | Deadlines may be missed | Use timers, priorities, and worst-case analysis | | Safety | Faults can cause injury or equipment damage | Add interlocks, watchdogs, diagnostics, and fail-safe states |
An embedded design is therefore not simply a smaller desktop program. Hardware behavior, electrical timing, and software behavior must be considered together. 2. Inputs, Outputs, and Signal Representation2.1 Digital input and outputA digital input is interpreted as one of two logical states, normally 0 or 1. A pushbutton, limit switch, or proximity sensor may provide a digital signal. A digital output can control an LED, relay driver, chip-select line, or logic input of another device. Electrical logic levels are not universally identical. A microcontroller may interpret a voltage below a specified low threshold as 0 and a voltage above a high threshold as 1. The input must not exceed the permitted voltage range. A floating input, caused by an open switch or disconnected signal, can change unpredictably; pull-up or pull-down resistors establish a defined default state. Mechanical switches also exhibit bounce: rapid transitions occur for a short period after contact. Software debouncing can require the input to remain stable for a specified interval before accepting a change. Hardware filtering or a Schmitt-trigger input may also be appropriate. 2.2 Analog inputAn analog-to-digital converter (ADC) maps an input voltage to a discrete code. For an ideal unipolar ADC with resolution N bits, reference voltage Vref, and input voltage Vin between 0 and Vref, the approximate code is: Code = floor((Vin / Vref) × (2N − 1)) Here, Code is an integer from 0 to 2^N − 1, and floor means rounding downward. The formula assumes an ideal ADC, a stable reference, an input within range, and no offset or gain error. The nominal voltage represented by a code can be estimated as: Vin ≈ Code × Vref / (2N − 1) The quantization step is approximately: q = Vref / (2N − 1) where q is in volts per code. Real converters also have noise, offset error, gain error, nonlinearity, and a finite acquisition time. Worked example. A 10-bit ADC uses a 3.3 V reference and measures 1.65 V. The ideal code is approximately 1.65/3.3 × 1023 = 511.5, so the returned code will be approximately 511 or 512 depending on conversion and rounding conventions. The nominal step is 3.3/1023 ≈ 3.23 mV per code. 2.3 Actuators and PWMMicrocontrollers commonly control actuators indirectly. A GPIO pin may drive a transistor, which supplies current to a relay or motor. A microcontroller pin should not directly drive a load unless the datasheet explicitly permits the voltage and current. Pulse-width modulation (PWM) controls average power by switching a signal rapidly. The duty cycle is: D = ton / T where D is dimensionless, t_on is the time the signal is high, and T is the period. It is often expressed as a percentage: 100D%. For an ideal resistive load supplied by voltage Vs, the average voltage is approximately: Vavg ≈ D × Vs This relationship is a simplified model. Motors, inductors, LEDs, and switching converters respond according to their electrical and mechanical dynamics; filtering, current limiting, flyback protection, and thermal constraints may be required. 3. Bitwise Operations and Hardware RegistersMicrocontroller peripherals are configured through registers. A register is a group of bits, each controlling or reporting a hardware feature. Bitwise operations allow a program to modify selected bits without accidentally changing other settings. For an integer value x and bit mask m: x & m tests or extracts selected bits.x | m sets selected bits to 1.x & ~m clears selected bits.x ^ m toggles selected bits.~x complements every bit, subject to the width of the data type.x << k shifts bits left by k positions.x >> k shifts bits right by k positions.
For example, to set bit 3 of an 8-bit register: PORT |= (uint8_t)(1u << 3); To clear it: PORT &= (uint8_t)~(1u << 3); To test it: if ((PORT & (1u << 3)) != 0u) {
/* bit 3 is set */
}The mask should be formed with an unsigned type of appropriate width. Careless use of signed integers, implicit promotions, or a complement wider than the register can produce errors. A read-modify-write operation may also be unsafe if an interrupt or peripheral changes the same register between the read and write. Datasheets sometimes provide atomic set, clear, or toggle registers to avoid this problem. Bit fields should be documented clearly. For a field occupying bits 4 through 6, a value v can be inserted using: REG = (REG & ~(0x7u << 4)) | ((v & 0x7u) << 4); The mask 0x7 reserves three bits, and the old field is cleared before the new value is inserted. 4. Timing and Real-Time BehaviorA real-time system is one in which correctness depends on both logical results and timing. A hard real-time deadline must not be missed, as in an emergency shutdown. A soft real-time deadline is desirable but occasional lateness may be tolerated, as in audio buffering. Important timing quantities include: - Period: time between recurring task releases.
- Deadline: latest acceptable completion time.
- Latency: delay between an event and the beginning or completion of its response.
- Jitter: variation in timing from one occurrence to another.
- Execution time: processor time required by a task.
For a periodic task with execution time C and period T, its utilization is: U = C / T C and T must use the same time units. For several tasks, total utilization is: Utotal = ∑(Ci / Ti) This quantity is useful for an initial feasibility check, but Utotal below 1 does not by itself prove that all deadlines will be met. Interrupt overhead, blocking, task priorities, release offsets, context switching, and worst-case rather than average execution time must also be considered. Worked scheduling example. Task A executes for 1 ms every 10 ms, and Task B executes for 2 ms every 20 ms. Their utilization is 1/10 + 2/20 = 0.1 + 0.1 = 0.2, or 20%. The remaining nominal processor capacity is 80%, but a real design must reserve capacity for interrupts, startup, diagnostics, and execution-time variation. A periodic loop can be implemented using a hardware timer: while (1) {
wait_for_tick();
read_sensors();
update_controller();
set_actuators();
}A delay-based loop is less robust because the actual period becomes the sum of the delay and variable execution times. Timer scheduling provides a more explicit time base. 5. Polling, Interrupts, and Concurrency5.1 PollingIn polling, the CPU repeatedly checks a device or flag: while (1) {
if (button_pressed()) {
process_button();
}
}Polling is simple and predictable when events are frequent and timing requirements are modest. It wastes CPU time when events are rare and may miss short pulses unless the polling rate is sufficiently high. 5.2 InterruptsAn interrupt allows hardware to request service asynchronously. When an interrupt occurs, the processor saves context, transfers control to an interrupt service routine (ISR), and later resumes the interrupted code. A good ISR should be short, deterministic, and limited to urgent work. It may capture a timestamp, read a peripheral register, place data into a buffer, and set a flag. Lengthy calculations, blocking calls, formatted printing, and waiting inside an ISR should generally be avoided. volatile bool sample_ready = false;
volatile uint16_t sample = 0;
void ADC_ISR(void) {
sample = ADC_DATA;
sample_ready = true;
}
int main(void) {
initialize_adc_interrupt();
while (1) {
if (sample_ready) {
disable_interrupts();
uint16_t local_sample = sample;
sample_ready = false;
enable_interrupts();
process_sample(local_sample);
}
}
}The volatile qualifier informs the compiler that a variable may change outside the current flow of code; it does not make a multi-byte access atomic and does not provide mutual exclusion. Critical sections must be as short as possible. A ring buffer is often preferable when multiple events can arrive before the main program processes them. 5.3 Concurrency hazardsConcurrency exists whenever operations overlap in time, whether through interrupts, an RTOS, DMA, or multiple processor cores. Common hazards include: - Race condition: output depends on the unpredictable order of accesses.
- Atomicity violation: a shared value is observed halfway through an update.
- Deadlock: tasks wait permanently for one another.
- Starvation: a task is repeatedly denied access to a resource.
- Priority inversion: a high-priority task waits for a resource held by a low-priority task.
Safe designs define ownership of data, use queues or mutexes where appropriate, and avoid sharing mutable state unnecessarily. A message-passing design often makes timing and ownership clearer than unrestricted global variables. 6. Finite-State MachinesA finite-state machine (FSM) models behavior as a finite set of states and transitions. A transition is caused by an event or condition and may produce an action. FSMs are especially useful for embedded systems because they make modes and exceptional behavior explicit. Consider a motor controller with states IDLE, RUNNING, FAULT, and STOPPING: typedef enum {
IDLE,
RUNNING,
STOPPING,
FAULT
} State;
void update_state(State *state, bool start, bool stop, bool fault) {
switch (*state) {
case IDLE:
if (fault) *state = FAULT;
else if (start) *state = RUNNING;
break;
case RUNNING:
if (fault) *state = FAULT;
else if (stop) *state = STOPPING;
break;
case STOPPING:
command_motor_off();
*state = IDLE;
break;
case FAULT:
command_motor_off();
if (fault_cleared() && reset_requested()) *state = IDLE;
break;
}
}A safety-oriented FSM should define a response for invalid inputs and unexpected events. The fault state should normally command a safe output, record diagnostic information, and require an explicit recovery condition. State diagrams can be implemented using switch statements, transition tables, or hierarchical state machines for larger systems. 7. Communication InterfacesUARTUART is an asynchronous serial interface commonly used for debug messages and point-to-point communication. Both devices must agree on baud rate, data bits, parity, and stop bits. A typical frame contains a start bit, data bits, optional parity, and stop bit. UART usually requires separate transmit and receive lines and a common reference. SPISPI is a synchronous interface using a clock, master-out/slave-in, master-in/slave-out, and chip-select signals. It is fast and supports full-duplex transfer. Each slave generally requires a chip-select signal. Clock polarity and phase must match the peripheral configuration. I²CI²C uses two shared open-drain lines: serial data and serial clock. Pull-up resistors are required, and devices use addresses. It supports multiple devices on the same bus but is generally slower than SPI and requires attention to address conflicts, bus capacitance, and stuck-low recovery. | Interface | Strength | Limitation | Typical use |
|---|
| UART | Simple point-to-point link | Usually no shared bus arbitration | Debugging, modules | | SPI | High speed and simple framing | More wires and chip-selects | Displays, ADCs, Flash | | I²C | Two-wire multi-device bus | Pull-ups and lower speed | Sensors, configuration ICs |
Communication software should validate message length, address, checksum or CRC where appropriate, timeout behavior, and malformed input. A device must not trust external data merely because it arrived through a valid interface. 8. Memory, Power, and Safety EngineeringMemory disciplineEmbedded software should use bounded arrays, explicit buffer sizes, and defensive checks. Dynamic allocation can cause fragmentation and unpredictable allocation failure; static allocation is often preferred in safety-critical or long-running systems. Stack usage should be measured, especially when interrupts or an RTOS are used. Recursion is generally avoided when maximum stack depth cannot be proven. Power managementPower-saving methods include clock gating, peripheral shutdown, reduced sampling rates, lower clock frequency, efficient communication, and sleep modes. A sleep strategy must preserve required wake-up sources and ensure that wake-up latency meets system deadlines. A lower clock frequency may reduce dynamic power but increases execution time; therefore, power and timing must be evaluated together. Safety mechanismsImportant mechanisms include: - independent watchdog timers;
- brownout detection and controlled reset;
- over-current, over-temperature, and over-voltage protection;
- redundant or plausibility-checked sensors;
- safe startup and shutdown sequences;
- hardware interlocks;
- range checks and timeout handling;
- diagnostic logs and fault codes.
Standards such as IEC 61508, ISO 26262, IEC 62304, and related sector-specific frameworks may apply depending on the product. The exact compliance process depends on the industry and safety classification. Software quality alone cannot compensate for inadequate electrical protection or unsafe system architecture. 9. Illustrative Case: Conveyor Over-Temperature ProtectionBackgroundConsider an illustrative industrial conveyor driven by a motor and monitored by a temperature sensor. The controller must sample temperature every 100 ms, display status, and stop the motor if temperature exceeds a limit or if the sensor becomes unavailable. ProblemA naïve implementation polls the sensor, updates the display, and controls the motor in one loop. Display communication occasionally takes 300 ms, causing the temperature check to be late. A disconnected sensor may also produce a plausible but stale value. AnalysisThe system has a timing requirement: temperature supervision must execute at least every 100 ms. The display task is less urgent. Therefore, the design should separate acquisition and supervision from display communication. A timer interrupt can trigger ADC sampling, while the main loop processes data and updates the display. A timestamp or freshness counter can identify stale samples. Approach and solutionThe design uses: - A timer event every 100 ms.
- An ADC conversion initiated by the timer or its interrupt routine.
- A bounded sample queue or latest-sample record.
- A state machine with
SAFE_STOP, READY, RUNNING, and FAULT states. - A sensor plausibility check for range and freshness.
- A watchdog reset if the main control path fails.
- A hardware motor-enable interlock independent of display software.
The control task reads a sample only after checking its validity. If no valid sample arrives within the allowed interval, it transitions to FAULT and disables the motor. The display receives a copy of status data and cannot prevent the safety task from running. OutcomeThe architecture isolates a low-priority communication function from the time-critical protection function. It also treats sensor failure as a controlled fault rather than assuming that every numerical reading is trustworthy. Learning pointsThe case demonstrates that timing requirements should be assigned by function, not by convenience. It also illustrates separation of concerns, explicit fault states, data freshness, bounded resource use, and defense in depth. 10. Development and Verification PracticesEmbedded debugging should combine source-level debugging with electrical measurement. A logic analyzer can reveal bus timing, pulse widths, and interrupt response. An oscilloscope can expose noise, ringing, incorrect rise times, and PWM behavior. Instrumentation should be designed so that it does not substantially alter timing. Useful tests include: - unit tests for calculations and state transitions;
- hardware-in-the-loop tests for sensor and actuator behavior;
- boundary tests for minimum and maximum values;
- fault injection for disconnected sensors and communication errors;
- timing tests under worst-case interrupt and communication load;
- power tests during sleep, startup, and peak activity;
- static analysis for unsafe constructs and out-of-bounds access.
Requirements should be traceable to implementation and tests. Code reviews should examine register configuration, concurrency, error handling, integer overflow, initialization order, and recovery behavior. Clear naming and comments should explain hardware assumptions rather than restating obvious syntax. 11. Contemporary DevelopmentsModern microcontrollers increasingly include multiple cores, DMA engines, hardware security modules, wireless interfaces, and machine-learning accelerators. These features can improve performance but introduce additional concurrency, memory-coherency, security, and verification issues. Edge AI can classify sensor data locally, reducing communication and latency, but models consume memory and energy and may behave unpredictably outside their training conditions. Secure boot, signed firmware, encrypted communication, unique device identities, and secure update mechanisms are now important for connected products. A connected embedded system must treat firmware updates and received commands as security-sensitive operations. Artificial intelligence does not remove the need for deterministic safety mechanisms; a conventional threshold, watchdog, or interlock may remain the appropriate final protection. Best-Practice ChecklistBefore releasing an embedded program, ask: - Are all inputs electrically within specification?
- Are floating inputs, switch bounce, and ADC errors addressed?
- Are timing periods, deadlines, latency, and jitter measured?
- Are ISRs short and shared data protected?
- Are buffers bounded and memory usage known?
- Are actuator drivers rated for the load and protected electrically?
- Does every major fault lead to a defined safe state?
- Are communication timeouts, malformed messages, and bus failures handled?
- Has behavior been tested under worst-case load and power conditions?
- Can another engineer understand the hardware assumptions and recovery logic?
Chapter SummaryEmbedded programming combines software with physical devices and must satisfy functional, temporal, resource, and safety requirements. Microcontrollers provide GPIO, ADC, PWM, timers, memory, and communication peripherals that firmware configures through registers. Bitwise masks permit individual hardware features to be changed without disturbing neighboring bits. Digital and analog signals require attention to voltage limits, quantization, noise, pull resistors, and signal conditioning. PWM provides a practical method for controlling average actuator power, but real loads require appropriate drivers and protection. Real-time behavior is described using periods, deadlines, execution time, latency, jitter, and utilization; average utilization alone cannot prove deadline compliance. Polling is simple but may waste processor time or miss events. Interrupts provide prompt response, but interrupt routines should remain short and shared data must be handled safely. Concurrency introduces races, deadlock, starvation, and priority inversion, making ownership and synchronization essential. Finite-state machines clarify operational modes and fault recovery. UART, SPI, and I²C serve different communication needs and require validation and timeout handling. Reliable embedded products use bounded memory, measured timing, power-aware design, watchdogs, interlocks, diagnostics, and systematic testing. The central engineering principle is that correct computation is necessary but insufficient: a dependable embedded system must compute the right result, at the right time, using safe interactions with the physical world. Key TermsEmbedded system: A computer system dedicated to a specific function within a larger product. Microcontroller: An integrated circuit containing a processor, memory, and peripherals. GPIO: General-purpose input/output pin used for digital signals. ADC: Analog-to-digital converter that represents voltage as a numerical code. PWM: Pulse-width modulation used to control average power or signal level. Register: Hardware-accessible storage location used for configuration or status. Bit mask: A binary pattern used to select particular bits. Interrupt: An event that temporarily transfers processor control to an ISR. ISR: Interrupt service routine that responds to an interrupt. Polling: Repeatedly checking a device or condition in software. Real-time system: A system whose correctness includes timing requirements. Latency: Delay between an event and the corresponding response. Jitter: Variation in the timing of repeated events. Race condition: Behavior dependent on the unpredictable ordering of concurrent accesses. Finite-state machine: A model consisting of states, events, actions, and transitions. UART: Asynchronous serial communication interface. SPI: Synchronous serial interface commonly used for high-speed peripheral communication. I²C: Two-wire addressed serial bus using open-drain signals. Watchdog timer: Hardware timer that resets or signals a system that fails to respond. Fail-safe state: A condition designed to minimize harm when a fault occurs. Review QuestionsShort Answer Questions- Why is a floating digital input undesirable, and how can it be prevented?
- What is the purpose of a bit mask when configuring a hardware register?
- Distinguish hard real-time from soft real-time behavior.
- Why should an interrupt service routine normally be short?
- State two differences between SPI and I²C.
Long Answer Questions- Explain the architecture of an embedded system from sensors and actuators to application firmware.
- Describe ADC quantization, reference voltage, resolution, and practical sources of conversion error.
- Explain polling, interrupts, and message passing, including suitable applications and limitations.
- Design a finite-state machine for a battery-powered device that has sleep, active, charging, and fault modes.
- Discuss how memory, timing, power, and safety constraints influence embedded software design.
Analytical / Application Questions- A 12-bit ADC uses a 3.3 V reference. Estimate the code for a 2.00 V input and calculate the nominal voltage step. State the assumptions used.
- A task executes for 0.8 ms every 5 ms, while a second task executes for 1.5 ms every 20 ms. Calculate total nominal utilization and explain why the result is not a complete schedulability proof.
- Propose a safe architecture for a motor controller whose temperature sensor may disconnect and whose display communication can block for 200 ms. Identify the timing, concurrency, and fault-handling decisions.
Multiple Choice Questions with AnswersQuestion 1Which property distinguishes a real-time system from an ordinary computational system? A. It always uses a high-frequency processor. B. Correctness includes meeting specified timing requirements. C. It cannot use interrupts. D. It must contain an operating system. Correct Answer: B. Correctness includes meeting specified timing requirements. Question 2Which operation sets selected bits in a register without changing unselected bits? A. Bitwise AND with the mask B. Bitwise OR with the mask C. Bitwise XOR with the complement of the mask D. Bitwise NOT of the register Correct Answer: B. Bitwise OR with the mask Question 3For an ideal 10-bit ADC with a 3.3 V reference, what is the approximate nominal voltage step? A. 0.323 mV B. 3.23 mV C. 32.3 mV D. 330 mV Correct Answer: B. 3.23 mV Question 4Which activity is generally inappropriate inside a short interrupt service routine? A. Capturing a peripheral value B. Setting an event flag C. Performing a long blocking communication operation D. Recording a timestamp Correct Answer: C. Performing a long blocking communication operation Question 5What is the main purpose of a finite-state machine in embedded software? A. To increase ADC resolution B. To represent modes and transitions explicitly C. To replace all hardware protection D. To eliminate the need for testing Correct Answer: B. To represent modes and transitions explicitly Question 6A task executes for 1 ms every 10 ms. What is its nominal utilization? A. 1% B. 10% C. วง? D. 100% Correct Answer: B. 10% Question 7Which interface normally uses open-drain data and clock lines with pull-up resistors? A. UART B. SPI C. I²C D. PWM Correct Answer: C. I²C Question 8What does the volatile qualifier primarily tell the compiler? A. The variable is stored permanently in Flash. B. The variable cannot change. C. The variable may change outside the ordinary program flow. D. Access to the variable is automatically atomic. Correct Answer: C. The variable may change outside the ordinary program flow. Question 9Which design best handles a sensor that may stop producing fresh data? A. Assume the last value remains valid indefinitely. B. Use a freshness timeout and enter a defined fault response. C. Increase display brightness. D. Disable the watchdog timer. Correct Answer: B. Use a freshness timeout and enter a defined fault response. Question 10For ideal PWM applied to a resistive load, with supply 12 V and duty cycle 25%, what is the approximate average voltage? A. 0.25 V B. 3 V C. 9 V D. 48 V Correct Answer: B. 3 V
|