95. PWM Constructor Modes

Create a class PWMController that simulates a simple PWM output channel on a microcontroller.
The goal is to practice constructor overloading, state initialization, and safe parameter handling in Embedded C++.

Details you must implement

  • Private members
    • int frequency — PWM frequency in Hz
    • int duty — duty cycle percentage (0–100)
    • bool enabled — PWM output state
  • Constructor 1: Basic Initialization
    • PWMController(int freq)
      • Sets frequency = freq
      • Sets duty = 0
      • Sets enabled = false
    • This constructor represents a PWM channel that is configured but not active.
  • Constructor 2: Full Initialization
    • PWMController(int freq, int dutyCycle)
      • Sets frequency = freq
      • Sets duty = dutyCycle, clamped to the range 0–100
      • Sets enabled = true
    • This constructor represents a PWM channel that is configured and enabled immediately.
  • Public methods
    • void setDuty(int d)
      • Updates the duty cycle
      • Duty must always be clamped to 0–100
    • void disable()
      • Sets enabled = false
    • void print()
      • Prints the current PWM state in the exact format:
        • F=<frequency> D=<duty> EN=<0 or 1>
          

Program flow (main())

  1. Read an integer mode
  2. If mode == 1
    • Read freq
    • Construct PWMController using Constructor 1
  3. If mode == 2
    • Read freq and duty
    • Construct PWMController using Constructor 2
  4. Read integer x
    • If x == -1, call disable()
    • Otherwise, call setDuty(x)
  5. Print the final PWM state using print()

 

Example 1 

Input:

1 
1000 
50 

Output:

F=1000 D=50 EN=0 

 

Example 2

Input:

2
2000 120
-1 

Example Output

F=2000 D=100 EN=0 

 

Constraints

  • Both constructors must be implemented
  • Duty cycle must always remain within 0–100
  • Output format must match exactly (spacing and order matter)

 

 

 

Need Help? Refer to the Quick Guide below

A Constructor is a special member function that runs automatically when an object is created. Its purpose is to guarantee that the object starts in a valid state (e.g., pointers assigned, hardware initialized, invariants checked) before any other code uses it.

It has no return type and shares the same name as the class.

Syntax & Usage

1. Basic & Parameterized Constructors

The most common forms used to initialize variables.

class GPIO {
    int pin;
public:
    // 1. Default Constructor (No args)
    GPIO() { pin = 0; }

    // 2. Parameterized Constructor (With args)
    GPIO(int p) { pin = p; }
};

GPIO led;        // Calls GPIO()
GPIO motor(9);   // Calls GPIO(int)

2. Member Initializer Lists (Crucial for Efficiency)

Initializes variables before the constructor body runs. This is mandatory for const members and References, and avoids "double-writing" variables.

class Sensor {
    const int id;
    int& bus_ref;
public:
    // Syntax: : member(value), member(value)
    Sensor(int i, int& bus) : id(i), bus_ref(bus) { 
        // Body is empty; work is already done.
    }
};

3. Advanced Variants (explicit, Copy, delete)

These control how objects are created or copied, preventing dangerous bugs.

  • explicit: Prevents accidental implicit conversions (e.g., passing int where a Driver object is expected).
  • Copy Constructor: Defines how to clone an object (crucial for Deep Copies of pointers).
  • = delete: Disables a constructor (used for Singletons or unique hardware drivers).
class Driver {
    int* buffer;
public:
    // PREVENT implicit conversion from int to Driver
    explicit Driver(int size) { buffer = new int[size]; }

    // CUSTOM Copy Constructor (Deep Copy)
    Driver(const Driver& other) {
        buffer = new int[10]; 
        memcpy(buffer, other.buffer, 10 * sizeof(int));
    }

    // DISABLE Copying entirely (Common for hardware drivers)
    // Driver(const Driver&) = delete; 
};

Initialization vs Assignment Flow

FeatureAssignment (Inside Body)Initializer List (: x(10))
MechanismVariable created (default), then overwritten.Variable created with value.
PerformanceSlower (Two steps).Faster (One step).
ConstraintsCannot init const or Reference.Required for const/Reference.

Relevance in Embedded/Firmware

1. Impossible-to-Forget Initialization

In C, forgetting UART_Init() crashes the system. 

In C++, constructors force you to provide configuration (e.g., Baud Rate) at creation time. The object never exists in an uninitialized state.

2. RAII (Resource Acquisition Is Initialization)

The "Scope Lock" pattern: A constructor acquires a resource (disables interrupts, takes a mutex), and the Destructor releases it.

{
    InterruptLock lock; // Constructor: __disable_irq();
    // Critical Section
} // Destructor: __enable_irq(); automatic at end of scope.

3. Unique Hardware Access

Using = delete on copy constructors ensures you don't have two software objects trying to control the same physical UART peripheral, preventing race conditions.

Common Pitfalls (Practical Tips)

PitfallDetails
❌ Heavy WorkAvoid complex hardware delays or checks in constructors for Global/Static objects. They run before main(), which can make the system appear to hang at boot.
❌ Initializer Order

Members are initialized in the order they are declared in the class, NOT the order in the list.

class A { int y; int x; A(): x(1), y(x) {} } reads uninitialized memory because y is declared first.

❌ Implicit ConversionWithout explicit, void func(Driver d) can be called as func(100), silently creating a 100-byte driver. This is confusing and dangerous.
✅ Default ConstructorIf you define any constructor, the compiler removes the default (empty) one. You must explicitly add GPIO() = default; if you still need it.