126. Clock-Dependent Peripheral Design

In embedded firmware, hardware peripherals must never be accessed unless their clock is enabled.
Accessing a peripheral before enabling its clock can result in bus faults, undefined behavior, or system resets.

Your task is to encode this rule directly into C++ class relationships using dependency-aware composition, such that invalid usage is impossible by design.

The following conditions must be enforced structurally, not by comments or runtime checks:

  • The clock is enabled before the peripheral becomes usable
  • The peripheral cannot exist without a valid clock
  • The driver owns and controls the lifetime of both the clock and the peripheral
  • Shutdown occurs in the reverse-safe order

Any solution that violates these rules is incorrect, even if it compiles.

Program Flow

  1. Read two integers:
    • Register offset
    • Value to write
  2. Create a Driver object inside a local scope
  3. Clock is enabled
  4. Peripheral becomes ready using the clock
  5. Driver performs one register write
  6. Scope ends
  7. Driver shuts down
  8. Peripheral shuts down
  9. Clock shuts down

Input

Two space-separated integers:

  • offset — register offset
  • value — value to write

Output

The program must print exactly the following lines, in order:

Clock enabled
Peripheral ready
Driver started
Clocked write: <offset> <value>
Driver stopped
Peripheral stopped
Clock disabled

Constraints

  • offset and value are non-negative integers
  • 0 ≤ offset ≤ 1024
  • 0 ≤ value ≤ 65535
  • Exactly one write operation must occur
  • ClockController must not be created in main()
  • Peripheral must not create or own a clock
  • Peripheral must depend on an existing clock instance
  • The clock must outlive the peripheral
  • No dynamic allocation
  • No smart pointers
  • No inheritance
  • Output format must match exactly

 

 

Need Help? Refer to the Quick Guide below

These three concepts form the backbone of robust C++ architecture.

  1. Composition ("Has-a"): Instead of inheriting from classes ("Is-a"), you build complex objects by combining smaller, simpler objects. A Car has an Engine, it is not an Engine.
  2. RAII (Resource Acquisition Is Initialization): The "Golden Rule" of C++. Resources (Memory, Locks, Files) are acquired in the Constructor and released in the Destructor. This ties resource management to the Scope, guaranteeing cleanup.
  3. Ownership: Defines who is responsible for the lifetime of a resource. "If I own it, I delete it."

Syntax & Usage

1. Composition (Building Blocks)

Composition allows you to swap parts easily and keeps dependencies clear.

class PWM { 
public: 
    void set(int duty) { /* ... */ } 
};

class Encoder { 
public: 
    int read() { /* ... */ } 
};

// Composition: MotorController "Has-a" PWM and "Has-a" Encoder
class MotorController {
    PWM pwm_driver;      // Sub-component 1
    Encoder enc_driver;  // Sub-component 2

public:
    void set_speed(int speed) {
        int current = enc_driver.read(); // Delegate work
        pwm_driver.set(speed);
    }
};

2. RAII (The Scope Guard)

The most common embedded use case is managing interrupts or mutexes.

class ScopedIntLock {
public:
    ScopedIntLock()  { __disable_irq(); } // Acquire
    ~ScopedIntLock() { __enable_irq(); }  // Release
};

void critical_function() {
    ScopedIntLock lock; // Interrupts disabled HERE
    
    // ... do critical work ...
    
    if (error) return;  // Safe! Destructor re-enables interrupts automatically.
} // Destructor runs here naturally.

Ownership Model

Ownership TypeConceptC++ Tool
Owner"I control this life. When I die, it dies."std::unique_ptr or Member Variable.
Shared Owner"We keep this alive as long as one of us needs it."std::shared_ptr.
Observer"I just look at it. I hope it's still there."Raw Pointer (T*) or Reference (T&).

Relevance in Embedded/Firmware

1. Composition over Inheritance

Embedded hierarchies can get messy ("Is a SPI driver a Stream? Is it a Bus?").

Composition is cleaner. A Sensor class shouldn't inherit from I2C. It should contain a pointer/reference to an I2C driver. This allows you to easily swap the I2C driver for a SPI driver without rewriting the Sensor class.

2. Leak-Free Error Handling

In C, if an error occurs in the middle of a function, you must remember to free() memory or unlock() mutexes before every return.

With RAII, you just return. The compiler inserts the cleanup code for you (Stack Unwinding), making firmware crash-proof.

3. Clear Lifetimes

By using Composition and RAII, you avoid "dangling pointers" where a driver tries to access a hardware object that has already been de-initialized. If the parent object (Robot) dies, its children (Motors) are destroyed automatically and in the correct order.

Common Pitfalls (Practical Tips)

PitfallDetails
❌ Deep InheritanceAvoid creating 5-layer deep class hierarchies (Driver -> Serial -> UART -> STM32_UART). It increases code size (v-tables) and makes debugging a nightmare. Prefer Composition.
❌ Raw Pointers as Owners

Never use a raw pointer (T*) to own data.

drivers.add(new UART()); is a leak waiting to happen. Use unique_ptr for ownership, raw pointers only for observing.

❌ Circular DependencyIf Object A owns B, and Object B owns A (via shared_ptr), they will keep each other alive forever (Memory Leak). Use weak_ptr or raw references to break the cycle.
✅ Dependency Injection

Pass the "Sub-components" into the Constructor rather than creating them inside.

Motor(PWM& pwm) is better than creating a new PWM inside Motor, because it allows you to pass a MockPWM for testing.